概要
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 設計:
startupProbe→readinessProbe→livenessProbeの順。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問題)
| # | 問題点 | 影響 | 改善方法 |
|---|---|---|---|
| 1 | 旧 annotation 形式の IngressClass | 1.28以降は非推奨。GKE では spec.ingressClassName を使うか Gateway API へ移行 | Gateway API(HTTPRoute)へ移行 |
| 2 | TLS 設定なし(HTTP のみ) | 平文通信でアクセストークン・クーポンコードが漏洩するリスク | Gateway の listener に TLS Terminate を設定 |
| 3 | HTTP → HTTPS リダイレクト未設定 | HTTP アクセスが平文のまま通過する | 専用 HTTPRoute で 301 リダイレクト |
セキュリティ(3問題)
| # | 問題点 | 影響 | 改善方法 |
|---|---|---|---|
| 4 | DB_PASSWORD が平文 env | YAML が git に commit されると認証情報が漏洩 | Secret Manager → K8s Secret → secretKeyRef |
| 5 | runAsRoot: true | コンテナエスケープ時にホストの root 権限を取得される | runAsNonRoot: true + capabilities.drop: [ALL] |
| 6 | NetworkPolicy 未定義 | Namespace 内の全 Pod が相互通信可能(横断侵害リスク) | NetworkPolicy で ingress/egress を最小権限に制限 |
スケーリング(3問題)
| # | 問題点 | 影響 | 改善方法 |
|---|---|---|---|
| 7 | image :latest タグ | ビルドごとに異なるイメージが使われ再現性がない | image digest 固定(@sha256:xxx) |
| 8 | CPU limits が requests の40倍 | GKE Autopilot がノードサイズを過小見積もりし CPU スロットリング多発 | Autopilot では requests ≈ limits(Guaranteed QoS 推奨) |
| 9 | HPA CPU ターゲット 90% | HPA が反応してから Pod が Ready になるまでのラグ(30〜60秒)の間に過負荷 | 65% に変更 + scaleDown stabilizationWindow でフラッピング防止 |
信頼性(2問題)
| # | 問題点 | 影響 | 改善方法 |
|---|---|---|---|
| 10 | readinessProbe / startupProbe なし + livenessProbe の initialDelay 短すぎ | 起動中のコンテナにトラフィックが流れてエラー率上昇。または起動前に Kill されて再起動ループ | 3種の Probe を設計・initialDelay を適切に設定 |
| 11 | PodDisruptionBudget 未定義・minReplicas: 1 | node drain 時に全 Pod が同時終了し単一障害点になる | PDB minAvailable: 1 + HPA minReplicas: 2 |
アーキテクチャ図 — Ingress (Before) vs Gateway API (After)
模範解答
# 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 では
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 の
GKE Autopilot はノードリソースを Pod の
requests 合計から計算してプロビジョニングする。limits が requests の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% を超えていないマージンが生まれる。
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 を意識した設計をすることが生産性を高める。
SecurityContext と NetworkPolicy は「設計段階から組み込む」ことが原則——後から追加すると既存の動作との衝突調査に時間がかかる。GKE Autopilot では特にリソース設計(requests ≈ limits)と読み取り専用 FS の前提が強く、最初から Autopilot を意識した設計をすることが生産性を高める。