冒頭まとめ
RunContainerError は、HTTPステータスコードではなく、Pod内のコンテナ状態に出る waiting.reason です。Podはノードに割り当てられ、Pod sandboxも作られた後、kubeletがcontainer runtimeへコンテナ起動を依頼した段階で失敗しています。
State: Waiting
Reason: RunContainerError
Message: <container runtime が返した実メッセージ>
ここで重要なのは、RunContainerError という文字だけでは原因が決まらないことです。原因は隣にある Message、直近のEvents、そして同じノード上の他Podの状態から切り分けます。
まず次の4つを分けてください。
- 表示されているreasonが本当に
RunContainerErrorなのか。 - messageがワークロード設定の問題を示しているのか。
- volume、権限、セキュリティ設定など、Pod定義とノード条件の組み合わせで失敗しているのか。
- containerd、CRI-O、runc、cgroup、ディスクなど、ノード側runtimeの問題なのか。
kubectl logs が空でも不思議ではありません。プロセスがまだ開始できていないため、アプリケーションログへ到達しないことがあります。最初に読むべきなのは、アプリログではなく kubectl describe pod のState、Message、Eventsです。
エラーの概要
KubernetesのPod起動は、ざっくり次の段階に分けられます。
Scheduling
↓
Image pull
↓
Pod sandbox 作成
↓
Container 作成
↓
Container 起動
↓
Application 実行
RunContainerError は、このうち Container作成または起動 の近辺で止まっている状態です。kubeletの実装では、RunContainerError はコンテナ起動時の失敗を表すエラーとして定義されています(sync_result.go)。
したがって、RunContainerError を見た時点で、少なくとも次の切り分けが必要です。
kubectl get pod <Pod名> -n <名前空間> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\t"}{.state.waiting.message}{"\n"}{end}'
initコンテナで止まっている場合は、見る場所が変わります。
kubectl get pod <Pod名> -n <名前空間> \
-o jsonpath='{range .status.initContainerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\t"}{.state.waiting.message}{"\n"}{end}'
似ている表示との境界も先に確定してください。
| 表示 | 失敗している段階 | 本記事の対象 |
|---|---|---|
RunContainerError | sandbox作成後、runtimeがコンテナを起動できない | 対象 |
CreateContainerConfigError | kubeletがコンテナ設定を解決できない | 対象外 |
FailedCreatePodSandbox | sandbox作成、CNI、pause container付近 | 対象外 |
ImagePullBackOff / ErrImagePull | イメージ取得 | 対象外 |
CrashLoopBackOff | 起動後にプロセスが終了し再起動を繰り返す | 対象外 |
Kubernetes公式のPodデバッグ手順でも、まずPodを確認し、kubectl describe で状態と最近のEventsを見る流れが示されています(Debug Pods)。
まず最初に:reason・message・Events・ノード偏りを分ける
第一に、reasonを確定します。
kubectl get pod <Pod名> -n <名前空間> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\n"}{end}'
ここが RunContainerError でなければ、この記事の手順をそのまま当てはめません。表示されたreasonが示す段階へ戻ってください。
第二に、messageをそのまま保存します。
kubectl describe pod <Pod名> -n <名前空間>
RunContainerError は段階名です。実際の原因は Message: にあります。実行ファイルがない、権限が拒否された、マウントできない、cgroupを作れない、runtimeが応答できない、といった語を探します。
第三に、Eventsで前段階の失敗が混ざっていないかを確認します。
kubectl get events -n <名前空間> \
--field-selector involvedObject.name=<Pod名> \
--sort-by=.lastTimestamp
FailedCreatePodSandbox が繰り返し出ているなら、主戦場はsandbox作成側です。CNI、pause image、container runtimeのsandbox設定を優先し、RunContainerError の記事から離れます。
第四に、ノード偏りを見ます。
kubectl get pod <Pod名> -n <名前空間> -o wide
kubectl get pods -A -o wide --field-selector spec.nodeName=<ノード名>
同じワークロードが別ノードでは動くなら、ノード側runtimeやノード状態を疑います。どのノードでも同じmessageで失敗するなら、Pod定義やイメージ内の実行設定を疑います。
よくある原因と解決手順
原因1:command、args、workingDir、実行権限が不正
command、args、workingDir、実行ファイルのパス、実行権限、runAsUser などが噛み合っていないと、runtimeはプロセスを開始できません。イメージ取得までは成功しているため、kubectl logs は空のままになりがちです。
Before(entrypointを上書きしている):
spec:
containers:
- name: app
image: <your-image>
command: ["/opt/app/bin/start"]
workingDir: /opt/app
securityContext:
runAsUser: 1000
After(まずイメージ既定で起動を確認する):
spec:
containers:
- name: app
image: <your-image>
修正後は、同じimageでPodが起動するかを確認します。
kubectl apply -f <manifest.yaml>
kubectl get pod <Pod名> -n <名前空間> -w
これで起動するなら、外した項目を1つずつ戻します。command だけ、workingDir だけ、securityContext だけ、という順に戻すと、どの設定がruntimeを止めているかが分かります。
原因2:volumeMount、Secret、ConfigMap、hostPath、権限が合っていない
ボリュームの実体や権限がPod定義と合っていない場合、runtimeがコンテナを作れず RunContainerError になることがあります。特に hostPath、ファイルとしてマウントするSecret/ConfigMap、読み書き権限、SELinuxやAppArmorの拒否はmessage側に出ます。
Before(hostPathとsecurityContextを同時に疑う必要がある):
spec:
containers:
- name: app
image: <your-image>
volumeMounts:
- name: app-data
mountPath: /var/lib/app
securityContext:
runAsUser: 1000
volumes:
- name: app-data
hostPath:
path: /var/lib/app
type: Directory
After(まずmountなしで起動段階を確認する):
spec:
containers:
- name: app
image: <your-image>
設定を棚卸しします。
kubectl get pod <Pod名> -n <名前空間> -o yaml \
| grep -A 60 -E 'volumeMounts:|volumes:|securityContext:'
同じノードで最小構成が起動するなら、原因はPod定義側です。mountを戻した瞬間に再発するなら、ノード上のパス、権限、ラベル、ファイル/ディレクトリの型を確認します。
原因3:containerd、CRI-O、runc、cgroup、ディスクなどノード側runtimeが壊れている
同じPodが特定ノードでだけ失敗する場合は、ノード側のcontainer runtimeを見ます。containerd、CRI-O、runc、cgroup、ディスク容量、展開済みimage layerの不整合が原因になることがあります。
Before(ノード偏りを見ずにPod定義だけ直そうとしている):
kubectl describe pod <Pod名> -n <名前空間>
# Pod定義だけを繰り返し編集する
After(失敗ノードを先に確定する):
kubectl get pod <Pod名> -n <名前空間> -o wide
kubectl get pods -A -o wide --field-selector spec.nodeName=<ノード名>
ノードに入れる場合は、kubeletとruntimeのログを確認します。
sudo journalctl -u kubelet --since "30 min ago" --no-pager
sudo systemctl status containerd
sudo journalctl -u containerd --since "30 min ago" --no-pager
df -h
CRI互換runtimeの状態を直接見る場合は crictl が使えます。Kubernetes公式ドキュメントは、ノード上のcontainer runtimeとアプリケーションを検査・デバッグするためのツールとして crictl を案内しています(Debugging Kubernetes nodes with crictl)。
containerdのCRI設定を疑う場合、containerd側ではCRI pluginの設定が plugins."io.containerd.grpc.v1.cri" セクションにまとまります(containerd CRI Plugin Config Guide)。ただし設定キーは版によって変わるため、実行中の版のドキュメントと現在の config.toml を突き合わせてください。
補足:似ているが別のもの
CreateContainerConfigError は、SecretやConfigMapの参照、環境変数、設定解決など、container runtimeへ起動を依頼する前の段階で止まる表示です。RunContainerError と同じ「コンテナが動かない」見た目でも、調査対象はPod定義の解決側です。
FailedCreatePodSandbox は、Pod sandbox、CNI、pause container付近の失敗です。アプリコンテナの起動以前で止まっています。Eventsにこのreasonが出ているなら、CNI pluginやcontainer runtimeのsandbox設定を先に見ます。
CrashLoopBackOff は、コンテナの起動には成功した後、アプリケーションが終了して再起動を繰り返している状態です。この場合は kubectl logs --previous や終了コードが主な手がかりです。
ImagePullBackOff と ErrImagePull は、image取得の失敗です。認証、タグ、registry到達性を確認します。RunContainerError はimage取得後の段階です。
OOMKilled は、起動後にメモリ制限などでプロセスが終了した結果です。RunContainerError はプロセス開始前または開始時点で止まるため、調査する時間軸が違います。
危険な対応を行う前の確認
RunContainerError でよくある危険な対応は、原因を確定しないまま権限や保護機構を広げることです。
次の対応は、原因切り分けの一時検証に限定してください。
securityContext:
privileged: true
securityContext:
runAsUser: 0
securityContext:
seLinuxOptions: null
privileged: true、root実行、SELinux/AppArmor/seccompの無効化、hostPathの広範囲な付与は、Podの隔離を弱めます。動いたから恒久対応にするのではなく、どの権限・ラベル・mount・実行ユーザーが必要だったのかを狭く戻します。
ノード側でruntimeを再起動する場合も、影響範囲を確認してください。
kubectl get pods -A -o wide --field-selector spec.nodeName=<ノード名>
kubectl cordon <ノード名>
本番ノードでcontainer runtimeを再起動すると、そのノード上のPodに影響します。まず対象ノードのPod、PodDisruptionBudget、冗長性を確認し、必要ならcordonやdrainの計画を立てます。
imageやレイヤを削除する場合も、対象を限定します。
# 実行前に対象ノードと対象imageを確認する
crictl images
不要だからといって広範囲にimageやcontainer stateを消すと、他のPodの再起動や復旧に影響します。messageとruntimeログで対象を絞ってから実行してください。
切り分けの順序
containerStatusesとinitContainerStatusesのreasonを確認し、RunContainerErrorであることを確定する。kubectl describe podでMessageをそのまま保存する。- Eventsを時系列で確認し、
FailedCreatePodSandboxやimage pull系の前段階エラーが主因でないか確認する。 kubectl logsが空でも、プロセス開始前なら異常ではないと判断する。command、args、workingDir、securityContextを外した最小構成で同じimageを起動する。- volumeMount、Secret、ConfigMap、hostPathを1つずつ戻し、どの設定で再発するか確認する。
- 同じPodを別ノードで動かし、ノード固有かワークロード固有かを分ける。
- ノード固有なら kubelet、containerd/CRI-O、runc、cgroup、ディスク容量を確認する。
- 権限緩和やruntime再起動を行う前に、影響範囲と戻し方を決める。
- 修正後は
waiting.reasonが消え、PodがRunningかつReadyになることを確認する。
確認コマンド集
# 1. Pod内の各コンテナのreason/messageを確認する
kubectl get pod <Pod名> -n <名前空間> \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\t"}{.state.waiting.message}{"\n"}{end}'
# 2. initコンテナ側も確認する
kubectl get pod <Pod名> -n <名前空間> \
-o jsonpath='{range .status.initContainerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\t"}{.state.waiting.message}{"\n"}{end}'
# 3. describeでState、Message、Eventsをまとめて確認する
kubectl describe pod <Pod名> -n <名前空間>
# 4. Eventsを時系列で見る
kubectl get events -n <名前空間> \
--field-selector involvedObject.name=<Pod名> \
--sort-by=.lastTimestamp
# 5. Pod定義を保存する
kubectl get pod <Pod名> -n <名前空間> -o yaml > pod-runcontainererror.yaml
# 6. 対象ノードを確認する
kubectl get pod <Pod名> -n <名前空間> -o wide
# 7. 同じノード上のPodを確認する
kubectl get pods -A -o wide --field-selector spec.nodeName=<ノード名>
# 8. ノード側でkubeletログを見る
sudo journalctl -u kubelet --since "30 min ago" --no-pager
# 9. containerdを使っている場合の状態とログ
sudo systemctl status containerd
sudo journalctl -u containerd --since "30 min ago" --no-pager
# 10. ディスク容量を確認する
df -h
# 11. CRI経由でruntime状態を見る
crictl ps -a
crictl pods
crictl images
Editor’s Note
RunContainerError が扱いにくい理由は、Kubernetesの表示が「runtimeが起動できなかった」という段階名で止まり、原因の本文はruntime由来のmessageに移ることです。Kubernetesの公式デバッグ手順はPodの状態とEventsを見るところから始めますが、RunContainerError ではそこからさらにcontainer runtime側の語彙へ降りる必要があります。
kubeletのソースにも、RunContainerError は CreatePodSandboxError や KillContainerError など段階の異なるエラーと並んで定義されています(sync_result.go)。つまり、この文字列は「Kubernetes全体が失敗した」という大ざっぱな印ではなく、Pod起動のどの境界で止まったかを示す印です。
一方で、ノード上の実態を見るにはKubernetes APIだけでは足りないことがあります。Kubernetes公式の crictl デバッグ手順は、ノード上のcontainer runtimeとコンテナを直接調べる方法を案内しています。RunContainerError では、Pod定義の修正だけでなく、runtimeログやCRIの状態確認まで含めて初めて原因が見えることがあります。
だから、RunContainerError を見たら、まず Message を読み、次にPod定義かノードかを分けてください。権限を広げる、runtimeを再起動する、image stateを消す、といった操作は、原因の層が分かってから行う最後の手段です。
免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。
この記事でエラーは解決しましたか?