skip to content

What is the Container Runtime Interface (CRI) in Kubernetes, and how does the kubelet use it to run containers with containerd?

level: middleimportance: should knowfreq 50%

answer

  1. gRPC over unix socket: ImageService + RuntimeService
  2. RunPodSandbox -> CNI -> pod IP -> containers
  3. kubelet -> containerd -> runc (+ shim per container)
  4. dockershim removed in 1.24; images still OCI
  5. debug with crictl, not docker

basics

~20 s

CRI is the gRPC API the kubelet uses to talk to a container runtime over a local socket. The kubelet calls it to pull images and to create pod sandboxes and containers; containerd implements CRI natively and delegates actual container creation to a low-level OCI runtime such as runc.

solid answer

~50 s

**CRI** is a stable gRPC contract between the kubelet and whatever runtime is installed on the node, exposed on a Unix socket (for example `/run/containerd/containerd.sock`) and configured with the kubelet's container-runtime endpoint. It has two services: **ImageService** (pull, list, remove images) and **RuntimeService** (RunPodSandbox, CreateContainer, StartContainer, exec/attach/logs, status). The kubelet's flow per Pod: `RunPodSandbox` creates the pod's shared network and IPC context and triggers CNI to assign the Pod IP; then each init and app container is created and started inside that sandbox. containerd implements CRI through its built-in CRI plugin and hands the final step to an **OCI runtime** — runc by default, or gVisor/Kata selected via RuntimeClass. CRI-O is an alternative implementation. Historically the kubelet spoke to Docker through the **dockershim** adapter; that was removed in Kubernetes 1.24, so nodes now run containerd or CRI-O directly. Docker-built images still work — they are OCI images. Debug with `crictl`, not `docker`.

code

bash · 7 lines
bash
crictl version
crictl info | head -20
crictl pods            # sandboxes
crictl ps -a           # containers
crictl logs <container-id>

grep -r containerRuntimeEndpoint /var/lib/kubelet/config.yaml

go deeper

for a junior

Know that the kubelet does not run containers itself — it asks a runtime like containerd through a standard interface called CRI.

for a middle

Name the two CRI services, describe the sandbox-then-containers flow, and place containerd and runc correctly in the stack.

for a senior

Add operational depth: crictl debugging, shim behaviour across containerd restarts, RuntimeClass for stronger isolation, and the concrete fallout of the dockershim removal.

for a principal

Frame CRI as the node's pluggability and isolation boundary — containerd versus CRI-O versus sandboxed runtimes, image-pull and registry-mirror policy, and what removing daemon sockets does to in-cluster tooling.

## Why CRI exists Early Kubernetes had runtime support compiled into the kubelet, with Docker-specific code paths. That coupled the kubelet's release cycle to individual runtimes and made adding a new one a core-Kubernetes change. CRI, introduced in 1.5, inverted it: the kubelet became a **client** of a gRPC API that any runtime can implement. The contract is deliberately small and lives on a local Unix socket — no network hop, and no authentication story beyond filesystem permissions, because the runtime is a node-local trusted component. ## The two services - **ImageService**: `PullImage`, `ListImages`, `ImageStatus`, `RemoveImage`, `ImageFsInfo`. The kubelet uses it to honour `imagePullPolicy`, pass pull secrets, and drive image garbage collection when disk fills. - **RuntimeService**: `RunPodSandbox`, `StopPodSandbox`, `RemovePodSandbox`, `CreateContainer`, `StartContainer`, `StopContainer`, `ListContainers`, `ContainerStatus`, the streaming calls `Exec`, `Attach`, `PortForward` and log access, plus `Version` and `Status` for health. ## The pod sandbox concept CRI makes the Pod a first-class runtime concept. Before any application container starts, the kubelet calls `RunPodSandbox`. The sandbox owns the resources shared by every container in the Pod: the network namespace (hence the shared Pod IP and localhost communication between containers), the IPC namespace, and the cgroup parent. Creating the sandbox is what triggers the **CNI** plugin to set up networking and allocate the Pod IP. With containerd the sandbox is typically realized as a small "pause" container that does nothing but hold the namespaces open, so individual app containers can restart without the Pod losing its IP. With sandboxed runtimes such as Kata the sandbox is a lightweight VM instead — same CRI call, different isolation. ## Where containerd fits The stack on a modern node has three layers: 1. **kubelet** — decides what should run. 2. **containerd** (CRI implementation) — manages images, filesystem snapshots and container lifecycle; speaks CRI upward. 3. **runc** (OCI runtime) — the low-level binary that creates the container process from an OCI bundle, then exits. A shim process per container stays around to hold stdio and reap the process, which is why containerd itself can be restarted without killing running containers. RuntimeClass lets a Pod select an alternative handler at the containerd level — gVisor or Kata — for stronger isolation on the same node, without the kubelet knowing anything beyond the class name. CRI-O is a leaner alternative implementing CRI and only CRI; it occupies the same slot as containerd. ## The dockershim story The kubelet used to include **dockershim**, an in-tree adapter translating CRI calls into Docker Engine API calls; Docker Engine then called containerd, which called runc — an extra hop and an extra daemon. Dockershim was deprecated in 1.20 and removed in **1.24**. Practical consequences to be precise about: - **Images are unaffected.** Docker builds OCI-compliant images and containerd runs them. "Docker deprecated" never meant "Docker images stop working". - **Node tooling changes.** `docker ps` no longer shows Kubernetes containers; use `crictl ps`, `crictl images`, `crictl logs`. - **Anything mounting the Docker socket** — in-cluster image builders, some logging or security agents — breaks and needs a different approach. - Third-party shims (cri-dockerd) exist if you truly must keep Docker Engine as the runtime. ## Failure modes you should recognize - A wrong or missing container-runtime endpoint means the kubelet cannot start pods; the node goes NotReady with a runtime error in `kubectl describe node`. - containerd down: the kubelet reports the container runtime is down; already-running containers survive for a while thanks to the shims, but nothing new starts. - Image pull errors surface as CRI ImageService errors and appear as `ErrImagePull` or `ImagePullBackOff`, including registry auth and mirror misconfiguration in containerd's config. - Sandbox creation failing with a CNI error means networking, not the runtime, is broken — the Pod stays in `ContainerCreating`.

  • Kubernetes removed dockershim in 1.24. Do images built with `docker build` still run?
    Yes. Docker produces OCI-compliant images and containerd runs OCI images, so build tooling is unaffected. What changed is the node runtime and its tooling: use crictl instead of docker on nodes, and anything that mounted the Docker socket inside the cluster needs reworking.
  • What is the pause container and why does each Pod have one?
    It is the sandbox container containerd creates to hold the Pod's shared namespaces — principally the network namespace — open. Because it owns the namespaces, individual application containers can crash and restart without the Pod losing its IP or its shared IPC context. It runs a trivial process that sleeps and reaps orphaned children.

CRI is a power-socket standard: the kubelet plugs in without knowing whether the wiring behind it is containerd, CRI-O, or something exotic.

saying these in an interview costs you the question

  • Saying Kubernetes 1.24 stopped supporting Docker-built images
  • Thinking CRI is a network API rather than a local gRPC socket
  • Claiming the kubelet calls runc directly
  • Not knowing the pod sandbox exists and assuming each container gets its own IP
  • Using `docker ps` to debug containers on a containerd node

context