Kubernetes 1.24, scheduled for early May 2022 (it slipped from mid-April), removes dockershim, the built-in adapter that let the kubelet drive Docker Engine. Clusters whose nodes still use Docker Engine as the container runtime cannot simply upgrade to 1.24. This post is a migration plan; the background is in the Docker 20.10 post from December 2020.
Versions in use on this date. Kubernetes 1.23, containerd 1.6 (February 2022). Containers built with Docker are unaffected, since they are standard OCI images; only the node runtime and tools that depend on the Docker daemon change.
What depends on Docker today
flowchart TB
K[kubelet] --> Q{Runtime endpoint}
Q -->|before 1.24 with Docker| DS[dockershim in kubelet]
DS --> DD[dockerd] --> CD1[containerd] --> R1[runc]
Q -->|containerd| CD2[containerd CRI plugin] --> R2[runc]
Q -->|CRI-O| CRIO[CRI-O] --> R3[runc or crun]
Q -->|Docker kept via adapter| CRD[cri-dockerd by Mirantis] --> DD
X1[Pods mounting docker.sock] -.->|break| DD
X2[Node scripts using docker ps] -.->|use crictl| CD2
Step 1: find out where you stand
kubectl get nodes -o wide # CONTAINER-RUNTIME column
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'
Managed services:
- EKS: Amazon Linux 2 AMIs for Kubernetes 1.21 and older default to Docker; containerd is available through the bootstrap flag
--container-runtime containerd. AWS has said containerd will become the default AMI runtime for newer Kubernetes versions, so check the EKS documentation for your version before relying on the default. - GKE: node images based on containerd are the default and Docker-based node images are being retired; check your release channel.
- AKS: new node pools use containerd from Kubernetes 1.19 on.
- Self-managed (kubeadm, kops, k3s, RKE): look at
/var/run/dockershim.sockversus/run/containerd/containerd.sockin the kubelet flags,kubeadm-flags.env, or/var/lib/kubelet/config.yaml.
Step 2: find workloads that talk to Docker
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.volumes[]?.hostPath.path=="/var/run/docker.sock") | .metadata.namespace + "/" + .metadata.name'
kubectl get ds,deploy,sts -A -o yaml | grep -n -E 'docker\.sock|/var/lib/docker|docker\.io/library/docker' | head
Typical offenders and their fixes:
| Workload | Why it breaks | Fix |
|---|---|---|
| CI agents building images with the host’s Docker | No Docker socket on nodes | Kaniko, BuildKit, Buildah, or build outside the cluster |
Log shippers reading /var/lib/docker/containers |
Different log path and format (CRI) | Update to the CRI log path /var/log/pods and format |
| Monitoring agents using the Docker API | API not present | Upgrade to versions supporting CRI/containerd |
| Security or backup tools needing the socket | Same | Vendor update or a containerd-aware version |
Node scripts or runbooks using docker ps |
No Docker CLI | crictl ps, crictl logs, crictl images |
Step 3: migrate nodes to containerd
Do it node pool by node pool, like any OS upgrade.
On kubeadm clusters:
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data
sudo systemctl stop kubelet
sudo apt-get install -y containerd.io # from Docker's repository, or distro package
sudo mkdir -p /etc/containerd && containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml # match kubelet cgroup driver
sudo systemctl restart containerd
# point kubelet at containerd (kubeadm-flags.env or kubelet config):
# --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock
sudo systemctl start kubelet
kubectl uncordon worker-1
kubectl get node worker-1 -o wide
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps
Details that cause trouble: the cgroup driver must match between kubelet and runtime (systemd on systemd hosts), registry mirrors and credentials move into containerd’s configuration (config.toml), images already pulled by Docker are not shared, and containerd uses a different storage path. For cloud node groups, easier: create a new node group with a containerd-based AMI, cordon and drain the old one, and delete it.
Alternative: keep Docker with cri-dockerd
If you must keep Docker Engine on nodes (for example for tooling), install cri-dockerd from Mirantis, an out-of-tree adapter, and point the kubelet at unix:///run/cri-dockerd.sock. It is a bridge, not a long-term plan: you still carry two runtimes and extra components.
Step 4: test and roll out
- Build a staging cluster or node pool on containerd; run the whole test suite, including log collection and monitoring.
- Verify image pulls from every registry you use (private registries, mirrors,
imagePullSecrets). - Roll production node pools gradually, watching pod restarts and node readiness.
- Only then upgrade the control plane to 1.24, after the removed-API check.
Habits to build
- Use
crictlaliases and learn its parity with Docker commands (crictl ps,crictl logs,crictl inspect,crictl pull). - Treat the container runtime as an independent upgrade you can do ahead of Kubernetes upgrades.
- Keep a list of every component that needs host-level integration and test it with each Kubernetes release.