システム設計/インフラ — GKE Gateway API 移行 × Probe 3種設計 × HPA + PDB + NetworkPolicy

2026-05-19 (Day 45) 火曜 B: システム設計/インフラ ★★★☆☆ GKE Autopilot 1.30+ / Gateway API v1 NetworkPolicy / HPA v2 / PodDisruptionBudget

概要

🌐

Ingress → Gateway API

Gateway(インフラ層)+ HTTPRoute(ルーティング層)の分離により、インフラチームとアプリチームが独立してリソース管理できる。TLS 終端・リダイレクト・ヘッダー制御も宣言的に定義。

🔍

Probe 3種の使い分け

startupProbe(起動完了まで liveness 無効)→ readinessProbe(トラフィック制御)→ livenessProbe(デッドロック検知)の順で設計。initialDelay の大小関係を誤ると再起動ループが起きる。

📈

HPA 65% + PDB minAvailable

CPU ターゲット 65% でスパイク前にスケールアウト。PDB で常に最低 1 Pod を保証。maxUnavailable: 0 の RollingUpdate と組み合わせてゼロダウンタイムを実現。

🔒

NetworkPolicy 最小権限

Ingress は Gateway コントローラーからのみ許可。Egress は DNS (UDP 53)・Cloud SQL (TCP 5432)・外部 HTTPS (443) のみ許可。デフォルト全拒否で攻撃対象範囲を最小化。

問題

GKE 上で動く EC サイトの販促 API(bad_ingress.yaml / bad_deployment.yaml)には複数の問題点がある。以下の観点から全て洗い出し、改善せよ。

  • ネットワーク/ルーティング: Kubernetes Ingress から Gateway API(HTTPRoute) への移行
  • セキュリティ: NetworkPolicy の欠落、コンテナ権限の過剰設定
  • スケーリング: HPA の誤設定、リソースリクエスト/リミットの不整合
  • 信頼性: Pod Disruption Budget(PDB)の未定義、Probe 設計の誤り

制約・前提条件

  • GKE Autopilot(1.30+)を使用すること
  • Gateway API(HTTPRoute)で gateway.networking.k8s.io/v1 を使用すること
  • シークレットは Secret Manager から Workload Identity + secretKeyRef で注入すること
  • NetworkPolicy で ingress/egress を最小限に制限すること
期待する回答形式: 問題点の列挙(番号付き)+ 改善後 YAML(Gateway / Deployment / HPA / PDB / NetworkPolicy)+ アーキテクチャ上の変化説明

悪いコード (Before)

2 つのファイルに合計 11 の問題点 が潜んでいます。見つけてください。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: coupon-api-ingress
  annotations:
    kubernetes.io/ingress.class: "gce"    # ❌ 旧 annotation 方式(非推奨・1.28以降は spec.ingressClassName へ)
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: coupon-api
            port:
              number: 80
  # ❌ TLS 設定なし(HTTP のみ。平文通信でトークン漏洩リスク)
  # ❌ BackendConfig(タイムアウト/ヘルスチェックカスタマイズ)なし
  # ❌ Ingress API は Gateway API に比べて表現力が低く、リダイレクト・ヘッダー操作が困難
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coupon-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: coupon-api
  template:
    metadata:
      labels:
        app: coupon-api
    spec:
      containers:
      - name: coupon-api
        image: asia-northeast1-docker.pkg.dev/myproject/coupon-api:latest  # ❌ latest タグ(再現性なし)
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "4"       # ❌ リクエストの40倍(Autopilot でノードサイズ過小見積もり)
            memory: "4Gi"  # ❌ リクエストの32倍(OOM kill リスク)
        env:
        - name: DB_PASSWORD
          value: "supersecret"  # ❌ 平文 env(K8s Secret / Secret Manager 未使用)
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 3   # ❌ 3秒は Gunicorn 起動に短すぎ(再起動ループ)
          periodSeconds: 10
        # ❌ readinessProbe なし(起動中もトラフィックを受けてエラー率上昇)
        # ❌ startupProbe なし(起動完了前に liveness が kill する)
        securityContext:
          runAsRoot: true   # ❌ root 実行(コンテナエスケープ時の影響が最大)
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: coupon-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coupon-api
  minReplicas: 1   # ❌ 最小1では単一障害点(node drain / 再起動で全停止)
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 90   # ❌ 90%では HPA 反応前に過負荷(推奨60〜70%)
# ❌ PodDisruptionBudget 未定義(デプロイ中に全 Pod が同時終了する可能性)
# ❌ NetworkPolicy 未定義(Namespace 内の全 Pod 間通信が許可されている)

ヒント(段階的開示)

ヒント1 — 方向性
問題点は「ルーティング設計」「セキュリティ」「スケーリング」「信頼性」の4象限に分類できる。Ingress → Gateway API の移行は IngressClass annotation を廃止し、Gateway + HTTPRoute リソースで宣言的に定義するのがポイント。GKE では gke-l7-global-external-managed という GatewayClass が標準で提供されている。
ヒント2 — アプローチ(構成要素)
  • Gateway API: Gateway で TLS 終端。HTTPRoute でパスルーティング + HTTP→HTTPS リダイレクト + レスポンスヘッダー(HSTS)設定
  • SecurityContext: runAsNonRoot: true + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities.drop: [ALL] の4点セット
  • Probe 設計: startupProbereadinessProbelivenessProbe の順。initialDelaySeconds は startup < readiness < liveness の順で大きく設定
  • HPA: CPU ターゲット 65%。scaleDown.stabilizationWindowSeconds: 300 でフラッピング防止
  • PDB: minAvailable: 1 で常に最低1 Pod を保証。maxUnavailable: 0 の RollingUpdate と組み合わせる
  • NetworkPolicy: Ingress は Gateway コントローラー Namespace からのみ。Egress は DNS / Cloud SQL / Secret Manager API のみ許可
ヒント3 — Gateway API と Probe の骨格
Gateway API の骨格
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: coupon-gateway
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
  - name: https
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: api-tls-secret
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: coupon-api-route
spec:
  parentRefs:
  - name: coupon-gateway
    sectionName: https
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/v1/coupons
    backendRefs:
    - name: coupon-api
      port: 8080
Probe 3種の骨格
# startupProbe: 起動完了まで liveness を無効化
startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 20   # 20 × 3秒 = 最大60秒
  periodSeconds: 3

# readinessProbe: トラフィック受け付け制御
readinessProbe:
  httpGet:
    path: /readyz
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

# livenessProbe: デッドロック検知(再起動トリガー)
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30  # readiness より長く
  periodSeconds: 15

問題点分析

ネットワーク / ルーティング(3問題)

#問題点影響改善方法
1annotation 形式の IngressClass1.28以降は非推奨。GKE では spec.ingressClassName を使うか Gateway API へ移行Gateway API(HTTPRoute)へ移行
2TLS 設定なし(HTTP のみ)平文通信でアクセストークン・クーポンコードが漏洩するリスクGateway の listener に TLS Terminate を設定
3HTTP → HTTPS リダイレクト未設定HTTP アクセスが平文のまま通過する専用 HTTPRoute で 301 リダイレクト

セキュリティ(3問題)

#問題点影響改善方法
4DB_PASSWORD が平文 envYAML が git に commit されると認証情報が漏洩Secret Manager → K8s Secret → secretKeyRef
5runAsRoot: trueコンテナエスケープ時にホストの root 権限を取得されるrunAsNonRoot: true + capabilities.drop: [ALL]
6NetworkPolicy 未定義Namespace 内の全 Pod が相互通信可能(横断侵害リスク)NetworkPolicy で ingress/egress を最小権限に制限

スケーリング(3問題)

#問題点影響改善方法
7image :latest タグビルドごとに異なるイメージが使われ再現性がないimage digest 固定(@sha256:xxx
8CPU limits が requests の40倍GKE Autopilot がノードサイズを過小見積もりし CPU スロットリング多発Autopilot では requests ≈ limits(Guaranteed QoS 推奨)
9HPA CPU ターゲット 90%HPA が反応してから Pod が Ready になるまでのラグ(30〜60秒)の間に過負荷65% に変更 + scaleDown stabilizationWindow でフラッピング防止

信頼性(2問題)

#問題点影響改善方法
10readinessProbe / startupProbe なし + livenessProbe の initialDelay 短すぎ起動中のコンテナにトラフィックが流れてエラー率上昇。または起動前に Kill されて再起動ループ3種の Probe を設計・initialDelay を適切に設定
11PodDisruptionBudget 未定義・minReplicas: 1node drain 時に全 Pod が同時終了し単一障害点になるPDB minAvailable: 1 + HPA minReplicas: 2

アーキテクチャ図 — Ingress (Before) vs Gateway API (After)

Before: Ingress(問題あり) Internet HTTP Ingress annotation 方式・TLS なし coupon-api Pod root実行 / latest タグ liveness のみ(delay 3秒) minReplicas: 1 / HPA 90% NetworkPolicy なし 全 Pod 間通信許可 PDB なし / PW 平文 After: Gateway API(改善後) Internet HTTP HTTPS Gateway TLS Terminate / HTTP→301 gke-l7-global-external-managed HTTPRoute /api/v1/coupons → service:8080 coupon-api Pod × 2+ runAsNonRoot + digest固定 startup / readiness / liveness HPA 65% / PDB minAvailable:1 Secret Manager → secretKeyRef readOnlyRootFilesystem: true NetworkPolicy(最小権限) Ingress: Gateway NS のみ Egress: DNS / SQL / HTTPS デフォルト全拒否で攻撃対象範囲を最小化 Cert Manager 自動更新

模範解答

# good_gateway.yaml
# ── Gateway(HTTPS 終端 + GKE マネージドロードバランサ)──────────────────
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: coupon-gateway
  namespace: coupon-ns
  annotations:
    networking.gke.io/certmap: "coupon-cert-map"  # Certificate Manager で自動更新
spec:
  gatewayClassName: gke-l7-global-external-managed  # ✅ GKE 標準 GatewayClass
  listeners:
  - name: https
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate                               # ✅ TLS 終端
      certificateRefs:
      - kind: Secret
        name: api-tls-secret
        namespace: coupon-ns
  - name: http-redirect
    port: 80
    protocol: HTTP                                  # ✅ HTTP 受付(リダイレクト用)
---
# ── HTTPRoute(HTTPS パスルーティング + HSTS ヘッダー)────────────────────
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: coupon-api-route
  namespace: coupon-ns
spec:
  parentRefs:
  - name: coupon-gateway
    namespace: coupon-ns
    sectionName: https                              # ✅ HTTPS listener に紐付け
  hostnames:
  - "api.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api/v1/coupons
    backendRefs:
    - name: coupon-api
      port: 8080
      weight: 100
    filters:
    - type: ResponseHeaderModifier
      responseHeaderModifier:
        set:
        - name: Strict-Transport-Security          # ✅ HSTS ヘッダー追加
          value: "max-age=31536000; includeSubDomains"
---
# ── HTTP → HTTPS 301 リダイレクト ─────────────────────────────────────────
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-to-https-redirect
  namespace: coupon-ns
spec:
  parentRefs:
  - name: coupon-gateway
    sectionName: http-redirect
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301                            # ✅ 永続リダイレクト
# good_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coupon-api
  namespace: coupon-ns
  labels:
    app: coupon-api
    version: "1.0.0"
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0        # ✅ ゼロダウンタイムデプロイ(PDB と組み合わせ)
  selector:
    matchLabels:
      app: coupon-api
  template:
    metadata:
      labels:
        app: coupon-api
    spec:
      serviceAccountName: coupon-api-sa  # ✅ Workload Identity 用 SA
      securityContext:
        runAsNonRoot: true               # ✅ 非 root 実行
        runAsUser: 1000
        fsGroup: 1000
      containers:
      - name: coupon-api
        # ✅ image digest 固定(latest タグ禁止・再現性確保)
        image: asia-northeast1-docker.pkg.dev/myproject/coupon-api@sha256:abc123def456
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: "250m"
            memory: "256Mi"
          limits:
            cpu: "500m"     # ✅ requests の2倍以内(Autopilot Guaranteed QoS に近い設定)
            memory: "512Mi" # ✅ requests の2倍(OOM kill リスクを抑えつつ余裕を確保)
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:                # ✅ Secret Manager → K8s Secret → secretKeyRef
              name: coupon-api-secrets
              key: db-password
        - name: APP_ENV
          value: "production"
        # ✅ startupProbe: 起動完了まで liveness を抑止(再起動ループ防止)
        startupProbe:
          httpGet:
            path: /healthz
            port: 8080
          failureThreshold: 20           # 最大 20 × 3秒 = 60秒 待機
          periodSeconds: 3
        # ✅ readinessProbe: トラフィック受け付け可否を制御
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 10        # Gunicorn 起動を考慮
          periodSeconds: 5
          failureThreshold: 3
          successThreshold: 1
        # ✅ livenessProbe: デッドロック検知・Pod 再起動(readiness より長い delay)
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 30        # readiness (10) より必ず長く
          periodSeconds: 15
          failureThreshold: 3
        securityContext:
          allowPrivilegeEscalation: false  # ✅ 特権昇格禁止
          readOnlyRootFilesystem: true     # ✅ 読み取り専用FS(GKE Autopilot 推奨)
          capabilities:
            drop:
            - ALL                          # ✅ 全 capability を削除
        volumeMounts:
        - name: tmp-dir
          mountPath: /tmp                  # ✅ 書き込み可能な /tmp は emptyDir で分離
      volumes:
      - name: tmp-dir
        emptyDir: {}
# HPA + PDB
# ── HorizontalPodAutoscaler(CPU 65% + scaleDown stabilization)─────────
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: coupon-api-hpa
  namespace: coupon-ns
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: coupon-api
  minReplicas: 2    # ✅ 単一障害点を排除(node drain 時も1 Pod が生き残る)
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 65   # ✅ スパイク前にスケールアウト開始(90%は遅すぎ)
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70   # ✅ CPU と複合指標でより正確なスケーリング
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # ✅ 5分待機でフラッピング防止
      policies:
      - type: Pods
        value: 1
        periodSeconds: 60              # 1分に最大1 Pod ずつスケールダウン
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Pods
        value: 2
        periodSeconds: 60              # 1分に最大2 Pod ずつスケールアップ
---
# ── PodDisruptionBudget(デプロイ / node drain 中の可用性保証)──────────
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: coupon-api-pdb
  namespace: coupon-ns
spec:
  minAvailable: 1    # ✅ 常に最低1 Pod が稼働(maxUnavailable: 0 の RollingUpdate と二重保護)
  selector:
    matchLabels:
      app: coupon-api
# NetworkPolicy(最小権限ネットワーク)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: coupon-api-netpol
  namespace: coupon-ns
spec:
  podSelector:
    matchLabels:
      app: coupon-api
  policyTypes:
  - Ingress
  - Egress
  ingress:
  # ✅ Gateway コントローラー(gke-system NS)からのみ 8080 受信を許可
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: gke-system
    ports:
    - protocol: TCP
      port: 8080
  egress:
  # ✅ DNS 名前解決(必須)
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
  # ✅ Cloud SQL Proxy(VPC 内部 IP のみ)
  - to:
    - ipBlock:
        cidr: 10.0.0.0/8
    ports:
    - protocol: TCP
      port: 5432
  # ✅ Secret Manager / Cloud Monitoring API(外部 HTTPS のみ)
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.0.0.0/8    # VPC 内部は上記ルールに委ねる
    ports:
    - protocol: TCP
      port: 443

ポイント解説

1 Ingress → Gateway API 移行の本質(役割分離)
Ingress は1つのリソースに「インフラ設定(ロードバランサ種別)」と「ルーティングロジック(パスマッピング)」が混在していた。Gateway API では GatewayClass(クラスター管理者)→ Gateway(インフラチーム)→ HTTPRoute(アプリチーム)の3層分離により、チーム間の権限境界が明確になる。GKE Autopilot では gke-l7-global-external-managed GatewayClass が標準で提供される。
2 Probe 3種の設計原則
startupProbe は「起動完了するまで liveness を無効化する」役割。failureThreshold × periodSeconds が最大起動待機時間になるため、重いアプリは大きめに設定する(例: 20 × 3秒 = 60秒)。initialDelaySeconds は必ず startup < readiness < liveness の順で大きくすること。逆にすると liveness が readiness より先に失敗して不必要な再起動ループが起きる。
3 GKE Autopilot での CPU requests:limits 設計
GKE Autopilot はノードリソースを Pod の requests 合計から計算してプロビジョニングする。limitsrequests の5〜10倍だと、実際の負荷がリクエスト値を大幅に超えた時にノードが過負荷になりスロットリングが多発する。Autopilot の推奨は requests == limits(Guaranteed QoS)または最大2倍以内(Burstable QoS)。
4 HPA ターゲット 65% の数学的根拠
HPA がスケールアウト決定 → Pod のスケジューリング → コンテナ起動 → readiness 通過 までのラグは通常 30〜60秒。CPU が 90% に達してからスケールアウトを開始すると、このラグの間 90%+ で動作し続けてタイムアウトが多発する。65% でトリガーすれば、Pod が Ready になる頃には 90% を超えていないマージンが生まれる。
5 PDB × RollingUpdate maxUnavailable: 0 の二重保護
RollingUpdate.maxUnavailable: 0 はデプロイ中に現在の Pod 数を維持する。PDB.minAvailable: 1 は Voluntary Disruption(node drain 等)時に最低1 Pod を保証する。この組み合わせにより、デプロイ・メンテナンス・スケールダウンのどのシナリオでも可用性が維持される。

実務への応用

  • Gateway API でのサービス分割: MOps バッチ(/batch/*)と クーポン配信 API(/api/*)を同一 Gateway で別 HTTPRoute に分割することで、スケーリング戦略を独立させられる(バッチは HPA なし固定 replica、API は HPA 有効)
  • readOnlyRootFilesystem の運用: GKE Autopilot のセキュリティプロファイル(Baseline/Restricted)では readOnlyRootFilesystem: true が強制される場合がある。アプリが /tmp/var/run への書き込みを必要とする場合は最初から emptyDir を設計に含めること
  • NetworkPolicy の段階的導入: 既存環境に NetworkPolicy を追加すると既存通信を遮断するリスクがある。まず policyTypes: [Ingress] のみで Ingress 制限を確認し、通信ログ(kubectl logs / DataDog)で問題がないことを確認してから Egress を追加するフェーズドアプローチが安全

今日のまとめ

GKE インフラ改善の核心は「Gateway API による責務分離」「Probe 3種による起動〜稼働の完全制御」「HPA + PDB + RollingUpdate の三位一体による SLO 維持」の3本柱。

SecurityContext と NetworkPolicy は「設計段階から組み込む」ことが原則——後から追加すると既存の動作との衝突調査に時間がかかる。GKE Autopilot では特にリソース設計(requests ≈ limits)と読み取り専用 FS の前提が強く、最初から Autopilot を意識した設計をすることが生産性を高める。

自己評価

自分の回答

気づき・メモ