────────────────────────
Introduction
Lately I’ve been diving into the rabbit hole of cloud native landscape. I came across the topic of containers and container runtime and found myself trying to make sense of the 3237 different technologies working together. After getting a faint idea, I decided to make this blog post, which originated from my notes on the same topic. The purpose is to make sure I am able to refer to this in the future in case I get confused again (will probably happen in like 8 hours after posting this).
If you’re also trying to make sense of container runtimes and have the following terms running around in your brain;
- Container 🗃️
- Container Runtime 🏃🏾🏃🏾
- OCI / Open Container Initiative 🏛️
containerd
¿¿¿runc
¿¿¿
Then do read on. If you end up more confused than before, or if you find any issue with my understanding of the topic, feel free to yell at me on Twitter.
Glossary
To set proper context for container runtimes, it is best to go over a few things.
Container
A container is a set of compute constraints used to execute an application in. Think of it as an environment for executing processes where you can configure the isolation and the limitations of the process.
As you probably know, the purpose of a container is to encapsulate a small microservice or binary application along with all their dependencies to run it reliably anywhere, regardless of where you run it*.
*As long as the “where” you run it is an OCI compliant container runtime. Read on, there’s a logic to this jargon
OCI (Open Container Initiative)
The OCI is an open governance structure (formed by Docker, CoreOS etc.) to decide on container formats and runtimes. It basically develops specifications and standards adhering to containers which are to be followed to keep things uniform (and less confusing than it already is!).
It currently has two specifications;
-
Runtime specification to define the working and lifecycle of a container during runtime
-
Example, it defines how exactly does your docker container work when you run
docker container create golang:alpine
-
In the specification, you can see that an OCI-spec container needs to have some properties.
-
-
Image specification to define the standard format of a container image.
So if you one day decided to make a docker killer and next-gen Kubernetes killer container orchestration daemon/client, you would have to adhere to the OCI specifications.
Container Runtimes (and the explanation to the container architecture)
Container runtime is the software component that executes the containerized applications on a host system. They are responsible for loading the images, monitoring, maintaining and isolating the system resources. Containers need to be launched in a standardized way no matter which platform they are running on.
Examples of some container runtimes are CRI-O, Docker, containerd and runC.
If you start reading up about all these container runtimes, you will notice some things in common.
Both, containerd
and cri-o
list OCI specification through using runC
. But, aren’t they container runtimes too? And isn’t runC
another container runtime?
There is a logic to this confusion too.
My explanation
- OCI defines how a container should be set up, run, executed, and what comprises in its state etc.
- runC is the lightweight and universal container runtime which handles all the low-level details of spawning a container and takes care of the Linux primitives like cgroups and namespaces which handle the system and resource isolation of a container.
- cri-o or containerd manage the lifecycle of a container starting from execution from an image, supervision, networking and killing the container.
- Extra - Docker (or more precisely
dockerd
) is another abstraction ofcontainerd
*. It manages even more things like orchestration, scaling, volumes etc.
{{< figure src=“https://i.stack.imgur.com/5aXF6.png” caption=“Source: Stack Overflow”>}}
{{< figure src=“https://www.tutorialworks.com/assets/images/container-ecosystem.drawio.png” caption=“Source: Tutorialworks” >}}
*
containerd
was developed by Docker and then donated to CNCF a while back and moreover, Kubernetes is deprecating Docker because usingcontainerd
directly is more efficient thandockerd
.
Getting some hands dirty with containerd
Okay! That was a lot of confusing theory. Fingers crossed I haven’t added to your confusion, and you’re only as confused as you were before, in which case, let’s get some hands dirty with containerd
.
If you have installed docker on your system before, then you will also have installed the containerd
daemon, and it’s CLI tool ctr
. If not, then go ahead and install containerd
on your system.
Go ahead and start containerd
daemon and leave it running. Let’s use ctr
to pull an image, start and manage containers
containerd
with ctr
Playing around with images
Just like with docker (albeit with more steps) you can pull an image, remove it or even import one.
Go ahead and pull an image;
You can list the images using;
If you want, you can delete the images too. Just use the rm
command;
If you have an existing Dockerfile, you can import that to containerd
too! First, go and save the image as a .tar
file.
Then go ahead and import it with;
Playing around with containers
To create a container, the container create
command is used;
Now that we have a container, we have to execute it. Note that we have just created the container and have to execute the tasks and processes inside it. As of now, it’s just an isolated and restricted box without a process running inside it. Hence, a container isn’t a process.
To “run” the container like you would in docker using docker run
, we start a task;
The result is that we see docker’s default hello-world container running! Notice how many more steps we had to follow to get this hello-world image, and then running it as compared to docker. That’s because as you go lower the abstraction layer you get more power but also more responsibility (of doing more work to achieve the same thing).
You can even get a shell if the container supports it.
As a bonus, go to the terminal where your containerd
was running to see its debug outputs while you create and execute containers.
To clean up, use the ctr container rm
command to delete the containers.
containerd
With Golang
Given containerd
is actually abstracted further and not directly used, it’s quite interesting to see how it’s done. Here we will see how to use containerd
programmatically using its Go package and APIs
If you visit the getting started page of containerd
, it gives you sample code to use the daemon. I’m not sure if I can do a better job of explaining the code better than them, but I am still showing my version of a basic getting started code. I added a function to view the existing images and print their names.
Wrapping up
So we’ve gone through understanding what a container runtime is, what containerd
does and where it plugs in. We messed around with it through ctr
and also got a brief idea how it’s abstracted and used by tools like docker etc.
Here is my rough understanding of using containerd
.
I hope I was moderately helpful in explaining or going through these concepts and haven’t confused you further. If you’re also studying cloud native technologies or just want to discuss security and tech with me, feel free to contact me!
References
I wouldn’t have been able to get a clear view of containers if it weren’t for Ivan Velichko’s blogs. If you’re like me, trying to make sense of the world of Containers, Kubernetes and Networking, do check out his blogs!
The containerd
getting started page and documentation is priceless in understanding its programmatic use.
────────────────────────