2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetes で Pod が Running にならない原因を切り分ける

2
Last updated at Posted at 2026-10-18

はじめに

kubectl get pod で Pending が続く場合や、Running でも Ready にならない場合、再起動を繰り返す場合の切り分け方法をまとめます。

STATUS 表示だけで原因を決めず、Conditions・Events・コンテナの終了状態とログを合わせて確認します。

切り分けでは、次の順番で Pod の状態を確認します。

  1. kubectl get pod <pod-name> -o yaml で、Pod の設定や Conditions、コンテナの現在・直前の状態を確認する
  2. kubectl describe pod <pod-name> で、Events を確認し、どの段階で何が失敗しているかを絞り込む
  3. kubectl logs <pod-name> で、起動したコンテナのログから具体的な原因を調べる

本記事では、この流れを軸に、それぞれのコマンドで確認すべき項目と、状態に応じた切り分け方法を詳しく説明します。

対象コンポーネントとバージョン

コンポーネント バージョン リポジトリ 本記事での役割
kubelet v1.37.1 kubernetes/kubernetes Phase 判定ロジック・Probe 失敗時の挙動・backoff の実装確認
kube-scheduler v1.37.1 kubernetes/kubernetes スケジューリング失敗時の Event Reason の確認
kube-controller-manager v1.37.1 kubernetes/kubernetes Readiness 失敗時の EndpointSlice への反映
containerd v2.3.4 containerd/containerd イメージ取得失敗時のエラー分類(NotFound / ErrUnexpectedStatus)の実装確認
kind v0.33.0 kubernetes-sigs/kind 検証環境。ノードイメージはkindest/node:v1.37.0

TL;DR

  • STATUS 列と status.phase は別物です。Phase が Running でも、コンテナが CrashLoopBackOff になっている場合があります。
  • Conditions・Events・ログを組み合わせます。配置前なら scheduler の Event、配置後なら起動状態や kubelet の Event、起動済みならログと終了理由を確認します。
  • ImagePullBackOff は再試行待ちの表示です。名前・認証・DNS・通信・証明書・レジストリ応答など、原因は直前の失敗メッセージから調べます。
  • Readiness の失敗だけでは再起動しません。Liveness / Startup の連続失敗は停止と再起動判断につながります。EndpointSlice に反映されることと、Service の転送先として使われることは別です。
  • 外部依存を Probe に含めるかは、失敗時の影響から考えます。共有 DB の障害で全 Pod が再起動したり、すべての Pod が Service の転送先から外れたりする構成を避けるよう設計します。

第1章: Pod が Running にならないとは

STATUS 列と status.phase の違いを整理し、Phase の判定ロジックを kubelet のソースコードで確認します。

1.1 Pod Phase の定義

Pod の status フィールドは PodStatusというオブジェクトで、phase フィールドは次の5つの値を取ります。

Phase 概要
Pending 配置待ちや、コンテナの初期化・イメージ取得などが残っている
Running ノードに割り当てられ、コンテナが起動済みで、少なくとも1つが実行中または起動・再起動中
Succeeded コンテナが正常終了し、再起動されない
Failed コンテナの失敗などにより、Pod が失敗として終了した
Unknown Pod の状態を取得できない場合の API 上の値。現在の通常の実装では設定されない

(参考: Pod Lifecycle)

ソースコード上でも、この5値は次のように定義されています。PodUnknownは2015年以降セットされていないとコメントに明記されています。

// PodPending means the pod has been accepted by the system, but one or more of the containers
// has not been started. This includes time before being bound to a node, as well as time spent
// pulling images onto the host.
PodPending PodPhase = "Pending"
// PodRunning means the pod has been bound to a node and all of the containers have been started.
// At least one container is still running or is in the process of being restarted.
PodRunning PodPhase = "Running"
// PodSucceeded means that all containers in the pod have voluntarily terminated
// with a container exit code of 0, and the system is not going to restart any of these containers.
PodSucceeded PodPhase = "Succeeded"
// PodFailed means that all containers in the pod have terminated, and at least one container has
// terminated in a failure (exited with a non-zero exit code or was stopped by the system).
PodFailed PodPhase = "Failed"
// PodUnknown means that for some reason the state of the pod could not be obtained, typically due
// to an error in communicating with the host of the pod.
// Deprecated: It isn't being set since 2015 (74da3b14b0c0f658b3bb8d2def5094686d0e9095)
PodUnknown PodPhase = "Unknown"

(参考: types.go#L3718-L3734)

ノードとの通信障害が、必ず Phase の Unknown として表れるわけではありません。ノード障害時の状態変化や代替 Pod が作成されるまでの流れは、Kubernetesノードが物理障害でNotReadyになってからPodが再スケジュールされるまでで詳しく整理しています。

Job などが正常終了して Succeeded になるのは、Running にならない障害とは区別します。

Phase は Pod の状態を大まかに示す値です。コンテナの詳細な状態や、リクエストを処理できるかどうかは、Phase だけでは判断できません。

(参考: Pod Lifecycle)

1.2 STATUS 列は Phase とは異なる

CrashLoopBackOff や Terminating は kubectl の STATUS 表示であり、Pod Phase の値ではありません。

(参考: Pod Lifecycle)

STATUS 列は、Pod やコンテナの状態からテーブルプリンタが組み立てる文字列で、Pod API の phase とは別物です。通常の kubectl get は API server に表形式を要求するため、表示の計算が必ず kubectl 内で行われるわけではありません。

(参考: printers.go#L975-L1056)

以下は既存の kind 検証記録です。YAML を crashloop.yaml として保存して再現できます。

apiVersion: v1
kind: Pod
metadata:
  name: crashloop
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "echo boom; exit 1"] # 異常終了を繰り返す例(restartPolicy はデフォルトの Always)
kind create cluster --name pod-debug --image kindest/node:v1.37.0
kubectl apply -f crashloop.yaml

再起動を繰り返して backoff 状態に入った後、kubectl get podのSTATUS列は次のようにCrashLoopBackOffまたはErrorと表示されます。

$ kubectl get pods
NAME        READY   STATUS             RESTARTS      AGE
crashloop   0/1     CrashLoopBackOff   3 (69s ago)   88s

ところが、同じタイミングでstatus.phaseを直接確認するとRunningです。

$ kubectl get pod crashloop -o jsonpath='{.status.phase}'
Running

別の取得時点の YAML では、次の項目を確認します。

status:
  containerStatuses:
  - lastState: # 直前の終了状態
      terminated:
        exitCode: 1 # 直前の実行も異常終了している
        reason: Error
    name: app
    ready: false # コンテナは Ready になっていない
    restartCount: 4 # 再起動回数。取得時点が異なるため、上の出力とは一致しない
    state: # 現在の状態。再起動中は Waiting や Terminated などに変わる
      terminated:
        exitCode: 1 # 今回の実行も異常終了している
        reason: Error
  phase: Running # コンテナが異常終了していても、再起動対象のため Running

この Pod は、デフォルトの restartPolicy: Always に従って再起動します。次節で Phase の判定ロジックを確認します。

1.3 コンテナの3状態からPhaseが決まるロジック

Podの中の各コンテナはWaiting・Running・Terminatedの3状態のいずれかを取ります。

(参考: Pod Lifecycle)

終了後に再起動するかは、終了理由・終了コードと再起動ポリシーで決まります。Always だけでなく、OnFailure でも失敗終了後は再起動します。コンテナ単位の restartPolicy / restartPolicyRules を指定する場合は、そのルールも確認します。

kubeletは、この3状態をPodの全コンテナについて集計し、getPhaseという関数でstatus.phaseを1つに決定しています。まず、Waiting状態のコンテナは次のように分類されます。

case containerStatus.State.Waiting != nil:
    if containerStatus.LastTerminationState.Terminated != nil {
        stopped++
        // (ContainerRestartRules有効時の再起動可否判定、省略)
    } else {
        waiting++
    }

(参考: kubelet_pods.go#L1800-L1814)

ポイントは、現在Waiting状態のコンテナであっても、直前の終了状態が記録されていれば (LastTerminationState.Terminated != nil)waitingではなくstoppedとしてカウントされることです。crashloop のように直前の終了状態が残っている再起動待ちのコンテナは、この集計では waiting に数えられません。

集計後の Phase 判定は、次の switch 文で行われます。以下は、Init Container の初期化待ちなどの分岐とログ出力を省略した抜粋です。

switch {
case waiting > 0:
    // One or more containers has not been started
    return v1.PodPending
case running > 0 && unknown == 0:
    // All containers have been started, and at least
    // one container is running
    return v1.PodRunning

(参考: kubelet_pods.go#L1839-L1846)

ここでの waiting は、過去の終了状態がない Waiting コンテナの集計です。初回起動待ちのコンテナが残れば、別のコンテナが Running でも Phase は Pending になり得ます。再起動待ちの Waiting まで一律に Pending の原因になるわけではありません。通常の Init Container の初期化待ちなども、先行する分岐で Pending になります。

そしてcrashloopのケースのように、全コンテナがstopped(かつwaitingが0)の場合は次の分岐に入ります。

if utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules) {
    if stopped != stoppedNotRestartable {
        // At least one containers are in the process of restarting
        return v1.PodRunning
    }
    if stopped == succeeded {
        return v1.PodSucceeded
    }
    return v1.PodFailed
}

(参考: kubelet_pods.go#L1863-L1872)

v1.37.1 でデフォルト有効の ContainerRestartRules では、再起動可能な停止コンテナが残っていると Running を返します。コンテナ単位のルールを指定しないこの例は、Pod の restartPolicy がデフォルトの Always なので再起動対象になります。この処理によって、1.2節で確認した「STATUS列はCrashLoopBackOffなのにstatus.phaseはRunning」という表示の違いを説明できます。

このため、Phase だけでは Pod の健全性を判断できません。PhaseがRunningであっても、実際にはクラッシュを繰り返しているだけかもしれません。次章で見るReady/ContainersReadyといったPod Conditionsや、STATUS列・RESTARTS列の方が、実際の状態を判断するうえでは有用です。

第2章: 一次切り分け

Podが期待通りRunningにならないとき、まずどのコマンドで何を確認すればよいかを整理します。

調査対象となるコンポーネントの位置は、公式のクラスタ構成図で確認できます。

Kubernetes の control plane と worker node の構成図。API server、scheduler、controller manager、etcd と、各ノードの kubelet、container runtime、kube-proxy の位置関係を示す。

Kubernetes のコンポーネント構成。配置先が未定なら scheduler と関連リソース、割り当て済みならそのノードの kubelet・ランタイム・ネットワーク・ストレージを確認します。

(参考: Kubernetes Components)

調査では、まず Pod の設定・状態と Events を確認し、コンテナが起動していればログを調べます。そこで得られた手がかりから、必要に応じて配置先ノードへ調査を進めます。確認する順番と分岐を、次のフローチャートにまとめます。

以降のコマンド例は、対象 namespace が現在の context に設定されている前提です。別の namespace を調べる場合は -n <namespace> を付けます。

削除済みの Pod は、メトリクスで時間帯を絞り、保存済みの Events・アプリケーションログを Pod 名・namespace・Pod UID で照合します。ログの収集・確認方法は、Kubernetesのログ入門を参照してください。

2.1 kubectl get pod: 一覧と YAML で状態を確認する

$ kubectl get pods
NAME                READY   STATUS                       RESTARTS      AGE
bad-configmap-key   0/1     CreateContainerConfigError   0             88s
bad-image           0/1     ImagePullBackOff             0             88s
crashloop           0/1     CrashLoopBackOff             3 (69s ago)   88s
unschedulable       0/1     Pending                      0             88s

STATUS 列に加えて、READY 列と RESTARTS 列を確認します。READY 列は、本体コンテナとサイドカー形式の Init Container のうち、Ready になった数と対象コンテナ数を示します。前章で確認した通り、STATUS列やstatus.phaseだけでは「本当に正常に稼働しているか」を判断できないためです。

対象の Pod を絞ったら、YAML で設定と状態を確認します。

kubectl get pod <pod-name> -o yaml

以下は、コンテナが異常終了し、再起動待ちになっている Pod を想定した出力例です。確認する設定と状態を抜粋しています。

spec:
  nodeName: worker-1 # 配置先ノードを確認
  restartPolicy: Always # 終了後の再起動ポリシーを確認
  containers:
  - name: app
    image: example.com/app:v1 # イメージ名・タグを確認
    command: ["/app/server"] # 起動コマンドを確認(args があれば併せて確認)
    env: # 環境変数の値や ConfigMap / Secret の参照先を確認
    - name: DB_HOST
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: db-host
    resources: # Requests / Limits を確認
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        memory: 256Mi
    readinessProbe: # Probe の種類・接続先・タイミング設定を確認
      httpGet:
        path: /ready
        port: 8080
status:
  phase: Running # Running だけでは正常稼働と判断しない
  conditions: # 各 Condition の status・reason・message を確認
  - type: PodScheduled
    status: "True"
  - type: Initialized
    status: "True"
  - type: ContainersReady
    status: "False"
    reason: ContainersNotReady
    message: 'containers with unready status: [app]'
  - type: Ready
    status: "False"
    reason: ContainersNotReady
    message: 'containers with unready status: [app]'
  containerStatuses: # Init Container は initContainerStatuses を確認
  - name: app
    ready: false # コンテナが Ready かを確認
    restartCount: 3 # 再起動回数を確認
    state: # 現在の状態。この例は異常終了後の再起動待ち
      waiting:
        reason: CrashLoopBackOff
        message: back-off restarting failed container
    lastState: # 直前の終了理由・終了コードを確認
      terminated:
        exitCode: 1
        reason: Error

続いて describe の Events とログから、異常終了の原因を調べます。

2.2 kubectl describe pod: Conditions と Events

kubectl describe pod では、Pod Conditions と Events をまとめて確認できます。

Pod Conditions はライフサイクルや準備状態を表します。status は True・False・Unknown のいずれかで、Condition によって更新するコンポーネントが異なります。

Type 意味
PodScheduled ノードへのスケジューリングが完了した
PodReadyToStartContainers sandbox・ネットワーク・ボリュームと、要求した動的リソースの準備が完了した
Initialized 通常の Init Container が正常終了し、サイドカー形式の Init Container が Started になった
ContainersReady 本体コンテナとサイドカー形式の Init Container がすべて Ready である
Ready ContainersReady に加え、指定した readinessGates がすべて True である

(参考: Pod Lifecycle)

Pod sandbox の役割や、再作成時のコンテナ・Pod IP への影響は、Pod sandbox とは何かで整理しています。

False の Condition から、起動が止まっている段階を絞ります。ただし、Conditions は現在の状態を示すため、一度 True になっても障害で False に戻る場合があります。Init Container がない Pod では、Initialized は sandbox 作成前に True になります。

更新するコンポーネントはDeployment を apply してから Pod が Ready になるまで、更新順序や Init Container の挙動はPod の起動順序を参照してください。

イメージ取得に失敗している bad-image の記録では、次の状態でした。

$ kubectl describe pod bad-image
...
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       False
  ContainersReady             False
  PodScheduled                True

この例では配置と sandbox の準備まで進んでいます。Initialized=True は、Init Container がない場合にも成立します。Ready / ContainersReady が False である理由は、起動前の失敗、Probe の失敗など複数あるため、コンテナの state と Events を合わせて確認します。

Events の Reason・message から、具体的な失敗を調べます。Event の発行元と発行条件は、KubernetesのEventログの一覧を参照してください。

2.3 kubectl logs: アプリケーションのログ

対象コンテナが起動済みでログが残っていれば、標準出力・標準エラーを確認できます。-c で対象コンテナを指定し、直前の再起動前のログには --previous を使います。Init Container も同じ方法で確認できます。

# 現在のコンテナのログを末尾100行取得
kubectl logs <pod-name> -c <container-name> --tail=100

# 直前に終了したコンテナのログを末尾100行取得
kubectl logs <pod-name> -c <container-name> --previous --tail=100

一度も起動していないコンテナは Events を確認します。ログの収集・保持や、複数 Pod を横断する確認方法は、Kubernetesのログ入門を参照してください。

2.4 kubectl describe node: Conditions と Events

Pod の情報だけで原因が分からない場合や、リソース不足が疑われる場合は、配置先のノードも確認します。kubectl get pods -o wide の NODE 列、または Pod の spec.nodeName で配置先を特定し、kubectl describe node <node-name> で Conditions・Events を確認します。同じノードの複数 Pod で障害が起きていないかも手がかりになります。

kubectl describe node <node-name>

Node Conditions は、ノードの準備状態やリソース不足の状態を表します。status は True・False・Unknown のいずれかです。主に次の項目を確認します。

Type 意味 調査の手がかりとなる状態
Ready ノードが Pod を実行できる準備状態にある False、またはノードからの状態報告が途絶えるなどして Unknown
MemoryPressure 利用可能なメモリが不足している True
DiskPressure ディスク空き容量や inode が不足している True
PIDPressure 利用可能なプロセス ID が不足している True
NetworkUnavailable ノードのネットワークが正しく構成されていない True

Ready は True が通常の状態ですが、Pressure 系の Conditions は True がリソース不足を示します。status に加え、Reason・Message から具体的な状況を確認します。

(参考: Node Status)

以下は、MemoryPressure が発生しているノードを想定した出力例です。確認する項目に絞り、時刻などの列や一部の行を省略しています。

$ kubectl describe node worker-1
...
Taints:             node.kubernetes.io/memory-pressure:NoSchedule
...
Conditions:
  Type             Status  ...  Reason                       Message
  ----             ------       ------                       -------
  MemoryPressure   True    ...  KubeletHasInsufficientMemory  kubelet has insufficient memory available
  DiskPressure     False   ...  KubeletHasNoDiskPressure      kubelet has no disk pressure
  PIDPressure      False   ...  KubeletHasSufficientPID       kubelet has sufficient PID available
  Ready            True    ...  KubeletReady                 kubelet is posting ready status
...
Non-terminated Pods:          (一部抜粋)
# 同じノード上の Pod と Requests / Limits を確認
  Namespace  Name     CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------  ----     ------------  ----------  ---------------  -------------  ---
  default    app-xxx  500m (25%)    1 (50%)     256Mi (6%)       512Mi (12%)    10m
  ...
Allocated resources:
# Requests / Limits の集計。実際の使用量ではない
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource  Requests     Limits
  --------  --------     ------
  cpu       1000m (50%)  2 (100%)
  memory    1Gi (25%)    2Gi (50%)
  ...
Events:
  Type    Reason                     Age  From     Message
  ----    ------                     ---  ----     -------
  # Type が Normal でも、メモリ不足を示す Event
  Normal  NodeHasInsufficientMemory  30s  kubelet  Node worker-1 status is now: NodeHasInsufficientMemory

Ready=True でも Pressure が発生するため、Reason・Message・Events を合わせて確認します。Pod の停止や Eviction は対象 Pod の状態・Events と照合し、使用量は障害発生時のメトリクスで調べます。詳しくは3.5節を参照してください。

2.5 Event Reason の一覧

代表的な Event Reason を、発生する段階別に整理します。

段階 Reason Type 典型的な意味
スケジューリング FailedScheduling Warning 適合するノードが無い(リソース不足・Affinity/Taint不一致・PVC未バインドなど)
スケジューリング Scheduled Normal ノードへのバインドに成功した
イメージ取得 Pulling / Pulled Normal イメージ取得の開始、取得完了または取得済みであることを示す
イメージ取得 Failed(ErrImagePull) Warning イメージの取得に失敗した
イメージ取得 BackOff(ImagePullBackOff) Warning イメージ取得の失敗後、再試行を待機している
コンテナ起動 Created / Started Normal コンテナの作成/起動に成功した
コンテナ起動 Failed(CreateContainerConfigError) Warning 存在しないConfigMap/Secretキーの参照など、コンテナ設定の解決に失敗した
コンテナ起動 BackOff(CrashLoopBackOff) Warning コンテナの終了後、再起動を待機している
Probe Unhealthy Warning Liveness/Readiness/Startup probeが失敗した
Probe など Killing Normal コンテナを停止している。Probe 失敗だけでなく Pod の終了などでも出るため message を読む
sandbox・ネットワーク FailedCreatePodSandBox Warning ランタイムや CNI などによる sandbox 作成の失敗
sandbox の再作成 SandboxChanged Normal sandbox の変更を検出し、コンテナの停止と sandbox の再作成を行う。発生原因はノード・kubelet・ランタイムのログなどで確認する
ボリューム FailedMount / FailedAttachVolume Warning ボリュームのマウント・アタッチの失敗

括弧内の ErrImagePull などはコンテナ状態の Reason やメッセージに現れる値で、Event の Reason 自体は Failed / BackOff です。

Pod リソースの作成から Running になるまでの流れに対応させると、次のようになります。2.2節の Conditions と合わせて、どの段階で止まっているかを絞り込みます。

図は主な処理の流れです。Event の発生順序や Running への遷移条件は、構成やコンテナの状態によって異なります。Init Container の詳細は3.3節を参照してください。

(参考: Pod Lifecycle)

第3章: 原因別の対応方法

一次切り分けで原因の見当がついたら、具体的な対応に入ります。ここでは主な原因を5つに分け、既存の kind 検証記録と確認コマンドを交えて整理します。出力の Age や再起動回数は取得時点で変わります。

次に読む節を選ぶための目安です。

3.1 Kubernetes のマニフェスト関連の不備

スケジューリング、イメージ取得、起動設定、API アクセス、Probe、設定更新の順に確認します。正常な起動の流れは、Deployment を apply してから Pod が Ready になるまでを参照してください。主な手がかりは次の Events です。

  • FailedScheduling
  • Failed(CreateContainerConfigError)
  • Failed(ErrImagePull)
  • BackOff(ImagePullBackOff)
  • Unhealthy(Probe設定自体の不備の場合)

Unhealthyは3.2節・3.4節でも登場するため、このReason単体ではマニフェスト起因かアプリ起因か外部要因かを区別できません。message の内容も確認します。設定更新が環境変数に反映されていない場合は、これらの Event が出ず、アプリケーションの接続エラーとして表れることもあります。

3.1.1 スケジューリングに失敗するケース

CPU 要求が大きすぎる Pod の例です。Filter の仕組みは、kube-scheduler が Pod のスケジュール先を決める仕組みを参照してください。

apiVersion: v1
kind: Pod
metadata:
  name: unschedulable
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "sleep 3600"]
    resources:
      requests:
        cpu: "1000" # 1000 CPU を要求し、配置先のリソース不足を再現
# 上の YAML を unschedulable.yaml として保存
kubectl apply -f unschedulable.yaml
$ kubectl describe pod unschedulable
...
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  17s   default-scheduler  0/1 nodes are available: 1 Insufficient cpu. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.

Insufficient cpuという文字列は、スケジューラのnoderesourcesプラグインにハードコードされた理由文字列です。

Reason:       "Insufficient cpu",

(参考: fit.go#L736)

同様に、nodeAffinity/nodeSelectorの不一致はnode(s) didn't match Pod's node affinity/selector、Taintの不許容はnode(s) had untolerated taint(s)という固定文字列で理由が返ります。

ErrReasonPod = "node(s) didn't match Pod's node affinity/selector"

(参考: node_affinity.go#L65、taint_toleration.go#L133)

即時バインドする PersistentVolumeClaim が未バインドの場合は、スケジューラのvolumebindingプラグインがpod has unbound immediate PersistentVolumeClaimsという理由を返します。

(参考: volume_binding.go#L372)

これらの理由は、FailedScheduling Event の message に現れます。配置待ちで Pending が続く場合は、kubectl describe pod でこのメッセージを確認します。

3.1.2 イメージ名やタグが存在しないケース

イメージ取得時の失敗には、ErrImagePull/ImagePullBackOff/InvalidImageName/ErrImageNeverPullといった理由があり、kubelet の images パッケージで定義されています。

// ErrImagePullBackOff - Container image pull failed, kubelet is backing off image pull
ErrImagePullBackOff = errors.New("ImagePullBackOff")
// ErrImagePull - General image pull error
ErrImagePull = errors.New("ErrImagePull")
// ErrImageNeverPull - Required Image is absent on host and PullPolicy is NeverPullImage
ErrImageNeverPull = errors.New("ErrImageNeverPull")
// ErrInvalidImageName - Unable to parse the image name.
ErrInvalidImageName = errors.New("InvalidImageName")

(参考: images/types.go#L28-L41)

ErrImagePull/ImagePullBackOffは、messageフィールドの文字列まで読まないと原因を区別できません。以下では代表例を確認します。認証・権限不足、証明書の検証失敗、レート制限など、ほかの原因もあります。

再現する場合は、次のコマンドで Pod を作成します。

kubectl run no-such-tag --image=busybox:this-tag-does-not-exist-xyz123

存在しないタグを指定したPodを作成すると、レジストリ自体には到達できているため、コンテナランタイム(今回の検証環境ではcontainerd)からNotFoundに相当するエラーが返ってきます。

$ kubectl describe pod no-such-tag
...
Warning  Failed  1s  kubelet  spec.containers{no-such-tag}: Failed to pull image "busybox:this-tag-does-not-exist-xyz123": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/busybox:this-tag-does-not-exist-xyz123": failed to resolve reference "docker.io/library/busybox:this-tag-does-not-exist-xyz123": docker.io/library/busybox:this-tag-does-not-exist-xyz123: not found

containerd側でも、レジストリが404を返した場合は明確にErrNotFoundとして分類されています。

if resp.StatusCode > 299 {
    if resp.StatusCode == http.StatusNotFound {
        if firstErrPriority < 2 {
            firstErr = fmt.Errorf("%s: %w", ref, errdefs.ErrNotFound)

(参考: resolver.go#L334-L343。検証環境のノードが使用していたcontainerdのバージョンに合わせてv2.3.4のソースを引用しています)

イメージ名・タグ・レジストリのパス、push の成否、削除されていないかを確認します。NotFound だけでアクセス権限が正常とは判断せず、認証情報も合わせて確認します。

3.1.3 レジストリに到達できない

次に、存在しないホスト名のレジストリを指定したケースです。同名の Pod がない環境では、次のコマンドで再現できます。

kubectl run bad-image --image=registry.example.invalid/no-such-image:v1
$ kubectl describe pod bad-image
...
Warning  Failed  4s  kubelet  spec.containers{app}: Failed to pull image "registry.example.invalid/no-such-image:v1": failed to pull and unpack image "registry.example.invalid/no-such-image:v1": failed to resolve reference "registry.example.invalid/no-such-image:v1": failed to do request: Head "https://registry.example.invalid/v2/no-such-image/manifests/v1": dial tcp: lookup registry.example.invalid on 192.168.5.2:53: no such host

出力例のコンテナ名は既存の検証記録では app ですが、上の kubectl run では bad-image になります。

dial tcp: lookup ... no such host は DNS 解決の失敗を示します。レジストリ名の誤字を確認し、名前が正しければノードの DNS・Proxy・Firewall などを調べます。

3.1.4 タイムアウト・HTTP エラー・認証失敗を調べる

タイムアウトは、レジストリの高負荷だけでなく、ノードの DNS・Proxy・Firewall・通信品質・ディスク I/O などでも起こり得ます。deadline exceeded だけでレジストリ側の障害と断定せず、ノードからの到達性とランタイムのログを確認します。

message の例 次に確認するもの
not found / NotFound 名前・タグ・パス、push 状況、アクセス権限
no such host レジストリ名、ノードの DNS 設定
connection refused / timeout / deadline exceeded 宛先・Proxy・Firewall・ノードからの到達性、ランタイムのログ
unauthorized / denied / 401 / 403 imagePullSecrets、Secret の namespace、レジストリの権限
x509 証明書の期限・名前・CA、ノード側の信頼設定
429 / 5xx レート制限、レジストリ・ミラー・Proxy の応答

containerd は予期しない HTTP 応答を、URL やステータスを含むエラーとして返します。5xx でも Proxy やミラーからの応答の場合があるため、どの URL・どのサーバーから返ったかを確認します。

(参考: errors.go#L42-L43)

ImagePullBackOff は原因ではなく、失敗後の再試行を待っていることを表します。待機中のメッセージだけで判断せず、直前の Failed Event を読みます。

(参考: Debug Pods)

3.1.5 ConfigMap / Secret の参照に不備があるケース

ConfigMap / Secret の名前やキーの誤りは、コンテナの起動を妨げます。

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  existing-key: hello # この ConfigMap に存在するキー
---
apiVersion: v1
kind: Pod
metadata:
  name: bad-configmap-key
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "sleep 3600"]
    env:
    - name: SOME_VALUE
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: missing-key # 存在しないキー。optional はデフォルトで false のため起動に失敗
# 上の YAML を bad-configmap-key.yaml として保存
kubectl apply -f bad-configmap-key.yaml
$ kubectl describe pod bad-configmap-key
...
State:          Waiting
  Reason:       CreateContainerConfigError
...
Events:
  Type     Reason   Age  From     Message
  ----     ------   ---- ----     -------
  Warning  Failed   10s  kubelet  spec.containers{app}: Error: couldn't find key missing-key in ConfigMap default/app-config

このメッセージは、kubeletが環境変数を解決する処理の中でそのまま返しているエラー文字列です。

return result, fmt.Errorf("couldn't find key %v in ConfigMap %v/%v", key, pod.Namespace, name)

(参考: kubelet_pods.go#L917、Secret版はkubelet_pods.go#L944)

3.1.6 起動コマンド・実行権限・イメージが合っていないケース

アプリケーションの処理が始まる前に、起動コマンドや実行環境の不一致で失敗する場合があります。kubectl describe pod のコンテナ状態・Events と、取得できる場合はコンテナのログから、次の点を確認します。

エラーや状況の例 確認する内容
実行ファイルが見つからない command / args、イメージ内のファイル、作業ディレクトリ。スクリプトでは shebang のインタープリターも確認する
permission denied ファイルの実行権限、実行ユーザー・グループ、マウント先の権限
exec format error イメージとノードの CPU アーキテクチャ、実行ファイルやスクリプトの形式
read-only file system readOnlyRootFilesystem やボリュームの読み取り専用設定と、アプリが書き込むパス

例えば、イメージ内にシェルがないのに command で /bin/sh を指定したり、非 root ユーザーで起動したアプリが書き込み権限のないディレクトリを使ったりするケースです。マニフェストの command / args と securityContext を、イメージが想定する起動方法や必要なファイル権限と照合します。

再発防止では、本番と同じ実行ユーザーやファイルシステムの制約で起動を確認し、書き込みが必要なパスには用途に合うボリュームを用意するのが良いと思います。

(参考: Configure a Security Context for a Pod or Container)

3.1.7 ServiceAccount / RBAC の権限不足

Kubernetes API を参照する Operator などでは、権限不足の Forbidden により起動や初期化に失敗する場合があります。ログで拒否された操作を確認し、Pod の serviceAccountName と Role / RoleBinding などを照合します。

kubectl-rolesum を導入すると、ServiceAccount に紐づく Role / ClusterRole と権限をまとめて確認できます。

kubectl rolesum <serviceaccount-name> -n <pod-namespace>

以下は、kind で ConfigMap の get / list / watch を付与した ServiceAccount の実行結果です(端末の色付けは省略)。

$ kubectl rolesum example-operator -n rolesum-demo
ServiceAccount: rolesum-demo/example-operator
Secrets:

Policies:
• [RB] rolesum-demo/example-operator-configmaps ⟶  [R] rolesum-demo/configmap-reader
  Resource    Name  Exclude  Verbs  G L W C U P D DC
  configmaps  [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖

RB は RoleBinding、R は Role を示します。この例では G(get)・L(list)・W(watch)が許可されています。

ConfigMap / Secret を環境変数やボリュームで参照するだけなら、Pod の ServiceAccount に読み取り権限は不要です。

3.1.8 Probe の設定自体に不備があるケース

Probe のポートと、アプリケーションの待受ポートが異なる例です。

apiVersion: v1
kind: Pod
metadata:
  name: probe-wrong-port
spec:
  containers:
  - name: app
    image: nginx:1.27
    ports:
    - containerPort: 80 # nginx の待受ポート
    readinessProbe:
      httpGet:
        path: /
        port: 8080 # 誤った接続先。80 に修正する
      periodSeconds: 2
    livenessProbe:
      httpGet:
        path: /
        port: 80 # Liveness は正しい接続先のため、この例では再起動しない
      periodSeconds: 10
# 上の YAML を probe-wrong-port.yaml として保存
kubectl apply -f probe-wrong-port.yaml
$ kubectl get pod probe-wrong-port
NAME               READY   STATUS    RESTARTS   AGE
probe-wrong-port   0/1     Running   0          15s

$ kubectl describe pod probe-wrong-port
...
Events:
  Type     Reason     Age              From      Message
  ----     ------     ----             ----      -------
  Warning  Unhealthy  0s (x5 over 6s)  kubelet   spec.containers{app}: Readiness probe failed: Get "http://10.244.0.5:8080/": dial tcp 10.244.0.5:8080: connect: connection refused

Readiness の失敗で READY=0/1 のままです。Events の接続先と実際の待受ポートを照合します。

3.1.9 Probe の設定と初回起動時間が合っていないケース

初期化中に Liveness が失敗すると、起動を完了する前に再起動を繰り返す場合があります。起動ログと Events を照合し、実際の起動時間に合わせて Probe を設定します。

apiVersion: v1
kind: Pod
metadata:
  name: slow-start-app
spec:
  containers:
  - name: app
    image: nginx:1.27
    # 初期化に60秒かかるアプリを模擬
    command: ["sh", "-c", "sleep 60; exec nginx -g 'daemon off;'"]
    startupProbe:
      httpGet:
        path: /
        port: 80
      periodSeconds: 5
      failureThreshold: 30 # 起動を待つ時間の目安は 5秒 × 30回 = 150秒
      # 成功するまで Liveness / Readiness は実行されない
      # 失敗がしきい値に達するとコンテナを停止する
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 10 # コンテナ起動からの遅延。Startup 成功後の追加10秒ではない
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 3 # 連続3回失敗すると停止し、再起動ポリシーに従う
    readinessProbe:
      httpGet:
        path: /
        port: 80
      periodSeconds: 5
      timeoutSeconds: 2
      # 失敗しても再起動せず、Ready を False にする

起動時間が一定なら Liveness の initialDelaySeconds で待つ方法もあります。ばらつきがある場合は、上の例のように Startup Probe で起動完了を確認します。

(参考: Protect slow starting containers with startup probes)

3.1.10 ConfigMap / Secret の更新が環境変数に反映されていないケース

ConfigMap / Secret を更新しても、実行中のコンテナの環境変数は自動更新されません。一部の Pod だけ接続に失敗する場合などは、設定の更新時刻とコンテナの起動時刻を照合します。

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "sleep 3600"]
    env:
    - name: DB_HOST
      valueFrom:
        configMapKeyRef:
          name: app-config # 同じ namespace にある ConfigMap を参照
          key: db-host # 名前・キーが正しいか確認
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials # 同じ namespace にある Secret を参照
          key: password
    # 値はコンテナ起動時に設定され、参照先を更新しても自動では変わらない
    # envFrom でまとめて取り込む場合も同じ
    # 設定変更後は Pod の再作成などで反映する

機密情報を含まない値は、コンテナ内に printenv があれば次のように確認できます。パスワードなどは出力せず、設定のバージョン情報などで照合します。

kubectl exec <pod-name> -c <container-name> -- printenv DB_HOST

Deployment の場合は、設定更新後にロールアウトして反映します。ConfigMap / Secret の更新だけでは、Pod は自動で再作成されません。

# Pod を再作成するロールアウトを開始
kubectl rollout restart deployment/<deployment-name> -n <namespace>

# ロールアウトの完了を待ち、結果を確認
kubectl rollout status deployment/<deployment-name> -n <namespace>

詳細は ConfigMaps と Distribute Credentials Securely Using Secrets を参照してください。設定変更をロールアウトにつなげる Kustomize の方法は、Declarative Management of Kubernetes Objects Using Kustomizeで説明されています。

3.2 アプリケーションが原因の場合

起動設定や Probe の接続先が正しくても、クラッシュや Probe の失敗が続く場合は、アプリケーションの処理を調べます。手がかりとなる Event Reason は次のとおりです。

  • BackOff(CrashLoopBackOff)
  • Unhealthy(Probe実装不備の場合)

3.2.1 プロセスがクラッシュを繰り返す場合

コンテナが一度は起動しても、短時間で終了して再起動を繰り返す場合です。kubectl logs(同じ Pod 内の直前のコンテナのログが必要な場合は --previous)で直接の原因を確認します。

# 現在のコンテナのログを末尾100行取得
kubectl logs <pod-name> -c <container-name> --tail=100

# 直前に終了したコンテナのログを末尾100行取得
kubectl logs <pod-name> -c <container-name> --previous --tail=100

ログだけでなく、describe の State / Last State の Reason・Exit Code・Signal、メモリ制限と使用量も確認します。OOMKilled、実行ファイルや引数の誤り、正常終了を Always で再起動している場合も、再起動を繰り返す原因になります。exit code 137 だけでは OOM と断定できません。

OOMKilled が記録されている場合の確認ポイントは、3.5.1節にまとめています。

公式ドキュメントも、CrashLoopBackOffの原因調査の最初のステップとして「kubectl logsでログを確認すること」を挙げています。

(参考: Pod Lifecycle)

kubectl logs だけで原因が分からない場合は、docker run や docker compose で同じイメージ・入力を使い、ローカルでも再現するか確認します。

  • 再現する場合: アプリケーション・イメージ・外部依存を調べます。
  • 再現しない場合: 環境変数、ConfigMap / Secret、リソース制限、ネットワークなどの環境差を調べます。

3.2.2 Probe の path とアプリケーションの応答を照合する

アプリケーションに存在しない path を Probe が確認する例です。

apiVersion: v1
kind: Pod
metadata:
  name: probe-wrong-path
spec:
  containers:
  - name: app
    image: nginx:1.27
    ports:
    - containerPort: 80
    readinessProbe:
      httpGet:
        path: /healthz # nginx のデフォルトでは存在せず、404 を返す
        port: 80
      periodSeconds: 2
    livenessProbe:
      httpGet:
        path: / # Liveness は応答する path を確認
        port: 80
      periodSeconds: 10
# 上の YAML を probe-wrong-path.yaml として保存
kubectl apply -f probe-wrong-path.yaml
$ kubectl get pod probe-wrong-path
NAME               READY   STATUS    RESTARTS   AGE
probe-wrong-path   0/1     Running   0          15s

$ kubectl describe pod probe-wrong-path
...
Events:
  Type     Reason     Age               From      Message
  ----     ------     ----              ----      -------
  Warning  Unhealthy  0s (x8 over 14s)  kubelet   spec.containers{app}: Readiness probe failed: HTTP probe failed with statuscode: 404

404 は HTTP 応答まで到達した手がかりです。Probe の path・virtual host・プロキシ設定と実装を照合します。connection refused の場合は、ポート・待受アドレス・プロセスの起動状態を確認します。

kubectl port-forward と curl で、Probe が指定した path の応答を確認できます。

# Pod の80番ポートをローカルの8080番へ転送(実行したままにする)
kubectl port-forward pod/probe-wrong-path 8080:80

別のターミナルで実行します。

# Readiness Probe と同じ path の応答を確認(この例では404)
curl -i http://127.0.0.1:8080/healthz

# ルートの応答と比較(この例では200)
curl -i http://127.0.0.1:8080/

HTTP Probe は通常 kubelet から Pod IP に接続するため、port-forward とは経路が異なります。port-forward で応答が正常でも Probe が失敗する場合は、待受アドレスやノードから Pod IP への到達性も確認します。

3.3 Init Container が原因の場合

Init Container の実行順序や、通常の Init Container とサイドカー形式の Init Container の違いは、Pod の起動順序の第2章で詳しく整理しています。

kubectl get podのSTATUS列がInit:から始まっている場合(例: Init:CrashLoopBackOff、Init:Error)、Init Container の状態と Events を確認します。検証のため、exit 1で終了するinitContainerを持つPodを作成しました。

apiVersion: v1
kind: Pod
metadata:
  name: initfail
spec:
  initContainers:
  - name: init
    image: busybox:1.36
    command: ["sh", "-c", "echo init-boom; exit 1"] # Init Container が異常終了
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "sleep 3600"] # Init Container の正常終了まで起動しない
# 上の YAML を initfail.yaml として保存
kubectl apply -f initfail.yaml

backoff状態に入った後、STATUS列は次のようにInit:CrashLoopBackOffと表示されます。

$ kubectl get pods
NAME       READY   STATUS                  RESTARTS      AGE
initfail   0/1     Init:CrashLoopBackOff   5 (93s ago)   4m38s

describe の Init Containers で終了状態を確認し、kubectl logs <pod-name> -c init でログを調べます。この例の Phase と Conditions は次のとおりです。

$ kubectl get pod initfail -o jsonpath='{.status.phase}'
Pending
$ kubectl get pod initfail -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
PodReadyToStartContainers=True
Initialized=False
Ready=False
ContainersReady=False
PodScheduled=True

Initialized=False で Pending が続く場合は、まず Init Container の状態とログを確認します。sandbox やボリュームの準備前でも初期化は進まないため、Initialized だけで原因を Init Container のアプリケーションに限定しません。

3.4 外部接続に起因する失敗

接続エラーが出ていても、外部サービス側の障害とは限りません。一部の Pod だけで失敗する場合などは、3.1.10節で説明した ConfigMap / Secret の更新が環境変数に反映されていないケースも確認します。

起動処理やヘルスチェックが外部の DB・API に接続する場合、依存先の障害や応答の遅延が、初期化の完了や Probe の結果に影響します。

切り分けでは、マニフェスト上の Probe の設定と、アプリケーション側の実装の両方を確認します。正常な初期化や応答に必要な時間と設定が合っていなければ、timeoutSeconds・periodSeconds・failureThreshold などの調整が選択肢になります。

一方、接続失敗でプロセスを終了する場合や、外部依存先の障害を一律にヘルスチェック失敗とする場合は、設定調整に加えて、接続エラーの扱いやヘルスチェックの実装も見直す必要があります。

アプリケーション自身の状態に加え、DB・API などの外部依存先への接続可否まで確認する方法を、本節では deep health check と呼びます。これを Probe に使う場合は、依存先の障害が Pod にどう影響するかを考える必要があります。

HTTP Probe で deep health check を行う例。外部依存先の確認は、ヘルスチェックを受けたアプリケーションが行います。

以下では、Startup・Liveness・Readiness の順に、失敗時の挙動と、設定・実装を見直すポイントを整理します。Startup Probe を設定している場合、Liveness / Readiness Probe はその成功後に実行されます。初めて Ready になる前にも、これらの Probe が失敗することはあります。

3.4.1 Startup の失敗: 起動に必要な初期化が完了しない

DB 接続などの確認が起動時だけ必要な場合は、Startup Probe の利用を検討できます。アプリケーションが必要な初期化を終えたことを確認し、成功後はそのコンテナの実行中に確認を繰り返しません。起動時だけ必要な外部依存の確認は、Liveness / Readiness Probe に含めず、役割を分ける設計が考えられます。

以下は、初期化の完了をファイルで通知し、Startup Probe で確認する例です。DB 接続を含む初期化に時間がかかる状況を、60秒の待機で模擬しています。

apiVersion: v1
kind: Pod
metadata:
  name: startup-wait
spec:
  containers:
  - name: app
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      rm -f /tmp/initialized
      sleep 60 # DB 接続など、起動時の初期化を模擬
      touch /tmp/initialized # 初期化が完了したことを通知
      exec sleep 3600
    startupProbe:
      exec:
        command: ["test", "-f", "/tmp/initialized"]
      periodSeconds: 5
      failureThreshold: 30 # 初期化を待つ時間の目安は 5秒 × 30回 = 150秒
      # 成功するまで Liveness / Readiness Probe は実行されない
      # 連続失敗がしきい値に達するとコンテナを停止する
# 上の YAML を startup-wait.yaml として保存
kubectl apply -f startup-wait.yaml

# Startup Probe の失敗やコンテナの状態を確認
kubectl describe pod startup-wait

# 初期化完了後に Started が true になったか確認
kubectl get pod startup-wait -o jsonpath='{.status.containerStatuses[0].started}{"\n"}'

Startup Probe の失敗が続く場合は、アプリケーションのログと Events を照合し、初期化のどの処理で待っているかを確認します。正常な初期化に時間がかかる場合は、periodSeconds と failureThreshold で待機時間を調整できます。ただし、連続失敗がしきい値に達するとコンテナを停止するため、依存先の復旧をどこまで待つかも考えて設定します。

接続エラーでプロセス自体が終了する場合は、Probe の待機時間を延ばしても防げません。実際のアプリケーションでは、一時的な接続失敗をハンドリングし、タイムアウトとリトライ回数の上限を設け、exponential backoff で再接続する設計を検討できます。Startup Probe は、その処理を含む初期化の完了を確認します。

Startup Probe の実行順序はPod の起動順序の第4章、起動時間に合わせた設定例は3.1.9節を参照してください。

(参考: Protect slow starting containers with startup probes)

3.4.2 Liveness の失敗: コンテナが再起動する

Liveness Probe が連続して失敗し、failureThreshold に達すると、kubelet はコンテナを停止します。その後、再起動ポリシーに従って再起動するかが決まります。

Liveness の失敗を再現する Pod です。

apiVersion: v1
kind: Pod
metadata:
  name: liveness-fail
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      livenessProbe:
        exec:
          command: ["sh", "-c", "exit 1"] # 外部障害の代わりに、常に Probe を失敗させる
        initialDelaySeconds: 3 # 起動から3秒後に確認を開始
        periodSeconds: 2
        failureThreshold: 1 # 1回の失敗でコンテナを停止し、再起動ポリシーに従う
# 上の YAML を liveness-fail.yaml として保存
kubectl apply -f liveness-fail.yaml

実行すると、Unhealthy に続いて Killing が記録され、コンテナが再起動しました。

$ kubectl describe pod liveness-fail
...
Events:
  Type     Reason     Age  From     Message
  ----     ------     ---- ----     -------
  Warning  Unhealthy  32s  kubelet  spec.containers{app}: Liveness probe failed:
  Normal   Killing    32s  kubelet  spec.containers{app}: Container app failed liveness probe, will be restarted

Liveness Probe がタイムアウトする場合は、障害発生時の負荷や応答時間と設定を照合します。正常な処理中にも失敗するなら、timeoutSeconds や failureThreshold の調整を検討します。

公式ドキュメントは、Liveness の誤った実装が連鎖障害を引き起こす可能性を説明しています。高負荷時の再起動によって残りの Pod へ負荷が集中し、障害を拡大する場合があります。

(参考: Liveness, Readiness, and Startup Probes)

deep health check を Liveness Probe に使う場合は、共有 DB や API の一時障害によって、依存する複数の Pod が相次いで再起動する可能性があります。再起動に伴う再接続が集中すると、依存先の負荷が増え、障害からの復旧を遅らせる原因にもなり得ます。 依存先の障害はコンテナを再起動しても解消せず、アプリケーションの初期化や接続処理も最初からやり直すことになります。

そのため、DB などへの接続失敗はアプリケーション自身でハンドリングし、タイムアウトとリトライ回数の上限を設け、exponential backoff を適用して再接続する設計も検討してください。kubelet のバックオフが制御するのはコンテナの再起動間隔であり、アプリケーション内の接続リトライはアプリケーション側で制御する必要があります。

Liveness Probe では、コンテナの再起動によって回復が見込める異常を検出するように設計するのが良いと思います。

3.4.3 Readiness の失敗: 再起動せず、Service の転送先から外れる

Readiness の失敗だけではコンテナは再起動しません。以下の Pod と Service で、失敗時の状態を確認します。

apiVersion: v1
kind: Pod
metadata:
  name: readiness-fail
  labels:
    app: readiness-fail
spec:
  containers:
    - name: app
      image: nginx:1.27
      readinessProbe:
        tcpSocket:
          port: 8080 # nginx が待ち受けていないポートで Readiness の失敗を再現
        periodSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
  name: readiness-fail
spec:
  selector:
    app: readiness-fail
  ports:
    - port: 80
      targetPort: 80 # アプリの待受ポート。Probe の接続先とは別
# 上の YAML を readiness-fail.yaml として保存
kubectl apply -f readiness-fail.yaml

既存の検証記録では、コンテナは Running のまま Ready が False となり、再起動回数は0でした。

$ kubectl describe pod readiness-fail
...
State:          Running
  Started:      Mon, 05 Oct 2026 14:19:32 +0900
Ready:          False
Restart Count:  0
...
Events:
  Type     Reason     Age                From     Message
  ----     ------     ----               ----     -------
  Warning  Unhealthy  0s (x25 over 68s)  kubelet  spec.containers{app}: Readiness probe failed: dial tcp 10.244.0.8:8080: connect: connection refused

EndpointSlice の ready / serving も false になりました。

$ kubectl get endpointslices -l kubernetes.io/service-name=readiness-fail -o yaml
...
  endpoints:
  - addresses:
    - 10.244.0.8
    conditions:
      ready: false # 通常の Service では新規トラフィックの転送先から外れる
      serving: false # Pod の Ready を反映。アドレス自体は残る

通常は ready=false の Endpoint が新規トラフィックの転送先から外れます。publishNotReadyAddresses などの例外や EndpointSlice の更新の仕組みは、Deployment を apply してから Pod が Ready になるまでの第7章を参照してください。

(参考: Liveness, Readiness, and Startup Probes)

この例では接続先ポートが原因なので、待ち時間ではなくポートの設定を修正します。タイムアウトによる失敗であれば、実際の応答時間と timeoutSeconds を照合します。failureThreshold や successThreshold は、一時的な失敗や回復を Ready 状態へ反映する条件として調整を検討できます。

Pod 単体の不調を検出する Readiness は、その Pod を Service の転送先から外すために使えます。

一方、deep health check を Readiness Probe に使う場合は、共有する依存先の障害によって、すべての Pod が Service の転送先から外れる可能性があります。例えば共有 DB が停止すると、依存するすべての Pod が NotReady となります。

しきい値の調整で一時的な失敗の影響を抑えられる場合もありますが、障害が続いたときに全 Pod を転送先から外すべきかは、ヘルスチェックの実装を含めて検討します。

DB への接続エラーを把握しているのは、接続を試みたアプリケーションです。Pod が NotReady になったことだけでは、呼び出し元に DB への接続失敗という原因は伝わりません。

そのため、依存先の障害を一律に NotReady にするのではなく、Pod 内のアプリケーションで接続エラーをハンドリングする設計も検討できます。例えば、DB を使わないリクエストは処理を続け、DB が必要なリクエストには 503 を返して、必要に応じて Retry-After ヘッダーを付ける対応が考えられます。具体的なエラーはログ・メトリクスに残し、原因を調べられるようにすることも検討できます。

3.5 リソース不足に起因する失敗

リソース不足では、コンテナが OOM Kill される場合と、Pod 自体が Eviction される場合を分けて確認します。

# 対象 Pod の設定・終了理由を確認
kubectl get pod <pod-name> -o yaml

# 障害が起きたノードの Conditions・Events を確認
kubectl describe node <node-name>

使用量を詳しく調べる場合は、障害発生時のメトリクスも確認します。障害後にリソースが解放され、確認時点では Pressure の Condition が解消している場合もあります。

3.5.1 メモリ不足による OOMKilled と Eviction

OOMKilled が記録されている場合は、コンテナのメモリ制限と使用量、ノード側のメモリ不足を確認します。終了したコンテナは再起動ポリシーに従って扱われるため、同じ Pod 内で再起動を繰り返す場合があります。exit code 137 だけでは OOM と断定できません。

一方、ノードのメモリ不足による Eviction は、Pod 自体を終了させる処理です。Evicted と表示される場合は、Pod の終了理由・メッセージと、ノードの MemoryPressure などを確認します。

メモリ使用量の見方や OOM / Eviction の切り分け、Request / Limit の見直し方は、Kubernetes のメモリ使用量と cgroup v2 の関係: OOM / Eviction の切り分けと Request の適正化で詳しく整理しています。

3.5.2 ディスク不足による DiskPressure と Eviction

ノードのディスク空き容量や inode が不足すると、DiskPressure となり、Pod の配置や継続稼働に影響する場合があります。Pending が続く場合は FailedScheduling のメッセージとノードの Taint を、Evicted と表示される場合は Pod の終了理由・メッセージとノードの状態を確認します。Eviction は、同じ Pod 内でコンテナが再起動する動作とは区別します。

ディスク領域の見方や DiskPressure / Eviction の切り分け、対策は、Kubernetes ノードのディスク領域と Disk Pressure Eviction についてで詳しく整理しています。

おわりに

「Pod が Running にならない」ときに、自分なら何を確認し、どのような手順で原因を切り分け、再発防止に向けて何を考えるかをまとめてみました。

文章にしてみると、思っていた以上にいろいろなことを考えながら作業していることや、幅広い知識が必要なことを改めて感じます。

Kubernetes に関する体系的な知識と、実際に対応してきたサポートケースの知見を組み合わせたので、実践的な手順になっていると思います。情報量が多いので、トラブルが起きたときに初めて読むよりも、平常時にゆっくり目を通しておくと、いざトラブルに遭遇したときの助けになるのではないかと思います。

参考

参考リンク

本文で登場する順に並べています。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?