Container Runtimes in Kubernetes
Container runtimes are the software that actually runs containers. This guide covers containerd, CRI-O, Docker Engine, and how they integrate with Kubernetes through the Container Runtime Interface (CRI).
A container runtime is the software that actually runs containers. It's responsible for pulling images, creating namespaces, managing cgroups, and executing container processes. Kubernetes doesn't run containers directly—it delegates to a container runtime through the Container Runtime Interface (CRI).
The most common container runtimes for Kubernetes are containerd (the current default), CRI-O (designed specifically for Kubernetes), and Docker Engine (via cri-dockerd). Each has different trade-offs in terms of features, performance, and compatibility.
The Container Runtime Interface (CRI) is a plugin interface that allows Kubernetes to use different container runtimes without recompiling. CRI defines gRPC APIs for managing containers, images, and pods. Any runtime that implements CRI can be used with Kubernetes.
# CRI API operations
- RunPodSandbox / StopPodSandbox
- CreateContainer / StartContainer / StopContainer
- ListImages / PullImage / RemoveImage
- ContainerStatus / PodSandboxStatus
# Check which CRI runtime your node is using
kubectl get nodes -o wide
# Look at CONTAINER-RUNTIME column
# Check runtime on node
crictl info
crictl ps
crictl images
OCI defines standards for container formats and runtimes. Most container runtimes (containerd, CRI-O, Docker, runc) are OCI-compliant. The OCI runtime specification defines how to run a container (using runc as the reference implementation).
| Runtime | Maintainer | Default In | CRI Support |
|---|---|---|---|
| containerd | Cloud Native Computing Foundation (CNCF) | Kubernetes v1.24+ | Native |
| CRI-O | Red Hat / CNCF | OpenShift, Fedora CoreOS | Native |
| Docker Engine | Docker Inc | Older K8s versions (via dockershim) | Via cri-dockerd adapter |
Containerd is a high-level container runtime that manages the complete container lifecycle. It's the default runtime for Kubernetes (v1.24 and later). Containerd is used by Docker (under the hood), and by many cloud providers. It's fast, stable, and production-proven at massive scale.
Key features: OCI image format support, container lifecycle management, image pull/push, network management (CNI integration), and snapshot management. Containerd is CNCF graduated and used by AWS EKS, Google GKE, and Azure AKS.
# Check containerd version
containerd --version
ctr version
# List containers with ctr
ctr containers list
# List images with ctr
ctr images list
# Namespaces in containerd
ctr namespace ls
ctr -n k8s.io containers list
# Check containerd configuration
cat /etc/containerd/config.toml
# Restart containerd
sudo systemctl restart containerd
CRI-O is a lightweight container runtime specifically designed for Kubernetes. It implements the CRI directly and uses OCI-compliant runtimes (like runc) underneath. CRI-O is developed by Red Hat and is the default runtime for OpenShift and Fedora CoreOS.
Key features: Native CRI implementation (no adapter needed), minimal footprint, strong focus on Kubernetes integration, and support for multiple OCI runtimes (runc, kata, gVisor).
# Check CRI-O version
crio --version
runc --version
# List containers with crictl
crictl ps
crictl images
# Check CRI-O configuration
cat /etc/crio/crio.conf
# Restart CRI-O
sudo systemctl restart crio
# View CRI-O logs
journalctl -u crio -f
Docker Engine was the original runtime for Kubernetes. In Kubernetes v1.20, dockershim (the Docker CRI adapter) was deprecated, and in v1.24 it was removed. To continue using Docker Engine, you need cri-dockerd, which translates CRI calls to Docker API calls.
While still possible, using Docker Engine with Kubernetes is no longer recommended for new clusters. It adds an extra layer (cri-dockerd) and has performance overhead compared to containerd or CRI-O.
# Install cri-dockerd
git clone https://github.com/Mirantis/cri-dockerd.git
cd cri-dockerd
make
sudo make install
# Start cri-dockerd service
sudo systemctl start cri-docker
# Check kubelet config to use cri-dockerd
# /var/lib/kubelet/kubeadm-flags.env
# --container-runtime=remote
# --container-runtime-endpoint=unix:///var/run/cri-dockerd.sock
# Check Docker version
docker version
# Check cri-dockerd logs
journalctl -u cri-docker -f
RuntimeClass allows you to use different container runtimes for different pods in the same cluster. This is useful for running specialized workloads (like Kata Containers or gVisor) alongside standard containers.
# Define RuntimeClass for Kata Containers
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
# Define RuntimeClass for gVisor
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
# Use RuntimeClass in pod
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
runtimeClassName: gvisor
containers:
- name: app
image: nginx
# List available RuntimeClasses
kubectl get runtimeclass
- Use containerd for most clusters - It's the default, mature, and supported by all major cloud providers.
- Use CRI-O for OpenShift or RHEL environments - Tight integration with Red Hat ecosystem, lightweight, and Kubernetes-native.
- Avoid Docker Engine for new clusters - Legacy option. Use containerd instead.
- Use RuntimeClass for special isolation - Kata Containers or gVisor for multi-tenant security.
# Check nodes for container runtime
kubectl get nodes -o wide
# Check kubelet logs
journalctl -u kubelet | grep -i "container runtime"
# On node: check containerd
sudo systemctl status containerd
crictl info
# On node: check CRI-O
sudo systemctl status crio
# On node: check Docker
sudo systemctl status docker
If you're still using Docker Engine with Kubernetes, plan to migrate to containerd. Here's a migration plan:
# Step 1: Drain the node
kubectl drain node-name --ignore-daemonsets
# Step 2: Stop kubelet
systemctl stop kubelet
# Step 3: Install containerd
apt-get update && apt-get install -y containerd
# Step 4: Configure containerd
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
systemctl restart containerd
# Step 5: Configure kubelet to use containerd
# Edit /var/lib/kubelet/kubeadm-flags.env
# Add: --container-runtime=remote
# Add: --container-runtime-endpoint=unix:///run/containerd/containerd.sock
# Step 6: Restart kubelet
systemctl start kubelet
# Step 7: Uncordon the node
kubectl uncordon node-name
Container runtimes are the foundation of Kubernetes. Choose containerd for most clusters, CRI-O for Red Hat environments, and use RuntimeClass for special isolation needs.