B システム設計/インフラ — GKE Autopilot × digest ピン留め × ESO シークレット管理 × Workload Identity v2 × PDB + HPA 複合メトリクス(MOps Bad→Good YAML)

2026-06-02 (Day 57) 火曜 B: システム設計/インフラ ★★★★☆ GKE Autopilot 1.30+ / ESO v0.9+ Workload Identity Federation v2 / HPA External Metrics

概要

📌

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_bindingroles/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.valuekubectl 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 を付与
  • 問題⑤: PodDisruptionBudgetminAvailable: 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 で自動更新
2CPU limit/request 比が 40倍(Autopilot 非推奨)コスト/性能limit は request の 2〜4倍以内に設定
3平文シークレット env.value(GitOps 漏洩リスク)セキュリティESO ExternalSecret + envFrom.secretRef
4Workload Identity: KSA annotation のみ(Terraform binding 不足)セキュリティTerraform で roles/iam.workloadIdentityUser binding
5PodDisruptionBudget が存在しない可用性minAvailable: 1 の PDB を追加
6HPA が CPU 単独・maxReplicas: 50 が過大スケーリングPub/Sub External メトリクス追加、maxReplicas: 20 に削減

アーキテクチャ図 — Bad vs Good の変換フロー

Bad YAML(変更前) ① image: batch:latest 再現性なし・ロールバック不可・セキュリティリスク ② CPU request:100m / limit:4000m(比 40倍) Autopilot は request でノード配置 → 小ノードにスケジュールされバースト時にスロットリング ⚠️ コスト爆発 + スロットリング頻発 ③ env.value: "supersecret123"(平文) GitOps リポジトリ・kubectl describe で露出 → 即座に漏洩 ④ KSA annotation のみ(Terraform binding なし) iam.gke.io/gcp-service-account: mops-sa@... ← annotation だけ ⚠️ roles/iam.workloadIdentityUser binding がないと認証失敗 ⑤ PodDisruptionBudget なし ノードアップグレード時に全 Pod 退避 → ゼロレプリカでサービス停止 ⑥ HPA CPU 単独 + maxReplicas: 50 Pub/Sub バックログが溜まっても CPU 低ければスケールしない ⚠️ maxReplicas: 50 はコスト爆発リスク セキュリティ・信頼性・コスト全てに問題あり Good YAML(変更後) ① image: batch@sha256:a1b2c3... (digest ピン留め) CI/CD で自動更新 → 再現性・ロールバック・セキュリティ担保 ② CPU request:500m / limit:1000m(比 2倍) 実測値から適切な request を設定 → Autopilot コスト最適化 kubectl top pods で実使用量を確認してから設定する ③ ESO ExternalSecret → envFrom.secretRef Secret Manager から refreshInterval: 1h で自動同期 → ローテーション対応 ④ KSA annotation + Terraform IAM binding google_service_account_iam_binding: roles/iam.workloadIdentityUser members: ["serviceAccount:PROJECT.svc.id.goog[mops/mops-ksa]"] ⑤ PodDisruptionBudget minAvailable: 1 ノードアップグレード中も最低1 Pod 稼働 → ゼロダウンタイムを保証 ⑥ HPA CPU(60%) + Pub/Sub External + maxReplicas: 20 num_undelivered_messages > 100/Pod でスケールアップ scaleDown.stabilizationWindowSeconds: 300 で急激な縮退を防止 セキュリティ・信頼性・コスト全てを改善 修正

模範解答

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.secretRefGitOps 安全・ローテーション自動対応
④ WI annotation のみTerraform IAM Binding 追加GCP リソースへの安全な認証(キーレス)
⑤ PDB なしminAvailable: 1ノードアップグレード中もゼロダウンタイム保証
⑥ HPA CPU 単独 + maxReplicas: 50Pub/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 はコンテナの 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 iam.gke.io/gcp-service-account と Terraform の google_service_account_iam_bindingroles/iam.workloadIdentityUserserviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA] に付与するのが正しいセットアップ。annotation だけでは GSA になりすます権限がなく、BigQuery や Pub/Sub へのアクセスは認証エラーになる。これによりサービスアカウントキーのローテーション管理が不要になり、漏洩リスクも排除できる。
5 PodDisruptionBudget でゼロダウンタイムを保証(問題⑤)
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 メトリクスで 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.version annotation で 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セットが本番ワークロードの必須チェックリストであり、:latest タグ・平文シークレット・PDB なし・CPU 単独 HPA はそれぞれ独立した障害/セキュリティリスクを持つ。

特に Workload Identity は annotation だけでは機能しない(Terraform IAM Binding が必須)という点と、GKE Autopilot の CPU request/limit 比は 2〜4 倍以内という設計制約を押さえておくこと。

自己評価

自分の回答

気づき・メモ