January 7, 2022·5 min read·engineeringkubernetescriruntimes

Dockershim removal exposes the runtime dependencies around Kubernetes

The January migration announcement keeps dockershim removal on the 1.24 schedule. The difficult boundary is between portable container workloads and tools that depend on Docker Engine.

Kubernetes has had a standard interface for container runtimes for years, yet removing its Docker adapter still requires a coordinated migration. That gap between an architectural interface and the surrounding operational dependencies explains the January announcement. An application image can remain portable while the tools used to build, inspect, or monitor it still depend on a particular daemon.

The January 7 announcement keeps dockershim removal targeted for Kubernetes 1.24. Its immediate contribution is a commitment to improve documentation and coordinate with dependent projects. It does not announce a completed migration guide. The distinction matters because the project is asking operators to prepare while acknowledging that some dependencies require work elsewhere in the ecosystem.

I support removing the special integration. Kubernetes is most useful to me when its controllers can operate against explicit interfaces, with implementation choices kept outside their reconciliation logic. That preference does not make a runtime replacement operationally trivial. It gives a reason to identify which assumptions belong to the interface and which have accumulated around an implementation.

Why the adapter exists

The history begins before the removal discussion. The original CRI introduction describes Docker and rkt as deeply integrated into kubelet through an internal interface. Adding another runtime required knowledge of kubelet internals and work inside Kubernetes. That arrangement made runtime development depend on the release and maintenance practices of another project.

The Container Runtime Interface changes where that integration happens. Kubelet acts as a gRPC client, and a runtime or its adapter implements the server. The protocol separates image operations from runtime operations. Pulling or inspecting an image belongs to ImageService. Creating a pod sandbox and controlling containers belongs to RuntimeService. Runtime developers can implement that contract without adding another deeply embedded integration to kubelet.

Docker Engine does not implement CRI itself. Dockershim supplies the translation that kubelet needs. The December 2020 explanation distinguishes Docker's broader developer tooling from the containerd runtime inside its stack. Kubernetes does not need every facility that makes Docker convenient for a person at a terminal.

This makes the removal narrower than abandoning Docker images, but broader than deleting an unused library. A cluster whose kubelet reaches Docker through dockershim has an active dependency on that adapter. Removing the adapter requires a supported replacement for that communication path. The surrounding Docker installation cannot supply the missing protocol merely because it can run containers independently.

Reconciliation still belongs to kubelet

CRI's design also explains why Kubernetes can change runtime implementations without handing its workload semantics to each runtime. The original proposal considered passing declarative Pod objects to the runtime. It rejected that approach because runtime implementations would then have to reproduce substantial kubelet behaviour. Pod features could also force changes throughout the runtime ecosystem.

Instead, kubelet retains responsibility for driving actual container state toward the declared workload state. Its calls to the runtime are imperative operations within that larger reconciliation process. A runtime creates and starts a container when requested. Kubelet decides how that operation fits the pod's restart policy and lifecycle.

The pod sandbox is another deliberate separation. CRI describes an environment for a pod without requiring every implementation to construct that environment identically. The original account discusses Linux namespaces and hypervisor-based isolation as different implementations. The contract lets kubelet request the environment while leaving runtime-specific construction outside its own code.

This is why the architectural argument for removal is persuasive. An abstraction offers less benefit when one implementation retains an exceptional maintenance path in the consumer. Keeping Docker compatibility through special code inside kubelet also keeps responsibility for that code inside the Kubernetes project. Removing it makes runtime integration follow the boundary the project already designed.

Image portability does not imply daemon compatibility

The practical confusion comes from treating a build artifact and the service that launches it as the same dependency. The December 2020 explanation explicitly says Docker-produced images continue to run with compatible runtimes. A Dockerfile describes how to construct an image. It does not require the receiving Kubernetes node to expose Docker Engine's API.

A build system that produces an image and pushes it to a registry therefore has a different relationship with this change from a pod that mounts the node's Docker socket. The former depends on the image format and registry exchange. The latter depends on a daemon reachable at a particular host path. Replacing the node runtime can preserve the first relationship while breaking the second.

The documented socket example is /var/run/docker.sock. A workload that expects that socket needs more than proof that its image starts under another runtime. Its operational purpose includes communicating with Docker. Calling the image portable does not establish that the daemon service exists on the replacement node.

Similar reasoning applies to node customizations. The 2020 explanation calls out runtime configuration, including logging, as something operators need to compare. Equivalent container execution does not prove equivalent configuration files or administrative commands. A migration description needs to account for the interfaces used by supporting software as well as the main application process.

I regard these dependencies as part of the cluster's declared operating environment. An undocumented host socket is still an interface that software depends on. Treating it explicitly makes the replacement understandable without pretending that every application needs to change its container build.

What the January commitment means

The January post reports more than 600 survey responses and acknowledges dependencies outside individual operators' control. Its promised work includes updating examples and tasks as well as producing migration guidance. That scope fits the problem. A technically correct runtime interface cannot update a tutorial that still teaches a Docker-specific assumption.

The announcement also says the project has already postponed removal once during 2021. As of this post, 1.24 remains the target, with April named in the announcement. That is a planned schedule, not evidence that a release has shipped. Operators can use the stated target to organize work without treating an expected release date as an accomplished event.

Kubernetes 1.23 retains dockershim, and the announcement states that the project will support that release for another year. It distinguishes upstream support from whatever longer period a vendor might offer. That gives operators a release-specific support fact, but it does not establish a universal extension for every managed service.

The immediate engineering task is therefore to describe the runtime path and its dependents accurately. A cluster already using a different CRI runtime has a different migration scope from one using Docker Engine through dockershim. A cluster with Docker-dependent build pods has additional work even if ordinary application images need no changes. Documentation that separates those cases lets operators assign work to the right software owners.