クラウドネイティブ GameDay: Quest 全問振り返りと解説

May 23, 2026

注意: この記事にはクラウドネイティブ会議で開催した GameDay のネタバレを含みます。

クラウドネイティブ会議 実行委員の @kikulabo です。

GameDay にご参加いただきありがとうございました。この記事では「どこを見ればよかったのか」「Verifier は実際に何を見ているのか」を解説します。


GameDay の概要

本 GameDay は OpenTelemetry Demo をベースとした EC マイクロサービス構成(frontendfrontend-proxycartcheckoutpaymentproduct-catalogrecommendationad などのサービス群)を題材としています。各チームには専用の 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 を数えます。

想定の解き方

  1. Code Server のターミナルを開く
  2. kubectl get pods を実行し、Running かつ READY が全コンテナ Ready な Pod の数を数える
  3. 数えた 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 でスパンの親子関係を読み解く観察クエストです。

想定の解き方

  1. https://<your-team>.gameday.kaigi.cloudnativedays.jp/jaeger/ui/ にアクセス
  2. Service を frontend に絞って Find Traces を実行
  3. 任意のトレースを開き、frontend スパンの親スパン(一段上)が何サービスか確認する
  4. そのサービス名(frontend-proxy)を回答する

Verifier の合格条件

回答が frontend-proxy であれば合格です。


quest-03 (top-service) — リクエスト数が多いサービスを特定せよ

  • カテゴリ: tutorial
  • 難易度: tutorial
  • 配点: 200 pt
  • ヒント: L1=50 / L2=50

シナリオ

Grafana ダッシュボードを使って、直近 5 分間でスループットが上位 2 つのサービスを特定してください。

何が壊れていたのか

障害注入なし。 Grafana でメトリクスを読む観察クエストです。

想定の解き方

  1. Grafana に移動し、Service Throughput & Latency ダッシュボードを開く
  2. 「Top 7 Services Mean Rate over Range」パネルで上位 2 サービスを確認する
  3. カンマ区切りで回答する

Verifier の合格条件

回答セットが {frontend, frontend-proxy} と順序を問わず完全一致すれば合格です。


quest-04 (gitops-deploy) — 変更をデプロイせよ

  • カテゴリ: gitops
  • 難易度: tutorial
  • 配点: 200 pt
  • ヒント: なし

シナリオ

チームの GitHub リポジトリに PR が 1 件用意されています。内容を確認して main にマージし、ArgoCD が自動同期して変更が適用されることを確認してください。

何が壊れていたのか

障害注入なし。 GitOps フロー(PR レビュー → マージ → ArgoCD 自動同期)を体験するクエストです。

想定の解き方

  1. チーム GitHub リポジトリを開く
  2. 用意されている PR の内容を確認する(frontend の replicaCount を 1 → 2 に増やすような変更)
  3. マージする
  4. ArgoCD UI(https://<your-team>.gameday.kaigi.cloudnativedays.jp/argocd/)で Application が Synced になり、frontend の Pod が増えることを確認する

Verifier の合格条件

  • ArgoCD Application が Synced になっていること
  • frontend Deployment の 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

想定の解き方

  1. kubectl get pods で payment が ImagePullBackOff 状態を確認
  2. kubectl describe pod <payment-pod> の Events でタグ名を確認
  3. payment Deployment のマニフェストを開き、image から hogehoge を削除する
  4. commit & push → ArgoCD が自動同期 → Pod が Running になる

Verifier の合格条件

  • payment Deployment の availableReplicas > 0 であること
  • payment Pod の 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

想定の解き方

  1. kubectl logs <cart-pod> でアプリのエラーを確認する
  2. Deployment の env セクションを確認し、VALKEY_ADDR の値を見つける
  3. kubectl get svc で実在する Service 名を確認して突き合わせる
  4. GitHub リポジトリで VALKEY_ADDR を valkey-cart:6379 に修正 → commit & push → ArgoCD 同期

Verifier の合格条件

  • cart Deployment の availableReplicas > 0 であること
  • cart Pod の 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)

想定の解き方

  1. kubectl describe pod <product-catalog-pod> の Events で "Liveness probe failed" を確認
  2. Deployment の livenessProbe.tcpSocket.port が containerPort と一致していないことを確認
  3. GitHub リポジトリで livenessProbe.tcpSocket.port を 8080 に修正する(または probe ブロックごと削除してベースに戻す)
  4. commit & push → ArgoCD 同期 → 新しい Pod が立ち上がる

Verifier の合格条件

  • product-catalog Deployment の availableReplicas > 0 であること
  • product-catalog Pod の 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

想定の解き方

  1. kubectl get pods で checkout が CrashLoopBackOff を確認
  2. kubectl describe pod <checkout-pod> の Last State で Reason が OOMKilled であることを確認
  3. checkout Deployment の resources.limits.memory を適切な値(最低 16Mi)に引き上げる
  4. commit & push → ArgoCD 同期

Verifier の合格条件

  • checkout Deployment の availableReplicas > 0 であること
  • checkout Pod の 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  ← 長いスパンがある

想定の解き方

  1. Jaeger で recommendation サービスのスパンを確認し、get_product_list が極端に遅いことを特定
  2. src/recommendation/recommendation_server.py を開く
  3. get_product_list 関数内の time.sleep(...) 行を削除する
  4. main ブランチに commit & push
  5. GitHub Actions が自動でイメージをビルドして push し、マニフェストのイメージタグを自動更新する(数分かかります)
  6. ArgoCD が新しいイメージで recommendation を再デプロイ
  7. 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-deployer CronJob)は毎分 ad Pod を再起動しています。gRPC は一度確立したコネクションを使い回す仕組みのため、Pod が再起動して古いコネクションが切れると、frontend は新しいコネクションを張り直すまでの間リクエストを送れません。この待ち時間が数百ミリ秒単位で発生するため、recommendation の遅延を除去しても応答全体が 550ms を超えることがあります。

ただし Verifier は 1 回しか計測しないため、たまたま ad が安定している瞬間に verify を実行すれば quest-10 未解決でも合格できます。つまり「必ず通らない」ではなく「タイミング次第で不安定」というのが正確な状況です。先に quest-10 を解いておくとこの運要素を排除できます。

Verifier の合格条件

  1. recommendation Deployment の availableReplicas > 0 かつ restart 合計 ≤ 5
  2. http://frontend-proxy.<team-ns>.svc.cluster.local:8080/api/recommendations?productIds=OLJCESPC7Z&sessionId=verify&currencyCode=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 中に以下が起きます:

  1. 新しい Pod が作成されるが、readiness probe がないため Ready 状態になる前から Service の Endpoints に登録される
  2. 古い Pod は SIGTERM を受けたほぼ直後に Endpoints から外されるが、すでに処理中だったリクエストが切れる
  3. 数秒間、接続失敗が散発的に発生する

結果として 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 分)と一致していることが手がかりになります。

想定の解き方

  1. kubectl get events で毎分 ad Pod が入れ替わっていることを把握する
  2. kubectl get cronjob で ad-deployer CronJob の存在を確認する
  3. frontend ログで ad への接続失敗が rollout restart のタイミングと一致することを確認する
  4. ad Deployment に以下のマニフェストを適用してグレースフルシャットダウンを実現する。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 の合格条件

  1. ad-deployer CronJob が namespace に存在すること
  2. Verifier がプローブ Pod を namespace に起動し、70 秒間 ad Service への TCP 接続を繰り返す(1 分周期の CronJob による rollout restart を少なくとも 1 回カバーする時間)
  3. 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 のバージョンアップと環境変数チューニングです。

  1. DaemonSet のコメント(egress-rate-shield.yaml)にある上流 OSS リポジトリ(cloudnativedaysjp/network-policy-enforcer)を調べ、より新しいバージョンや設定オプションを確認する
  2. v2.5.0-rc.1 では POLICY_DESTINATION_CIDRS と POLICY_PEER_SYN_RATE_PPS という環境変数が追加されていることを発見する(Issue #1
  3. 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 が短時間に大量のリクエストを送る検証を行っても、制限に引っかからなくなります。

  1. ArgoCD が egress-rate-shield DaemonSet を新しい設定で再デプロイするまで待つ(DaemonSet の Rolling Update)
  2. Verifier を実行する

Verifier の合格条件

  1. egress-baseline NetworkPolicy が namespace に存在すること
  2. egress-rate-shield DaemonSet が namespace に存在すること
  3. Verifier がプローブ Pod を namespace に起動し、namespace 内サービスへ短時間に大量のリクエストを送信する
  4. 最後に取得した集計の success rate が 0.95 以上であれば合格

作問メンバー紹介と謝辞

クラウドネイティブ GameDay の作問・運営に携わってくれた以下のメンバーに、心より感謝いたします。皆さんのご協力があってこそ、このイベントが成立しました。本当にありがとうございました!

共有: