概要
digest ピン留めで再現性を保証
:latest タグは同じタグで異なるイメージが push されると挙動が変わる。SHA256 digest ピン留め(@sha256:...)により、同一 digest = 同一バイナリが保証され、ロールバックも確実に機能する。
ESO + Secret Manager でシークレット撲滅
env.value の平文シークレットは GitOps リポジトリや kubectl describe で露出する。ESO ExternalSecret が Secret Manager から自動同期し、envFrom.secretRef で注入する。
Workload Identity Federation v2
KSA の annotation + Terraform の google_service_account_iam_binding で roles/iam.workloadIdentityUser を付与。Pod が GCP リソースに認証情報なしでアクセスできる。annotation だけでは不十分。
PDB + HPA 複合メトリクスで可用性とコストを両立
PodDisruptionBudget でノードアップグレード中のゼロレプリカを防ぎ、Pub/Sub バックログ External メトリクスで CPU 低負荷でもキューが溜まればスケールアップする。
問題
ECサイトの MOps バッチ処理ワークロードを GKE Autopilot 上で運用しているが、以下の YAML 群には 6つの設計上の問題 が潜んでいる。問題点を全て洗い出し、GKE Autopilot 1.30+・Workload Identity Federation v2・ESO v0.9+ の最新プラクティスに従って修正せよ。
制約・前提条件
- GKE Autopilot 1.30+(
asia-northeast1) - Workload Identity Federation v2(Terraform 管理)
- シークレットは Secret Manager + ESO v0.9+ で管理
- HPA は CPU + Pub/Sub バックログの複合スケーリング
- PodDisruptionBudget でローリングアップデート中の可用性を保証
- イメージタグは
latest禁止、SHA256 digest ピン留め
期待する回答形式: 問題点の列挙(番号付き)+ 改善後 YAML(全ファイル)+ 設計意図の説明
悪い YAML (Before)
この YAML 群には 6つの設計上の問題 が隠れています。
bad_deployment.yaml — 問題だらけのデプロイメント
apiVersion: apps/v1
kind: Deployment
metadata:
name: mops-batch
namespace: mops
spec:
replicas: 3
selector:
matchLabels:
app: mops-batch
template:
metadata:
labels:
app: mops-batch
spec:
containers:
- name: mops-batch
# 問題①: :latest タグ(再現性なし・ロールバック不可)
image: asia-northeast1-docker.pkg.dev/my-project/mops/batch:latest
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
# 問題②: limit/request 比が 40倍(Autopilot 非推奨)
cpu: "4000m"
memory: "4Gi"
env:
# 問題③: 平文シークレット(GitOps に漏洩リスク)
- name: DB_PASSWORD
value: "supersecret123"
# 問題④: Workload Identity 設定漏れ(Terraform IAM Binding なし)
serviceAccountName: mops-ksa
bad_hpa.yaml + bad_workload_identity.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mops-batch-hpa
namespace: mops
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mops-batch
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
# 問題⑤: PodDisruptionBudget が存在しない
# → ノードアップグレード時にゼロレプリカになり得る
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: mops-ksa
namespace: mops
annotations:
# 問題⑥: annotation のみ(Terraform IAM Binding が不足)
# roles/iam.workloadIdentityUser の binding なしでは
# GSA へのなりすましができない
iam.gke.io/gcp-service-account: "mops-sa@my-project.iam.gserviceaccount.com"
問題点サマリー(6点)
1
:latest タグ(再現性なし) — 同じタグで異なるイメージが push されると挙動が変わる。SHA256 digest ピン留めに変更し CI/CD で自動更新する2CPU limit/request 比が 40倍(GKE Autopilot 非推奨) — Autopilot は
request ベースでノード配置・課金する。100m → 4000m は小ノードに配置されつつバースト時に大量リソースを要求する矛盾。比は2〜4倍以内が推奨3平文シークレット(
env.value) — kubectl describe pod や GitOps リポジトリでシークレットが露出。ESO ExternalSecret + envFrom.secretRef に移行する4Workload Identity: KSA annotation のみ — annotation だけでは GSA へのなりすましができない。Terraform で
google_service_account_iam_binding + roles/iam.workloadIdentityUser の binding が必須5PodDisruptionBudget が存在しない — ノードアップグレード・ローリングアップデート時に全 Pod が同時退避されゼロレプリカになる可能性がある。
minAvailable: 1 で最低1 Pod の稼働を保証する6HPA が CPU 単独・maxReplicas が過大 — Pub/Sub キューのバックログが溜まっても CPU が低ければスケールしない。External メトリクスと複合スケーリングにし、
maxReplicas: 50 はコスト爆発リスクがあるため 20 以下に抑制するヒント(段階的開示)
ヒント1 — 方向性
:latest タグはイメージの再現性を破壊し、ロールバックを不可能にする。CPU limit と request の比が大きすぎると GKE Autopilot のノード配置が非効率になる(Autopilot は request ベースで課金・配置する)。平文 env.value によるシークレット注入は GitOps リポジトリに漏洩リスクがある。Workload Identity では annotation だけでなく Terraform IAM Binding も必要。
ヒント2 — アプローチ
- 問題①: イメージタグを
@sha256:<digest>形式にピン留め、CI/CD で digest を自動更新 - 問題②: GKE Autopilot では CPU limit/request 比は 2〜4倍以内が推奨。実測値(
kubectl top pods)から適切な値を設定する - 問題③: ESO の
ExternalSecretリソースで Secret Manager から自動同期し、envFrom.secretRefで注入 - 問題④: Terraform で
google_service_account_iam_bindingを使いserviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA]にroles/iam.workloadIdentityUserを付与 - 問題⑤:
PodDisruptionBudgetでminAvailable: 1を設定 - 問題⑥: HPA に External メトリクス(Pub/Sub
num_undelivered_messages)を追加し、behaviorでスケールダウン安定化ウィンドウを設定
ヒント3 — コードの骨格
# ExternalSecret(ESO v0.9+)骨格
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: mops-db-secret
namespace: mops
spec:
refreshInterval: 1h
secretStoreRef:
name: gcp-secret-store
kind: ClusterSecretStore
target:
name: mops-db-secret
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: mops/db-password
version: latest
---
# PodDisruptionBudget 骨格
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mops-batch-pdb
namespace: mops
spec:
minAvailable: 1
selector:
matchLabels:
app: mops-batch
---
# HPA + Pub/Sub External メトリクス骨格
metrics:
- type: External
external:
metric:
name: pubsub.googleapis.com|subscription|num_undelivered_messages
selector:
matchLabels:
resource.labels.subscription_id: mops-batch-sub
target:
type: AverageValue
averageValue: "100"
問題点分析(6点)
| # | 問題点 | 分類 | 改善方法 |
|---|---|---|---|
| 1 | :latest タグ(再現性なし・ロールバック不可) | 信頼性 | SHA256 digest ピン留め、CI/CD で自動更新 |
| 2 | CPU limit/request 比が 40倍(Autopilot 非推奨) | コスト/性能 | limit は request の 2〜4倍以内に設定 |
| 3 | 平文シークレット env.value(GitOps 漏洩リスク) | セキュリティ | ESO ExternalSecret + envFrom.secretRef |
| 4 | Workload Identity: KSA annotation のみ(Terraform binding 不足) | セキュリティ | Terraform で roles/iam.workloadIdentityUser binding |
| 5 | PodDisruptionBudget が存在しない | 可用性 | minAvailable: 1 の PDB を追加 |
| 6 | HPA が CPU 単独・maxReplicas: 50 が過大 | スケーリング | Pub/Sub External メトリクス追加、maxReplicas: 20 に削減 |
アーキテクチャ図 — Bad vs Good の変換フロー
模範解答
apiVersion: apps/v1
kind: Deployment
metadata:
name: mops-batch
namespace: mops
labels:
app: mops-batch
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: mops-batch
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最大1 Pod 超過起動
maxUnavailable: 0 # ゼロダウンタイム更新(PDB と組み合わせる)
template:
metadata:
labels:
app: mops-batch
annotations:
# DataDog APM + OTLP 自動注入(オブザーバビリティ基盤)
admission.datadoghq.com/python-lib.version: "v2.10.0"
spec:
serviceAccountName: mops-ksa # Workload Identity v2 対応 KSA
securityContext:
runAsNonRoot: true # 非 root で実行
seccompProfile:
type: RuntimeDefault
containers:
- name: mops-batch
# 修正①: SHA256 digest ピン留め(CI/CD パイプラインで自動更新)
image: asia-northeast1-docker.pkg.dev/my-project/mops/batch@sha256:a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890
imagePullPolicy: IfNotPresent # digest ピン留めなので Always 不要
resources:
requests:
cpu: "500m" # 修正②: request を実態に合わせて引き上げ
memory: "512Mi"
limits:
cpu: "1000m" # 修正②: limit/request 比を 2倍(Autopilot 推奨)
memory: "1Gi"
envFrom:
# 修正③: ExternalSecret が作成した Secret を一括注入(平文撲滅)
- secretRef:
name: mops-db-secret
env:
- name: ENV
value: "production"
- name: GCP_PROJECT_ID
value: "my-project"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 2
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 起動最大 300秒 (30 × 10s)
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
# 修正③: ESO v0.9+ で Secret Manager から DB パスワードを自動同期
# refreshInterval: 1h でローテーションに対応
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: mops-db-secret
namespace: mops
spec:
refreshInterval: 1h # 1時間ごとに Secret Manager から再取得
secretStoreRef:
name: gcp-secret-store
kind: ClusterSecretStore
target:
name: mops-db-secret
creationPolicy: Owner # ESO が Secret の所有者(削除時に一緒に削除)
deletionPolicy: Retain # ExternalSecret 削除後も Secret は残す(安全側)
data:
- secretKey: DB_PASSWORD # Kubernetes Secret のキー名
remoteRef:
key: mops/db-password # Secret Manager のシークレット名
version: latest # 常に最新バージョンを取得
- secretKey: DB_CONNECTION_STRING
remoteRef:
key: mops/db-connection-string
version: latest
# 修正⑥: CPU + Pub/Sub バックログの複合スケーリング
# CPU averageUtilization を 80 → 60 に下げてスロットリング前に早めスケール
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mops-batch-hpa
namespace: mops
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mops-batch
minReplicas: 2 # 最低2レプリカ(単一障害点を排除)
maxReplicas: 20 # 50→20に削減(コスト管理)
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 5分間の安定化ウィンドウ
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0 # スケールアップは即時
policies:
- type: Pods
value: 4
periodSeconds: 60
metrics:
# メトリクス①: CPU 使用率(60%でスケール開始)
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
# メトリクス②: Pub/Sub バックログ(100メッセージ/Pod を超えたらスケール)
- type: External
external:
metric:
name: pubsub.googleapis.com|subscription|num_undelivered_messages
selector:
matchLabels:
resource.labels.subscription_id: mops-batch-sub
target:
type: AverageValue
averageValue: "100"
---
# 修正⑤: PDB でローリングアップデート中の可用性を保証
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mops-batch-pdb
namespace: mops
spec:
minAvailable: 1 # 常に最低1 Pod が稼働(Deployment の replicas=3 前提)
selector:
matchLabels:
app: mops-batch
# 修正④⑥: Workload Identity Federation v2 の正しい設定
# KSA の annotation + Terraform IAM Binding の両方が必要
apiVersion: v1
kind: ServiceAccount
metadata:
name: mops-ksa
namespace: mops
annotations:
# GKE Workload Identity: KSA と GSA をバインド
iam.gke.io/gcp-service-account: "mops-sa@my-project.iam.gserviceaccount.com"
---
# Terraform 側で必ず設定する(annotation だけでは認証が通らない):
#
# resource "google_service_account_iam_binding" "workload_identity" {
# service_account_id = google_service_account.mops_sa.name
# role = "roles/iam.workloadIdentityUser"
# members = [
# "serviceAccount:my-project.svc.id.goog[mops/mops-ksa]",
# ]
# }
#
# これにより Pod は GCP リソース(BigQuery, Pub/Sub, Secret Manager)に
# 認証情報なしでアクセスできる(サービスアカウントキー不要)
| 問題 | 修正内容 | 効果 |
|---|---|---|
| ① latest タグ | SHA256 digest ピン留め | 再現性・ロールバック保証・サプライチェーン攻撃対策 |
| ② CPU limit/request 比 40倍 | 500m/1000m(比 2倍) | Autopilot ノード配置最適化・コスト削減・スロットリング防止 |
| ③ 平文シークレット | ESO ExternalSecret + envFrom.secretRef | GitOps 安全・ローテーション自動対応 |
| ④ WI annotation のみ | Terraform IAM Binding 追加 | GCP リソースへの安全な認証(キーレス) |
| ⑤ PDB なし | minAvailable: 1 | ノードアップグレード中もゼロダウンタイム保証 |
| ⑥ HPA CPU 単独 + maxReplicas: 50 | Pub/Sub External メトリクス + maxReplicas: 20 + behavior | バックログ起因のスケーリング対応・コスト管理 |
ポイント解説
1
イメージ digest ピン留め(問題①)
:latest タグは同じタグで異なるイメージが push されるとデプロイのたびに挙動が変わる。SHA256 digest ピン留め(@sha256:...)により同一 digest = 同一バイナリが保証され、ロールバックも確実に機能する。サプライチェーン攻撃(base image の改ざん)の影響を受けにくくなる副次効果もある。CI/CD(Cloud Build / GitHub Actions)でビルド後に docker inspect --format='{{index .RepoDigests 0}}' で digest を取得し、kustomize edit set image または Argo CD Image Updater で自動更新する。
2
GKE Autopilot の resource request/limit 設計(問題②)
Autopilot はコンテナの
Autopilot はコンテナの
request に基づいてノードサイズを決定し課金する。request: 100m / limit: 4000m は「小さなノードに配置されるが、スパイク時に 4 vCPU 必要」という矛盾を生む。Autopilot は limit も考慮してノードを選択するが、request が低いとスロットリングが頻発する。limit/request 比は 2〜4 倍以内が Autopilot 推奨。実際の使用量を kubectl top pods --containers で測定してから設定すること。
3
ESO + Secret Manager でシークレット管理(問題③)
env.value に平文を書くと GitOps リポジトリや kubectl describe pod でシークレットが露出する。ESO ExternalSecret は Secret Manager から refreshInterval で自動同期し、Kubernetes Secret を作成する。envFrom.secretRef でコンテナに注入することでアプリコードの変更不要・ローテーション対応が実現できる。deletionPolicy: Retain は ESO を誤って削除してもシークレットが残るため本番環境では必須。
4
Workload Identity Federation v2(問題④⑥)
KSA の annotation
KSA の annotation
iam.gke.io/gcp-service-account と Terraform の google_service_account_iam_binding で roles/iam.workloadIdentityUser を serviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA] に付与するのが正しいセットアップ。annotation だけでは GSA になりすます権限がなく、BigQuery や Pub/Sub へのアクセスは認証エラーになる。これによりサービスアカウントキーのローテーション管理が不要になり、漏洩リスクも排除できる。
5
PodDisruptionBudget でゼロダウンタイムを保証(問題⑤)
GKE がノードをアップグレードする際、対象ノード上の Pod を退避(evict)する。PDB がない場合、全 Pod が同時に退避されてゼロレプリカになり得る。
GKE がノードをアップグレードする際、対象ノード上の Pod を退避(evict)する。PDB がない場合、全 Pod が同時に退避されてゼロレプリカになり得る。
minAvailable: 1 は「退避中も最低 1 Pod は稼働させる」という保証で、Deployment.spec.strategy.rollingUpdate.maxUnavailable: 0 と組み合わせることでゼロダウンタイムを実現する。minReplicas は PDB の minAvailable より必ず大きくなければ PDB が機能しない点に注意。
6
HPA の複合メトリクス + behavior(問題⑥)
CPU 単独では Pub/Sub キューのバックログが溜まっても(CPU が低ければ)スケールしない。External メトリクスで
CPU 単独では Pub/Sub キューのバックログが溜まっても(CPU が低ければ)スケールしない。External メトリクスで
num_undelivered_messages を監視し、バックログが増えたら即時スケールアップする。behavior.scaleDown.stabilizationWindowSeconds: 300 は急激な縮退防止(コールドスタートコスト削減)に有効。maxReplicas: 50 は Pub/Sub スパイク時にコストが爆発するリスクがあるため、実際の処理能力とコスト上限から適切な値を設定する。
実務への応用
- MOps/Argo Workflows バッチ処理: Argo Workflows の WorkflowTemplate に resource request を明示し、Autopilot のコスト最適化と組み合わせる。PDB はワークフロー実行中のノードアップグレードによる中断を防ぐ。digest ピン留めと組み合わせることで「同じワークフロー定義 = 同じバイナリ」を保証できる。
- BigQuery データパイプライン: Pub/Sub → GKE ワーカー → BigQuery のパイプラインで HPA External メトリクスを使い、Pub/Sub バックログに応じてワーカーを動的にスケールする。Workload Identity v2 により BigQuery へのアクセスはキーレスで安全に行える。
- DataDog オブザーバビリティ:
admission.datadoghq.com/python-lib.versionannotation で Python APM を自動注入する。OTel OTLP エンドポイントへのメトリクス送信と DataDog Agent v7 のネイティブ OTLP 受信を組み合わせ、K8s メタデータ(Pod名・Namespace・バージョン)を自動タグ付けする。 - セキュリティ強化:
securityContext.readOnlyRootFilesystem: true+capabilities.drop: ["ALL"]は GKE Autopilot のデフォルトセキュリティポリシーに準拠し、コンテナのホスト侵害リスクを低減する。Kyverno または OPA Gatekeeper でrunAsNonRoot: trueを Admission Webhook で組織全体に強制する。
今日のまとめ
GKE Autopilot では「digest ピン留め × ESO シークレット管理 × Workload Identity v2(annotation + Terraform IAM Binding)× PDB + HPA 複合メトリクス」の4セットが本番ワークロードの必須チェックリストであり、
特に Workload Identity は annotation だけでは機能しない(Terraform IAM Binding が必須)という点と、GKE Autopilot の CPU request/limit 比は 2〜4 倍以内という設計制約を押さえておくこと。
:latest タグ・平文シークレット・PDB なし・CPU 単独 HPA はそれぞれ独立した障害/セキュリティリスクを持つ。特に Workload Identity は annotation だけでは機能しない(Terraform IAM Binding が必須)という点と、GKE Autopilot の CPU request/limit 比は 2〜4 倍以内という設計制約を押さえておくこと。