Upgrade to Pro — share decks privately, control downloads, hide ads and more …

初めてのKubernetes 本番運用でハマった話

初めてのKubernetes 本番運用でハマった話

イベント登壇資料

イベント:
【K8s Noviceコラボ】Jagu'e'r O11y分科会 K8s/GKE O11yの第一歩
Jagu'e'r オブザーバビリティ分科会 × Kubernetes Novice Meetup

Avatar for Kobujee_53

Kobujee_53

June 30, 2026

Other Decks in Programming

Transcript

  1. J a g u ' e ' r O 1

    1 y 分 科 会 初めてのKubernetes 本番運用でハマった話 503をログから追いかけて [株式会社Hajimari / 奥 琉之介(@Kobujee_53) ] © Hajimari Inc.
  2. 自己紹介 𝕏 @Kobujee_53 奥 琉之介 株式会社Hajimari / コアテクノロジーチーム LDR —

    フリーランス人材紹介事業 アプリ〜インフラ横断 で開発・運用。 — SREチーム立ち上げ。昨年から始まり、現在は3人のチーム — 最近の関心:キャンプはじめました。 © Hajimari Inc. 02 / 17
  3. 前提と構成 — 人材紹介事業の顧客管理システム(CRM)。開発チーム + 兼任SRE1名。 — 構成:NestJS API + Next.js

    / GKE Autopilot + Gateway + NEG/HPAでオートスケール。 — GKE Autopilot を初導入したプロダクトのリリース初期。 ユーザー → Cloud LB → GKE Gateway / NEG → crm-api Pod 群 → AlloyDB © Hajimari Inc. 04 / 17
  4. 事件の発生 ある日の昼、特定APIが 503 を返した PUT /talent/{talentId}/call-in/judge/approve → 503 (failed_to_connect_to_backend) 業務影響の逆引きで発覚:承認処理が未実行

    → DBレコード未作成 → Slack通知 も飛ばない。 [画像枠:Cloud Logging LBログ / status:503 / failed_to_connect_to_backend / serverIp] 12:12:09 1回目 再起動開始 12:12:13 起動完了 12:14:16 503発生 ← 起動2分後に再クラッシュ 12:15:04 2回目 再起動開始 12:15:06 起動完了 © Hajimari Inc. 05 / 17
  5. 観測の型 監視基盤は未整備。ログだけで全体像を組み立てる ! 当時、監視基盤はほぼゼロ──ダッシュボードもPrometheusもアラ ートも無い。頼れるのは ログだけだった。 Cloud Logging 外から見た失敗(LBの503)を時系列で抽出・集計 KHI

    Kubernetes History Inspector クラスタ内部の状態遷移をタイムラインで可視化 http_load_balancer 外から見た失敗 ↕ 同一時間軸 k8s_container 中で起きた再起動 2つのログを同じ時間軸に重ねて調査することで、点だった事象が線でつながる © Hajimari Inc. 06 / 17 503 / serverIp Starting / started
  6. ハマり ① startupProbe不在 → Liveness Killループ 症状 Podが起動しては数十秒で殺され、再起動を繰り返す。 原因 ·

    初期化が間に合わないのに startupProbeが無かった。 · 旧liveness:initialDelay 30s + period 10s × threshold 3 ≒ 起動後約60秒 で強制Kill。 · liveness も readiness も同じ / を見て役割が分かれていなかった。 Probe 役割 endpoint startupProbe 起動の猶予(最大60s待つ) /readyz livenessProbe プロセス生存(DB非依存) /livez readinessProbe 受入可否(DB依存) /readyz 学び:起動が遅い前提でstartupProbeを"初手で必ず置く"。liveness/readinessは役割を分ける。 © Hajimari Inc. 07 / 17
  7. ハマり ② NEG/EndpointSlice の readiness 非同期レース 症状 スケールアウトで増えたPodが、Readyになる前にLBからトラフィック を受けて503。 調査

    KHI(Kubernetes History Inspector)と監査ログでEndpointSlice遷移を追 跡。 1 HPA が replicas 2 → 3(自動スケール) 2 新Podが ready:false でEndpointSliceに登録 3 その間にLBがルーティング → 503 4 数分後 ready:true で解消 学び:「PodがReady」と「LBが送り始める」は別事象。NEG構成のreadiness伝播は同期しない。 © Hajimari Inc. 08 / 17
  8. ハマり ③ LB HCの検知ラグ(in-place restart) 症状 Podは生きているのに中のコンテナだけがin-place restartしている間、LBが気づか ず約30秒間 死んだコンテナに送り続けて503バースト。

    決め手 503の serverIp を集計 → 同一Pod IPに約29秒で69件集中。SIGTERM→Created container→started の時間窓と一致。 [画像枠:serverIp別の503集計 / 同一IPに集中した表・棒グラフ] 対策(PR #2501 / CORETEC-914) checkIntervalSec 15 → 5(ラグ 約30s→約10s) timeoutSec default 15 → 3 を明示 requestPath /readyz → /(shallow) 役割分担 LB HC = shallow(/) / kubelet readiness = deep (/readyz) 。/readyzをLB HCにするとDB瞬断で全Pod一斉Unhealthy=カス ケード障害リスク。 学び:「Podは生きているがコンテナが死ぬ」レイヤを意識。ヘルスチェックは"深さ"で役割を分ける。 © Hajimari Inc. 09 / 17
  9. ハマり ④ keepAliveTimeout × LB の Keep-Alive 不整合 症状 再起動と無関係に、1〜2件/分の散発503

    (backend_connection_closed_before_data_sent_to_client )が常時発生。 原因 Node.jsの keepAliveTimeout がデフォルト5秒のまま → GCP LBの backend keepaliveは600秒固定。アプリ側が先に接続を閉じる瞬間に来た リクエストが503。 commit 588d7f91d await app.listen(port); + const httpServer = app.getHttpServer(); + httpServer.keepAliveTimeout = 620_000; // LB(600s)より 長く + httpServer.headersTimeout = 625_000; // keepAliveよ り大きく 学び:アプリとLBの"接続を閉じる順序"を合わせる。クラウドLBのkeepaliveは固定値。アプリ側を必ず上回らせる。 © Hajimari Inc. 10 / 17
  10. ハマり ⑤ ★真因 GKE Autopilotのノード自動削除 × PDB未設定 「夜間も続く503」の真因はオートスケールではなかった 14時間の実測 crm-api

    Pod の Killing 25 件 クラスタ全体の Node 削除 255 件 ( ≈18/h) Kill ↔ 同ホストNode削除 の相 関 24/25 (96%) Kill → Node削除 の時間差 +6 分 HPA起因の kill 8 件のみ 真因 · Autopilotが bin-packing最適化でnodeを高頻度削除(≈18/h) 、crm-api Podも巻き添えevict。 · PDB未設定 → 「いつevictしてもよい」と扱われる。minReplicas:2 は voluntary disruption を保護しな い。 · available が瞬間 1.5〜1.8 にディップ=一瞬1Pod体制 → 503バーストの条件。 対策 PR #2989 / CORETEC-1422(反映済) PDB minAvailable: 2 を全環境に追加 →「新Podがreadyになってから旧Podをevict」を強制。 ! 学び:minReplicasは可用性保証ではない(PDBが別途必須) 。マネージドでも"nodeはいつでも消える"前提。症状から原因を早合点しない。 © Hajimari Inc. 11 / 17
  11. まとめ 初めてのK8s本番運用で踏んだ5つの落とし穴 1 startupProbe不在 → Liveness Killループ 2 NEG/EndpointSlice の

    readiness 非同期レース 3 LBヘルスチェックの検知ラグ(in-place restart) 4 Node.js keepAliveTimeout × LB の Keep-Alive不整合 5 GKE Autopilotのノード自動削除 × PDB未設定(夜間の巻き添えevict) 通底する学び 「Podが生きている」と「リクエストを正しく返せる」 は別物 オブザーバビリティは"異常時のため"だと思っていたが、"自分が何をしているか理解するため"にも必要だと分かった © Hajimari Inc. 13 / 17
  12. 発展編 本当のゴールは「起きる前に光らせる / 起きた瞬間に気づく」 — 5つの落とし穴は、皮肉にも どれも事前アラートが無かった。最速の検知器 が"人間の通報"だった状態。 — 「追えた」=オブザーバビリティの

    最低ライン。 現状 業務通報 → ログ手集計(人間が起点) ↓ あるべき アラート自動(向こうから気づく) MTTRを縮める前に、まずMTTD(検知時間)を人間の通報より速くする © Hajimari Inc. 14 / 17
  13. 発展編 今どこにいて、次どこへ L0 受動(業務通報で知る) スタート地点 L1 可視(ダッシュボードで後追い) 既存 L3 予測(バジェット枯渇前に動く)

    L4 自動(検知→緩和が自動) 3本柱の現状ギャップ ◎ メトリクス ◎ ログ △ トレース — OTel+Sentryはあるが、障害調 査で常用されていない=開発チームも含め た運用化が課題 © Hajimari Inc. 15 / 17