Podが強制退去エラー「The node was low on resource memory」をKubernetesで修正する方法

intermediate☸️ Kubernetes2026-05-05| Kubernetes 1.20以降、任意のクラウドプロバイダー(EKS、GKE、AKS)またはオンプレミスクラスター

Error Message

The node was low on resource: memory. Threshold quantity: 100Mi, available: 95Mi.
#kubernetes#pod#eviction#memory#resources#node

エラーの内容

クラスターを確認すると、ポッドが Evicted 状態で止まっています。kubectl describe pod を実行すると、次のように表示されます:

Status: Failed
Reason: Evicted
Message: The node was low on resource: memory. Threshold quantity: 100Mi, available: 95Mi.

kubelet がノードの安定性を守るためにポッドを強制終了させました。これは OOMKilled とは異なります — OOMKilled はコンテナ自身のメモリ制限を超えた場合に発生します。エビクションは、そこまで悪化するに kubelet が行う予防的な措置です。ノードの空きメモリが設定済みのしきい値(デフォルトは 100Mi)を下回ると、kubelet はポッドを事前に削除し始めます。

なぜこの問題が起きるのか

kubelet は利用可能なメモリをエビクションしきい値と照合して監視しています。memory.available が 100Mi を下回ると、ポッドを次の順序でエビクトし始めます:BestEffort ポッドが最初、次に Burstable、最後に Guaranteed ポッドです。あなたのポッドは不運にも対象になってしまいました。

よくある原因:

  • requestslimits が設定されていないポッド — スケジューラーが実際に必要なメモリ量を把握できない
  • アプリ本体または依存関係のメモリリーク
  • 実行しているワークロードに対してノードのスペックが明らかに不足している
  • 隣のポッドが OOMKill される直前に制限を大幅に超えてメモリを消費した
  • リソースリクエストが未設定のため、ポッドが一つのノードに詰め込まれすぎている

ステップ 1:エビクトされたポッドと原因を特定する

まず、全ネームスペースのエビクトされたポッドを一覧表示します:

kubectl get pods -A --field-selector=status.phase=Failed | grep Evicted

特定のポッドのエビクションメッセージを詳しく確認します:

kubectl describe pod <pod-name> -n <namespace>

次に、負荷がかかっていたノードを確認します:

kubectl describe node <node-name> | grep -A 10 'Conditions:'

MemoryPressure: True になっているか確認してください。また、ノードレベルの現在のリソース使用状況を確認するために次のコマンドも実行します:

kubectl top nodes

ステップ 2:エビクトされたポッドを削除する

エビクトされたポッドは自動的には消えません。Failed 状態のまま残り続け、API オブジェクトのスロットを消費します。以下のコマンドで削除してください:

# 特定のネームスペース内のエビクトされたポッドをすべて削除
kubectl delete pods -n <namespace> --field-selector=status.phase=Failed

# または全ネームスペースを対象に削除
kubectl delete pods -A --field-selector=status.phase=Failed

ステップ 3:リソースの requests と limits を設定する

resources ブロックがない場合は、まずそこを修正してください。スケジューラーはリクエストのないポッドをコストゼロとして扱い、すでに負荷の高いノードにも平気で配置します。そういったポッドはエビクション発生時に真っ先に対象となります。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: my-app
        image: my-app:latest
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

requests は通常負荷時のアプリの使用量に合わせて設定してください — 数日間 kubectl top pod で確認しながら決めましょう。limits は許容できる上限値に設定します。この設定を誤ることが、エビクション問題の最大の原因です。

ステップ 4:ノードの容量と割り当てリソースを確認する

問題のノードで実際に割り当てられているリソースを確認します:

kubectl describe node <node-name> | grep -A 8 'Allocated resources'

出力例:

Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                1850m (46%)  3200m (80%)
  memory             6Gi (95%)    8Gi (127%)

メモリリクエストが 95%、リミットが 127% ですか?そのノードはオーバーコミット状態です。少しでもスパイクが起きればエビクションが発生します。ノードのスペックをアップグレードするか、クラスターにノードを追加するか、一部のポッドを別の場所に移動する必要があります。

ステップ 5:メモリを大量消費しているポッドを特定する

メモリ消費量が多い順にポッドをソートして、何が最も消費しているかを確認します:

kubectl top pods -A --sort-by=memory | head -20

リクエスト値に近い、またはそれを超えた状態が続いているポッドは、設定値が低すぎるかメモリリークが発生しています。実際の使用量と設定値を照合してください:

kubectl top pod <pod-name> -n <namespace>
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.containers[*].resources}'

ステップ 6:QoS でクリティカルなポッドを保護する

Kubernetes はリソース設定に基づいて Quality of Service クラスを決定します。エビクションで最後まで残る Guaranteed ステータスを得るには、requests と limits を同じ値に設定します:

resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "500m"

本当にクリティカルなものには、PriorityClass も合わせて設定してください:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
---
# Deployment の spec 内:
spec:
  template:
    spec:
      priorityClassName: high-priority

ステップ 7:ノードのスケールアップまたは追加(必要な場合)

ノードのスペックが単純に不足している場合もあります。マネージドクラスターでキャパシティを追加するには:

# EKS — ノードグループを更新
eksctl scale nodegroup --cluster=<cluster> --nodes=5 --name=<nodegroup>

# GKE — ノードプールをリサイズ
gcloud container clusters resize <cluster> --node-pool=<pool> --num-nodes=5

# またはクラスターオートスケーラーに任せる
kubectl get deployment cluster-autoscaler -n kube-system

修正の確認

# ノードに MemoryPressure がないことを確認
kubectl get nodes
# STATUS が Ready であり、MemoryPressure でないことを確認

# ノードの状態を確認
kubectl describe node <node-name> | grep MemoryPressure
# 期待値: MemoryPressure   False

# ポッドが Running 状態を維持しているか監視
kubectl get pods -n <namespace> -w

# エビクトされたポッドが残っていないことを確認
kubectl get pods -A --field-selector=status.phase=Failed

再発を防ぐために

  • **リソースリクエストは必ず設定する — 例外なし。**小さなポッドも同様です。LimitRange を使ってクラスター全体に最小値を強制し、設定漏れが起きないようにしましょう。
  • **まず VPA を推奨モードで実行してみましょう。**Vertical Pod Autoscaler が数日間ポッドを監視し、実際に設定する前に現実的なリクエスト値を提案してくれます。
  • **MemoryPressure が問題になる前にアラートを設定しましょう。**深夜 2 時にデプロイが壊れて初めてエビクションに気づくのは避けたいものです。
  • ステートフルまたはクリティカルなサービスには PodDisruptionBudget を追加し、エビクション時に最低限の稼働レプリカ数が確保されるようにしましょう。
  • ネームスペースレベルで LimitRange を使ってリソース指定を強制する — リクエストが指定されていない場合にポッドの作成をブロックします:
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: my-namespace
spec:
  limits:
  - default:
      memory: 512Mi
      cpu: 500m
    defaultRequest:
      memory: 256Mi
      cpu: 250m
    type: Container

Related Error Notes