Source: Kata Containers | @Github
It’s not uncommon for me on any given day to get completely pulled in by some emerging (or legacy) alternative technology. My rabbit hole du jour ended up being Kata Containers. The original objective was only to make a few settings modifications, which quickly turned into me tanking my entire install.
…fantastic.
The Computer Gods had not shown on me this day, or so I thought. The steps to reconstructing my Kata Containers installation ended up being a huge learning experience in disguise. Who says it doesn’t pay to go off the beaten path.
If you’re not familiar with the technology, here’s a “little” introduction.
Primer
Kata Containers is an open source project supported by the collaborative efforts of the
OpenStack Foundation and Intel with the objective of creating a hybrid linux container technology; fusing the isolation and relative security of virtual machines with the lightweight profile and ephemeral instrumentation of linux containers. Built upon Intel’s earlier virtualization effort “Clear Containers” and
Hyper.sh
’s container runtime runv
, the Kata Containers technology is architected using QEMU as a hypervisor to spin up a lightweight virtual machine that within itself creates linux containers. The kata-runtime
(or kata shim in the case of Kubernetes) sits on the host and communicates with the kata-proxy
, which in turn issues commands through the hypervisor to the kata agent sitting on the kernel of the virtual machine. This chain of communication allows the host container management or orchestration software (Docker, Kubernetes, etc.) to create it’s linux containers within the VM environment.
There are several deployment options for Kata Containers, providing
automatic (via the kata-manager
),
manual and
scripted installation steps for
several linux distros as well as using
snap as a universal installation method. Unfortunately, my distro of choice wasn’t on the aformentioned list. So in lieu of installing the snap package I opted to do this on “
Dante must Die
” difficulty and build the whole shebang from source. Can you tell i’m not the biggest fan of snap packages yet?
Dependencies
As the
documentation states, first we gotta cover our dependencies, namely
go
, make
, git
and gcc
. All these should be easily obtainable using any distro’s package manager. Now, since we’re working with go
, we gotta set our $GOPATH
.
$ echo "export GOPATH=$HOME/go" >> ~/.zshrc
Feel free to replace .zshrc with .bashrc if you use bash
. The fish
users can just go…
$ set -x -U GOPATH $HOME/go
Chances are this setting was automatically set if you installed go
from your package manager. If you’re not sure just type…
$ echo $GOPATH
…and if /home/${user}/go
doesn’t pop on the terminal screen then follow the former instructions.
Installing the kata-runtime
, shim and kata-proxy
Next we’ll be pulling the kata-runtime
sources from github using the go get
command.
$ go get -d -u github.com/kata-containers/runtime
Now, you may get an error telling you ...no Go files in...
, but you can ignore that. Move to the source code’s directory and compile the kata-runtime
using make
.
$ cd $GOPATH/src/github.com/kata-containers/runtime $ make && sudo -E PATH=$PATH make install
This drops in the runtime as /usr/local/bin/kata-runtime
and the kata configuration file as /usr/share/defaults/kata-containers/configuration.toml
in your system. To make sure all is well run a quick
$ sudo kata-runtime kata-check
…to make sure were looking good. If not, you’ll get a brief error and explanation on your terminal. If you get command not found
, make sure that /usr/local/bin
is displayed when you type
echo $PATH
into the terminal. If not, do…
echo "export PATH=/usr/local/bin:$PATH" >> ~/.zshrc
to add the directory to your $PATH
, again replacing .zshrc
with the configuration file for your shell. Fish is a little different…
set -U -x PATH /usr/local/bin $PATH
but if you’re using fish i’m probably preaching to the choir.
Next, let’s provide the means of communications between the VM and the container management/orchestration software installed on the host, the kata-proxy
$ go get -d -u github.com/kata-containers/proxy $ cd $GOPATH/src/github.com/kata-containers/proxy && make && sudo make install
…and the Kata shim
incase we want to use Kata Containers with Kubernetes in the future.
$ go get -d -u github.com/kata-containers/shim $ cd $GOPATH/src/github.com/kata-containers/shim && make && sudo make install
Building the rootfs
and guest kernel
Now, since we are isolating the guest containers away from our host kernel in a VM, we’ll need to provide the essentials to get the VM and containers to run; a root file system image, a kernel and the hypervisor. For that we’ll be using the osbuilder
scripts to build our guest vm up from scratch.
$ go get -d -u github.com/kata-containers/osbuilder
Whenever we run go get
to fetch source code from github, the downloaded files are always deposited in the $HOME/go/src/github.com
directory. This will come into play later…
In addition to the aformentioned dependencies, i’ll be using docker
as a build environment for the following steps. Chances are you at least have that installed. If not, dig through
here and see if you can find an appropriate install guide for your host. If you do have docker installed and you have taken advantage of other runtimes in the past, make sure that runc
is set as your default-runtime
in the /etc/docker/daemon.json
file.
{ "default-runtime": "runc" }
Kata containers can operate using either an initrd or rootfs image. For the sake of relative simplicity, i’ll be covering using rootfs. Here are instructions covering initrd image creation . Let’s set the configuration file.
$ sudo mkdir -p /etc/kata-containers/ $ sudo install -o root -g root -m 0640 /usr/share/defaults/kata-containers/configuration.toml /etc/kata-containers $ sudo sed -i 's/^\(initrd =.*\)/# \1/g' /etc/kata-containers/configuration.toml
Feel free to $ sudo vim /etc/kata-containers/configuration.toml
and make sure the line beginning with initrd =
is commented(#
) out. Having both the image =
line and the initrd =
line uncommented will cause an error with the runtime upon operation. Now to create a local rootfs using the rootfs.sh
script in $GOPATH/src/github.com/kata-containers/osbuilder/rootfs-builder
.
$ export ROOTFS_DIR=${GOPATH}/src/github.com/kata-containers/osbuilder/rootfs-builder/rootfs $ sudo rm -rf ${ROOTFS_DIR} $ cd $GOPATH/src/github.com/kata-containers/osbuilder/rootfs-builder $ script -fec 'sudo -E GOPATH=$GOPATH USE_DOCKER=true SECCOMP=no ./rootfs.sh ${distro}'
Now the documentation states that in the script above, ${distro}
must be replaced with one of the following linux distributions before running:
alpine
fedora
euleros
centos
clearlinux
…but what it doesn’t tell you is that when we build the rootfs image in the next step, the script defaults to fedora
. I originally tried alpine
for the local rootfs and had errors when building the rootfs image. Since then i’ve just defaulted to fedora
to avoid random build issues. Feel free to experiment at your leisure though.
Now we’ll build the image…
$ cd $GOPATH/src/github.com/kata-containers/osbuilder/image-builder $ script -fec 'sudo -E USE_DOCKER=true ./image_builder.sh ${ROOTFS_DIR}'
and install it.
$ commit=$(git log --format=%h -1 HEAD) $ date=$(date +%Y-%m-%d-%T.%N%z) $ image="kata-containers-${date}-${commit}" $ sudo install -o root -g root -m 0640 -D kata-containers.img "/usr/share/kata-containers/${image}" $ (cd /usr/share/kata-containers && sudo ln -sf "$image" kata-containers.img)
Done.
Now let’s work on building us a kernel. Let’s run $ go get -d /github.com/kata-containers/packaging
to get the scripts we need for the build. We’ll change directories into the kernel
directory and pass the appropriate arguments to the build-kernel.sh
script to get the job done.
$ cd ${GOPATH}/src/github.com/kata-containers/packaging/kernel $ ./build-kernel.sh setup $ ./build-kernel.sh build $ ./build-kernel.sh install
Now, when you ran ./build-kernel.sh install
, did you have any issues? If you’re doing this as a user or administrator on your machine, you may have. The script installs the kernel into /usr/share/kata-containers
, a directory that you may not be able to change without root privileges. Running the script using sudo
won’t work either. My fix was…
$ sudo chown -R ${username}:${username} /usr/share/kata-containers $ ./build-kernel.sh install $ sudo chown -R root:root /usr/share/kata-containers
replacing ${username}
with the name of your user account. This temporarily gives you ownership of the directory and its subdirectories and hands it back to root after the script is complete. Groovy. On to the real fun, installing QEMU, our hypervisor.
Building QEMU from source
The Documentation states that we’ll either need to have a QEMU directory with source code ready or we can opt to use the Kata Containers fork qemu-lite
and build using the configure-hypervisor.sh
script.
Seems easy enough, we just run $ go get -d github.com/kata-containers/qemu
, switch branches using git
and set a few aliases…
$ qemu_branch=$(grep qemu-lite- ${GOPATH}/src/github.com/kata-containers/runtime/versions.yaml | cut -d '"' -f2) $ cd ${GOPATH}/src/github.com/kata-containers/qemu $ git checkout -b $qemu_branch remotes/origin/$qemu_branch $ your_qemu_directory=${GOPATH}/src/github.com/kata-containers/qemu
now go get
the github.com/kata-containers/packaging
repo and make the kata.cfg
configuration file…
$ go get -d github.com/kata-containers/packaging $ cd $your_qemu_directory $ ${GOPATH}/src/github.com/kata-containers/packaging/scripts/configure-hypervisor.sh qemu > kata.cfg $ eval ./configure "$(cat kata.cfg)" --python=$(which python2)
run $ make -j $(nproc)
to compile the source code and…
util/memfd.c:40:12: error: static declaration of ‘memfd_create’ follows non-static declaration
40 | static int memfd_create(const char *name, unsigned int flags)
| ^~~~~~~~~~~~
In file included from /usr/include/bits/mman-linux.h:111,
from /usr/include/bits/mman.h:34,
from /usr/include/sys/mman.h:41,
from /home/heaven/go/src/github.com/kata-containers/qemu/include/sysemu/os-posix.h:29,
from /home/heaven/go/src/github.com/kata-containers/qemu/include/qemu/osdep.h:104,
from util/memfd.c:28:
/usr/include/bits/mman-shared.h:50:5: note: previous declaration of ‘memfd_create’ was here
50 | int memfd_create (const char *__name, unsigned int __flags) __THROW;
| ^~~~~~~~~~~~
make: *** [/home/heaven/go/src/github.com/kata-containers/qemu/rules.mak:66: util/memfd.o] Error 1
make: *** Waiting for unfinished jobs....
…wait, what?
Man, I should have known documented steps pre-empted with the ominous disclaimer “Developers and hackers only” couldn’t be this easy.
Now, depending on your host distro and kernel, that make
might work for you, but it came out snakes eyes for me. So after spamming the aformentioned make
command in disbelief a few times, then hitting the search engines, it looks like this is a
known issue in the build process for earlier versions of QEMU. This issue actually works in our best interests since we should be using the latest sources for qemu. Having the most up-to-date sources is critical since our hypervisor is acting as a defense mechanism against untrusted and potentially malicious containers.
$ go get -d github.com/qemu/qemu $ cd $GOPATH github.com/qemu/qemu $ your_qemu_directory=${GOPATH}/src/github.com/qemu/qemu $ ${GOPATH}/src/github.com/kata-containers/packaging/scripts/configure-hypervisor.sh qemu > kata.cfg $ eval ./configure "$(cat kata.cfg)" --python=$(which python2)
It’s possible the command above will throw an error such as…
ERROR: unknown option --disable-libssh2
If so, note the unknown flag and use reasoning to try to resolve the issue. For this particular error I ran vim ./kata.cfg
and switched --disable-libssh2
to --disable-libssh
, then saved and the configuration ran smoothly.
Now to make and install qemu.
$ make -j $(nproc) $ sudo -E make install
Done!
Wrapping up
Well…almost. Since we’re using docker we need to tell the daemon that we have a new runtime and where to find it. I’m also going to make the kata-runtime
my default runtime and set my storage driver to overlay2
. We can do this in /etc/docker/daemon.json
$ vim /etc/docker/daemon.json
{ "default-runtime": "kata-runtime", "storage-driver": "overlay2", "runtimes": { "kata-runtime": { "path": "/usr/local/bin/kata-runtime" } } }
$ sudo systemctl daemon-reload $ sudo systemctl restart docker.service docker.socket
Awesome. Now if we run a simple docker run
command, we should be greeted with a fresh shell.
$ docker run -it --rm alpine:latest / #_
Nice.
Now that we’ve got Kata fully installed, we can
hack around with it, try out container images of
distros we’ve never used
, deploy a
web application or set up a
VPN interface in a docker container all with increased isolation from the host kernel and filesystem. Also, since we are working with a VM here, feel free to tweak the default_vcpus
and/or default_memory
parameters within the /etc/kata-containers/configuration.toml
config file, along with any other tweaks you see fit. There’s a chance you could break something, but you can always delete the config file, copy /usr/share/defaults/kata-containers/configuration.toml
into /etc/kata-containers/
, comment out the initrd =
parameter, run sudo systemctl restart docker.service
and you’re back in business. Who knows what you might discover…
Even more interesting is the possibility of leveraging Kata Containers with amazon’s Firecracker “microVM” virtualization technology that leverages a KVM hypervisor and reportedly has even more relative security than using the default QEMU hypervisor, though there are a few limitations . This is what powers AWS Lambda and AWS Fargate so it might be worth a gander.
Feel free to check out the installation docs . I decided to save that for another time, I already went down my rabbit hole for the day.
-Heaven