The kubelet does not start containers itself. It calls a runtime through the Container Runtime Interface (CRI), a gRPC API for sandboxes, containers and images. The two common runtimes are containerd 234,762 , the daemon under Docker Engine 514 (How Linux Isolates), and CRI-O 669,542 , built for Kubernetes 5,150 alone. The kubelet's adapter for Docker Engine, dockershim, was removed in 1.24 (May 2022); Docker-built images run unchanged because they are OCI images. crictl 2,016 (github.com/kubernetes-sigs/cri-tools (https://github.com/kubernetes-sigs/cri-tools 2,016 )) is a CRI client for debugging a node:
W=l3-booknest-worker
docker exec $W crictl version | grep Runtime
docker exec $W crictl pods --state ready -o json \
| jq -r '.items[] | "\(.id[0:13]) \(.metadata.namespace)/\(.metadata.name)"'
docker exec $W crictl ps -o json \
| jq -r '.containers[] | "\(.podSandboxId[0:13]) container \(.metadata.name)"'RuntimeName: containerd RuntimeVersion: v2.3.4 RuntimeApiVersion: v1 acc5b3690c727 default/web-64546c896b-fh6fn abdac73884d88 default/web-64546c896b-7r29z 054b7e9ba989d kube-system/kube-proxy-grdh5 432273f0c3908 kube-system/kindnet-9t4ld acc5b3690c727 container booknest-web abdac73884d88 container booknest-web 432273f0c3908 container kindnet-cni 054b7e9ba989d container kube-proxy
Every Pod first gets a sandbox: containerd starts a tiny pause container (registry.k8s.io/pause:3.10, 320 kB) whose only job is to hold the Pod's network namespace and IP address. The app containers then join that namespace, which is why containers in one Pod reach each other on localhost and why a restarting container keeps its Pod IP. ps inside the node shows one containerd-shim per Pod with its pause and app processes beneath it, so containerd itself can restart without killing containers.

crictl bypasses the API server: a container you stop with crictl stop is simply restarted by the kubelet, because the Pod object still asks for it.