Kubernetes で Pod が CrashLoopBackOff の原因と切り分け手順
Kubernetes でアプリケーションをデプロイしたとき、Pod がCrashLoopBackOffという状態になって起動しない経験はありませんか?
このエラーは、Kubernetes を使い始めた方が最初にぶつかる壁の一つです。Pod が何度も再起動を繰り返し、アプリケーションが正常に動作しない状態は、原因が分からないと非常に焦りますよね。
本記事では、CrashLoopBackOff エラーの原因を理解し、体系的な切り分け手順を学ぶことで、トラブルシューティングを効率的に行えるようになります。初心者の方でも実践できるように、具体的なコマンド例とともに詳しく解説していきましょう。
背景
Kubernetes における Pod のライフサイクル
Kubernetes では、コンテナアプリケーションを「Pod」という単位で管理します。Pod は、Kubernetes における最小のデプロイ単位で、1 つ以上のコンテナで構成されていますね。
Pod は作成されてから削除されるまで、いくつかの状態(Phase)を遷移します。主な状態には以下のようなものがあります。
| # | 状態 | 説明 |
|---|---|---|
| 1 | Pending | Pod が作成され、スケジューリング待ちまたはイメージのダウンロード中 |
| 2 | Running | Pod が正常に起動し、少なくとも 1 つのコンテナが実行中 |
| 3 | Succeeded | すべてのコンテナが正常に終了した状態(バッチ処理など) |
| 4 | Failed | すべてのコンテナが終了し、少なくとも 1 つが異常終了した状態 |
| 5 | Unknown | Pod の状態が取得できない状態(ノードとの通信エラーなど) |
以下の図は、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 は「バックオフ(待機時間)」を設けて再起動を試みるようになるんですね。
この待機時間は以下のように増加します。
| # | 再起動回数 | 待機時間 |
|---|---|---|
| 1 | 1 回目 | 0 秒 |
| 2 | 2 回目 | 10 秒 |
| 3 | 3 回目 | 20 秒 |
| 4 | 4 回目 | 40 秒 |
| 5 | 5 回目以降 | 最大 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
このエラーメッセージから、以下の情報が読み取れます。
| # | 項目 | 値 | 意味 |
|---|---|---|---|
| 1 | NAME | my-app-5d4f8b7c9-xyz12 | Pod 名(Deployment 名-ランダム文字列) |
| 2 | READY | 0/1 | 準備完了コンテナ数/総コンテナ数 |
| 3 | STATUS | CrashLoopBackOff | Pod の現在の状態 |
| 4 | RESTARTS | 5 | コンテナの再起動回数 |
| 5 | AGE | 3m | Pod が作成されてからの経過時間 |
RESTARTS の値が増え続けている場合は、恒久的な問題が存在する可能性が高いですね。
CrashLoopBackOff の主な原因
CrashLoopBackOff が発生する原因は多岐にわたりますが、主に以下のカテゴリに分類できます。
アプリケーションエラー
コンテナ内のアプリケーション自体にバグや設定ミスがあるケースです。
| # | 原因 | 具体例 |
|---|---|---|
| 1 | アプリケーションのクラッシュ | NullPointerException、未処理の例外 |
| 2 | 起動スクリプトのエラー | シェルスクリプトの構文エラー、パスの誤り |
| 3 | 設定ファイルの不備 | 必須パラメータの欠落、フォーマットエラー |
| 4 | 依存関係の問題 | ライブラリの不足、バージョン不一致 |
設定エラー
Kubernetes のマニフェストファイルやコンテナの設定に問題があるケースです。
| # | 原因 | 具体例 |
|---|---|---|
| 1 | イメージの指定ミス | 存在しないタグ、プライベートレジストリの認証エラー |
| 2 | コマンド・引数の誤り | 存在しないコマンド、誤った引数 |
| 3 | 環境変数の不足 | 必須の環境変数が未設定 |
| 4 | Liveness/Readiness Probe の設定ミス | タイムアウト値が短すぎる、エンドポイントの誤り |
リソース不足
コンテナに割り当てられたリソースが不足しているケースです。
| # | 原因 | 具体例 |
|---|---|---|
| 1 | メモリ不足(OOMKilled) | アプリケーションが limits.memory を超過 |
| 2 | CPU 不足 | 起動時に必要な CPU リソースが確保できない |
| 3 | ディスク容量不足 | ログファイルの肥大化、一時ファイルの蓄積 |
外部依存関係の問題
コンテナが依存する外部サービスへの接続に失敗するケースです。
| # | 原因 | 具体例 |
|---|---|---|
| 1 | データベース接続エラー | 接続先の誤り、認証エラー、タイムアウト |
| 2 | 外部 API 接続エラー | ネットワーク不通、エンドポイントの変更 |
| 3 | ConfigMap/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オプションを付けることで、以下の追加情報が表示されます。
| # | 項目 | 説明 |
|---|---|---|
| 1 | IP | Pod に割り当てられた IP アドレス |
| 2 | NODE | Pod がスケジューリングされたノード名 |
| 3 | NOMINATED NODE | 次にスケジューリングされる候補ノード |
| 4 | READINESS GATES | Readiness 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
イベントから読み取れる情報は以下の通りです。
| # | Type | Reason | 意味 |
|---|---|---|---|
| 1 | Normal | Scheduled | Pod がノードに正常にスケジューリングされた |
| 2 | Normal | Pulled | イメージの取得が完了した |
| 3 | Normal | Created | コンテナが作成された |
| 4 | Normal | Started | コンテナが起動した |
| 5 | Warning | BackOff | バックオフによる再起動待機中 |
よくあるエラーイベント
以下は、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
確認すべき重要項目:
| # | 項目 | 確認ポイント |
|---|---|---|
| 1 | State | 現在の状態(Waiting、Running、Terminated) |
| 2 | Reason | 状態の理由(CrashLoopBackOff、Error、OOMKilled など) |
| 3 | Exit Code | 終了コード(0 以外はエラーを示す) |
| 4 | Restart Count | 再起動回数(増え続けている場合は注意) |
Exit Code の意味
Exit Code は、コンテナの終了理由を示す重要な手がかりです。
| # | Exit Code | 意味 | 一般的な原因 |
|---|---|---|---|
| 1 | 0 | 正常終了 | バッチジョブの正常完了 |
| 2 | 1 | 一般的なエラー | アプリケーションのクラッシュ、例外 |
| 3 | 2 | 使用方法の誤り | コマンドライン引数の誤り |
| 4 | 126 | コマンド実行不可 | 実行権限がない、バイナリが見つからない |
| 5 | 127 | コマンドが見つからない | コマンドのパスが間違っている |
| 6 | 130 | Ctrl+C による中断 | SIGINT シグナル受信 |
| 7 | 137 | 強制終了(OOMKilled) | メモリ不足により SIGKILL 受信 |
| 8 | 143 | 正常なシャットダウン | 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
| # | 項目 | 確認ポイント |
|---|---|---|
| 1 | image | タグが正しいか、タグが存在するか |
| 2 | imagePullPolicy | Always、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
| # | 項目 | 確認ポイント |
|---|---|---|
| 1 | initialDelaySeconds | アプリケーションの起動時間より長く設定されているか |
| 2 | timeoutSeconds | レスポンスを返すのに十分な時間か |
| 3 | failureThreshold | 失敗回数が適切に設定されているか |
| 4 | path | ヘルスチェックエンドポイントが正しいか |
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"]
修正のポイントは以下の通りです。
| # | 改善点 | 理由 |
|---|---|---|
| 1 | package.json を先にコピー | Docker のレイヤーキャッシュを活用 |
| 2 | npm 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 | -Xmx | 1536m | 最大ヒープサイズ(limits の 75%程度) |
| 2 | -Xms | 512m | 初期ヒープサイズ(requests と同程度) |
| 3 | limits.memory | 2Gi | Pod のメモリ上限 |
| 4 | requests.memory | 1Gi | 最低限必要なメモリ |
変更を適用します。
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_HOSTが127.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
設定の問題点を整理すると:
| # | 項目 | 設定値 | 問題点 |
|---|---|---|---|
| 1 | initialDelaySeconds | 10 秒 | アプリケーションの起動(45 秒)より短い |
| 2 | timeoutSeconds | 3 秒 | レスポンスを返すには短すぎる可能性 |
| 3 | failureThreshold | 3 回 | 失敗許容回数が少なすぎる |
原因
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 | パラメータ | 変更前 | 変更後 | 理由 |
|---|---|---|---|---|---|
| 1 | Liveness | initialDelaySeconds | 10 秒 | 60 秒 | 起動時間 45 秒より長く設定 |
| 2 | Liveness | timeoutSeconds | 3 秒 | 5 秒 | タイムアウトを長めに設定 |
| 3 | Readiness | 新規追加 | なし | 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 の運用において、エラーの早期発見と迅速な対応は非常に重要です。本記事で紹介した手法を活用して、安定したアプリケーション運用を実現しましょう。
関連リンク
articleJava の OutOfMemoryError を根治する:ヒープ/メタスペース/スレッドの診断術
articleKubernetes で Pod が CrashLoopBackOff の原因と切り分け手順
article本番計測とトレース:Zustand の更新頻度・差分サイズを可視化して改善サイクル化
articleSvelte 可観測性の実装:Sentry/OpenTelemetry/Web Vitals 連携ガイド
article【緊急警告】React/Next.js の RSC 機能に CVSS 10.0 の RCE 脆弱性「CVE-2025-55182」が発覚
articleWebSocket Close コード早見表:正常終了・プロトコル違反・ポリシー違反の実務対応
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来