Technical Series: Understanding Containers — Part 2

Omolola Olamide
3 min readJan 13, 2022

--

Photo by Tri Eptaroka Mardiana on Unsplash

This post is a sequel to my last article on containers. I kept digging deeper into the world of containers, and I discovered that containers are not only built on Linux kernel primitives. There are ways to run VMs as containers, and we have windows containers. So, how can we define containers?

According to Open Container Initiative(OCI), a Standard Container (referred to in this article simply as a container) is

a unit of software delivery

In other words, a container is an isolated environment that encloses a software component with all its dependencies. Containers can be run by any compliant runtime regardless of its underlying system architecture.

Figure 1: Container Management Architecture

Starting a container requires setting up the environment by creating namespaces, cgroups, and other required tasks. A container runtime is a tool that understands how to set up the container environment and start up the software component within the container. Since the operations required to set up the container environment are standardized by OCI, any OCI compliant runtime can replace another. A common OCI compliant runtime is runc. Runc is a command-line tool and is also used by docker for creating containers. Meaning if you have docker installed on your Linux box, you probably have runc too.

Figure 2: Creating a container with Runc

Any OCI compliant runtime requires a config file, i.e., config.json, and an OCI bundle containing the filesystem for the container. The config file describes the properties of the container.

From the code in Figure 2, we created the OCI bundle from the busybox docker image and generated a basic config file with runc spec in the code above. Even though we can make our containers directly with OCI runtimes, it is rarely advisable. Higher-level runtimes (that I referred to as “container manager” in Figure 1) and tools focused on developer experience, e.g., Docker, are preferred to create containers.

Moving on, we observe in Figure 1 that the container manager (a higher level runtime that wraps the low-level OCI runtime) communicates with the low-level OCI runtime and the container through the container shim. While runc (or any low-level OCI runtime for that matter) can start and create a container, they often only live as long as the container is in operation. Sometimes, runc exits once it forks the container creation process when it is run in background mode. The fact that runc lives as long as the container or sometimes even shorter presents a dilemma. How can we manage containers effectively? What do we do if we need to restart a failed container? How can we automate the creation process and scale it to hundreds or thousands of containers? These high-level tasks led to the creation of container managers. A famous example is Containerd.

Containerd manages the lifecycle of containers, handles image management (push, pull) and many more tasks. Containerd communicates with containers and the OCI runtime through a container shim. The container shim is an interface that enables the container manager to operate independently of the containers it manages. This separation is helpful in the face of failure in the container manager. The container shim also keeps the container’s stdio pipe open in case of the container manager’s failure.

--

--

Omolola Olamide
Omolola Olamide

Written by Omolola Olamide

Christian | Systems Engineer | Entrepreneur | Writer (I write to glorify God!)

No responses yet