Introduction to Containers

What Are Containers?

They start life as an “image”: all the files and configuration needed to do their assigned task. When an image is “run”, the files and configuration are copied into a separated sandbox and system resources allocated to the task. This is the container.

Schematic diagram showing links between images, container, container runtime and host computer.

The container shares the operating system kernel with all the other containers and any other processes running on the local host computer.

This contrasts with virtual machines (VMs). Like with containers, the VM host also has a shared kernel but each VM has its own kernel and supporting processes. This increases the load in the host such that, for a given amount of computing power, you can get more work done using containers.

Schematic diagram showing VM operation demonstrating the additional overhead beyond that required for containers.

The drawback of containers is that the barriers between processing units are not as robust as the barriers between VMs so security needs to be considered carefully.

Why Use Containers?

Easy, fast deployment

Once you have prepared an image and made it accessible to a host, all you need to run the container is issue one command. This will download the image, create a container file system, allocate system resources and start the container. No need to step through install wizards or configure the host.

Build once, deploy many times

If you need to deploy software several times, such as during development of enterprise software, you just need to prepare the image once. Subsequent deployments reuse this effort and an instance can be refreshed (perhaps to reflect a bug fix) by simply deleting and recreating the container.

Scalable

If you need to increase through-put by a factor of ten, you can create ten or more containers (assuming your process is amenable to parallel processing) potentially on more than one host. Because containers cannot see the file-system, processes or memory of other containers, there should be no need to worry about interplay (you would however have to consider contention of external resources).

What do you need to run Containers?

Container runtime

The container runtime software sets up and controls the link between the container and the host operating system. It looks after allocating kernel namespaces and control groups and the detail of container operation. It is very much focused on the local machine and assumes the image being started is available locally.

One of the oldest container runtimes is lxc (Linux containers) which coalesced around 2008 and is still available today though not used that often.

The most widely used low-level container runtime is “runc”. The leading container software company Docker started out using LXC as their runtime but later wrote their own, known at the time as “libcontainer”. This, in time, became runc. It has been gifted to the open source community under the Open Container Initiative (OCI). OCI has defined a standard construction for images: the OCI image spec. It has also defined a standard for runtimes which defines how a runtime should interact with OCI images. “runc” is the reference implementation.

Another container runtime is “crun”. This is a port of runc written in the language “C” (runc is written in Golang). It is said to run faster and take up less memory but it is subject to the security uncertainties that led to runc being written in Golang in the first place.

Diagram comparing components of  Docker, Podman and lxc/lxd frameworks.

Container manager

Container managers (or container engines – the terms seem pretty fluid) take user requests, obtain images from remote servers if necessary and prepare the host for operation before invoking a container runtime.

Container runtimes are inward facing. They are all about local images, local kernels and local system calls. Container managers are outward looking. They connect to external container registries and download images. They interact with users.

Lxd is a kind of container manger for lxc. It interacts with users, prepares the container and hands over to lxc for ongoing operation.

There are two mainstream container managers, Docker and Podman, both end up using the low-level runc. Docker uses a high-level runtime daemon ‘containerd’ which, as the final ‘d’ indicates, is a daemon process that is constantly running. It takes commands from users, obtains images and invokes runc/crun. As a daemon, it runs on the host as user root. Processes started by containerd also run as root. That means the containers run on the host as root. This presents a security issue. If an attacker were to be able to break out of the container they would find themselves with root privileges on the host system. Not ideal. The alternative, Podman, is “daemonless”. As you expect there is no daemon. The user that requests a container is the user that executes the container on the host operating system. If your start a container with userX then someone breaking out the container will only have the privileges of userX. Not running as root does however have its drawbacks (e.g. podman containers can’t connect to TCP ports below 1024).

User Interaction

Containerd does not take commands direct from users. It needs another tool to deal with the user and pass on requests (there is a “ctr” interactive utility but it is a development tool/test harness and is not intended to control containers in production). This is where Docker currently sits. Docker’s tools take commands from users and, when it needs to interact with containers, passes them on to containerd.

Podman does take the commands direct from users and has been written to be a direct replacement for the docker command line tool. Podman therefore does the job of Docker command line and containerd but both approaches end up using runc/crun as low-level container runtime.

Image Build Tools

Where do the file-system images used to start your container come from?

Base Image: The simplest option is to use one of the pre-prepared ‘base images’ of a vanilla operating system (such as Fedora or Ubuntu or the more lightweight Alpine Linux). There are collections of these images available on the web in “registries” – remote stores of downloadable images.

Install and ‘Snapshot’: Base images do not have application software installed and so don’t do too much useful. You can download and run one of these containers, enter an interactive shell and then go through the install process for your desired application. Once the application is running as you like, you can then take a ‘snapshot’ of the container as your new master image. This master image can then be exported and moved to other hosts or uploaded to a image registry. Every step you take will be baked into the image including missteps.

Build Tools: You can also create a script which takes one of the base images and then applies the necessary steps to create your application container image. You need a special tool to take this script and action it.

  • Docker: the script is known as a “dockerfile” and the image is built using the “docker build” command.
  • Podman: to create the image without using Docker tools you can take the same dockerfile and use “buildah” to create the image using the command “buildah bud” (bud = build using dockerfile), Buildah has other options for creating images.

You can read more about using LXC/LXD here, read more about Docker here and read more about Podman here. The differences between Docker and Podman are outlined here.

Some other terms

Lightwight VM: Given the security issues around containerd, there is an alternative approach often called “lightweight VMs”. Examples are kata-containers or firecraker. Each ‘container’ has its own kernel to give an improved level of isolation but effort has been applied to keep the performance overhead to a minimum.

Kubernetes: Kubernetes is the de-facto standard ‘container orchestration’ software. You ask Kubernetes to run 10 of containerA and 10 of containerB and it will go off and do everything for you. It coordinates work across hosts but is expected to work with container runtimes like containerd to achieve its goals. It sits above the containerisation software and you can read more about Kubernetes here.

CRI-O: This is a high-level container runtime for use with Kubernetes. It does not take commands from users: there is no command line tool or external API. It only works within a Kubernetes implementation as the link to the low-level container runtime (e.g. runc) as an alternative to Docker or containerd.

Obsolete terms

  • runv: a container runtime. Last release 2017
  • railcar: a container runtime. Last release 2017
  • rkt / rocket: a container manager. Last release 2018