March 28, 2022

Kata Containers: Build and configure QEMU

Picking up from where we left in our previous post, we will now install QEMU and configure Kata Containers to use QEMU as their hypervisor.

Build QEMU

First, we need to build qemu-system for the CPU architecture of our host machine. Kata Containers provide scripts to manage the build of QEMU both for x86 and arm64 hosts. We will be using them to make sure that our QEMU installation is suitable for usage with Kata Containers.

Build QEMU for x86

We need to get the correct version of QEMU from the versions file:

export GOPATH=$(go env GOPATH) && export GO111MODULE=off
source ${GOPATH}/src/
qemu_version=$(get_from_kata_deps "assets.hypervisor.qemu.version")

Next, we need to get the QEMU source from the matching branch:

go get -d
cd ${GOPATH}/src/
git checkout ${qemu_version}

We need to see which version of QEMU we will be building.

echo ${qemu_version}
# v6.2.0

Now we will use the scripts provided to apply the neccessary patches to QEMU. Make sure to change the commands below to match the version of QEMU that you are targeting.

$packaging_dir/scripts/ $packaging_dir/qemu/patches/6.2.x/

Once the patches are successfully applied, we need to install some apt packages that are required to build qemu-system:

sudo apt install ninja-build pkg-config \
  libglib2.0-dev \
  libpixman-1-dev \
  libseccomp-dev \
  libcap-ng-dev \
  librados-dev \
  libpmem-dev \
  librbd-dev \

Now, we can finally build QEMU:

cd $your_qemu_directory
$packaging_dir/scripts/ kata-qemu > kata.cfg
eval ./configure "$(cat kata.cfg)"
make -j $(nproc)
sudo -E make install

If you followed the instructions in our previous post and you have set the PREFIX variable to /opt/kata the qemu-system-x86_64 binary will be stored under the /opt/kata/bin/ directory, tools/virtiofsd/virtiofsd under the /opt/kata/libexec/kata-qemu directory and all the other files will be stored under the /opt/kata/share/kata-qemu directory.

We can also install virtiofsd and qemu-system-x86_64 under the default location using the script.

go get -d
script -fec 'sudo -E ${GOPATH}/src/'

Build QEMU for aarch64

Install requierements:

sudo apt-get install -y build-essential pkg-config \
  libglib2.0-dev libpixman-1-dev \
  libaio-dev libseccomp-dev \
  libcap-ng-dev librados-dev \
  librbd-dev libzstd-dev

Set required environment variables

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

Build QEMU using the script:

sudo rm -rf ${GOPATH}/src/ && \
  go get -d && \
  script -fec 'sudo -E ${GOPATH}/src/'

Configure Kata Containers

Next, we need to install the Kata Containers configuration file. We will use this file to configure Kata Containers to use the initrd image we built in our previous post. You could also use a rootfs image, if you prefer. We will also enable guest seccomp.

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

Make sure that /opt/kata/configs/configuration.toml has a initrd entry pointing to the initrd we created:

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

Configure containerd

You need to edit the containerd config file (usually /etc/containerd/config.toml.

First we need to remove cri from the diabled plugins list:

#disabled_plugins = ["cri"]
disabled_plugins = []

We also need to add the relevant handler for the kata runtime:

         runtime_type = "io.containerd.kata.v2"
         privileged_without_host_devices = true
           ConfigPath = "/opt/kata/configs/configuration.toml"

Once the changes are saved, restart containerd: sudo systemctl restart containerd.

To verify our installation is successfull, we can launch an Ubuntu test container:

sudo ctr images pull
sudo ctr run --runtime -t --rm ubuntu-kata-test uname -a
# Linux e678ab3c6fc3 5.15.26 #2 SMP Sat Mar 19 21:02:56 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux

Note 1 for aarch64:

We noticed that, in some instances, trying to launch a container produced the following error:

# sudo ctr images pull
# ctr run --runtime -t --rm hello1 sh
ctr: failed to create shim: failed to launch qemu: exit status 1, error messages from qemu log: qemu-system-aarch64: warning: For GICv2 max-cpus must be equal to smp-cpus
qemu-system-aarch64: warning: Overriding specified max-cpus(4) with smp-cpus(1)
qemu-system-aarch64: warning: global kvm-pit.lost_tick_policy has invalid class name
Restoring 1 CPU interfaces, kernel only has 4
: unknown

A workaround for that problem was to set the default_vcpus = 8 in /opt/kata/configs/configuration.toml.

Note 2 for aarch64:

We tried running on an older kernel, on an NVIDIA Jetson AGX Xavier and on an NVIDIA Jetson Nano, versions 4.9.253-tegra-virt (getting KVM to work on these boards is also quite a challenge, more details on a subsequent post). Apart from the GIC issue above, we saw that virtio-fs is not backported, nor is there support for the nvdimm stuff, so the following changes to the config file are needed:

--- configuration-qemu-stock.toml   2022-03-24 20:55:47.010284096 +0000
+++ configuration-qemu.toml    2022-03-24 21:34:39.725554126 +0000
@@ -103,7 +103,7 @@
 # vCPUs supported by the SB/VM. In general, we recommend that you do not edit this variable,
 # unless you know what are you doing.
 # NOTICE: on arm platform with gicv2 interrupt controller, set it to 8.
-default_maxvcpus = 0
+default_maxvcpus = 1

 # Bridges can be used to hot plug devices.
 # Limitations:
@@ -151,7 +151,7 @@
 #   - virtio-fs (default)
 #   - virtio-9p
 #   - virtio-fs-nydus
-shared_fs = "virtio-fs"
+shared_fs = "virtio-9p"

 # Path to vhost-user-fs daemon.
 virtio_fs_daemon = "/opt/kata/libexec/kata-qemu/virtiofsd"
@@ -292,7 +292,7 @@
 # nvdimm is not supported when `confidential_guest = true`.
 # Default is false
-#disable_image_nvdimm = true
+disable_image_nvdimm = true

 # VFIO devices are hotplugged on a bridge by default.
 # Enable hotplugging on root bus. This may be required for devices with

Interenstingly enough, default_vcpus must be equal to default_maxvcpus on such platforms.