Skip to content

Kata-containers with vAccel

In this post, we will be going through the steps to build our downstream branch for kata-containers with vAccel from source, both for amd64 and arm64 architectures.

** Note ** This is a lengthy process. A TL;DR script (highly experimental!) is available here. We have tried the steps below on Ubuntu 20.04 and 22.04 x86_64 & aarch64. This process takes approximately 30-40 minutes.

Additionally, we provide pre-built binaries for x86_64 & aarch64 and the respective installation script here, which should take less than a minute (~200MB artifacts).

The process is highly experimental and for showcase purposes -- please don't use these scripts on a production machine ;-)

Install requirements

To build Kata Containers we need to install Rust v1.58.1, Go v1.18.0, Docker and some apt/snap packages. The specific versions may change, so make sure to check the versions database.

Apt/Snap Packages

We need to install gcc, make and yq v3. containerd and runc are installed by the Docker install script, in the following steps.

sudo apt update && sudo apt upgrade -y
sudo apt install gcc make snapd bc -y
sudo snap install yq --channel=v3/stable

Rust (version 1.58.1)

We will use rustup to install and set Rust 1.58.1 as our default toolchain:

down_dir=$(mktemp -d)
pushd $down_dir
wget -q https://static.rust-lang.org/rustup/dist/$(uname -p)-unknown-linux-gnu/rustup-init
sudo chmod +x rustup-init
./rustup-init -q -y --default-toolchain 1.58.1
source $HOME/.cargo/env
popd
rm -rf $down_dir

Go (version 1.18)

We will download the appropriate Go binaries and add them to the PATH environment variable:

down_dir=$(mktemp -d)
pushd $down_dir
wget -q https://go.dev/dl/go1.18.linux-$(dpkg --print-architecture).tar.gz
sudo mkdir -p /usr/local/go1.18
sudo tar -C /usr/local/go1.18 -xzf go1.18.linux-$(dpkg --print-architecture).tar.gz
echo 'export PATH=$PATH:/usr/local/go1.18/go/bin' >> $HOME/.profile
source $HOME/.profile
popd
rm -rf $down_dir

Docker

We will install Docker using the provided convenience script:

sudo apt-get remove docker docker-engine docker.io containerd runc -y > /dev/null 2>&1
sudo rm -rf /var/lib/docker/
down_dir=$(mktemp -d)
pushd $down_dir
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
popd
rm -rf $down_dir

Build Kata components

Build kata-runtime

First, we need to set the correct Go environment variables:

export PATH=$PATH:$(go env GOPATH)/bin && \
  export GOPATH=$(go env GOPATH) && \
  export GO111MODULE=off

We will use go get to download kata-containers source code:

go get -d -u github.com/kata-containers/kata-containers

Checkout our downstream branch:

cd $GOPATH/src/github.com/kata-containers/kata-containers
git remote add nbfc https://github.com/nubificus/kata-containers
git fetch -u nbfc
git checkout vaccel-v3.0

We are now ready to build the kata-runtime:

pushd $GOPATH/src/github.com/kata-containers/kata-containers/src/runtime
export GO111MODULE=on
export PREFIX=/opt/kata
make
popd

To install the binaries to a specific path (say /opt/kata) we need to specify the PREFIX environment variable prior to installing:

pushd $GOPATH/src/github.com/kata-containers/kata-containers/src/runtime
export PREFIX=/opt/kata
sudo -E PATH=$PATH -E PREFIX=$PREFIX make install
popd

Kata binaries are now installed in /opt/kata/bin and configs are installed in /opt/kata/share/defaults/kata-containers/.

It is recommended you add a symbolic link to /opt/kata/bin/kata-runtime and /opt/kata/bin/containerd-shim-kata-v2 in order for containerd to reach these binaries from the default system PATH.

sudo ln -s /opt/kata/bin/kata-runtime /usr/local/bin
sudo ln -s /opt/kata/bin/containerd-shim-kata-v2 /usr/local/bin

Create a rootfs

We can use either a rootfs or initrd image to launch Kata Containers with QEMU. However, AWS Firecracker does not work with initrd images, so we will be using a rootfs image for Kata with Firecracker.

Create the rootfs base image:

export ROOTFS_DIR=${GOPATH}/src/github.com/kata-containers/kata-containers/tools/osbuilder/rootfs-builder/rootfs
cd $GOPATH/src/github.com/kata-containers/kata-containers/tools/osbuilder/rootfs-builder
# you may change the distro (in this case we used ubuntu). to get supported distros list, run ./rootfs.sh -l
script -fec 'sudo -E GOPATH=$GOPATH AGENT_INIT=yes USE_DOCKER=true ./rootfs.sh ubuntu'

Note for arm64:

We noticed that in some instances the kata-agent compilation failed. A possible workaround was to remove the USE_DOCKER variable. This requires qemu-img command to be available on your system. You can install it with sudo apt install -y qemu-utils.

export ROOTFS_DIR="${GOPATH}/src/github.com/kata-containers/kata-containers/tools/osbuilder/rootfs-builder/rootfs-ubuntu"
sudo rm -rf ${ROOTFS_DIR}
cd $GOPATH/src/github.com/kata-containers/kata-containers/tools/osbuilder/rootfs-builder
script -fec 'sudo -E GOPATH=$GOPATH AGENT_INIT=yes ./rootfs.sh ubuntu'

Build a kata rootfs image:

cd $GOPATH/src/github.com/kata-containers/kata-containers/tools/osbuilder/image-builder && \
  script -fec 'sudo -E USE_DOCKER=true -E AGENT_INIT=yes ./image_builder.sh ${ROOTFS_DIR}'

Install the kata rootfs image:

export PREFIX=/opt/kata
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 "$PREFIX/share/kata-containers/${image}" && \
  (cd $PREFIX/share/kata-containers && sudo ln -sf "$image" kata-containers.img)

Build Kata Containers kernel

First, we need some additional packages to build the kernel:

sudo apt install -y libelf-dev bison flex

Setup the kernel source code:

cd $GOPATH/src/github.com/kata-containers/kata-containers/tools/packaging/kernel
./build-kernel.sh -d setup

Build the kernel:

./build-kernel.sh -d build

Install the kernel in the default path for Kata:

export PREFIX=/opt/kata
sudo -E PATH=$PATH -E PREFIX=$PREFIX ./build-kernel.sh -d install

Note:

We noticed that in some instances the installation or build process failed with the following error: ERROR: path to kernel does not exist, use build-kernel.sh setup. We mitigated this problem by specifying the version:

./build-kernel.sh -d -v 5.15.26 build
export PREFIX=/opt/kata
sudo -E PATH=$PATH -E PREFIX=$PREFIX ./build-kernel.sh -d -v 5.15.26 install

At this point we have successfully built all the Kata components. All the binaries we built are stored under the /opt/kata/bin dir:

$ ls -l /opt/kata/bin/
total 142296
-rwxr-xr-x 1 root root 50919312 Mar  25 15:32 containerd-shim-kata-v2
-rwxr-xr-x 1 root root    16691 Mar  25 15:32 kata-collect-data.sh
-rwxr-xr-x 1 root root 42093616 Mar  25 15:32 kata-monitor
-rwxr-xr-x 1 root root 52673784 Mar  25 15:32 kata-runtime

The rootfs image, the initrd image and the kernel are stored under the /opt/kata/share/defaults/kata-containers dir:

$ ls -l /opt/kata/share/kata-containers/
total 221972
-rw-r--r-- 1 root root     72480 Μαρ  25 15:51 config-5.15.26
-rw-r----- 1 root root 134217728 Μαρ  25 15:41 kata-containers-2022-03-25-15:41:55.534872004+0200-486322a0
lrwxrwxrwx 1 root root        59 Μαρ  25 15:41 kata-containers.img -> kata-containers-2022-03-25-15:41:55.534872004+0200-486322a0
-rw-r----- 1 root root  27627256 Μαρ  24 14:43 kata-containers-initrd-2022-03-24-14:43:59.501993241+0200-853dd98b
-rw-r----- 1 root root  27626874 Μαρ  25 15:42 kata-containers-initrd-2022-03-25-15:42:28.034074480+0200-486322a0
lrwxrwxrwx 1 root root        66 Μαρ  25 15:42 kata-containers-initrd.img -> kata-containers-initrd-2022-03-25-15:42:28.034074480+0200-486322a0
-rw-r--r-- 1 root root  38736168 Μαρ  25 15:51 vmlinux-5.15.26-90
lrwxrwxrwx 1 root root        18 Μαρ  25 15:51 vmlinux.container -> vmlinux-5.15.26-90
-rw-r--r-- 1 root root   5795664 Μαρ  25 15:51 vmlinuz-5.15.26-90
lrwxrwxrwx 1 root root        18 Μαρ  25 15:51 vmlinuz.container -> vmlinuz-5.15.26-90

The configuration files are stored under the /opt/kata/share/defaults/kata-containers dir:

ls -l /opt/kata/share/defaults/kata-containers
total 72
-rw-r--r-- 1 root root  9717 Μαρ  25 15:32 configuration-acrn.toml
-rw-r--r-- 1 root root 13535 Μαρ  25 15:32 configuration-clh.toml
-rw-r--r-- 1 root root 15364 Μαρ  25 15:32 configuration-fc.toml
-rw-r--r-- 1 root root 25701 Μαρ  25 15:32 configuration-qemu.toml
lrwxrwxrwx 1 root root    23 Μαρ  25 15:32 configuration.toml -> configuration-qemu.toml

Build Firecracker

Kata Containers support AWS Firecracker v1.1.0. To build Firecracker, we will clone the Github repo and checkout to the 1.1.0 version:

git clone https://github.com/firecracker-microvm/firecracker.git -b v1.1.0 --depth 1 &&\
  cd firecracker &&\
  git submodule update --init

Now we can build the binaries:

Note AWS Firecracker uses docker to build the image, so make sure your user can access the docker daemon, or just run with sudo.

sudo ./tools/devtool -y build --release
toolchain="$(uname -m)-unknown-linux-musl"
sudo cp build/cargo_target/${toolchain}/release/firecracker /opt/kata/bin/firecracker &&\
sudo cp build/cargo_target/${toolchain}/release/jailer /opt/kata/bin/jailer

devmapper snapshotter

AWS Firecracker requires a block device as the backing store for a VM. To interact with containerd and kata we use the devmapper snapshotter. To check support for your containerd installation, you can run:

ctr plugins ls |grep devmapper

if the output of the above command is:

io.containerd.snapshotter.v1    devmapper                linux/amd64    ok

then you can skip this section and move on to Configure Kata Containers to use Firecracker

If the output of the above command is:

io.containerd.snapshotter.v1    devmapper                linux/amd64    error

then we need to setup devmapper snapshotter. Based on a very useful guide from docker, we can set it up using the following scripts:

#!/bin/bash
set -ex

DATA_DIR=/var/lib/containerd/io.containerd.snapshotter.v1.devmapper
POOL_NAME=containerd-pool

mkdir -p ${DATA_DIR}

# Create data file
sudo touch "${DATA_DIR}/data"
sudo truncate -s 100G "${DATA_DIR}/data"

# Create metadata file
sudo touch "${DATA_DIR}/meta"
sudo truncate -s 10G "${DATA_DIR}/meta"

# Allocate loop devices
DATA_DEV=$(sudo losetup --find --show "${DATA_DIR}/data")
META_DEV=$(sudo losetup --find --show "${DATA_DIR}/meta")

# Define thin-pool parameters.
# See https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt for details.
SECTOR_SIZE=512
DATA_SIZE="$(sudo blockdev --getsize64 -q ${DATA_DEV})"
LENGTH_IN_SECTORS=$(bc <<< "${DATA_SIZE}/${SECTOR_SIZE}")
DATA_BLOCK_SIZE=128
LOW_WATER_MARK=32768

# Create a thin-pool device
sudo dmsetup create "${POOL_NAME}" \
    --table "0 ${LENGTH_IN_SECTORS} thin-pool ${META_DEV} ${DATA_DEV} ${DATA_BLOCK_SIZE} ${LOW_WATER_MARK}"

cat << EOF
#
# Add this to your config.toml configuration file and restart containerd daemon
#
[plugins]
  [plugins.devmapper]
    pool_name = "${POOL_NAME}"
    root_path = "${DATA_DIR}"
    base_image_size = "10GB"
    discard_blocks = true
EOF

Make it executable and run it:

sudo chmod +x ~/scripts/devmapper/create.sh && \
  cd ~/scripts/devmapper/ && \
  sudo ./create.sh

Now, we can add the devmapper configuration provided from the script to /etc/containerd/config.toml and restart containerd.

sudo systemctl restart containerd

We can use dmsetup to verify that the thin-pool was created successfully. We should also check that devmapper is registered and running:

sudo dmsetup ls
# devpool (253:0)
sudo ctr plugins ls | grep devmapper
# io.containerd.snapshotter.v1    devmapper                linux/amd64    ok

This script needs to be run only once, while setting up the devmapper snapshotter for containerd. Afterwards, make sure that on each reboot, the thin-pool is initialized from the same data dir. Otherwise, all the fetched containers (or the ones that you’ve created) will be re-initialized. A simple script that re-creates the thin-pool from the same data dir is shown below:

#!/bin/bash
set -ex

DATA_DIR=/var/lib/containerd/io.containerd.snapshotter.v1.devmapper
POOL_NAME=containerd-pool

# Allocate loop devices
DATA_DEV=$(sudo losetup --find --show "${DATA_DIR}/data")
META_DEV=$(sudo losetup --find --show "${DATA_DIR}/meta")

# Define thin-pool parameters.
# See https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt for details.
SECTOR_SIZE=512
DATA_SIZE="$(sudo blockdev --getsize64 -q ${DATA_DEV})"
LENGTH_IN_SECTORS=$(bc <<< "${DATA_SIZE}/${SECTOR_SIZE}")
DATA_BLOCK_SIZE=128
LOW_WATER_MARK=32768

# Create a thin-pool device
sudo dmsetup create "${POOL_NAME}" \
    --table "0 ${LENGTH_IN_SECTORS} thin-pool ${META_DEV} ${DATA_DEV} ${DATA_BLOCK_SIZE} ${LOW_WATER_MARK}"

We can create a systemd service to run the above script on each reboot:

sudo nano /lib/systemd/system/devmapper_reload.service

The service file:

[Unit]
Description=Devmapper reload script

[Service]
ExecStart=/path/to/script/reload.sh

[Install]
WantedBy=multi-user.target

Enable the newly created service:

sudo systemctl daemon-reload
sudo systemctl enable devmapper_reload.service
sudo systemctl start devmapper_reload.service

Configure Kata Containers to use Firecracker

Next, we need to install the Kata Containers-Firecracker configuration file. We will use this file to configure Kata Containers to use the rootfs image we built previously.

sudo mkdir -p /opt/kata/configs
sudo install -o root -g root -m 0640 /opt/kata/share/defaults/kata-containers/configuration-fc-vaccel.toml /opt/kata/configs
sudo sed -i 's/^\(initrd =.*\)/# \1/g' /opt/kata/configs/configuration-fc-vaccel.toml
# enable seccomp
sudo sed -i '/^disable_guest_seccomp/ s/true/false/' /opt/kata/configs/configuration-fc-vaccel.toml

Make sure that /opt/kata/configs/configuration-fc-vaccel.toml has an image entry pointing to the rootfs we created:

17   | image = "/opt/kata/share/kata-containers/kata-containers.img"

Configure containerd

Next, we need to configure containerd. Add a file in your path (eg. /usr/local/bin/containerd-shim-kata-fc-vaccel-v2) with the following contents:

#!/bin/bash
KATA_CONF_FILE=/opt/kata/configs/configuration-fc-vaccel.toml /usr/local/bin/containerd-shim-kata-v2 $@

Make it executable:

sudo chmod +x /usr/local/bin/containerd-shim-kata-fc-vaccel-v2

Add the relevant section in containerd’s config.toml file (/etc/containerd/config.toml):

[plugins.cri.containerd.runtimes]
  [plugins.cri.containerd.runtimes.kata-fc-vaccel]
    runtime_type = "io.containerd.kata-fc-vaccel.v2"

Restart containerd:

sudo systemctl restart containerd

Verify the installation

We are now ready to launch a container using Kata-vaccel with Firecracker to verify that everything worked:

sudo ctr images pull --snapshotter devmapper docker.io/library/ubuntu:latest
sudo ctr run --snapshotter devmapper --runtime io.containerd.run.kata-fc-vaccel.v2 -t --rm docker.io/library/ubuntu:latest ubuntu-kata-fc-test uname -a

You should see the vaccelrt-agent running alongside the containerd process.

Install vAccel components

Before we proceed to run our first vAccel enabled kata container, we need to install the required vAccel components:

down_dir=$(mktemp -d)
pushd $down_dir
wget -q https://s3.nbfc.io/nbfc-assets/github/vaccelrt/main/$(uname -m)/Release-deb/vaccel-0.6.0-Linux.deb
sudo dpkg -i vaccel-0.6.0-Linux.deb
wget -q https://s3.nbfc.io/nbfc-assets/github/vaccelrt/agent/main/$(uname -m)/Release-deb/vaccelrt-agent-0.3.0-Linux.deb
sudo dpkg -i vaccelrt-agent-0.3.0-Linux.deb
popd
rm -rf $down_dir

Run a vAccel-enabled kata container

To run a vAccel enabled kata container, first, you have to get a container image with vaccel installed. We built one with docker, based on the following container image file:

FROM ubuntu:20.04

ENV DEBIAN_FRONTEND="noninteractive"

RUN apt-get update && apt-get install -y wget unzip && apt-get clean

# Install vAccelrt core library
RUN wget https://s3.nbfc.io/nbfc-assets/github/vaccelrt/main/$(uname -m)/Release-deb/vaccel-0.6.0-Linux.deb && dpkg -i vaccel-0.6.0-Linux.deb

# Install the vsock plugin
RUN wget https://s3.nbfc.io/nbfc-assets/github/vaccelrt/plugins/vsock/master/$(uname -m)/Release-deb/vaccelrt-plugin-vsock-0.1.0-Linux.deb && dpkg -i vaccelrt-plugin-vsock-0.1.0-Linux.deb

# Set some env variables
ENV LD_LIBRARY_PATH=/usr/local/lib
ENV VACCEL_BACKENDS=/usr/local/lib/libvaccel-vsock.so 

# This is the default value, no need to set it
# Also, this value is passed on from the Kata runtime
#ENV VACCEL_VSOCK=vsock://2:2048

# Uncomment for debug messages
#ENV VACCEL_DEBUG_LEVEL=4

CMD ["sleep", "infinity"]

Build it, or pull a pre-built one:

$ sudo ctr image pull --snapshotter devmapper docker.io/nubificus/vaccel-app-container:$(uname -m)
docker.io/nubificus/vaccel-app-container:x86_64:                                  resolved       |++++++++++++++++++++++++++++++++++++++| 
manifest-sha256:60c94495bfdf0bdcceaab4fe20fa1b427df25ddb5e6ad107d249e91a948a7bed: done           |++++++++++++++++++++++++++++++++++++++| 
config-sha256:1bf757ff35444f01a96f9481b61cf82a0ced9afe53e37e2a04e1f3d943b4d241:   done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:7a8454f39a31d19d7b1b497aa5dea0f717605b93fc5e9d10405f1a04cecb6a88:    exists         |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:db0a0fb32697f17998cf1e72c2d38a41d13c79effa25ee9a4cac3870bc4980c0:    exists         |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:45dd8fb4f524a0182c1b79769dd6a2ee05eaee8b095b4af343f324c699848fb7:    exists         |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:846c0b181fff0c667d9444f8378e8fcfa13116da8d308bf21673f7e4bea8d580:    exists         |++++++++++++++++++++++++++++++++++++++| 
elapsed: 2.7 s                                                                    total:  1.1 Ki (430.0 B/s)                                       
unpacking linux/amd64 sha256:60c94495bfdf0bdcceaab4fe20fa1b427df25ddb5e6ad107d249e91a948a7bed...
done

Finally, run the container with the kata-fc-vaccel runtime using the following command:

sudo ctr run --snapshotter devmapper --runtime io.containerd.run.kata-fc-vaccel.v2 -t --rm docker.io/nubificus/vaccel-app-container:$(uname -m) ubuntu-kata-fc-vaccel /bin/bash

You should be presented with the prompt of the container. Then, run the vaccel classify example, using an image from the vAccel release:

classify /usr/local/share/images/example.jpg 1

The full output is shown below:

$ sudo ctr run --snapshotter devmapper --runtime io.containerd.run.kata-fc-vaccel.v2 -t --rm docker.io/nubificus/vaccel-app-container:$(uname -m) ubuntu-kata-fc-vaccel /bin/bash
root@clr-5746603866294b7885b6f2a30c04c7b7:/# classify /usr/local/share/images/example.jpg 1
Initialized session with id: 1
Image size: 79281B
classification tags: 99.902% banana

If you inspect the host while the container is running, you can see the vAccelrt agent running, bound to the specific container instance:

$ ps -fe |grep ubuntu-kata-fc
root      983620  566482  0 18:14 pts/16   00:00:00 ctr run --snapshotter devmapper --runtime io.containerd.run.kata-fc-vaccel.v2 -t --rm docker.io/nubificus/vaccel-app-container:x86_64 ubuntu-kata-fc-vaccel /bin/bash
root      983888       1  0 18:14 ?        00:00:00 /opt/kata/bin/containerd-shim-kata-v2 -namespace default -address /run/containerd/containerd.sock -publish-binary /usr/bin/containerd -id ubuntu-kata-fc-vaccel
root      983900  983888  0 18:14 ?        00:00:01 /opt/kata/bin/firecracker --api-sock /run/vc/firecracker/ubuntu-kata-fc-vaccel/root/run/firecracker.socket --config-file /run/vc/firecracker/ubuntu-kata-fc-vaccel/root/fcConfig.json
root      983906  983888  0 18:14 ?        00:00:02 /opt/vaccel-v0.4.0/bin/vaccelrt-agent --server-address unix:///run/vc/firecracker/ubuntu-kata-fc-vaccel/root/kata.hvsock_2048
root      994531  894257  0 18:19 pts/17   00:00:00 grep ubuntu-kata-fc