At this point of our #lci-series, we have seen how to hack around and create containers. In this post, we will introduce some order and standardization. We aimed to demonstrate what goes on under the hood, just one abstraction layer below -- a scratch on the surface.
Open Container Initiative (OCI)
The OCI was set up as an open governance structure to create open industry standards around container formats and runtimes. It develops specifications for standards on Operating System and application containers.
OCI has two specs, an Image spec and a Runtime spec. The Image spec defines the archive format of OCI container images, which consists of a manifest, an image index, a set of filesystem layers and a configuration. The Runtime spec specifies the configuration, execution environment and lifecycle of a container.
What is runc
?
runc
is a CLI tool for spawning and running containers according to the OCI specification. The diagram below demonstrates how runc
fits in the whole "container platform" ecosystem.
Create a container with runc
I know this is the moment you've been waiting for; let's get to it!
We will pick up from where we were in our last post. We used debootstrap
to get a full root filesystem that formed the basis of our container. We will re-use the same filesystem here.
If you are joining us mid-journey, take a glance at the previous post on chroot; it's a brief one.
So, I'm here where I have my ubuntufs
directory within $HOME
, looking like this and I'm on WSL2 on a Windows machine:
~/container-intro$ ls ubuntufs/
bin boot dev etc home lib lib32 lib64 libx32 media ...
We will first build runc
from sources. So, first start by installing Go on your Linux system.
$ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.2.linux-amd64.tar.gz
$ mkdir ~/.go # just my preference, you can name it as you wish
# update $PATH
# add this to your .bashrc (or equivalent file)
export PATH=$PATH:/usr/local/go/bin
# setup $GOPATH
export GOPATH=$HOME/.go
# chek that all is good
$ go version
I'm on a 1 version older Go by the time of this writing, but it shouldn't matter for now, at least in March 2023.
~/container-intro$ go version
go version go1.19.4 linux/amd64
Next, let's install runc
:
$ go install github.com/opencontainers/runc@latest
# check version, it's marked unknown since we installed
# the latest from "dev" branch
$ $GOPATH/bin/runc --version
runc version unknown
spec: 1.0.2-dev
go: go1.19.4
Generate a template runtime spec that will be used to spin off our container:
$ $GOPATH/bin/runc spec
A config.json
file will be generated with the spec. I'll draw your attention to fields like mounts
and linux.namespaces
. Do you see some familiar stuff from what we have covered in the series?
In the config.json
file, update root.path
to the name of your filesystem directory, it is ubuntufs
for my case.
Next, let's run a sample container:
$ sudo $GOPATH/bin/runc run my-cont
On a new tab on the host, you can run sudo $GOPATH/bin/runc run list
, you should see the container listed:
This gets you in the container shell the same way we've been doing in the previous posts, but this time, it's in a standard and spec-compliant way. Go ahead and play around with it, knock yourself off with things like ps
, htop
, test the limits and more.
So far, our filesystem is readonly, if you want to change that you can update
root.readonly
in theconfig.json
file.
Get more details on runc
here.
Sorry, no spoiler alerts on where our flight is headed next for this series. Maybe networking, maybe dissecting an OCI image, maybe explaining the whole trip of what happens when you run a command like
docker run
, how aboutdocker pull
? Stick with us!
For reading thus far, the custom dictates you have to pick your gift here ๐ Thanks for reading, see you next!