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

Podは生きているのにGoだけが落ちる:GOGCとGOMEMLIMITで追うInvisible...

Podは生きているのにGoだけが落ちる:GOGCとGOMEMLIMITで追うInvisible OOM Killの謎

Avatar for Watanabe Takeshi(Buzz)

Watanabe Takeshi(Buzz)

September 10, 2026

More Decks by Watanabe Takeshi(Buzz)

Other Decks in Technology

Transcript

  1. About Me Buzz | Takeshi Watanabe @buzz_tkc Company 株式会社エウレカ Role

    Senior Backend Engineer Hobbies トライアスロン, スノボ, サッカー 2
  2. 3

  3. 問題があったコンテナを分析する コンテナは技術的には複数プロセスを動かせるが、1コンテナ1プロセス が推奨。当時は歴史的経緯でNginx + Go の複数プロセス構成だった。 コンテナ ✓ Alive PID1

    = init (起動シェル / entrypoint) コンテナの「親」プロセス ✓ Alive ✕ Killed Nginx Go App ⼦プロセス 前段のリバースプロキシ ⼦プロセス‧PID≠1 アプリ本体 15
  4. 鍵はkillされたプロセスがPID1かどうか Linux OOM Killer はcontainer memory limitを超えた場合、 コンテナ内のメモリを⼀番使⽤しているプロセスを選んで kill する。

    PID1(init)をkill → Container ⾃体が終了 → Status = OOMKilled (=普通はこう⾒える) (今回)⾮PID1 のGoプロセスをkill → PID1 と Nginx は⽣存 → Container は落ちない → OOMKilledもTerminatedも出ない NginxとGoが1つのコンテナに同居しているせいで、OOMの発⽣がログか ら⾒えづらい(Invisible)状態になっていた。 16
  5. 補⾜:Kubernetesのバージョンで挙動が変わる 必ずGoだけが kill されると⼀般化はできず、Kubernetes / cgroup の バージョンで挙動が変わる。 バージョン 挙動

    Kubernetes 1.27 以前 ⼀番メモリを使⽤しているプロセスのみ killされる。 (=Goだけ落ちる、今回のケース) Kubernetes 1.28 以降 (cgroup v2 +memory.oom.group) 同じcgroup内の任意のプロセスがまとめて killされうる。 17
  6. 実際に起きていた挙動の流れ ③liveness probeが失敗(Goの応答なし) ① 何かしらの原因で Container OOM Killが⾛り、 Go だけが

    kill される。 kubelet Kernel Go App Nginx ⑤コンテナ再起動で復活 ④Nginxがupstream(Go)不在 で502/503を返し続ける ②PID1とNgixが残るためContainer は即時終了しない Client 18
  7. Invisible OOMの調査結果 謎 調査結果 • Pod/Containerのstatusに OOMが出ない。 • メトリクス上だと⼀瞬5xxエ ラーが発⽣しては、すぐに回

    復する挙動が繰り返される。 • Go(⾮PID1)がKernelにOOM Kill されていた。 • PID1とNginxが⽣存しているため Containerは落ちず、OOMが Pod/ContainerのStatusに出ず観 測しづらい状態となっていた。 20
  8. ⾒えないスパイクがあったと推測 • 観測グラフ上では container memory limitの超過は確認できなかった • KernelのOOM Logから、メモリの超過が発⽣したのは確実 ◦

    短期間のメモリスパイクが発⽣したと推測 コンテナメモリの推移 メモリ 使用量 container memory limit 到達で即 OOM Kill 実際には⾒えていなかった推移 時間 22
  9. なぜOOMが発⽣したかの調査結果 謎 調査結果 • メトリクス上だとcontainer memory limitの超過は観測 できなかった。 • GoのGCが⾛らなかった。

    • KernelのOOM Logと突き合わせ て、短期的かつ散発的なスパイ クが発⽣したと予想。 • GOMEMLIMIT未設定が原因で memory limitの前にGCを発⽕さ せられなかった。 25
  10. 実はGOGC=500を設定していたが機能していなかった GOGCとは以下の計算式で算出される、次回のGC実⾏を決める指標。 次回GCの⽬標 = live heap ×(1 + GOGC/100) GOGC=500

    live heap × 6 GCが稀に発⽕ (CPU負荷↓) メモリ 使用量 GOGC=100 live heap × 2 GCが頻繁に発⽕ (CPU負荷↑) live heap (前回GC後に⽣きていたheap) 時間 27
  11. 今まで起きていた問題を整理 問題 起きていた事象 NginxとGoが 同⼀コンテナに同梱 • • 解消法 コンテナメモリのメトリクス NginxとGoを別コンテナ

    に分離する。 が見にくい。 PID1がベースアプリにな らない。 Goがcontainer memory limitを 知らない Goはcontainer memory limitを知らずにheapを伸ば し続ける。 GOMEMLIMITを設定す る。 GCが相対値で決まる スパイク時にGCのラインが GOGC=offを設定する。 container memory limitを超え る。 31
  12. 今まで起きていた問題を整理 問題 起きていた事象 NginxとGoが 同⼀コンテナに同梱 • • 解消法 コンテナメモリのメトリクス NginxとGoを別コンテナ

    に分離する。 が見にくい。 PID1がベースアプリにな らない。 Goがcontainer memory limitを 知らない Goはcontainer memory limitを知らずにheapを伸ば し続ける。 GOMEMLIMITを設定す る。 GCが相対値で決まる スパイク時にGCのラインが GOGC=offを設定する。 container memory limitを超え る。 32
  13. 今まで起きていた問題を整理 問題 起きていた事象 NginxとGoが 同⼀コンテナに同梱 • • 解消法 コンテナメモリのメトリクス NginxとGoを別コンテナ

    に分離する。 が見にくい。 PID1がベースアプリにな らない。 Goがcontainer memory limitを 知らない Goはcontainer memory limitを知らずにheapを伸ば し続ける。 GOMEMLIMITを設定す る。 GCが相対値で決まる スパイク時にGCのラインが GOGC=offを設定する。 container memory limitを超え る。 34
  14. GCのタイミングをGOMEMLIMITに⼀本化する 従来:GOGC=500 だけ 今回:GOGC=off + GOMEMLIMIT 次回GCの⽬標 = live heap

    ×(1 + GOGC/100) • • live heapに応じてGCのラインが 相対的に動く スパイク時にGCラインが container memory limitを超え うる。(GCより先にOOM Killが 発⽣) • 相対的なGCラインは使⽤しない • GOMEMLIMITに近づいたら、Go runtimeにGCを積極的に実⾏さ せる 35
  15. Kubernetesのコンテナ上限から逆算してGCを制御 Kubernetesのcontainer memory limitの⼿前にGCラインを設定し、スパ イクが発⽣してもOOM前にGCを⾛らせる戦略。 GOMEMLIMIT(GC line) = container memory

    limit × 0.8 memory limit メモリ 使用量 GCが積極的に発動するエリア memory limit到達までの余白を作成 ※container momory.requests を applicationのmemory limitとしている GOMEMLIMIT(GC line) memory limit × 0.8 時間 36
  16. まとめ 謎 原因 改善 第⼀の謎 Invisible OOM NginxとGoが同一コンテナに同梱。Go がOOM KillされてもContainerは生存

    し、StatusにOOMが出ない。 NginxとGoを別コンテナに分離。 メトリクスを個別に監視。 第⼆の謎 なぜOOM? GOMEMLIMITが未設定。 Goがcontainer memory limitを認識 せず、memory limit前にGCを実⾏で きない。 GOMEMLIMITを設定。 container memory limitの⼿前 でGCを⾛らせる。 第三の謎 なぜGCが⾛ら ない? GOGC=500はlive heap基準の相対判 GOGC=off + GOMEMLIMITに 定。GCラインがcontainer memory limit 一本化。相対値ではなく上限基 を超え、GCより先にOOMが発生。 準でGCさせる。 39