containerd vs Docker: Runtime Differences Explained - 夜莺博客

containerd vs Docker: Runtime Differences Explained

The question "containerd or Docker" confuses two different layers. Docker is a developer-facing platform — CLI, build engine, Compose, registry plumbing — while containerd is the container runtime that actually creates namespaces, mounts filesystems and executes processes. Kubernetes removed its Docker shim in 2022 for exactly that reason: it was talking to containerd through Docker anyway. This guide maps the stack, shows where the CLIs overlap and diverge, and covers the practical work of running both on the same host without breaking image availability or logging.

The stack, layer by layer

Docker CLI / Compose / BuildKit      <-- developer interface
                |
            dockerd (daemon: API, networks, volumes, build)
                |
            containerd  <-- container supervisor (images, snapshots, tasks)
                |
             runc  <-- OCI runtime, creates the process and namespaces
                |
        Linux kernel: cgroups, namespaces, overlayfs

containerd can be driven directly by ctr (low level, built for debugging) or nerdctl (Docker-compatible UX). Kubernetes uses a CRI plugin inside containerd, which is why a cluster needs nothing else. If you install Docker Desktop or the Docker engine on a Kubernetes node, containerd is already running underneath — you have two front doors to one runtime.

Commands side by side

Task Docker nerdctl (containerd)
List containers docker ps -a nerdctl ps -a
Run interactively docker run -it --rm alpine sh nerdctl run -it --rm alpine sh
Inspect namespaces docker inspect nerdctl inspect / ctr -n k8s.io c ls
Images docker images nerdctl images
Namespaces Not applicable nerdctl --namespace k8s.io ps
System info docker info ctr version, nerdctl info
# containerd namespaces are a container-isolation feature, not kernel namespaces
sudo nerdctl namespace list
sudo ctr namespace list

# Kubernetes containers live in the k8s.io namespace
sudo nerdctl --namespace k8s.io ps

# inspect a running pod's container from the node
sudo ctr -n k8s.io containers list | grep web
sudo ctr -n k8s.io tasks list

The namespace detail catches people out: images pulled by Kubernetes are not visible to nerdctl images unless you pass --namespace k8s.io, and vice versa. If you pre-pull images with ctr into the wrong namespace, the kubelet carefully pulls them again while the originals sit on disk.

Image storage and the overlay2 question

# where the disk actually goes on a containerd node
sudo du -sh /var/lib/containerd
sudo ctr -n k8s.io images list | wc -l
sudo crictl images | head
sudo crictl rmi --prune          # remove unused images

# Docker host equivalent
sudo du -sh /var/lib/docker
docker system df
docker image prune -a

A node that runs out of inodes or disk is almost always holding unused images. Kubernetes has imagefs garbage collection thresholds in the kubelet config, and Docker has its own — but neither knows about the other, so a host running both will fill up twice as fast.

Logging and debugging differences

# Docker: logs through the daemon
docker logs -f mycontainer
docker stats

# containerd: logs on disk, read with crictl or journalctl
sudo crictl logs -f <container-id>
sudo journalctl -u containerd -f
sudo ctr -n k8s.io tasks exec --exec-id debug <container> sh

# namespace-level troubleshooting
sudo lsns -p $(pgrep -f "nginx")
sudo nsenter -t <pid> -n ip addr

For deep debugging, jumping into a container's network namespace with nsenter is the same regardless of runtime — and it is the fastest way to prove whether a connectivity problem lives inside the container or in the host. The packet-level workflow is covered in the tcpdump examples guide.

Running Docker and containerd on one host

  1. Decide which runtime owns port publishing. Two daemons competing for iptables rules produce rules that delete each other.
  2. Give each runtime its own data root on separate filesystems if possible. Sharing /var/lib trees between Docker and containerd is a recipe for disk exhaustion and confusing cleanup.
  3. Pin the CNI configuration. Docker creates docker0 and its own bridge; containerd-based Kubernetes uses CNI plugins. Firewall zone definitions must cover both.
  4. Monitor both. Add node_filesystem_avail_bytes alerts per mount and per runtime root, not one generic disk alert.
  5. Prefer one runtime per role: Kubernetes nodes run containerd; a single Docker host for a Compose application runs Docker. Mixing is supportable but rarely worth it. If you do run container orchestration with Docker, the networking model differs substantially — see the Swarm overlay networking guide and the Docker networking drivers comparison.

Migrating a Docker workload to containerd/Kubernetes

# 1. export the effective container configuration from Docker
docker inspect mycontainer | jq '.[0] | {Image, Config, HostConfig}'

# 2. translate to a manifest; verify the image digest, not just the tag
docker image inspect myapp:1.4 --format '{{index .RepoDigests 0}}'
kubectl run myapp --image=registry.example.com/myapp:1.4 --dry-run=client -o yaml > myapp.yaml

# 3. confirm the node runtime and cgroup driver before applying
sudo ctr version
cat /etc/containerd/config.toml | grep -A3 SystemdCgroup
kubectl get nodes -o wide

Match the cgroup driver end to end: containerd, the kubelet and the container must all agree on systemd versus cgroupfs, or pods fail with resource errors that look like scheduler problems. This is the single most common migration fault. Clean up stale image layers as you go with the overlay2 disk cleanup procedure so a node does not fill mid-cutover.

Best practices

  • Pin images by digest in production manifests; tags move, digests do not.
  • Run one runtime per node role and document it in your build images.
  • Alert on image filesystem usage, not just root filesystem usage.
  • Use crictl on Kubernetes nodes — it speaks the CRI, so it works with containerd and CRI-O alike, unlike docker ps.
  • Keep a tested rollback path for runtime upgrades: a node whose containerd fails to start after an upgrade takes every pod with it, so upgrade one node at a time with the node cordoned. Lightweight distributions such as a k3s control plane bundle their runtime, which changes the upgrade mechanics but not the concepts.

原文链接:https://containerd.io/docs/