OOM イベントのトラブルシューティング


このページでは、Google Kubernetes Engine(GKE)で発生したメモリ不足(OOM)イベントのトラブルシューティングと解決を行う方法について説明します。OOM イベントの一般的な原因を特定し、コンテナレベルとノードレベルの発生を区別して、解決策を適用する方法を確認します。

このページは、アプリが正常にデプロイされるようにしたいアプリ デベロッパー、OOM イベントの根本原因を把握し、プラットフォーム構成を確認したいプラットフォーム管理者と運用担当者を対象としています。 Google Cloud のコンテンツで使用されている一般的なロールとタスクの例の詳細については、一般的な GKE Enterprise ユーザーロールとタスクをご覧ください。

OOM イベントの一般的な原因

通常、OOM イベントは、アプリのメモリ使用量が急増し、コンテナ用に構成されたメモリ上限に達する負荷またはトラフィックの急増時に発生します。

次のシナリオでは、OOM イベントが発生する可能性があります。

  • メモリ上限が不足している: Pod のマニフェストの resources.limits.memory 設定が、アプリの一般的なメモリ需要またはピーク時のメモリ需要に対して低すぎます。
  • メモリ リクエストまたは上限が未定義: resources.limits.memoryresources.requests.memory の両方が定義されていない場合、コンテナのメモリ使用量は無制限になります。
  • 負荷が高い、または急上昇している: 負荷が突然急上昇すると、通常は十分な上限が設定されていても、メモリなどのシステム リソースが過負荷になる可能性があります。
  • メモリリーク: アプリに、メモリを適切に解放できないコードの欠陥がある可能性があります。

OOM イベントは、トラフィックを処理するコンテナの数が減り、残りのコンテナの負荷が増加するため、カスケード障害を引き起こす可能性があります。これらのコンテナも終了する可能性があります。

Kubernetes による OOM イベントの処理方法

Linux OOM Killer はすべての OOM イベントを処理します。OOM Killer は、システムのメモリが非常に少なくなったときにアクティブになるカーネル プロセスです。その目的は、プロセスを戦略的に終了してリソースを解放し、システム全体のクラッシュを防ぐことです。カーネルはスコアリング システムを使用して、停止するプロセスを選択します。これは、システムの安定性を維持し、データ損失を最小限に抑えることを目的としています。

Kubernetes 環境では、OOM Killer は 2 つの異なるスコープで動作します。1 つは 1 つのコンテナに影響するコントロール グループ(cgroup)、もう 1 つはノード全体に影響するシステムです。

コンテナレベルの OOM 強制終了

コンテナレベルの OOM 強制終了は、コンテナが事前定義されたメモリ上限を超えようとしたときに発生します。Kubernetes は、各コンテナをハードメモリ上限のある特定の cgroup に割り当てます。コンテナのメモリ使用量がこの上限に達すると、カーネルはまずその cgroup 内のメモリを回収しようとします。このプロセスでカーネルが十分なメモリを再利用できない場合、cgroup OOM Killer が呼び出されます。特定 cgroup 内のプロセスを終了して、リソース境界を適用します。

コンテナのメインプロセスがこのように終了すると、Kubernetes はそのイベントを監視し、コンテナのステータスを OOMKilled としてマークします。Pod の構成済みの restartPolicy によって結果が決まります。

  • Always または OnFailure: コンテナが再起動されます。
  • Never: コンテナは再起動されず、終了状態のままになります。

OOM Killer は、障害が発生したコンテナを分離することで、単一の障害のある Pod がノード全体をクラッシュさせるのを防ぎます。

cgroup バージョンが OOM Killer の動作に与える影響

OOM 強制終了の動作は、cgroup のバージョンによって大きく異なる場合があります。使用している cgroup バージョンがわからない場合は、クラスタノードの cgroup モードを確認してください。

  • cgroup v1 では、コンテナのメモリ cgroup 内の OOM イベントにより、予期しない動作が発生する可能性があります。OOM Killer は、コンテナのメインプロセス(PID 1)ではない子プロセスを含め、その cgroup 内のプロセスを終了する可能性があります。

    この動作は、Kubernetes にとって大きな課題となります。Kubernetes は主にメイン コンテナ プロセスの健全性をモニタリングするため、このような「部分的な」OOM 強制終了を認識しません。重要な子プロセスが終了しても、メイン コンテナ プロセスは引き続き実行されることがあります。この動作により、Kubernetes やオペレーターにはすぐに表示されないものの、ノードのシステム ジャーナル(journalctl)には表示される微妙なアプリの障害が発生する可能性があります。

  • cgroup v2 は、OOM Killer の動作をより予測可能にします。

    cgroup v2 環境でワークロードの完全性を保証するために、OOM Killer は部分的な強制終了を防ぎ、その cgroup と子孫に属するすべてのタスクを終了する(Kubernetes に障害が認識される)か、ワークロードが過剰なメモリを使用するタスクを持たない場合はワークロードをそのまま残して予期しない内部プロセスの終了なしで実行を継続するかの 2 つの結果のいずれかを確保します。

    単一のプロセスを終了する cgroup v1 の動作が必要なシナリオでは、kubelet は cgroup v2singleProcessOOMKill フラグを提供します。このフラグを使用すると、よりきめ細かい制御が可能になり、OOM イベント中に cgroup 全体ではなく個々のプロセスを終了できます。

システムレベルの OOM 強制終了

システムレベルの OOM 強制終了は、単一のコンテナだけでなく、ノード全体で使用可能なメモリが不足した場合に発生する、より深刻なイベントです。このイベントは、すべてのプロセス(すべての Pod とシステム デーモンを含む)のメモリ使用量の合計がノードの容量を超えた場合に発生する可能性があります。

このノードのメモリが不足すると、グローバル OOM Killer がノード上のすべてのプロセスを評価し、プロセスを終了してシステム全体のメモリを再利用します。選択されるプロセスは通常、短時間で大量のメモリを使用するプロセスです。

Kubernetes は、深刻な OOM 状況を防ぐために、ノード圧力による削除を使用してノードリソースを管理します。このプロセスでは、メモリやディスク容量などのリソースが非常に少なくなると、重要度の低い Pod がノードから強制排除されます。システムレベルの OOM 強制終了は、このエビクション プロセスで問題を回避するのに十分な速さでメモリを解放できなかったことを示します。

OOM Killer がコンテナのプロセスを終了した場合、通常、その影響は cgroup によってトリガーされた強制終了と同じです。コンテナは OOMKilled とマークされ、ポリシーに基づいて再起動されます。ただし、重要なシステム プロセスが強制終了された場合(まれなケースです)、ノード自体が不安定になる可能性があります。

OOM イベントを調査する

以降のセクションでは、最もシンプルな Kubernetes ツールからより詳細なログ分析まで、OOM イベントの検出と確認に役立つ情報を提供します。

表示可能な OOM イベントの Pod ステータスを確認する

OOM イベントを確認する最初の手順は、Kubernetes が OOM イベントを検出したかどうかを確認することです。Kubernetes は、コンテナのメインプロセスが強制終了されたときにイベントを監視します。これは cgroup v2 環境の標準動作です。

  • Pod のステータスを調べます。

    kubectl describe pod POD_NAME
    

    POD_NAME を調査する Pod の名前に置き換えます。

    表示可能な OOM イベントが発生した場合、出力は次のようになります。

    ...
      Last State:     Terminated
        Reason:       OOMKilled
        Exit Code:    137
        Started:      Tue, 13 May 2025 19:05:28 +0000
        Finished:     Tue, 13 May 2025 19:05:30 +0000
    ...
    

Reason フィールドに OOMKilled が表示されていれば、イベントは確認済みです。Exit Code137 の場合も、OOM 強制終了を示します。Reason フィールドの値が異なる場合や、アプリエラーが発生しているにもかかわらず Pod がまだ実行中の場合は、次のセクションに進んでさらに調査します。

表示されない OOM イベントのログを検索する

子プロセスが強制終了されてもメイン コンテナ プロセスが実行を継続する場合(cgroup v1 環境でよくあるシナリオ)、Kubernetes は OOM 強制終了を認識しません。これらのイベントの証拠を見つけるには、ノードのログを検索する必要があります。

表示されない OOM 強制終了を見つけるには、ログ エクスプローラを使用します。

  1. Google Cloud コンソールで、[ログ エクスプローラ] に移動します。

    [ログ エクスプローラ] に移動

  2. クエリペインで、次のいずれかのクエリを入力します。

    • OOM イベントが発生したと思われる Pod がすでにある場合は、その特定の Pod をクエリします。

      resource.type="k8s_node"
      jsonPayload.MESSAGE:(("POD_NAME" AND "ContainerDied") OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      

      次のように置き換えます。

      • POD_NAME: クエリを実行する Pod の名前。
      • CLUSTER_NAME: Pod が属するクラスタの名前。
    • OOM イベントが発生した Pod またはノードを確認するには、すべての GKE ワークロードをクエリします。

      resource.type="k8s_node"
      jsonPayload.MESSAGE:("ContainerDied" OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      
  3. [クエリを実行] をクリックします。

  4. 出力で、文字列 TaskOOM を含むログエントリを検索して、OOM イベントを見つけます。

  5. 省略可: すべての GKE ワークロードの OOM イベントを検索し、OOM イベントが発生した特定の Pod を特定する場合は、次の操作を行います。

    1. 各イベントについて、関連付けられているコンテナ ID をメモします。
    2. ContainerDied という文字列を含むログエントリを探し、OOM イベントの直後に発生したログエントリを探して、コンテナの停止を特定します。OOM イベントのコンテナ ID を対応する ContainerDied 行と照合します。

    3. container IDs を照合すると、通常、ContainerDied 行には失敗したコンテナに関連付けられた Pod 名が含まれます。この Pod は OOM イベントの影響を受けました。

リアルタイム情報に journalctl を使用する

システムのリアルタイム分析を行う必要がある場合は、journalctl コマンドを使用します。

  1. SSH を使用してノードに接続します。

    gcloud compute ssh NODE_NAME --location ZONE
    

    次のように置き換えます。

    • NODE_NAME: 調べるノードの名前。
    • ZONE: ノードが属する Compute Engine ゾーン。
  2. シェルで、ノードのシステム ジャーナルからカーネル メッセージを確認します。

    journalctl -k
    
  3. 出力を分析して、イベントタイプを区別します。

    • コンテナレベルの強制終了: ログエントリに memory cgroupmem_cgroupmemcg などの用語が含まれている場合、cgroup の上限が適用されたことを示します。
    • システムレベルの強制終了: ログエントリは、cgroup を言及しない Out of memory: Killed process... のような一般的なメッセージです。

OOM イベントを解決する

OOM イベントを解決するには、次の解決策を試してください。

  • メモリ上限を引き上げる: 最も直接的な解決策です。Pod マニフェストを編集して、アプリのピーク時の使用量に対応するより高い resources.limits.memory 値を指定します。上限の設定について詳しくは、Kubernetes ドキュメントの Pod とコンテナのリソース管理をご覧ください。
  • メモリ リクエストを追加または調整する: Pod のマニフェストで、resources.requests.memory フィールドが一般的な使用状況の現実的な値に設定されていることを確認します。この設定により、Kubernetes は十分なメモリを持つノードに Pod をスケジュールできます。
  • ワークロードを水平方向にスケーリングする: トラフィック負荷を分散し、単一の Pod のメモリ負荷を軽減するには、レプリカの数を増やします。Kubernetes でワークロードを事前にスケーリングするには、水平 Pod 自動スケーリングを有効にすることを検討してください。
  • ノードを垂直方向にスケーリングする: ノード上の多くの Pod が上限に近い場合、ノード自体が小さすぎる可能性があります。ノードのサイズを増やすには、より多くのメモリを持つノードプールにワークロードを移行します。Kubernetes でノードを事前にスケーリングするには、垂直 Pod 自動スケーリングの有効化を検討してください。
  • アプリを最適化する: アプリを見直して、メモリリークを特定して解決し、トラフィックの急増時に大量のメモリを消費するコードを最適化します。
  • 問題のあるワークロードを削除する: 重要度の低いワークロードの最後の手段として、Pod を削除してクラスタの負荷を直ちに軽減します。

次のステップ

  • このドキュメントに問題のソリューションが見当たらない場合は、サポートを受けるで、次のトピックに関するアドバイスなど、詳細なヘルプをご覧ください。