containerd’s CRI plugin is how Kubernetes actually talks to the container runtime to get pods running.

Let’s see it in action. Imagine we have a Kubernetes cluster and we want to deploy a simple Nginx pod.

First, on our Kubernetes node, containerd needs to be running and configured. A typical containerd configuration might look something like this in /etc/containerd/config.toml:

disabled_plugins = ["cri"]

[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "registry.k8s.io/pause:3.9"
  [plugins."io.containerd.grpc.v1.cri".containerd]
    snapshotter = "overlayfs"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
        runtime_type = "io.containerd.runc.v2"
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
          SystemdCgroup = true

Notice the disabled_plugins = ["cri"] line. If this is present, the CRI plugin is disabled, and Kubernetes won’t be able to use containerd. To enable it, we’d remove that line or comment it out.

The sandbox_image is crucial. It’s a minimal container that containerd uses to set up the network namespace and other basic infrastructure for a pod before any of the pod’s actual containers are started. Kubernetes expects this to be available. If it’s not, pods will fail to start with errors related to sandbox creation.

The snapshotter (overlayfs is common) dictates how container images are stored and layered on disk. Different snapshotters have different performance characteristics and storage requirements.

The runtimes section specifies the actual container runtime containerd will use. io.containerd.runc.v2 is the default and most common. SystemdCgroup = true tells containerd to use systemd for cgroup management, which is how Kubernetes isolates resources for containers.

When kubelet on the node starts, it looks for the CRI socket, typically at /run/containerd/containerd.sock. It then uses this socket to communicate with containerd’s CRI plugin.

Here’s a simplified flow of what happens when you kubectl apply a pod definition:

  1. kubelet receives the pod spec: The Kubernetes control plane sends the pod definition to the kubelet on the designated node.
  2. kubelet calls containerd CRI: kubelet serializes the pod spec into the CRI API format and sends it over the socket to containerd’s CRI plugin.
  3. containerd creates sandbox: The CRI plugin instructs containerd to pull the sandbox_image (e.g., registry.k8s.io/pause:3.9) and create a sandbox container. This sandbox container establishes the pod’s network namespace, PID namespace, etc.
  4. containerd creates pod containers: For each container defined in the pod spec, the CRI plugin tells containerd to pull the container image, create the container within the sandbox’s namespaces, and start it.
  5. containerd reports status: containerd continuously monitors the state of the sandbox and its containers and reports status updates back to kubelet via the CRI API. kubelet then updates the pod’s status in the Kubernetes API server.

If you want to see containerd’s CRI endpoint, you can run sudo ctr plugins ls and look for io.containerd.grpc.v1.cri.

The most surprising truth about the CRI plugin is that it’s not just a simple translator; it’s a sophisticated adapter that bridges the high-level abstractions of Kubernetes with the low-level realities of container execution, managing everything from image lifecycle to network plumbing and resource isolation.

The core of its configuration lies in mapping Kubernetes concepts like Pods and Containers to containerd’s internal objects and operations. It handles image pulling, container creation, starting, stopping, and status reporting, all while adhering to the CRI specification.

When troubleshooting, the crictl tool is your best friend. You can use it to directly interact with the CRI runtime. For example, sudo crictl ps -a will list all containers managed by the CRI runtime, including those in a PodSandbox state. If you see a pod stuck in ContainerCreating, sudo crictl logs <container-id> or sudo crictl inspect <container-id> can give you low-level details.

The plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options.SystemdCgroup setting is a prime example of how Kubernetes’ resource management (like CPU/memory limits) is directly translated into systemd cgroup configurations, ensuring that containers are properly constrained. If this is false and you’re using systemd, you might see resource limits ignored or misapplied.

The next big hurdle is understanding how containerd’s network plugins (like veth pairs or CNI plugins) integrate with the CRI to provide pod networking.

Want structured learning?

Take the full Containerd course →