Learnitweb

The Docker Engine

1. Introduction

In this tutorial, we’ll explore the inner workings of the Docker Engine.

2. Docker Engine

Docker Engine is an open-source containerization platform used for building and packaging applications into containers. It operates as a client-server application consisting of the following components:

  • A server with a long-running daemon process dockerd.
  • APIs which specify interfaces that programs can use to talk to and instruct the Docker daemon.
  • A command line interface (CLI) client docker.

The Docker engine is the fundamental software responsible for running and managing containers. It is commonly referred to as just Docker. The Docker engine is designed with a modular structure, composed of various small, specialized components that work together to create and run containers – Docker daemon, the build system, containerd, runc and various plugins for networking and volumes. A majority of these components are derived from the Moby project (https://mobyproject.org/) and follow open standards, such as those defined by the Open Container Initiative (OCI).

Initially, when the Docker was created, the Docker engine had two major components:

  • The Docker daemon (sometimes referred to as just “the daemon”)
  • LXC

The Docker engine was later restructured, with all the container execution and runtime code entirely removed from the daemon. This code was refactored into smaller, specialized tools, streamlining the overall architecture. All Docker versions since 2016 implement the OCI specifications.

3. runc

runc is a lightweight, low-level container runtime in Docker that is responsible for executing containers. As mentioned earlier, runc is the reference implementation of the OCI container-runtime-spec. Docker, Inc. played a key role in shaping the specification and developing runc. At its core, runc is a minimal, lightweight CLI wrapper around libcontainer. The primary function of runc is to create containers. It can also be used independently of Docker to interact directly with containers, but it’s a very basic, low-level tool.

4. containerd

As part of the initiative to simplify the Docker daemon, all container execution logic was extracted and restructured into a new tool called containerd (pronounced “container-dee”). containerd was originally developed by Docker, Inc. and donated to the Cloud Native Computing Foundation (CNCF).

The primary role of containerd is to handle container lifecycle operations, including starting, stopping, pausing, and removing containers. This refactoring aimed to make Docker’s architecture more modular by delegating container management to a dedicated tool.
containerd is available as a daemon for both Linux and Windows, and Docker has utilized it on Linux since version 1.11. Within the Docker engine stack, containerd sits between the Docker daemon and runc, functioning at the OCI layer.

Initially, containerd was designed to be small, lightweight, and focused solely on managing container lifecycle operations, such as starting, stopping, and removing containers. However, over time, it has expanded to take on additional responsibilities, including handling image pulls, managing volumes, and overseeing network operations.

The benefit of creating such decoupled architecture is that it is possible to maintain and update daemon without affecting containers. If the container runtime logic is implemented in the daemon then starting and stopping daemon will kill the containers.

5. Process of starting a new container

The common way of starting container is using the docker run command with Docker CLI. The following command will start a new container based on the ubuntu:latest image.

C:\>docker run -it ubuntu:latest /bin/bash
root@8ea5ab101534:/#

The process to create a new container can be summarized in following steps:

  • When you enter commands into the Docker CLI, the Docker client translates them into the corresponding API payload and sends them via a POST request to the API endpoint provided by the Docker daemon.
  • The API is implemented within the Docker daemon and can be accessed through either a local socket or over a network connection. On Linux, the socket is located at /var/run/docker.sock, while on Windows, it is available at \pipe\docker_engine.
  • Once the daemon receives the command to create a new container, it forwards that request to containerd. It’s important to note that the daemon does not have any code for creating containers. The daemon interacts with containerd through a CRUD-style API using gRPC for communication. containerd cannot actually create a container. It converts the required Docker image into an OCI bundle and tells runc to use this to create a new container.
  • runc interacts with the OS kernel to gather all the necessary components for creating a container, such as namespaces and cgroups. Once the container process is initiated as a child process of runc, it will exit immediately after the process starts.

In Linux, these are implemented as binaries:

  • /usr/bin/dockerd (the Docker daemon)
  • /usr/bin/containerd
  • /usr/bin/containerd-shim-runc-v2
  • /usr/bin/runc

6. shim

A shim is a lightweight process that acts as an intermediary between the container runtime (runc) and the container’s main process. containerd forks a new instance of runc for every container it creates. Once each container is created, the runc process exits. This allows us to run hundreds of containers without needing to launch hundreds of instances of runc. When the parent runc process of a container exits, the corresponding containerd-shim process takes over as the new parent of that container. The shim serves several important functions:

  • Isolation: It allows the container process to run independently from the container runtime, ensuring that the runtime can exit or restart without affecting the running container.
  • Management: The shim manages the lifecycle of the container process, handling tasks such as starting, stopping, and monitoring it.
  • Input/Output: It can also manage the standard input, output, and error streams for the container, facilitating communication between the host and the container.
  • Reaping Zombies: The shim ensures that the container’s main process is properly reaped after it terminates, preventing “zombie” processes from accumulating.

7. Conclusion

In conclusion, the Docker Engine serves as the core component of Docker’s containerization platform, enabling the creation, management, and execution of containers. Its modular design leverages several specialized tools like runc, containerd, and the containerd-shim, which work together to streamline container operations while ensuring efficiency and scalability. By delegating container lifecycle management to containerd and runtime tasks to runc, Docker achieves a flexible and high-performing architecture. Understanding the role of each component helps in optimizing and troubleshooting Docker environments, making it a powerful tool for developers and system administrators alike.