Pod が ContainerCreating のままで、アプリが動き始めない。
イメージの取得を待っているのか、ネットワークの準備に失敗したのか、コンテナを起動できないのか。
同じ表示でも、止まっている場所は違います。
この章では、kubelet がランタイムに頼む仕事を、起動の順に追います。
その窓口である CRI に、crictl というコマンドで接続し、準備のどこまで進んだかを確かめます。
GOAL この章のゴール
- Pod の準備からコンテナの起動まで、依頼の順序を説明できる
- イベントから起動処理のどこで失敗したかを調べられる
- crictl でランタイムが持つコンテナの状態を確認できる
CRI と OCI の位置関係
kubelet は、コンテナの起動や状態確認を containerd などのランタイムに頼みます。 この呼び出し方を定めるのが CRI です。 同じノード上のプログラム同士をつなぐ UNIX socket を使い、gRPC という方式でやり取りします。
一方、containerd が runc に渡す実行設定や、コンテナの起動・停止の決まりは OCI Runtime Spec が定めます。 CRI が Kubernetes からの依頼を扱うのに対して、こちらはコンテナを実際に作るための設定と操作を扱います。 図で、2 つがどの間にあるかを見てください。
2 つの仕様は、頼む内容が違います。
CRI は「Pod 単位でコンテナ群を作り、イメージを管理し、ログや exec を中継する」という、Pod やイメージといった Kubernetes の概念で書かれています。
OCI Runtime Spec は「この config.json の通りに namespace と cgroup を作ってプロセスを起動しろ」という、Linux の概念で書かれています。
containerd の仕事は、前者を後者に翻訳することです。
図の RuntimeClass は、Part 3 で見た gVisor などを Pod ごとに選ぶためのリソースです。
RuntimeClass の handler の名前が CRI の RunPodSandbox に runtime_handler として渡され、containerd は自分の設定 (/etc/containerd/config.toml の runtimes.<handler>) からどの低レベルランタイムを使うかを引きます。
2 つの gRPC サービス
イメージを用意する仕事と、コンテナを動かす仕事は、別々の窓口になっています。
CRI では、この 2 つの窓口をサービスと呼びます。
RuntimeService は Pod sandbox とコンテナの操作、ImageService はイメージの取得と削除です。
どちらも同じ UNIX socket で提供され、kubelet の設定 containerRuntimeEndpoint にそのパスが書いてあります。
RuntimeService には Exec / Attach / PortForward もあります。
CRI の Exec の応答は「コマンドを実行するストリーミングサーバーの URL」で、kubelet はその URL に接続して apiserver との間を中継します。
kubectl exec でコンテナの中に入る操作も、apiserver → kubelet → CRI という同じ道を通っています。
Pod が起動するまでの 4 呼び出し
kubelet が Pod を 1 つ起動するとき、CRI の呼び出しは大きく 4 段です。 containerd のドキュメントが、コンテナ 1 つの Pod を例に、この流れを説明しています。
RunPodSandbox
Pod sandboxポッドサンドボックスPod 内のコンテナが共有する namespace (net / ipc / uts) を保持するもの。containerd では pause コンテナがこの役を担う。用語集で見る を作ります。containerd はまず Pod の network namespace を作り、それを CNI で設定し (Pod の IP が決まるのはこの時点です)、次に pause コンテナを起動して Pod の cgroup と namespace に入れます。PullImage
各コンテナのイメージを ImageService で取得します。containerd は、イメージがノードに無いときだけ pull します。CreateContainer
sandbox の ID と ContainerConfig (イメージ、コマンド、環境変数、マウント、デバイス) を渡します。containerd はここで OCI のconfig.jsonを生成し、runc でコンテナを作ります。StartContainer
コンテナのプロセスを起動します。init コンテナがあれば、それが終わるのを待ってからアプリのコンテナで 3 と 4 を繰り返します。
Pod が ContainerCreating で止まるとき、kubelet のイベントにはこのどれで止まったかが出ます。
FailedCreatePodSandBox なら 1 (多くは CNI 側)、ErrImagePull なら 2、CreateContainerError なら 3 です。
どの段で止まったかが分かれば、疑う場所が決まります。
年表
実機: kubelet と同じ socket に繋ぐ
kubectl debug node/$(kubectl get node -o jsonpath='{.items[0].metadata.name}') -it \
--profile=sysadmin --image=busybox:1.37 -- chroot /host nsenter -t 1 -m -u -i -n -p -- bash抜けるときは exit。デバッグ用 Pod は自動では消えないので、終わったら kubectl delete pod -l app.kubernetes.io/managed-by=kubectl-debug かkubectl get pods で確認して消してください。
まず kubelet がどこに繋いでいるかを設定ファイルで確かめます。
grep -E 'containerRuntimeEndpoint|cgroupDriver' /var/lib/kubelet/config.yaml出力例
cgroupDriver: systemd
containerRuntimeEndpoint: unix:///run/containerd/containerd.sock次に crictl (CRI 互換のランタイムを検査、デバッグするための CLI) で同じ socket に繋ぎます。
info は RuntimeService の Status を呼び、ランタイムの状態と、containerd の CRI プラグインが持っている設定を返します。
crictl --runtime-endpoint unix:///run/containerd/containerd.sock info | jq '.status.conditions, .config.cni, (.config.containerd.runtimes | keys)'出力例
[
{ "type": "RuntimeReady", "status": true, ... },
{ "type": "NetworkReady", "status": true, ... }
]
{ "binDir": "/opt/cni/bin", "confDir": "/etc/cni/net.d", ... }
[ "runc" ]CRI の仕様は RuntimeReady と NetworkReady を「kubelet が正しく動くために必須の条件」と定め、どちらかが満たされないとき Node は NotReady になる、と書いています。
NetworkReady は「コンテナネットワークを必要とするコンテナを受け付けられる状態か」を表し、containerd では CNI の設定が読めているかに対応します。
CRI の Status に CNI の状態が混ざっているのは、containerd が RunPodSandbox の中で CNI を呼ぶ側だからです。
次の章で、この confDir の中身を読みます。
crictl pods
crictl ps出力例
POD ID CREATED STATE NAME NAMESPACE ATTEMPT
3f1c2a9b8d7e 2 hours ago Ready cilium-abcde kube-system 0
...
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
9a8b7c6d5e4f quay.io/... 2 hours ago Running cilium-agent 0 3f1c2a9b8d7ecrictl pods が返すのは RunPodSandbox で作られた sandbox、crictl ps は CreateContainer で作られたコンテナです。
kubectl get pods と同じものが見えますが、こちらは apiserver を経由していません。
ノードの上の事実をそのまま読んでいます。
ふりかえり
QCNI の ADD が呼ばれるのは CRI のどのメソッドの処理中?
Pod の network namespace は sandbox が持ちます。containerd は RunPodSandbox の中で network namespace を作って CNI で設定し、pause コンテナをそこに入れます。
QCRI と OCI Runtime Spec の関係として正しいのは?
CRI は Kubernetes の語彙 (Pod、イメージ、exec) で書かれ、OCI Runtime Spec は Linux の語彙 (namespace、cgroup、プロセス) で書かれています。containerd が前者を後者に翻訳します。
Qcrictl info の NetworkReady が false のとき、何が起きる?
CRI の仕様は RuntimeReady と NetworkReady を必須の条件とし、どちらかが満たされないと Node は NotReady になります。NotReady の Node には新しい Pod がスケジュールされません。
参考
- Container Runtime Interface (CRI), Kubernetes Documentation: https://kubernetes.io/docs/concepts/architecture/cri/
- CRI API 定義 (
api.proto), kubernetes/cri-api: https://github.com/kubernetes/cri-api/blob/master/pkg/apis/runtime/v1/api.proto - Architecture of The CRI Plugin, containerd: https://github.com/containerd/containerd/blob/main/docs/cri/architecture.md
- Runtime Class, Kubernetes Documentation: https://kubernetes.io/docs/concepts/containers/runtime-class/
- Debugging Kubernetes nodes with crictl, Kubernetes Documentation: https://kubernetes.io/docs/tasks/debug/debug-cluster/crictl/
- Dockershim Removal FAQ, Kubernetes Blog: https://kubernetes.io/blog/2022/02/17/dockershim-faq/
- Introducing Container Runtime Interface (CRI) in Kubernetes (2016), Kubernetes Blog: https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/
- Open Container Initiative Runtime Specification: https://github.com/opencontainers/runtime-spec/blob/main/spec.md