Kubernetes環境におけるOOMKilledの原因分析

Kubernetes環境におけるOOMKilledの原因分析

概要

プロジェクトを進めていると、OOMまたはOOMKilledに遭遇することがあります。特にKubernetes環境では、「PodがOOMKilledによって再起動された」という状況にしばしば遭遇します。

OOMKilledは単にサーバーが再起動されたように見えることがありますが、原因を把握しないまま放置すると、サービス負荷が高い状況で繰り返し発生し、致命的な障害につながる可能性があります。

この文書は、プロジェクトを進める中で遭遇したOOMKilledとは何か、なぜ発生するのか、そしてどのように分析・解決すべきかを調査・検討した内容をまとめたものです。

OOMとOOMKilledの違い

OOMであれOOMKilledであれ、どちらもメモリ割り当てに関連して発生する問題です。しかし、両者には発生主体と動作方式に違いがあります。

OOM (Out Of Memory Error)はJVM内部で発生します。Heapメモリがいっぱいになり、これ以上オブジェクトを割り当てられなくなると、JVMがjava.lang.OutOfMemoryErrorを発生させ、プロセスが終了します。つまり、JVM自身が限界を認識してエラーを投げるのです。

OOMKilledはJVMの外部、つまりKubernetesのようなContainer環境で発生します。Containerに設定されたメモリLimitを超えた瞬間に、Kubernetesが該当プロセスを強制終了します。JVMがエラーを認識する前に外部から先にkillするため、OOMとは異なり個別のエラーログが残らず、原因の把握がより困難です。

区分

OOM

OOMKilled

発生主体

JVM内部

Kubernetes (cgroup)

原因

Heapメモリの枯渇

Container Limitの超過

エラーログ

OutOfMemoryErrorのログが残る

ログなしで強制終了

Exit Code

1

137

対象メモリ

Heap領域

Heap + Non-Heap + Off-Heap全体

OOMKilledが発生すると、Kubernetesの再起動ポリシー(restartPolicy)に従ってPodが自動的に再起動されるため、単にトラフィックが少ない時間帯に発生したのであれば、大きな問題にならないこともあります。しかし、サービス負荷が高い状況でOOMKilledが発生すると、事態は深刻になる可能性があります。Podの状況によっては、再起動中にサービスが停止することもあり、再起動直後にトラフィックが再び集中するとメモリが急速に消費され、連続的なOOMKilledが繰り返される可能性があります。最終的にはCPUとメモリの負荷が増大し、サービス全体に致命的な障害をもたらす可能性があります。

メモリの分類

OOMKilledに関連してメモリについて言及する際は、Heapメモリの割り当てだけでなく、Container、JVMなど、より詳細なメモリについて理解する必要があります。

Containerメモリ

Kubernetes環境で注意すべき点は、メモリ設定の範囲です。Containerに設定するメモリLimitはJVMだけを対象とするものではなく、Container全体を対象とします。
つまりContainerメモリ > JVMメモリという関係を必ず認識しておく必要があります。

Container 메모리 
│
├── JVM 프로세스 메모리
│   ├── Heap
│   ├── Non-Heap
│   └── Off-Heap (Direct Buffer)
│
├── OS 및 시스템 라이브러리
└── 사이드카 Container (Istio sidecar 등) 등 기타…

JVMメモリ

JVMメモリは大きく3つの領域に分けられます。

Heap領域

Javaアプリケーションで生成されるオブジェクトが保存される領域です。GC(Garbage Collection)の対象となる領域でもあります。

指標

説明

Heap Used

現在実際に使用されているメモリ

Heap Committed

JVMがOSからあらかじめ確保しておいたメモリ。GC後もすぐには返却しない

Heap Max

JVMが使用可能な最大Heapサイズ

3つの指標の関係は常にUsed ≤ Committed ≤ Maxであり、CommittedはFull GC時に整理されます。

Non-Heap領域

JVM自体の運用のための領域で、GCの対象ではありません。

領域

説明

Metaspace

クラスメタデータ、Static変数の保存

Metaspaceは-XX:MaxMetaspaceSizeを設定しない場合、理論上無制限に増加する可能性があります。

Off-Heap領域(Direct Buffer)

HeapでもNon-Heapでもない領域で、JVMがOSに直接要求して割り当てるメモリです。NIOベースのファイル処理やネットワークI/Oで主に使用されます。

GCの対象ではないため、明示的に解放されなければ蓄積する可能性があり、-XX:MaxDirectMemorySizeで上限を設定しない場合は無制限に増加する可能性があります。

ContainerメモリとJVMメモリの関係

JVMのHeapサイズがContainer Limitより小さく設定されていれば問題ないと考えがちです。しかし、HeapだけでなくNon-Heap、Off-HeapもすべてContainer Limit内で一緒に管理されるため、3つの領域を合算して慎重に計算する必要があります。特にシステムの特性によっては、ファイル処理が多いサービスはOff-Heapを、クラスローディングが多いサービスはNon-Heapを多く使用する可能性があります。そのため、単純にHeapサイズだけを見るのではなく、自身のシステムの特性を十分に把握したうえでメモリを設計する必要があります。

基本的にNon-Heapが200MB、Off-Heapが100MB、JVM以外のその他のメモリが50MBを使用するシステムで、Container Limitを1Gi、-XX:MaxRAMPercentage=80に設定すると、以下のような状況が発生します。

Heap Max (80%)  :  819MB  ← JVM이 사용 가능하다고 인식하는 Heap 한도
Non-Heap        :  200MB
Off-Heap        :  100MB
JVM 외 기타      :   50MB
─────────────────────────
Heap 외 합계     :  350MB
실질적으로 Heap에 허용되는 메모리 : 1,024MB - 350MB = 674MB

JVMはHeapを819MBまで使用できると認識し、Full GCを実行せずにメモリを割り当て続けます。しかし、KubernetesはHeap + Non-Heap + Off-Heap + その他をすべて合算してContainer Limitと比較するため、Heap使用量が674MBを超えた瞬間にContainer Limit 1Giを超過し、JVMが認識する前にKubernetesがプロセスを強制終了します。これがOOMKilledです。

JVM設定

以下では、OOMKilledに関連するJVMオプションについて簡単に定義して説明します。

MaxRAMPercentage

  • Container Limitに対するHeap Maxサイズの割合を設定します。小さくしすぎるとメモリを無駄にする可能性があり、高く設定しすぎるとNon-HeapやOff-Heapの消費量も合算され、Container Limitを超過する可能性があります。

MaxMetaspaceSize

  • Non-Heap領域のうち、Metaspaceの最大サイズを制限します。設定しない場合、Metaspaceは理論上無制限に増加する可能性があります。現在の使用量に余裕を持たせて上限を設定することを推奨します。

MaxDirectMemorySize

  • Off-Heap(Direct Buffer)の最大サイズを制限します。NIOベースのファイル処理やネットワークI/Oを多く使用するサービスで設定しない場合、Direct Bufferが無制限に増加してContainer Limitを超過する可能性があります。このオプションを設定すると、上限超過時にOutOfMemoryError: Direct buffer memoryというログが残るため、原因の把握が容易になるという副次的なメリットもあります。

OOMKilledサーバー分析ケース

以下は、OOMKilledが発生したサービスのメモリ状況を入力としてAIで分析した結果を要約した内容です。最終的にJVMオプションを修正して適用した後、OOMKilledの問題は解決しました。

元のNotionリンク: https://app.notion.com/p/vizend/Pod-OOMKilled-38b35bc54c13806699e8e3ae329bf38f

メモリ状況の分析

1. Containerリソース設定

項目

Memory Limit

1Gi (1,073 MB)

Memory Request

256Mi

QoS Class

Burstable

2. JVMメモリ状況(現在時点)

領域

MB換算

Heap Used

376,595,248 bytes

~359 MB

Heap Committed

512,229,376 bytes

~488 MB

Heap Max

859,308,032 bytes

~819 MB

Non-Heap Used

210,018,744 bytes

~200 MB

└ Metaspace

127,576,696 bytes

~122 MB

3. 主要な問題の分析

Container Limit          :  1,073 MB  (1Gi)
────────────────────────────────────────
Heap Max (JVM 설정)       :    819 MB
Non-Heap Used (현재)      :    200 MB
────────────────────────────────────────
합산 최대 사용 가능       :  1,019 MB  ← 한계에 매우 근접
나머지 여유               :    ~54 MB  ← 위험 수준

JVMのHeap Max + Non-Heapだけで、Container Limitの約95%を占めています。

ここにJVM内部のオーバーヘッド(Thread Stack、GCメタデータなど)が加わると、1Giを超過してOOMKilledが発生します。

4. GC方式の確認

ActuatorのレスポンスにPS Eden Space、PS Old Gen、PS Survivor Spaceが表示されています。

G1GCの使用を推奨します。

5. 原因の要約

原因

説明

Heap Maxが過大

819MBのHeap + 200MBのNon-Heap = 1,019MBで、Limit超過の直前

GC方式が不適切

Parallel GC → Containerメモリの認識不足

JVMオプションが未設定

-XX:MaxRAMPercentageなど、Container認識用オプションが未設定と推定

QoS Burstable

Request ≠ Limit → メモリ保証なし、OOMの優先対象

MaxRAMPercentageのシミュレーション結果

MaxRAMPercentageを現在の80%から60%まで比較分析しました。

Heap使用量

項目

80%

70%

65%

60%

Container Limit

1,073MB

1,073MB

1,073MB

1,073MB

Heap Max

858MB

~751MB

~698MB

~644MB

Non-Heapの実測値

~200MB

~200MB

~200MB

~200MB

Thread Stackなどのオーバーヘッド

~50~80MB

~50~80MB

~50~80MB

~50~80MB

合計推定

~1,108~1,131MB

~1,001~1,031MB

~948~978MB

~894MB

余裕

~42~72MB

~95~125MB

~179MB

オプション別比較

設定

Heap Max

余裕

評価

比較

現在80%

~858MB

~15MB

危険

Non-Heap、Off-Heapを追加使用すると問題が発生

70%

~751MB

~42~72MB

不安定

G1GCとともにContainer Limitを増加 (1.5Gi)

60%

~644MB

~149~179MB

推奨

G1GC適用

50%

~536MB

~257MB

安全だがHeap不足の可能性

検討対象外

  • 最も安全なアプローチは60%に設定し、モニタリングしながら必要に応じて引き上げることです。

  • Non-HeapまたはOff-Heapの200MBは固定値ではなく、追加のファイルアップロードなどの負荷状況でOOMKilledを引き起こす可能性があります。

推奨JVMオプション (最終)

# 현재
-XX:InitialRAMPercentage=25
-XX:MaxRAMPercentage=80
-XX:+UseParallelGC
# 권장
-XX:InitialRAMPercentage=25
-XX:MaxRAMPercentage=60          # Heap ~644MB로 제한
-XX:+UseG1GC                     # Container 친화적 GC
-XX:MaxMetaspaceSize=192m        # Metaspace 상한 설정 (현재 122MB 사용 중)
-XX:MaxDirectMemorySize=140m     # Direct Buffer 상한 설정 (현재 108MB 사용 중)
  • Heap Max: ~644MB

  • Non-Heap: ~200MB

  • 合計: ~844MB → Container Limit 1Gi内で余裕を確保

sby

Site footer