LearnKubernetes
Kubernetes1. Docker Foundations·ADVANCED·4 min read

Container Runtime (CRI)

Container Runtime Interface: containerd, CRI-O & the Runtime Landscape

TL;DR

  • CRI: gRPC API kubelet uses to talk to container runtimes (containerd, CRI-O, etc.) — decouples K8s from any single runtime.
  • Dockershim removal (K8s 1.24): kubelet talks directly to containerd via CRI instead of Docker Engine. Docker images still work (OCI format).
  • Common runtimes: containerd (most common, what Docker uses internally), CRI-O (minimal K8s-specific), gVisor/Kata (sandboxed).
  • RuntimeClass: selects alternative runtimes per-pod (e.g., gVisor for untrusted workloads).
  • crictl: low-level debugging tool to interact directly with CRI runtime on nodes.

Core Concepts

CRI is gRPC interface kubelet uses to any conformant runtime; implementations vary (containerd vs CRI-O), but interface is identical.


1. Why CRI Exists

Before CRI (pre-1.5), the kubelet had Docker-specific code hardwired in — adding support for any other runtime meant patching kubelet itself. CRI abstracted this into a generic gRPC interface (RuntimeService + ImageService), so kubelet talks to any CRI-compliant runtime identically:

kubelet ──gRPC (CRI)──▶ containerd / CRI-O / any CRI-compliant runtime


                        OCI Runtime (runc, gVisor, Kata)


                          Actual container process

2. What “Docker Support Was Removed” Actually Meant (v1.24+)

Docker Engine itself was never CRI-compliant — Kubernetes used a shim called dockershim (built into kubelet) to translate CRI calls into Docker Engine API calls. Maintaining this shim inside kubelet was a growing burden, and since Docker Engine internally already uses containerd anyway, Kubernetes removed dockershim in 1.24 and clusters moved to talk to containerd directly via native CRI — skipping the Docker Engine layer entirely.

Critical clarification for interviews: images built by docker build are still perfectly compatible and runnable — they’re standard OCI images, and OCI compliance was never Docker-specific. What changed is purely the runtime path kubelet uses to run them, not the image format itself. cri-dockerd exists as a community-maintained standalone shim for anyone who genuinely needs to keep using Docker Engine as the runtime.


3. Runtime Options Compared

Runtime Notes
containerd Most widely used today; graduated CNCF project; what Docker Engine itself uses internally
CRI-O Purpose-built minimal runtime specifically for Kubernetes, no extra Docker-compatible API surface
cri-dockerd Community shim for teams that specifically require Docker Engine underneath
gVisor / Kata Containers Sandboxed/VM-isolated runtimes (via RuntimeClass) for stronger workload isolation than standard runc
# Selecting an alternative runtime per-pod via RuntimeClass
apiVersion: v1
kind: Pod
metadata:
  name: sandboxed-workload
spec:
  runtimeClassName: gvisor
  containers:
  - name: untrusted-app
    image: myorg/app:1.0

4. Hands-on Command Cheat Sheet

Check which container runtime a node is using:

kubectl get node worker-1 -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}'

Interact with the runtime directly via crictl (works identically regardless of underlying CRI implementation):

crictl ps -a
crictl images
crictl logs <container-id>
crictl inspect <container-id>

Check kubelet’s configured CRI socket:

cat /var/lib/kubelet/kubeadm-flags.env | grep container-runtime-endpoint

List available RuntimeClasses:

kubectl get runtimeclass

5. Architectural Interview Q&A

Q: A junior engineer says “our images won’t run anymore since we removed Docker support.” What’s actually true here? A: This conflates two separate things: the container image format (OCI-compliant, produced identically by docker build, buildah, kaniko, or any other builder) versus the runtime that executes containers (which changed from talking to Docker Engine via dockershim to talking to containerd directly via native CRI). Any image built with docker build remains a completely standard OCI image and runs identically under containerd — nothing about image compatibility changed at all; only kubelet’s internal communication path to actually run that image changed.

Q: Why would a team deliberately choose CRI-O over containerd, given containerd is more widely adopted? A: CRI-O was purpose-built from day one exclusively for Kubernetes, with a minimal surface area and release cadence tightly aligned to Kubernetes versions themselves (a new CRI-O version ships alongside each Kubernetes minor release) — teams wanting the smallest possible attack surface and the tightest guaranteed compatibility with a specific Kubernetes version sometimes prefer this over containerd’s broader, more general-purpose scope (which also serves non-Kubernetes use cases like standalone Docker Engine and other container platforms).


6. Common Pitfalls

[!WARNING] Assuming crictl and docker Commands Are Interchangeable crictl intentionally has a different command surface than the docker CLI (no crictl run, no crictl build — it’s a low-level debugging tool for the CRI runtime, not a full container management CLI) and its output/behavior can differ subtly across different CRI implementations (containerd vs CRI-O). Engineers who reflexively reach for Docker CLI muscle memory (docker exec, docker cp) often get confused when crictl doesn’t offer equivalent conveniences — for interactive debugging inside a running container, kubectl exec is almost always the right tool, with crictl/ctr reserved for genuine runtime-level troubleshooting on the node itself.

🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
5 scenario questions on this topic
Take the quiz →
Related in 1. Docker Foundations
Docker Storage
INTERMEDIATE