クラウドネイティブ GameDay: Quest 全問振り返りと解説
注意: この記事にはクラウドネイティブ会議で開催した GameDay のネタバレを含みます。
クラウドネイティブ会議 実行委員の @kikulabo です。
GameDay にご参加いただきありがとうございました。この記事では「どこを見ればよかったのか」「Verifier は実際に何を見ているのか」を解説します。
GameDay の概要
本 GameDay は OpenTelemetry Demo をベースとした EC マイクロサービス構成(frontend、frontend-proxy、cart、checkout、payment、product-catalog、recommendation、ad などのサービス群)を題材としています。各チームには専用の Kubernetes namespace と、Code Server / Grafana / Jaeger / ArgoCD / GitHub リポジトリが用意されており、注入された障害を発見・修正してスコアを競う形式で進行しました。
- Quest 数: 全 11 問
- 合計配点: 4,800 pt
- カテゴリ: tutorial / gitops / troubleshooting / performance / networking
- 基本フロー: 障害発見 → GitHub のマニフェスト修正 → commit & push → ArgoCD が自動同期 → Verifier で合否判定
用語の説明
本記事に登場する GameDay 環境固有の用語を整理します。
- team namespace: 各チームに割り当てられた Kubernetes namespace。Pod・Service・Deployment などのリソースはすべてこの namespace 内に閉じています。
- Code Server: ブラウザ上で利用できるエディタ環境。ターミナルから
kubectlを実行したり、GitHub リポジトリのマニフェストを編集したりするのに使います。 - Verifier: 各 Quest の合否を自動判定するシステム。クラスタの状態や HTTP/TCP 応答などをチェックし、所定の条件を満たせば合格となります。
- ArgoCD: GitOps による自動デプロイを担うコンポーネント。GitHub リポジトリの変更を検知して Kubernetes にマニフェストを適用します。
- ヒント (L1 / L2 / L3): 各 Quest にはレベル別のヒントが用意されており、開示するとその点数分が配点から差し引かれます。
Quest 一覧
| ID | Slug | カテゴリ | 難易度 | 配点 |
|---|---|---|---|---|
| quest-01 | pod-count | tutorial | tutorial | 200 |
| quest-02 | trace-deps | tutorial | tutorial | 200 |
| quest-03 | top-service | tutorial | tutorial | 200 |
| quest-04 | gitops-deploy | gitops | tutorial | 200 |
| quest-05 | fix-image | troubleshooting | normal | 300 |
| quest-06 | fix-envvar | troubleshooting | normal | 500 |
| quest-07 | fix-probe | troubleshooting | normal | 500 |
| quest-08 | fix-oom | performance | normal | 500 |
| quest-09 | fix-latency | performance | hard | 500 |
| quest-10 | fix-graceful-shutdown | networking | hard | 700 |
| quest-11 | fix-nftables | networking | hard | 1000 |
quest-01 (pod-count) — 環境を確認せよ
- カテゴリ: tutorial
- 難易度: tutorial
- 配点: 200 pt
- ヒント: なし
シナリオ
Code Server のターミナルを開いて kubectl get pods を打ち、チームの namespace にどんな Pod が存在するかを把握するチュートリアルです。
何が壊れていたのか
障害注入なし。 このクエストは環境確認が目的で、正常状態の Pod を数えるだけです。
観測されるはずの症状
NAME READY STATUS RESTARTS AGE
frontend-... 1/1 Running 0 5m
cart-... 1/1 Running 0 5m
checkout-... 1/1 Running 0 5m
...
STATUS が Running かつ READY が 1/1(全コンテナが Ready)の Pod を数えます。
想定の解き方
- Code Server のターミナルを開く
kubectl get podsを実行し、RunningかつREADYが全コンテナ Ready な Pod の数を数える- 数えた Pod の数を回答に提出
Verifier の合格条件
Verifier はその時点の namespace の Running かつ全コンテナ Ready な Pod 数を取得し、回答との差が 1 以内であれば合格です(誤差±1 を許容)。
quest-02 (trace-deps) — サービスの依存関係を調べよ
- カテゴリ: tutorial
- 難易度: tutorial
- 配点: 200 pt
- ヒント: L1=50 / L2=50
シナリオ
大規模なマイクロサービスシステムでは、「どのサービスが誰に呼ばれているのか」を把握することが障害対応の第一歩です。分散トレーシング(Jaeger)を使って frontend サービスを直接呼び出している上流サービスを特定してください。
何が壊れていたのか
障害注入なし。 Jaeger UI でスパンの親子関係を読み解く観察クエストです。
想定の解き方
https://<your-team>.gameday.kaigi.cloudnativedays.jp/jaeger/ui/にアクセス- Service を
frontendに絞ってFind Tracesを実行 - 任意のトレースを開き、
frontendスパンの親スパン(一段上)が何サービスか確認する - そのサービス名(
frontend-proxy)を回答する
Verifier の合格条件
回答が frontend-proxy であれば合格です。
quest-03 (top-service) — リクエスト数が多いサービスを特定せよ
- カテゴリ: tutorial
- 難易度: tutorial
- 配点: 200 pt
- ヒント: L1=50 / L2=50
シナリオ
Grafana ダッシュボードを使って、直近 5 分間でスループットが上位 2 つのサービスを特定してください。
何が壊れていたのか
障害注入なし。 Grafana でメトリクスを読む観察クエストです。
想定の解き方
- Grafana に移動し、
Service Throughput & Latencyダッシュボードを開く - 「Top 7 Services Mean Rate over Range」パネルで上位 2 サービスを確認する
- カンマ区切りで回答する
Verifier の合格条件
回答セットが {frontend, frontend-proxy} と順序を問わず完全一致すれば合格です。
quest-04 (gitops-deploy) — 変更をデプロイせよ
- カテゴリ: gitops
- 難易度: tutorial
- 配点: 200 pt
- ヒント: なし
シナリオ
チームの GitHub リポジトリに PR が 1 件用意されています。内容を確認して main にマージし、ArgoCD が自動同期して変更が適用されることを確認してください。
何が壊れていたのか
障害注入なし。 GitOps フロー(PR レビュー → マージ → ArgoCD 自動同期)を体験するクエストです。
想定の解き方
- チーム GitHub リポジトリを開く
- 用意されている PR の内容を確認する(
frontendのreplicaCountを 1 → 2 に増やすような変更) - マージする
- ArgoCD UI(
https://<your-team>.gameday.kaigi.cloudnativedays.jp/argocd/)で Application がSyncedになり、frontendの Pod が増えることを確認する
Verifier の合格条件
- ArgoCD Application が
Syncedになっていること frontendDeployment のavailableReplicas >= 2であること
quest-05 (fix-image) — Pod の起動失敗を解消せよ
- カテゴリ: troubleshooting
- 難易度: normal
- 配点: 300 pt
- ヒント: L1=75 / L2=75
シナリオ
payment サービスの Pod が起動していません。原因を調査して復旧させてください。
何が壊れていたのか
payment Deployment のコンテナイメージ名に意図的な typo が入っています。
# 壊れている状態
containers:
- name: payment
image: ghcr.io/open-telemetry/demo:2.2.0-payment-hogehoge # ← 存在しないタグ
# 正しい状態
containers:
- name: payment
image: ghcr.io/open-telemetry/demo:2.2.0-payment
ghcr.io に :2.2.0-payment-hogehoge というタグは存在しないため、kubelet がイメージを Pull できません。
観測されるはずの症状
NAME READY STATUS RESTARTS AGE
payment-xxx-xxx 0/1 ImagePullBackOff 0 30m
kubectl describe pod payment-xxx-xxx の Events:
Normal BackOff 3m45s (x945 over 3h38m) kubelet Back-off pulling image "ghcr.io/open-telemetry/demo:2.2.0-payment-hogehoge"
Warning Failed 3m45s (x945 over 3h38m) kubelet Error: ImagePullBackOff
想定の解き方
kubectl get podsでpaymentがImagePullBackOff状態を確認kubectl describe pod <payment-pod>の Events でタグ名を確認paymentDeployment のマニフェストを開き、imageからhogehogeを削除する- commit & push → ArgoCD が自動同期 → Pod が
Runningになる
Verifier の合格条件
paymentDeployment のavailableReplicas > 0であることpaymentPod の restart 合計が 5 以下であること
quest-06 (fix-envvar) — カートサービスの障害を復旧せよ
- カテゴリ: troubleshooting
- 難易度: normal
- 配点: 500 pt
- ヒント: L1=150 / L2=150
シナリオ
「カートに商品を追加できない」というユーザー報告が来ています。cart サービスを調査して復旧させてください。
何が壊れていたのか
cart Deployment の環境変数 VALKEY_ADDR(Valkey/Redis の接続先)が存在しないホスト名に変更されています。
# 壊れている状態
env:
- name: VALKEY_ADDR
value: "valkey-cart-typo:6379" # ← 存在しない Service 名
# 正しい状態
env:
- name: VALKEY_ADDR
value: "valkey-cart:6379"
namespace 内に valkey-cart という Service は実在しますが、valkey-cart-typo は存在しないため名前解決に失敗します。
観測されるはずの症状
kubectl get pods で cart Pod が CrashLoopBackOff と表示されています。
kubectl logs <cart-pod> には接続エラーが出ています。
Defaulted container "cart" out of: cart, wait-for-valkey-cart (init)
fail: cart.cartstore.ValkeyCartStore[0]
Wasn't able to connect to redis
Unhandled exception. System.ApplicationException: Wasn't able to connect to redis
at cart.cartstore.ValkeyCartStore.EnsureRedisConnected() in /usr/src/app/src/cartstore/ValkeyCartStore.cs:line 101
想定の解き方
kubectl logs <cart-pod>でアプリのエラーを確認する- Deployment の env セクションを確認し、
VALKEY_ADDRの値を見つける kubectl get svcで実在する Service 名を確認して突き合わせる- GitHub リポジトリで
VALKEY_ADDRをvalkey-cart:6379に修正 → commit & push → ArgoCD 同期
Verifier の合格条件
cartDeployment のavailableReplicas > 0であることcartPod の restart 合計が 5 以下であること
quest-07 (fix-probe) — product-catalog の障害を解消せよ
- カテゴリ: troubleshooting
- 難易度: normal
- 配点: 500 pt
- ヒント: L1=150 / L2=150
シナリオ
product-catalog サービスが正常に稼働していません。Pod の状態と Events から原因を特定して修正してください。
何が壊れていたのか
product-catalog Deployment に 誤ったポートを参照する liveness probe が注入されています。
# 壊れている状態(注入された patch)
livenessProbe:
tcpSocket:
port: 9090 # ← アプリは 8080 で listen しているのに 9090 を見ている
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
アプリ本体は containerPort: 8080 で listen しているため、kubelet が 9090 へ TCP 接続を試みるたびに connection refused になります。3 回失敗するとコンテナが再起動されます。
観測されるはずの症状
NAME READY STATUS RESTARTS AGE
product-catalog-xxx-xxx 0/1 CrashLoopBackOff 31 87m
kubectl describe pod <product-catalog-pod> の Events
Normal Created 57m (x60 over 3h52m) kubelet Container created
Normal Killing 36m (x65 over 3h51m) kubelet Container product-catalog failed liveness probe, will be restarted
Warning Unhealthy 12m (x217 over 3h52m) kubelet Liveness probe failed: dial tcp 10.1.52.27:9090: connect: connection refused
Warning BackOff 82s (x201 over 3h52m) kubelet Back-off restarting failed container product-catalog in pod product-catalog-85b859b798-6jpc2_team-kikulabo(93d50669-4aca-46a2-916f-07dfd6ba2b30)
想定の解き方
kubectl describe pod <product-catalog-pod>の Events で "Liveness probe failed" を確認- Deployment の
livenessProbe.tcpSocket.portがcontainerPortと一致していないことを確認 - GitHub リポジトリで
livenessProbe.tcpSocket.portを8080に修正する(または probe ブロックごと削除してベースに戻す) - commit & push → ArgoCD 同期 → 新しい Pod が立ち上がる
Verifier の合格条件
product-catalogDeployment のavailableReplicas > 0であることproduct-catalogPod の restart 合計が 0 であること
quest-08 (fix-oom) — checkout サービスを安定させよ
- カテゴリ: performance
- 難易度: normal
- 配点: 500 pt
- ヒント: L1=100 / L2=100 / L3=100
シナリオ
checkout サービスが繰り返し再起動しています。安定して稼働するよう修正してください。
何が壊れていたのか
checkout Deployment に極端に小さい memory limit が設定されています。
# 壊れている状態
resources:
limits:
cpu: 100m
memory: 8Mi # 極端に小さい値が設定されている
requests:
cpu: 50m
memory: 4Mi
# 正しい状態(例)
resources:
limits:
cpu: 100m
memory: 16Mi # 適切な値に設定する
requests:
cpu: 50m
memory: 4Mi
Go ランタイムは GOMEMLIMIT=256MiB を信じてメモリを確保しようとしますが、cgroup の上限は 8Mi なので Linux カーネルに OOM Kill されます。
観測されるはずの症状
NAME READY STATUS RESTARTS AGE
checkout-c84974566-qjdjf 0/1 OOMKilled 50 (5m52s ago) 4h6m
kubectl describe pod <checkout-pod> の Last State:
Last State: Terminated
Reason: OOMKilled ← ここ
Exit Code: 137
想定の解き方
kubectl get podsでcheckoutがCrashLoopBackOffを確認kubectl describe pod <checkout-pod>の Last State で Reason がOOMKilledであることを確認checkoutDeployment のresources.limits.memoryを適切な値(最低 16Mi)に引き上げる- commit & push → ArgoCD 同期
Verifier の合格条件
checkoutDeployment のavailableReplicas > 0であることcheckoutPod の restart 合計が 0 であること
quest-09 (fix-latency) — recommendation の遅い API を改善せよ
- カテゴリ: performance
- 難易度: hard
- 配点: 500 pt
- ヒント: L1=150 / L2=150
シナリオ
recommendation サービスのレスポンスが異常に遅いです。原因を調査し、コードを修正してデプロイし直してください。
何が壊れていたのか
recommendation Deployment のイメージが、意図的な time.sleep を埋め込んだカスタムビルドに差し替えられています。
# 壊れている状態
containers:
- name: recommendation
image: ghcr.io/cloudnativedaysjp/cnk-gameday/gameday-recommendation:latest # time.sleep 入り
imagePullSecrets:
- name: ghcr-pull-secret
# 正しい状態(修正後)
containers:
- name: recommendation
image: ghcr.io/open-telemetry/demo:2.2.0-recommendation
このカスタムイメージ内の src/recommendation/recommendation_server.py の get_product_list 関数に time.sleep(0.5 + random.random()) が仕込まれており、リクエストごとに数秒の遅延が発生します。
観測されるはずの症状
Pod は 1/1 Running で Restart もなし。しかし Jaeger でトレースを確認すると、recommendation サービスの get_product_list スパンだけが異常に長い:
frontend-proxy ──────────────────────────────────────────── 5.23s
frontend ──────────────────────────────────────────────── 5.21s
recommendation get_product_list ──────────────────────── 5.19s ← 長いスパンがある
想定の解き方
- Jaeger で
recommendationサービスのスパンを確認し、get_product_listが極端に遅いことを特定 src/recommendation/recommendation_server.pyを開くget_product_list関数内のtime.sleep(...)行を削除する- main ブランチに commit & push
- GitHub Actions が自動でイメージをビルドして push し、マニフェストのイメージタグを自動更新する(数分かかります)
- ArgoCD が新しいイメージで
recommendationを再デプロイ - Verifier を実行(
/api/recommendationsの応答が 550ms 以下になっていれば合格)
※ quest-10 を先に解くと quest-09 が安定して通りやすくなります
Verifier は
frontend-proxy経由で/api/recommendationsを 1 回叩き、550ms 以内の応答を要求します。OTel Demo のfrontendは/api/recommendationsを処理する際、recommendation サービスだけでなく ad サービスにも並列でリクエストを送ります。quest-10 の注入障害(
ad-deployerCronJob)は毎分adPod を再起動しています。gRPC は一度確立したコネクションを使い回す仕組みのため、Pod が再起動して古いコネクションが切れると、frontendは新しいコネクションを張り直すまでの間リクエストを送れません。この待ち時間が数百ミリ秒単位で発生するため、recommendationの遅延を除去しても応答全体が 550ms を超えることがあります。ただし Verifier は 1 回しか計測しないため、たまたま
adが安定している瞬間に verify を実行すれば quest-10 未解決でも合格できます。つまり「必ず通らない」ではなく「タイミング次第で不安定」というのが正確な状況です。先に quest-10 を解いておくとこの運要素を排除できます。
Verifier の合格条件
recommendationDeployment のavailableReplicas > 0かつ restart 合計 ≤ 5http://frontend-proxy.<team-ns>.svc.cluster.local:8080/api/recommendations?productIds=OLJCESPC7Z&sessionId=verify¤cyCode=USDへの GET リクエストの応答時間が 550ms 以下
quest-10 (fix-graceful-shutdown) — ad サービスのデプロイ時エラーを解消せよ
- カテゴリ: networking
- 難易度: hard
- 配点: 700 pt
- ヒント: L1=100 / L2=100 / L3=200
シナリオ
広告チームから「広告のインプレッション(imp)数が想定より数%少なく、ムラがある」との報告があります。アプリログにはエラー率 0% と表示されており、広告チームは「ad サービスは問題ない」と主張しています。原因を調査して解消してください。
何が壊れていたのか
ad-deployer という CronJob が namespace に注入されています。この CronJob は毎分 kubectl rollout restart deployment/ad を実行します。
apiVersion: batch/v1
kind: CronJob
metadata:
name: ad-deployer
spec:
schedule: "* * * * *" # 毎分
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
serviceAccountName: ad-deployer
containers:
- name: ad-deployer
image: bitnami/kubectl:latest
command: [kubectl, rollout, restart, deployment/ad]
ad Deployment にはもともと readinessProbe も lifecycle.preStop も設定されていません。そのため、毎分の rollout restart 中に以下が起きます:
- 新しい Pod が作成されるが、readiness probe がないため Ready 状態になる前から Service の Endpoints に登録される
- 古い Pod は SIGTERM を受けたほぼ直後に Endpoints から外されるが、すでに処理中だったリクエストが切れる
- 数秒間、接続失敗が散発的に発生する
結果として ad アプリ自体はエラー 0%(アプリから見ると正常終了)でも、クライアント側(frontend)から見ると接続断が散発します。imp 数が「数%少なく、ムラがある」(rollout restart の周期と一致した周期的失敗)という症状になります。
観測されるはずの症状
NAME READY STATUS RESTARTS AGE
ad-78c678ff99-k9295 1/1 Running 0 45s ← 数十秒ごとに入れ替わる
Deployment の metadata.generation が競技開始から 1 分ごとに増え続けます。
kubectl get events -n <team-ns> --sort-by=.lastTimestamp | grep ad を実行すると、毎分ほぼ同じイベント列が繰り返されます:
28s Normal Pulling pod/ad-deployer-29645771-k2g66 Pulling image "bitnami/kubectl:latest"
27s Normal Pulled pod/ad-deployer-29645771-k2g66 Successfully pulled image "bitnami/kubectl:latest" in 943ms
27s Normal Created pod/ad-deployer-29645771-k2g66 Container created
26s Normal Started pod/ad-deployer-29645771-k2g66 Container started
25s Normal Scheduled pod/ad-5f649bcdd8-fgw9c Successfully assigned .../ad-5f649bcdd8-fgw9c to ...
25s Normal Pulled pod/ad-5f649bcdd8-fgw9c Container image already present on machine
25s Normal Created pod/ad-5f649bcdd8-fgw9c Container created
25s Normal SuccessfulCreate replicaset/ad-5f649bcdd8 Created pod: ad-5f649bcdd8-fgw9c
24s Normal Started pod/ad-5f649bcdd8-fgw9c Container started
24s Normal Killing pod/ad-7886f794f9-vzq7q Stopping container ad ← 古い Pod が SIGTERM
24s Normal SuccessfulDelete replicaset/ad-7886f794f9 Deleted pod: ad-7886f794f9-vzq7q
23s Normal Completed job/ad-deployer-29645771 Job completed
kubectl logs <frontend-pod> では、ad への gRPC 接続失敗が約 1 分おきにまとめて記録されます:
# --- 06:26:35 頃(古い ad Pod が SIGTERM を受けた直後)---
Error: 14 UNAVAILABLE: No connection established. Last error: connect ECONNREFUSED 172.20.131.140:8080
Error: 14 UNAVAILABLE: Connection dropped
# --- 約 1 分後: 06:27:15 頃(次の rollout restart)---
Error: 14 UNAVAILABLE: No connection established. Last error: connect ECONNREFUSED 172.20.165.95:8080
Error: 14 UNAVAILABLE: Connection dropped
エラーの発生間隔が CronJob の実行周期(1 分)と一致していることが手がかりになります。
想定の解き方
kubectl get eventsで毎分 ad Pod が入れ替わっていることを把握するkubectl get cronjobでad-deployerCronJob の存在を確認するfrontendログで ad への接続失敗が rollout restart のタイミングと一致することを確認するadDeployment に以下のマニフェストを適用してグレースフルシャットダウンを実現する。commit & push して ArgoCD に同期させる。
spec:
strategy:
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: ad
readinessProbe: # 新 Pod が Ready になるまで Endpoints に入らない
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop: # SIGTERM の前に Endpoints から外れる時間を稼ぐ
exec:
command: ["/bin/sh", "-c", "sleep 5"]
※ 想定解は上記ですが ad-deployer CronJob を修正して検証チェックに合格している人も何人かいました(Verifier でチェックできていませんでした)
Verifier の合格条件
ad-deployerCronJob が namespace に存在すること- Verifier がプローブ Pod を namespace に起動し、70 秒間 ad Service への TCP 接続を繰り返す(1 分周期の CronJob による rollout restart を少なくとも 1 回カバーする時間)
- 70 秒後に集計した success rate が 0.95 以上であれば合格
quest-11 (fix-nftables) — namespace 内 Pod 間通信が散発的に 5xx で失敗する問題を解消せよ
- カテゴリ: networking
- 難易度: hard
- 配点: 1000 pt(最高難度)
- ヒント: なし(ノーヒント設計)
シナリオ
frontend や namespace 内のサービス間通信に散発的な 5xx / connection refused が発生しています。負荷が上がると数〜十数リクエストに 1 回の頻度で発生します。Pod は Running で再起動もなく、アプリログにもエラーが見当たりません。
プラットフォームチームが「侵害された Pod が DoS / C2 beacon の踏み台になるのを防ぐ」目的で egress-rate-shield DaemonSet を namespace に展開したことが判明しました。障害発生時刻と展開時刻が一致します。
ゴール(両立必須):
- クラスタ外への過剰な egress は引き続き shield で drop されること(セキュリティ要件を維持)
- namespace 内の正規 Pod 間通信の成功率を 95% 以上に回復させること
何が壊れていたのか
2 種類のリソースが namespace に注入されています。
① egress-baseline NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-baseline
spec:
podSelector:
matchExpressions:
- key: app.kubernetes.io/part-of
operator: NotIn
values: [egress-shield] # shield 自身を除く全 Pod に適用
policyTypes: [Ingress, Egress]
ingress:
- from:
- podSelector: {} # 同 namespace の Pod からのみ許可
egress:
- to:
- podSelector: {} # in-namespace pod-to-pod
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53 # DNS 解決のみ kube-system へ
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # cluster pod CIDR を除外したインターネット egress
② egress-rate-shield DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: egress-rate-shield
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: egress-shield
template:
spec:
hostNetwork: true # ホストの network namespace を使用
hostPID: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: enforcer
image: ghcr.io/cloudnativedaysjp/network-policy-enforcer:2.4.0
securityContext:
privileged: true
capabilities:
add: [NET_ADMIN] # nftables を書き換える権限
egress-rate-shield はファイアウォール機能(nftables)を使って、送信元の Pod ごとに短時間に開始できる新規 TCP 接続数を制限し、上限を超えた接続のパケットを破棄します。これはクラスター外への過剰な通信(DoS 攻撃やデータ流出の踏み台になる通信)を防ぐためのルールです。
しかし、namespace 内で Pod が大量に起動・再起動したり、短時間に通信が集中すると、内部の Pod 間通信も同じ接続数制限に引っかかってパケットが破棄されてしまいます。
観測されるはずの症状
- Pod は全部 Running でエラーなし
- アプリログにはエラーなし
- しかし frontend から各バックエンドへのリクエストが散発的に失敗し、5xx が時折発生する
想定の解き方
egress-baseline NetworkPolicy と egress-rate-shield DaemonSet は削除してはいけません(削除するとコンプライアンス違反で不合格になります)。この 2 つを保ちながら内部通信の成功率を回復させます。
鍵は network-policy-enforcer のバージョンアップと環境変数チューニングです。
- DaemonSet のコメント(
egress-rate-shield.yaml)にある上流 OSS リポジトリ(cloudnativedaysjp/network-policy-enforcer)を調べ、より新しいバージョンや設定オプションを確認する v2.5.0-rc.1ではPOLICY_DESTINATION_CIDRSとPOLICY_PEER_SYN_RATE_PPSという環境変数が追加されていることを発見する(Issue #1)services/egress-rate-shield.yamlを以下のように修正し、commit & push する
# 変更箇所のみ抜粋
- image: ghcr.io/cloudnativedaysjp/network-policy-enforcer:2.4.0
+ image: ghcr.io/cloudnativedaysjp/network-policy-enforcer:v2.5.0-rc.1
env:
- name: REFRESH_INTERVAL
value: "5"
+ - name: POLICY_DESTINATION_CIDRS
+ value: "172.16.0.0/12,192.168.0.0/16"
+ - name: POLICY_PEER_SYN_RATE_PPS
+ value: "5000"
POLICY_DESTINATION_CIDRS が肝です。デフォルトでは nftables の遮断ルールが 10.0.0.0/8(クラスター内 Pod の IP 帯域)宛てのパケットにも適用されており、Pod 間通信(10.x.x.x → 10.x.x.x)が「瞬間的に 5 接続まで」という制限に引っかかってドロップされていました。この変数に 172.16.0.0/12,192.168.0.0/16 を指定することで遮断ルールの対象をクラスター外のアドレス帯域のみに絞り込み、Pod 間通信への影響を排除できます。
POLICY_PEER_SYN_RATE_PPS: "5000" は Pod 間通信の新規接続数制限を 100/秒 から 5000/秒 に緩和します。これにより Verifier が短時間に大量のリクエストを送る検証を行っても、制限に引っかからなくなります。
- ArgoCD が
egress-rate-shieldDaemonSet を新しい設定で再デプロイするまで待つ(DaemonSet の Rolling Update) - Verifier を実行する
Verifier の合格条件
egress-baselineNetworkPolicy が namespace に存在することegress-rate-shieldDaemonSet が namespace に存在すること- Verifier がプローブ Pod を namespace に起動し、namespace 内サービスへ短時間に大量のリクエストを送信する
- 最後に取得した集計の success rate が 0.95 以上であれば合格
作問メンバー紹介と謝辞
クラウドネイティブ GameDay の作問・運営に携わってくれた以下のメンバーに、心より感謝いたします。皆さんのご協力があってこそ、このイベントが成立しました。本当にありがとうございました!
- @miura さん
- @south__1003 さん
- @VOID さん
- @sokasanan さん
- @BigBaBy さん
- @syossan27 さん
- @jacopen さん
- @katzchang さん(とテストプレイにご協力頂いた Splunk の方々)