KubernetesのResource Limit設定を甘く見て、深夜にアラートで叩き起こされた話

KubernetesのResource Limit設定を甘く見て、深夜にアラートで叩き起こされた話

数ヶ月前、自社サービスのバックエンドをDocker Composeでの運用からKubernetes(EKS)へ移行した。コンテナ化自体はそれほど難しくなかったし、デプロイの自動化も進んで「これで運用が楽になるぞ」と、かなり手応えを感じていた。

ところが、移行して数週間経ったある日の深夜。PagerDutyのアラートで叩き起こされた。Podが次々と `CrashLoopBackOff` を繰り返しているという内容だ。

ログを確認しても、アプリケーション自体は正常に起動プロセスを進めているように見える。エラーメッセージすら出ていない。まるで、何かに突然断ち切られたかのように、プロセスが忽然と消えているのだ。

結局、原因はKubernetesの `Resource Limits` 設定にあった。

「とりあえず」で設定したLimitの罠

Docker Composeの時代は、物理マシンのリソースをフルに使えるので、メモリやCPUの制限をあまり気にしなくてよかった。しかし、Kubernetesでは `requests` と `limits` の設計が運用の肝になる。

今回、私は「念のため」という理由で、メモリの `limits` を少し低めに設定していた。これが致命的だった。JavaやNode.jsといったランタイムは、起動時にある程度のメモリを確保しようとする。その際、Kubernetes側の `limits` に達してしまうと、OS(カーネル)のOOM Killerによってプロセスが強制終了させられる。

このとき、アプリケーション側には「メモリが足りない」というエラーログすら吐かせることができない。これが、デバッグを困難にする最大の要因だ。

以下に、問題が発生した時のマニフェストの一部を載せておく。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-api
spec:
  template:
    spec:
      containers:
      - name: api-container
        image: my-app:latest
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            # ここが小さすぎた。起動直後にOOMKilledされる原因。
            memory: "512Mi" 
            cpu: "500m"

`requests` を低く設定しすぎると、ノードの空きリソースを過信してしまい、負荷が増えた瞬間にPodが不安定になる。逆に `limits` を小さくしすぎると、今回のようにプロセスが不意に死ぬ。

調査から学んだこと

このトラブルを解決するために、まずは `kubectl describe pod [pod-name]` で状態を確認した。そこでようやく `Reason: OOMKilled` という文字を見つけたときは、正直ゾッとした。

ここから、実際のメトリクス(CloudWatch Container Insights)を遡って、アプリケーションのメモリ使用量の推移と、Podが死んだタイミングを照らし合わせる作業に入った。

経験上、コンテナ化におけるリソース設計には以下の3つのポイントが重要だと痛感している。

まず、`requests` は「最低限これだけは保証してほしい」という値として、実際の起動時・稼動時のベースラインに基づいて設定すること。そして `limits` は、スパイク(急激な負荷増)を許容できるだけのバッファを持たせること。

次に、言語特有の挙動を考慮することだ。例えばJVMを使っている場合、`-Xmx` の設定とKubernetesの `limits.memory` が矛盾していると、コンテナ全体が死ぬ。コンテナ内のメモリ使用量だけでなく、ランタイムが認識しているヒープサイズとの整合性を取る必要がある。

最後に、監視体制。単に「Podが生きているか」を見るだけでなく、`container_memory_working_set_bytes` と `container_spec_memory_limit_bytes` の差分をグラフ化しておくべきだった。これさえ見ていれば、限界に達する前に予兆を察知できたはずだ。

結局、設定を見直して `limits` を十分に引き上げ、負荷試験を通じて適切なラインを見極めるまで、数日間は神経を使う運用が続いた。コンテナオーケストレーションは強力だが、その分、インフラ層とアプリケーション層の両方の知識がないと、こうした「音のない死」に対処できない。