Container Deep Dive 2: Container Engines
Tags: docker, kubernetes, beginners, devops
Published: 2022-12-01 17:35:14


In the last post of this series we had a closer look at Container Runtimes.  
However, for us developers interacting with Container Runtimes is tedious and not straightforward since they have not been designed to be consumed directly by the end-user.

This issue is solved by Container Engines!  
They provide convenient APIs for interacting with Container Runtimes without having to know all the low-level details.  
Furthermore, this abstraction allows using **different** Container Runtimes if necessary.

If you started a container on your local machine once, you already interacted with a Container Engine since Docker is one of the most popular engines out there.

The `docker` Command Line Interface (CLI) allows us to interact with the heart of the Container engine.  
In Docker terms called the Docker Daemon.  
This CLI provides a high level of abstraction for the end-user since it focuses on the **most** important parts of a container's life cycle.  
The CLI allows us to:

- start a new container
- stop a container
- restart a stopped container
- fetch a new image
- ... and much more

Based on that brief overview of functionality we can extract the main responsibilities of a Container Engine:

- Management of the container life cycle (forwarding proper commands to the underlying runtime)
- Pulling and pushing container images to image repositories
- Configuring storage for containers

Since we are now more familiar with the functionalities provided by a Container Engine, let's have a closer look at two very popular engines:

## Docker

The Docker Engine consists of the following parts:

- `dockerd` a long-running daemon process which acts as server. Also called Docker daemon
- the `docker` CLI which acts as a client and communicates with `dockerd` via the `Docker Engine API`

The Docker daemon comes with it's one CLI called [`dockerd`](https://docs.docker.com/engine/reference/commandline/dockerd/).  
It can be configured either by passing flags to `dockerd` or a JSON configuration file.

Once a request from the `Docker Engine API` is received, the Docker daemon takes care of processing the request and reporting the status back to the `docker` CLI.  
Under the hood `containerd` is used for interacting with the underlying Container runtime (in this case `runc`).  
So the Docker daemon does not interact with the Container runtime directly but makes use of another abstraction layer on top of `runc`, called `containerd`.  
If you haven't heard about Container Runtimes, check out the first article of this series: [Container Runtimes](https://dev.to/tobhai/container-deep-dive-part-1-container-runtime-4n1g).

If you want to tinker with the `Docker Engine API` you can use the API directly via HTTP or integrate a provided SDK into your code.  
See [API docs](https://docs.docker.com/engine/api/) for more information.

The client-server architecture of Docker allows the `docker` CLI to easily interact with remote Docker daemons as well. The Docker daemon process can listen to requests from the `Docker Engine API` via three different socket types:

- unix
- fd
- tcp

TCP sockets enable the possibility for remote connections. The other two socket types are bound to the host and cannot be access from a remote machine.

Docker is a prominent example of a Container Engine. It gained a lot of popularity since the initial release in 2013.  
However, with the rise of Kubernetes another popular engine was born.  
Let's have a look at `CRIO-O`.

## CRI-O

> The [CRI-O](http://cri-o.io/) container engine provides a stable, more secure, and performant platform for running [Open Container Initiative](https://www.opencontainers.org/) (OCI) compatible runtimes.  
> CRI-Os purpose is to be the container engine that implements the Kubernetes Container Runtime Interface (CRI) for OpenShift Container Platform and Kubernetes, replacing the Docker service.  
> [Source](https://docs.openshift.com/container-platform/3.11/crio/crio_runtime.html)

The CRI-O container engine can launch a container by using an OCI-compliant Container Runtime like `runc`.

It uses `containers/image` library to pull images from a registry. Therefore, it supports Docker schema2/version 1 and schema2/version2.

Although it is not really necessary to interact with CRI-O directly, CRI-O comes with a bunch of CLIs and tools:

- `crictl`: allows debugging issue with the underlying runtime without having to set up other Kubernetes components
- `runc`: CLI for interacting directly with the runtime
- `podman`: offers the same command-line features as the `docker` CLI and additional features on top of it
- `buildah`: allows building of OCI images **with** and **without** a Dockerfile
- `skopeo`: helps with the management of container images

For more information regarding the bundled tools see: [CRI tools](https://github.com/containers/buildah/tree/main/docs/containertools).

Why was `CRI-O` created?  
`CRI-O` was started by Red Hat, with the goal of decoupling Kubernetes from Docker.  
It is now a project of the Cloud Native Computing Foundation.  
With it, it is possible to use any OCI compliant Container Runtime in Kubernetes.  
Since both `containerd` and `CRI-O` implement the Kubernetes Container Runtime Interface both can be used easily in Kubernetes.

## Wrap up

Let's summarize what was covered in this article:

First, we had a look at the responsibilities of a Container Engine.  
Then we had a closer look at how the Docker Container Engine works under the hood.  
Last but not least, `CRI-O` was introduced as an alternative to Docker.

Cover photo by <a href="https://unsplash.com/@garett3?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Garett Mizunaka</a> on <a href="https://unsplash.com/@garett3?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>