T-CREATOR

Kubernetes で Pod が CrashLoopBackOff の原因と切り分け手順

Kubernetes で Pod が CrashLoopBackOff の原因と切り分け手順

Kubernetes でアプリケーションをデプロイしたとき、Pod がCrashLoopBackOffという状態になって起動しない経験はありませんか?

このエラーは、Kubernetes を使い始めた方が最初にぶつかる壁の一つです。Pod が何度も再起動を繰り返し、アプリケーションが正常に動作しない状態は、原因が分からないと非常に焦りますよね。

本記事では、CrashLoopBackOff エラーの原因を理解し、体系的な切り分け手順を学ぶことで、トラブルシューティングを効率的に行えるようになります。初心者の方でも実践できるように、具体的なコマンド例とともに詳しく解説していきましょう。

背景

Kubernetes における Pod のライフサイクル

Kubernetes では、コンテナアプリケーションを「Pod」という単位で管理します。Pod は、Kubernetes における最小のデプロイ単位で、1 つ以上のコンテナで構成されていますね。

Pod は作成されてから削除されるまで、いくつかの状態(Phase)を遷移します。主な状態には以下のようなものがあります。

#状態説明
1PendingPod が作成され、スケジューリング待ちまたはイメージのダウンロード中
2RunningPod が正常に起動し、少なくとも 1 つのコンテナが実行中
3Succeededすべてのコンテナが正常に終了した状態(バッチ処理など)
4Failedすべてのコンテナが終了し、少なくとも 1 つが異常終了した状態
5UnknownPod の状態が取得できない状態(ノードとの通信エラーなど)

以下の図は、Pod のライフサイクルと状態遷移を示しています。

mermaidstateDiagram-v2
    [*] --> Pending: Pod作成
    Pending --> Running: コンテナ起動成功
    Pending --> Failed: イメージ取得失敗
    Running --> Succeeded: 正常終了
    Running --> Failed: 異常終了
    Running --> Unknown: ノード通信エラー
    Failed --> [*]: Pod削除
    Succeeded --> [*]: Pod削除

この図から分かるように、Pod は複数の状態を遷移しながら、最終的に Succeeded または Failed の状態に到達します。

CrashLoopBackOff とは

CrashLoopBackOff は、Pod のStatus(状態)の一つで、コンテナが起動に失敗し、Kubernetes が自動的に再起動を試みている状態を指します。

Kubernetes には、コンテナが異常終了した場合に自動的に再起動する「再起動ポリシー(Restart Policy)」という機能があります。しかし、コンテナが起動直後にクラッシュし続けると、Kubernetes は「バックオフ(待機時間)」を設けて再起動を試みるようになるんですね。

この待機時間は以下のように増加します。

#再起動回数待機時間
11 回目0 秒
22 回目10 秒
33 回目20 秒
44 回目40 秒
55 回目以降最大 5 分まで倍増

待機時間を設けることで、一時的な問題が解決する可能性を考慮しつつ、システムリソースの無駄な消費を防いでいます。

CrashLoopBackOff が発生する仕組み

以下の図は、CrashLoopBackOff が発生するまでのフローを示しています。

mermaidflowchart TD
    start["Pod作成"] --> schedule["ノードへスケジューリング"]
    schedule --> pull["コンテナイメージ取得"]
    pull --> start_container["コンテナ起動"]
    start_container --> crash_check{コンテナ<br/>クラッシュ?}
    crash_check -->|正常| running["Running状態"]
    crash_check -->|異常終了| restart_policy{再起動<br/>ポリシー確認}
    restart_policy -->|Always/OnFailure| wait["バックオフ待機"]
    wait --> restart_count{再起動<br/>回数確認}
    restart_count --> backoff["CrashLoopBackOff"]
    backoff --> wait_time["待機時間経過"]
    wait_time --> start_container
    restart_policy -->|Never| failed_status["Failed状態"]

図で理解できる要点:

  • コンテナがクラッシュすると、再起動ポリシーに基づいて再起動が試みられる
  • 繰り返しクラッシュすると、待機時間が徐々に長くなる
  • この状態が CrashLoopBackOff として検出される

この仕組みにより、一時的なエラーには自動復旧のチャンスを与えつつ、恒久的な問題がある場合はシステムへの負荷を軽減しているのです。

課題

CrashLoopBackOff のエラーメッセージ

CrashLoopBackOff が発生すると、kubectl get podsコマンドで以下のような出力が表示されます。

bashkubectl get pods

実際のエラー出力例:

plaintextNAME                     READY   STATUS             RESTARTS   AGE
my-app-5d4f8b7c9-xyz12   0/1     CrashLoopBackOff   5          3m

このエラーメッセージから、以下の情報が読み取れます。

#項目意味
1NAMEmy-app-5d4f8b7c9-xyz12Pod 名(Deployment 名-ランダム文字列)
2READY0/1準備完了コンテナ数/総コンテナ数
3STATUSCrashLoopBackOffPod の現在の状態
4RESTARTS5コンテナの再起動回数
5AGE3mPod が作成されてからの経過時間

RESTARTS の値が増え続けている場合は、恒久的な問題が存在する可能性が高いですね。

CrashLoopBackOff の主な原因

CrashLoopBackOff が発生する原因は多岐にわたりますが、主に以下のカテゴリに分類できます。

アプリケーションエラー

コンテナ内のアプリケーション自体にバグや設定ミスがあるケースです。

#原因具体例
1アプリケーションのクラッシュNullPointerException、未処理の例外
2起動スクリプトのエラーシェルスクリプトの構文エラー、パスの誤り
3設定ファイルの不備必須パラメータの欠落、フォーマットエラー
4依存関係の問題ライブラリの不足、バージョン不一致

設定エラー

Kubernetes のマニフェストファイルやコンテナの設定に問題があるケースです。

#原因具体例
1イメージの指定ミス存在しないタグ、プライベートレジストリの認証エラー
2コマンド・引数の誤り存在しないコマンド、誤った引数
3環境変数の不足必須の環境変数が未設定
4Liveness/Readiness Probe の設定ミスタイムアウト値が短すぎる、エンドポイントの誤り

リソース不足

コンテナに割り当てられたリソースが不足しているケースです。

#原因具体例
1メモリ不足(OOMKilled)アプリケーションが limits.memory を超過
2CPU 不足起動時に必要な CPU リソースが確保できない
3ディスク容量不足ログファイルの肥大化、一時ファイルの蓄積

外部依存関係の問題

コンテナが依存する外部サービスへの接続に失敗するケースです。

#原因具体例
1データベース接続エラー接続先の誤り、認証エラー、タイムアウト
2外部 API 接続エラーネットワーク不通、エンドポイントの変更
3ConfigMap/Secret の不在マウントしようとしたリソースが存在しない
4ボリュームマウントエラーPVC が存在しない、権限エラー

以下の図は、これらの原因がどのように関連しているかを示しています。

mermaidflowchart LR
    crash["CrashLoopBackOff"] --> app["アプリケーション<br/>エラー"]
    crash --> config["設定エラー"]
    crash --> resource["リソース不足"]
    crash --> external["外部依存関係<br/>の問題"]

    app --> app1["バグ・例外"]
    app --> app2["起動スクリプト<br/>エラー"]
    app --> app3["設定ファイル<br/>不備"]

    config --> config1["イメージ指定<br/>ミス"]
    config --> config2["コマンド・引数<br/>誤り"]
    config --> config3["Probe設定<br/>ミス"]

    resource --> resource1["OOMKilled"]
    resource --> resource2["CPU不足"]
    resource --> resource3["ディスク容量<br/>不足"]

    external --> external1["DB接続<br/>エラー"]
    external --> external2["API接続<br/>エラー"]
    external --> external3["ConfigMap/Secret<br/>不在"]

図で理解できる要点:

  • CrashLoopBackOff の原因は大きく 4 つのカテゴリに分類される
  • それぞれのカテゴリには、さらに具体的な原因が複数存在する
  • 効率的なトラブルシューティングには、原因の特定が不可欠である

トラブルシューティングの難しさ

CrashLoopBackOff のトラブルシューティングが難しい理由は、以下の点にあります。

まず、エラーメッセージだけでは根本原因が分からないことが多いですね。kubectl get podsで表示されるCrashLoopBackOffというステータスは、「コンテナが繰り返しクラッシュしている」という症状を示すだけで、なぜクラッシュしているのかは教えてくれません。

次に、複数の原因が複合的に絡んでいる場合があります。たとえば、設定ミスによってデータベースに接続できず、その結果アプリケーションがクラッシュするといったケースですね。

さらに、コンテナがすぐに終了してしまうため、リアルタイムでのデバッグが困難です。通常の環境であれば、実行中のプロセスに接続してログを確認したり、デバッガを使ったりできますが、CrashLoopBackOff の状態ではコンテナが数秒で終了してしまうため、それができません。

最後に、Kubernetes の自動再起動により、問題の再現性が複雑になります。バックオフの待機時間が長くなると、問題の確認や修正の検証に時間がかかってしまうのです。

これらの課題を解決するには、体系的なアプローチが必要になります。

解決策

CrashLoopBackOff の切り分け手順

CrashLoopBackOff のトラブルシューティングは、以下の手順で体系的に行うと効率的です。

以下の図は、推奨される切り分けフローを示しています。

mermaidflowchart TD
    start["CrashLoopBackOff<br/>発生"] --> step1["ステップ1:<br/>基本情報の収集"]
    step1 --> step2["ステップ2:<br/>イベント確認"]
    step2 --> step3["ステップ3:<br/>ログ分析"]
    step3 --> step4["ステップ4:<br/>詳細情報の取得"]
    step4 --> step5["ステップ5:<br/>リソース状況確認"]
    step5 --> step6["ステップ6:<br/>設定の検証"]
    step6 --> resolve{原因<br/>特定?}
    resolve -->|はい| fix["修正適用"]
    resolve -->|いいえ| advanced["高度な<br/>デバッグ手法"]
    fix --> verify["動作確認"]
    advanced --> verify

図で理解できる要点:

  • トラブルシューティングは段階的なアプローチが効果的
  • 基本情報から詳細情報へと順に確認を進める
  • 原因が特定できない場合は、高度なデバッグ手法を使用する

各ステップで使用するコマンドと確認ポイントを詳しく見ていきましょう。

ステップ 1: 基本情報の収集

最初に、Pod の基本的な状態を確認します。

Pod 一覧の確認

bashkubectl get pods

このコマンドで、すべての Pod の現在の状態を一覧表示できます。

特定の Pod の詳細確認

bashkubectl get pod <pod-name> -o wide

-o wideオプションを付けることで、以下の追加情報が表示されます。

#項目説明
1IPPod に割り当てられた IP アドレス
2NODEPod がスケジューリングされたノード名
3NOMINATED NODE次にスケジューリングされる候補ノード
4READINESS GATESReadiness Gate の状態

すべての Namespace を確認

bashkubectl get pods --all-namespaces

問題の Pod がデフォルトの Namespace にない場合は、このコマンドで全体を確認します。

ステップ 2: イベント確認

Kubernetes は、Pod に関連するイベントを記録しています。これらのイベントは、問題の原因を特定する重要な手がかりになりますね。

describe コマンドでイベントを確認

bashkubectl describe pod <pod-name>

このコマンドは、Pod の詳細情報とイベント履歴を表示します。出力の最下部にあるEventsセクションに注目しましょう。

典型的なエラーイベントの例:

plaintextEvents:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  5m                 default-scheduler  Successfully assigned default/my-app-5d4f8b7c9-xyz12 to node-1
  Normal   Pulled     4m (x4 over 5m)    kubelet            Container image "my-app:v1.0" already present on machine
  Normal   Created    4m (x4 over 5m)    kubelet            Created container my-app
  Normal   Started    4m (x4 over 5m)    kubelet            Started container my-app
  Warning  BackOff    1m (x10 over 4m)   kubelet            Back-off restarting failed container

イベントから読み取れる情報は以下の通りです。

#TypeReason意味
1NormalScheduledPod がノードに正常にスケジューリングされた
2NormalPulledイメージの取得が完了した
3NormalCreatedコンテナが作成された
4NormalStartedコンテナが起動した
5WarningBackOffバックオフによる再起動待機中

よくあるエラーイベント

以下は、CrashLoopBackOff 発生時によく見られるエラーイベントです。

ImagePullBackOff の場合:

plaintextWarning  Failed     3m (x4 over 5m)    kubelet  Failed to pull image "my-app:v1.0": rpc error: code = Unknown desc = Error response from daemon: pull access denied for my-app, repository does not exist or may require 'docker login'

OOMKilled の場合:

plaintextWarning  OOMKilling  2m    kubelet  Memory cgroup out of memory: Killed process 1234 (java) total-vm:2048576kB, anon-rss:524288kB, file-rss:0kB, shmem-rss:0kB

Exit Code が記録されている場合:

plaintextWarning  BackOff  1m (x10 over 4m)  kubelet  Back-off restarting failed container my-app in pod my-app-5d4f8b7c9-xyz12_default(uuid)
Normal   Pulled   1m (x5 over 5m)   kubelet  Container image "my-app:v1.0" already present on machine
Warning  Failed   1m (x5 over 5m)   kubelet  Error: container exited with non-zero status 1

ステップ 3: ログ分析

コンテナのログは、アプリケーションレベルのエラーを特定する最も重要な情報源です。

現在のログを確認

bashkubectl logs <pod-name>

複数のコンテナがある場合は、コンテナ名を指定します。

bashkubectl logs <pod-name> -c <container-name>

直前のコンテナのログを確認

CrashLoopBackOff の場合、現在のコンテナはまだ起動していないか、すぐに終了してしまうことがあります。その場合は、--previousフラグを使って前回のコンテナのログを確認しましょう。

bashkubectl logs <pod-name> --previous

これは非常に重要なコマンドで、クラッシュしたコンテナの最後のログを取得できます。

リアルタイムでログを追跡

bashkubectl logs <pod-name> -f

-fフラグを付けることで、ログをリアルタイムで表示し続けます。これは、コンテナが数秒間でも起動する場合に有効ですね。

ログの末尾のみを表示

bashkubectl logs <pod-name> --tail=50

大量のログがある場合は、最新の 50 行だけを表示することで、確認が容易になります。

タイムスタンプ付きでログを表示

bashkubectl logs <pod-name> --timestamps

タイムスタンプを付けることで、エラーの発生タイミングが正確に把握できます。

ステップ 4: 詳細情報の取得

describe コマンドで、Pod の詳細な設定情報を確認します。

Pod の完全な定義を確認

bashkubectl describe pod <pod-name>

このコマンドの出力には、以下の重要な情報が含まれます。

Containers セクション:

plaintextContainers:
  my-app:
    Container ID:   docker://abc123def456...
    Image:          my-app:v1.0
    Image ID:       docker-pullable://my-app@sha256:abc123...
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1
      Started:      Wed, 06 Dec 2025 12:00:00 +0900
      Finished:     Wed, 06 Dec 2025 12:00:05 +0900
    Ready:          False
    Restart Count:  5

確認すべき重要項目:

#項目確認ポイント
1State現在の状態(Waiting、Running、Terminated)
2Reason状態の理由(CrashLoopBackOff、Error、OOMKilled など)
3Exit Code終了コード(0 以外はエラーを示す)
4Restart Count再起動回数(増え続けている場合は注意)

Exit Code の意味

Exit Code は、コンテナの終了理由を示す重要な手がかりです。

#Exit Code意味一般的な原因
10正常終了バッチジョブの正常完了
21一般的なエラーアプリケーションのクラッシュ、例外
32使用方法の誤りコマンドライン引数の誤り
4126コマンド実行不可実行権限がない、バイナリが見つからない
5127コマンドが見つからないコマンドのパスが間違っている
6130Ctrl+C による中断SIGINT シグナル受信
7137強制終了(OOMKilled)メモリ不足により SIGKILL 受信
8143正常なシャットダウンSIGTERM シグナル受信

Exit Code 137 は OOMKilled(メモリ不足)を示す最も一般的なコードですね。

ステップ 5: リソース状況確認

リソース不足が原因の場合、以下のコマンドで確認できます。

Pod のリソース使用状況

bashkubectl top pod <pod-name>

このコマンドは、Pod の現在の CPU とメモリの使用量を表示します。

出力例:

plaintextNAME                     CPU(cores)   MEMORY(bytes)
my-app-5d4f8b7c9-xyz12   250m         512Mi

ノードのリソース状況

bashkubectl top nodes

Pod がスケジューリングされているノードのリソース状況を確認します。

リソース制限の確認

Pod のマニフェストファイルで設定されているリソース制限を確認しましょう。

bashkubectl get pod <pod-name> -o yaml | grep -A 10 resources

出力例:

yamlresources:
  limits:
    cpu: '1'
    memory: 512Mi
  requests:
    cpu: 500m
    memory: 256Mi

メモリのlimitsを超えると、コンテナは OOMKilled されます。

ステップ 6: 設定の検証

Pod の設定に問題がないかを確認します。

マニフェストファイルの取得

bashkubectl get pod <pod-name> -o yaml > pod-definition.yaml

実行中の Pod の完全な定義を YAML 形式で取得し、ファイルに保存します。

設定の確認項目

取得した YAML ファイルで、以下の項目を確認しましょう。

イメージ設定:

yamlspec:
  containers:
    - name: my-app
      image: my-app:v1.0
      imagePullPolicy: Always
#項目確認ポイント
1imageタグが正しいか、タグが存在するか
2imagePullPolicyAlways、IfNotPresent、Never の適切な設定

コマンドと引数:

yamlspec:
  containers:
    - name: my-app
      command: ['/bin/sh']
      args: ['-c', 'npm start']

コマンドやシェルスクリプトの構文が正しいか確認します。

環境変数:

yamlspec:
  containers:
    - name: my-app
      env:
        - name: DATABASE_URL
          value: 'postgresql://localhost:5432/mydb'
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: api-secret
              key: key

必須の環境変数が設定されているか、参照先の Secret や ConfigMap が存在するか確認しましょう。

Liveness と Readiness Probe:

yamlspec:
  containers:
    - name: my-app
      livenessProbe:
        httpGet:
          path: /health
          port: 8080
        initialDelaySeconds: 30
        periodSeconds: 10
        timeoutSeconds: 5
        failureThreshold: 3
#項目確認ポイント
1initialDelaySecondsアプリケーションの起動時間より長く設定されているか
2timeoutSecondsレスポンスを返すのに十分な時間か
3failureThreshold失敗回数が適切に設定されているか
4pathヘルスチェックエンドポイントが正しいか

Probe の設定が厳しすぎると、起動中の Pod が誤って終了されてしまうことがあります。

ステップ 7: 高度なデバッグ手法

基本的な手順で原因が特定できない場合は、以下の高度な手法を試してみましょう。

デバッグコンテナの起動

Kubernetes 1.23 以降では、kubectl debugコマンドを使って、既存の Pod にデバッグコンテナを追加できます。

bashkubectl debug <pod-name> -it --image=busybox --target=<container-name>

これにより、問題のあるコンテナと同じ環境でデバッグツールを実行できますね。

Ephemeral Containers を使用

一時的なデバッグ用コンテナを追加して、Pod の内部状態を調査します。

bashkubectl debug <pod-name> -it --image=ubuntu --share-processes --copy-to=<new-pod-name>

Pod の設定を一時的に変更してテスト

問題の切り分けのため、Pod の設定を一時的に変更してテストします。

例:コマンドを sleep に変更

yamlspec:
  containers:
    - name: my-app
      image: my-app:v1.0
      command: ['sleep']
      args: ['3600']

この設定で Pod が正常に起動すれば、イメージやリソース設定の問題ではなく、アプリケーション自体に問題があることが分かります。

ローカルでコンテナを実行

Kubernetes の外で、Docker を使ってコンテナをローカル実行してみます。

bashdocker run -it my-app:v1.0 /bin/sh

コンテナ内でシェルを起動し、手動でアプリケーションを実行することで、エラーの詳細を確認できます。

具体例

具体例 1: アプリケーションのクラッシュ(Exit Code 1)

Node.js アプリケーションが CrashLoopBackOff になるケースを見てみましょう。

問題の症状

bashkubectl get pods
plaintextNAME                       READY   STATUS             RESTARTS   AGE
node-app-7d9f8c6b5-abc12   0/1     CrashLoopBackOff   7          10m

ログの確認

bashkubectl logs node-app-7d9f8c6b5-abc12 --previous
plaintextError: Cannot find module 'express'
Require stack:
- /app/server.js
- /app/index.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:815:15)
    at Function.Module._load (internal/modules/cjs/loader.js:667:27)
    at Module.require (internal/modules/cjs/loader.js:887:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.<anonymous> (/app/server.js:1:17)
npm ERR! code 1

このログから、expressモジュールが見つからないことが原因だと分かりますね。

describe で詳細確認

bashkubectl describe pod node-app-7d9f8c6b5-abc12
plaintextLast State:     Terminated
  Reason:       Error
  Exit Code:    1
  Started:      Wed, 06 Dec 2025 12:00:00 +0900
  Finished:     Wed, 06 Dec 2025 12:00:02 +0900

Exit Code 1 は、アプリケーションの一般的なエラーを示しています。

原因

Docker イメージのビルド時にnpm installが実行されていないか、node_modulesディレクトリが含まれていないことが原因です。

解決方法

Dockerfile を修正します。

修正前の Dockerfile:

dockerfileFROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["npm", "start"]

修正後の Dockerfile:

dockerfileFROM node:18-alpine
WORKDIR /app

# package.json と package-lock.json をコピー
COPY package*.json ./

# 依存関係をインストール
RUN npm ci --only=production

# アプリケーションのソースコードをコピー
COPY . .

# アプリケーションを起動
CMD ["npm", "start"]

修正のポイントは以下の通りです。

#改善点理由
1package.json を先にコピーDocker のレイヤーキャッシュを活用
2npm ci を使用npm install より高速で再現性が高い
3--only=production オプション本番環境では開発依存関係は不要

イメージを再ビルドしてデプロイします。

bashdocker build -t my-app:v1.1 .
docker push my-app:v1.1
kubectl set image deployment/node-app node-app=my-app:v1.1

動作確認

bashkubectl get pods
plaintextNAME                       READY   STATUS    RESTARTS   AGE
node-app-7d9f8c6b5-xyz34   1/1     Running   0          30s

Pod が正常に起動しました。

具体例 2: メモリ不足(OOMKilled / Exit Code 137)

Java アプリケーションがメモリ不足でクラッシュするケースです。

問題の症状

bashkubectl get pods
plaintextNAME                        READY   STATUS             RESTARTS   AGE
java-app-6c8d7f9b4-def56    0/1     CrashLoopBackOff   12         20m

イベントの確認

bashkubectl describe pod java-app-6c8d7f9b4-def56
plaintextEvents:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Warning  BackOff    2m (x50 over 20m)    kubelet            Back-off restarting failed container
  Normal   Pulled     1m (x13 over 20m)    kubelet            Container image "java-app:v1.0" already present on machine
  Normal   Created    1m (x13 over 20m)    kubelet            Created container java-app
  Normal   Started    1m (x13 over 20m)    kubelet            Started container java-app
  Warning  OOMKilling 1m (x13 over 20m)    kubelet            Memory cgroup out of memory: Killed process 1234

OOMKillingイベントが記録されています。これはメモリ不足が原因ですね。

Exit Code の確認

bashkubectl describe pod java-app-6c8d7f9b4-def56 | grep "Exit Code"
plaintextExit Code:    137

Exit Code 137 は、SIGKILL シグナルによる強制終了を示しており、OOMKilled を意味します。

リソース制限の確認

bashkubectl get pod java-app-6c8d7f9b4-def56 -o yaml | grep -A 10 resources
yamlresources:
  limits:
    memory: 512Mi
  requests:
    memory: 256Mi

メモリ制限が 512MiB に設定されていますが、Java アプリケーションには不足しています。

原因

Java アプリケーションのヒープサイズが Pod のメモリ制限を超えていることが原因です。

解決方法

メモリ制限を増やし、Java のヒープサイズを適切に設定します。

Deployment マニフェストの修正:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: java-app
spec:
  template:
    spec:
      containers:
        - name: java-app
          image: java-app:v1.0
          resources:
            limits:
              memory: 2Gi
            requests:
              memory: 1Gi

メモリ制限を 512MiB から 2GiB に増やしました。

Java ヒープサイズの設定:

環境変数で Java のヒープサイズを明示的に設定します。

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: java-app
spec:
  template:
    spec:
      containers:
        - name: java-app
          image: java-app:v1.0
          env:
            - name: JAVA_OPTS
              value: '-Xmx1536m -Xms512m'
          resources:
            limits:
              memory: 2Gi
            requests:
              memory: 1Gi

設定のポイント:

#パラメータ説明
1-Xmx1536m最大ヒープサイズ(limits の 75%程度)
2-Xms512m初期ヒープサイズ(requests と同程度)
3limits.memory2GiPod のメモリ上限
4requests.memory1Gi最低限必要なメモリ

変更を適用します。

bashkubectl apply -f deployment.yaml

動作確認

bashkubectl get pods
plaintextNAME                        READY   STATUS    RESTARTS   AGE
java-app-6c8d7f9b4-ghi78    1/1     Running   0          2m
bashkubectl top pod java-app-6c8d7f9b4-ghi78
plaintextNAME                        CPU(cores)   MEMORY(bytes)
java-app-6c8d7f9b4-ghi78    200m         1200Mi

メモリ使用量が制限内に収まり、Pod が正常に動作しています。

具体例 3: データベース接続エラー

アプリケーションがデータベースに接続できずにクラッシュするケースです。

問題の症状

bashkubectl get pods
plaintextNAME                         READY   STATUS             RESTARTS   AGE
webapp-5f7d8c9a6-jkl90       0/1     CrashLoopBackOff   5          8m

ログの確認

bashkubectl logs webapp-5f7d8c9a6-jkl90 --previous
plaintextStarting application...
Connecting to database...
Error: connect ECONNREFUSED 127.0.0.1:5432
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 5432
}
Database connection failed. Exiting...

ECONNREFUSEDエラーは、接続が拒否されたことを示しています。

環境変数の確認

bashkubectl get pod webapp-5f7d8c9a6-jkl90 -o yaml | grep -A 20 env
yamlenv:
  - name: DATABASE_HOST
    value: '127.0.0.1'
  - name: DATABASE_PORT
    value: '5432'
  - name: DATABASE_USER
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: username

DATABASE_HOST127.0.0.1(ローカルホスト)になっています。これは誤った設定ですね。

原因

Kubernetes 環境では、データベースは別の Pod または Service として実行されているため、127.0.0.1では接続できません。正しい Service 名を使用する必要があります。

解決方法

環境変数を正しい Service 名に修正します。

ConfigMap の作成:

yamlapiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
data:
  DATABASE_HOST: 'postgres-service'
  DATABASE_PORT: '5432'
  DATABASE_NAME: 'myapp'

正しい Service 名postgres-serviceを設定しました。

Deployment の修正:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  template:
    spec:
      containers:
        - name: webapp
          image: webapp:v1.0
          envFrom:
            - configMapRef:
                name: webapp-config
          env:
            - name: DATABASE_USER
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: username
            - name: DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password

ConfigMap を使用することで、環境ごとに設定を簡単に変更できます。

ConfigMap と Deployment を適用します。

bashkubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml

接続確認

デバッグ用の Pod を起動して、データベースへの接続を確認します。

bashkubectl run -it --rm debug --image=postgres:15 --restart=Never -- psql -h postgres-service -U myuser -d myapp

接続が成功すれば、設定は正しいことが確認できます。

動作確認

bashkubectl get pods
plaintextNAME                         READY   STATUS    RESTARTS   AGE
webapp-5f7d8c9a6-mno12       1/1     Running   0          1m
bashkubectl logs webapp-5f7d8c9a6-mno12
plaintextStarting application...
Connecting to database...
Database connected successfully!
Server listening on port 3000

アプリケーションが正常にデータベースに接続できました。

具体例 4: Liveness Probe の設定ミス

Liveness Probe の設定が厳しすぎて、起動中の Pod が誤って終了させられるケースです。

問題の症状

bashkubectl get pods
plaintextNAME                          READY   STATUS             RESTARTS   AGE
slow-app-8e9f1d2c3-pqr34      0/1     CrashLoopBackOff   8          15m

RESTARTS の回数が多く、定期的に増えています。

イベントの確認

bashkubectl describe pod slow-app-8e9f1d2c3-pqr34
plaintextEvents:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Started    3m (x9 over 15m)     kubelet            Started container slow-app
  Warning  Unhealthy  2m (x27 over 14m)    kubelet            Liveness probe failed: Get "http://10.1.2.3:8080/health": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
  Normal   Killing    2m (x9 over 14m)     kubelet            Container slow-app failed liveness probe, will be restarted

Liveness probe failedというメッセージが繰り返し表示されています。

ログの確認

bashkubectl logs slow-app-8e9f1d2c3-pqr34
plaintextStarting application...
Loading configuration...
Initializing database connections...
Starting web server on port 8080...
Application ready (startup took 45 seconds)

アプリケーション自体は正常に起動していますが、起動に 45 秒かかっています。

Liveness Probe の設定確認

bashkubectl get pod slow-app-8e9f1d2c3-pqr34 -o yaml | grep -A 10 livenessProbe
yamllivenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3

設定の問題点を整理すると:

#項目設定値問題点
1initialDelaySeconds10 秒アプリケーションの起動(45 秒)より短い
2timeoutSeconds3 秒レスポンスを返すには短すぎる可能性
3failureThreshold3 回失敗許容回数が少なすぎる

原因

Liveness Probe のinitialDelaySecondsが、アプリケーションの実際の起動時間(45 秒)より短い 10 秒に設定されているため、起動中にヘルスチェックが失敗し、コンテナが再起動されてしまいます。

解決方法

Liveness Probe と Readiness Probe を適切に設定します。

Deployment マニフェストの修正:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: slow-app
spec:
  template:
    spec:
      containers:
        - name: slow-app
          image: slow-app:v1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3

改善ポイント:

#Probeパラメータ変更前変更後理由
1LivenessinitialDelaySeconds10 秒60 秒起動時間 45 秒より長く設定
2LivenesstimeoutSeconds3 秒5 秒タイムアウトを長めに設定
3Readiness新規追加なし30 秒トラフィック受信前の準備完了確認

Liveness Probe と Readiness Probe の役割の違い:

Readiness Probe(準備完了プローブ):

  • Pod がトラフィックを受け付ける準備ができているかを確認
  • 失敗してもコンテナは再起動されない(トラフィックが送られないだけ)
  • 起動時の準備完了確認に使用

Liveness Probe(生存プローブ):

  • コンテナが正常に動作しているかを確認
  • 失敗するとコンテナが再起動される
  • デッドロックや無限ループの検出に使用

変更を適用します。

bashkubectl apply -f deployment.yaml

動作確認

bashkubectl get pods
plaintextNAME                          READY   STATUS    RESTARTS   AGE
slow-app-8e9f1d2c3-stu56      1/1     Running   0          2m
bashkubectl describe pod slow-app-8e9f1d2c3-stu56
plaintextEvents:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  2m    default-scheduler  Successfully assigned default/slow-app-8e9f1d2c3-stu56 to node-1
  Normal  Pulled     2m    kubelet            Container image "slow-app:v1.0" already present on machine
  Normal  Created    2m    kubelet            Created container slow-app
  Normal  Started    2m    kubelet            Started container slow-app

エラーイベントがなく、Pod が安定して動作しています。

以下の図は、今回解説した 4 つの具体例における原因と解決策の関係を示しています。

mermaidflowchart TD
    issues["CrashLoopBackOff<br/>の具体例"]

    issues --> case1["具体例1:<br/>依存関係不足"]
    issues --> case2["具体例2:<br/>メモリ不足"]
    issues --> case3["具体例3:<br/>DB接続エラー"]
    issues --> case4["具体例4:<br/>Probe設定ミス"]

    case1 --> sol1["Dockerfileで<br/>npm install実行"]
    case2 --> sol2["メモリ制限増加<br/>+ヒープサイズ調整"]
    case3 --> sol3["Service名に<br/>修正"]
    case4 --> sol4["initialDelaySeconds<br/>を長く設定"]

    sol1 --> result["Pod正常起動"]
    sol2 --> result
    sol3 --> result
    sol4 --> result

図で理解できる要点:

  • CrashLoopBackOff の原因は多様だが、それぞれに対応する解決策がある
  • 適切な診断手順により、原因を特定し解決できる
  • 各具体例から学んだパターンは、他の類似問題にも適用できる

まとめ

CrashLoopBackOff は、Kubernetes で最もよく遭遇するエラーの一つですが、体系的なアプローチで原因を特定し、解決することができます。

本記事では、以下の内容を学びました。

まず、CrashLoopBackOff の基本的な仕組みを理解しました。これは、コンテナが繰り返しクラッシュし、Kubernetes が再起動を試みている状態です。バックオフの待機時間が徐々に長くなることで、システムリソースの無駄な消費を防いでいましたね。

次に、主な原因を 4 つのカテゴリに分類しました。アプリケーションエラー、設定エラー、リソース不足、外部依存関係の問題です。それぞれの原因には特徴的な症状があり、適切な診断により特定できます。

そして、体系的な切り分け手順を 7 つのステップで解説しました。基本情報の収集から始まり、イベント確認、ログ分析、詳細情報の取得、リソース状況確認、設定の検証、そして高度なデバッグ手法まで、段階的にアプローチすることが重要です。

最後に、4 つの具体例を通して、実践的なトラブルシューティングを体験しました。依存関係不足、メモリ不足(OOMKilled)、データベース接続エラー、Liveness Probe の設定ミスといった、実際によくあるケースを取り上げましたね。

これらの知識とスキルを身につけることで、CrashLoopBackOff エラーに遭遇しても、冷静かつ効率的に対処できるようになります。トラブルシューティングは経験を積むほど上達しますので、ぜひ実際の環境で試してみてください。

Kubernetes の運用において、エラーの早期発見と迅速な対応は非常に重要です。本記事で紹介した手法を活用して、安定したアプリケーション運用を実現しましょう。

関連リンク