Kubernetes is the de facto standard in container orchestration today, and Docker has played a pivotal role in containers from its inception to today, and is the default container engine in Kubernetes. However, in December 2020, the Kubernetes community decided to move forward with removing Dockershim-related code from its repositories, which was significant for both the Kubernetes and Docker communities.

I’m sure most developers have heard of Kubernetes and Docker and know that we can use Kubernetes to manage Docker containers, but they may not have heard of Dockershim, the Docker shim. As shown in the diagram above, the node agent Kubelet in Kubernetes needs to go through the community-maintained Dockershim first in order to access the services provided by Docker, and Dockershim will forward the request to the Docker service that manages the containers.

In fact, from the architecture diagram above, we can guess the reason why the Kubernetes community removed Dockershim from the code repository.

  • Kubernetes introduced the Container Runtime Interface (CRI) to isolate different container runtime implementation mechanisms, and the container orchestration system should not depend on a specific runtime implementation.
  • Docker does not support and does not intend to support Kubernetes’ CRI interface, requiring the Kubernetes community to maintain Dockershim in the repository.

Scalability

Kubernetes decouples container management from a specific runtime by introducing a new container runtime interface that no longer relies on a specific runtime implementation. Many open source projects provide an out-of-the-box experience in the early days to reduce the cost of use for users, and as the user community expands, more interfaces are introduced to meet more customized needs and provide greater scalability. kubernetes provides scalability for different modules through a series of interfaces as follows.

Kubernetes introduced interfaces such as CRD, CNI, CRI, and CSI in earlier versions, and only the scheduling framework for extending the scheduler is a relatively new feature in Kubernetes. Rather than analyzing the other interfaces and extensions, we will briefly introduce the container runtime interfaces here.

Kubernetes supported both rkt and Docker runtimes in the code repository as early as 1.3, but these codes made it very difficult to maintain Kubelet components, not only to maintain different runtimes, but also to access new runtimes; the Container Runtime Interface (CRI) is a new feature of Kubernetes. The Container Runtime Interface (CRI) is a new interface introduced in Kubernetes 1.5 that allows Kubelets to use a variety of container runtimes. In fact, the release of the CRI means that Kubernetes will definitely remove the Dockershim code from the repository.

CRI is a set of gRPC interfaces for managing container runtimes and images, and we can find two services in its definition, RuntimeService and ImageService, whose names nicely explain their respective roles.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
service RuntimeService {
    rpc Version(VersionRequest) returns (VersionResponse) {}

    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
    rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}

    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
    rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
    rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}
    rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}

    ...
}

service ImageService {
    rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
    rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
    rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
    rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
    rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
}

Anyone with a little knowledge of Kubernetes will find some familiar methods in the above definitions, all of which are interfaces that the container runtime needs to expose to the Kubelet. kubernetes implements the CRI shim as a gRPC server communicating with clients in the Kubelet, and all requests are forwarded to the container runtime for processing.

Declarative interfaces are very common in Kubernetes, and as a fan of declarative interfaces, it sounds ‘very weird’ that CRI doesn’t use them. However, the Kubernetes community considered having the container runtime reuse Pod resources so that the container runtime could implement different control logic to manage the containers, which would greatly simplify the interface between the Kubelet and the container runtime, but the community ultimately did not choose a declarative interface due to two considerations.

  1. all runtimes would need to re-implement the same logic to support many Pod-level features and mechanisms.
  2. the definition of Pods evolved very quickly at the time of CRI design, and functions such as initializing containers required runtime cooperation.

While the community ultimately chose an imperative interface for the CRI, Kubelet still ensures that the state of the Pod is constantly migrating to the desired state.

Incompatible Interfaces

Docker is more of a complex developer tool than a container runtime, providing a full set of features from build to run. Developers can quickly get started with Docker and run and manage a number of Docker containers locally, but container runtimes running in a cluster often don’t require such complex functionality, and Kubernetes requires only those interfaces defined in the CRI.

The official documentation for Docker is probably the thickness of a book, and I don’t believe any developer is skilled at using all the features that Docker offers. In addition, many of the new features proposed by the community, such as cgroups v2 and user namespaces, are not available in Dockershim, although Docker contains all the features needed for the CRI.

As Kubernetes is a relatively loose open source community, each member, especially the SIG members, only spends a limited amount of time on the open source community, and the sig-node that maintains the Kubelet is particularly busy, so many new features are put on hold because the maintainers don’t have enough energy, so since the Docker community doesn’t seem to have any intention of supporting So since the Docker community doesn’t seem to have any intention of supporting Kubernetes’ CRI interface, and maintaining Dockershim takes a lot of effort, it’s understandable why Kubernetes would remove Dockershim.

Summary

Kubernetes is a very mature project today, and its focus is gradually changing from providing better functionality to providing better scalability so that it can meet the customized business needs of different scenarios and different companies. We are able to experience the development and progress of the container field from this process.

The seeds of removing Docker were actually planted when CRI was released, and Dockershim was always a temporary decision taken by Kubernetes to gain market compatibility with Docker. Let’s revisit here two reasons why Kubernetes removed Docker support from its repositories.

  • Kubernetes introduced CRI in earlier versions to get rid of dependency on a specific container runtime dependency, shielding many of the underlying implementation details and allowing Kubernetes to focus more on container orchestration.
  • Docker itself is not compatible with the CRI interface, and there is no official intention to implement CRI, and also does not support some new requirements for containers, so the maintenance of Dockershim becomes a burden that the community wants to get rid of;.

At the end of the day, let’s look at some of the more open related issues, and interested readers can think carefully about the following questions.

  • What other modules in Kubernetes provide good scalability?
  • Besides CRI-O and Containerd mentioned in the article, what other container runtimes support CRI?