本地运行 Kata Containers CI

基于 Vagrant + VirtualBox 方案

Table of Contents

本文主要参考 《Kata Containers CI》,大部分内容是直接从上面的文章中翻译的,但是我会根据我自己的实践,对原文做一定的改变,比如启动虚拟机使用 vagrant 等。Happy hacking on Kata Containers' CI!

Vagrant 安装

这部分内容参见斌哥的《使用 VirtualBox 安装虚拟机开发环境》

安装 Virtual Box 参见 virtualbox

$ wget https://download.virtualbox.org/virtualbox/7.1.4/virtualbox-7.1_7.1.4-165100~Ubuntu~jammy_amd64.deb \
-O virtualbox-7.1.4.deb
$ sudo dpkg -i virtualbox-7.1.4.deb

安装 Vagrant 参见 Install Vagrant,我的测试环境是 ubuntu 22.04,其他的系统参见前面的连接自行安装。

$ wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
$ echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
$ sudo apt update && sudo apt install vagrant

安装 vagrant-disksize 插件,否则虚拟机启动会报错。

$ vagrant plugin install vagrant-disksize

安装 vagrant-proxyconf 插件,负责配置代理。

$ vagrant plugin install vagrant-proxyconf

创建虚拟机

首先要确定虚拟机的规格,以 kata-containers-ci-on-push / run-docker-tests-on-garm / run-docker-tests (clh) 这个 CI 测试为例,去 workflows 中找到 run-docker-tests-on-garm.yaml,在 runs-on 可以看到运行的环境是 "garm-ubuntu-2304-smaller"。

OS 对应的 vagrant 镜像

  • Ubuntu-2304: bento/ubuntu-23.04

规格

  • Small: 2 CPUs, 8 GiB
  • Normal: 4 CPUs, 16 GiB

(如果已经有配置,可以跳过本步骤)使用 Vagrant 创建一个 ubuntu-2304-small 规格的虚拟机配置。

$ VM_NAME=kata-ubuntu-2304-small
$ VM_IMAGE=bento/ubuntu-23.04
$ mkdir -p $HOME/vagrant/$VM_NAME
$ cd $HOME/vagrant/$VM_NAME
$ cat > Vagrantfile <<EOF
Vagrant.configure("2") do |config|
config.disksize.size = "30GB"
config.vm.box = "$VM_IMAGE"
config.vm.define "$VM_NAME" do |kata|
kata.vm.hostname = "$VM_NAME"
kata.vm.network "private_network", type: "dhcp"
kata.vm.provider "virtualbox" do |vb|
vb.cpus = 2
vb.memory = "8192"
vb.customize ["modifyvm", :id, "--nested-hw-virt", "on"]
end
end
if Vagrant.has_plugin?("vagrant-proxyconf")
config.proxy.http = "http://127.0.0.1:7890/"
config.proxy.https = "http://127.0.0.1:7890/"
config.proxy.no_proxy = "localhost,127.0.0.1,.example.com,10.0.0.0/8,192.168.0.0/16"
end
end
EOF

启动虚拟机,启动之前请确保其它虚拟机/ Kata 容器没有启动。如果启动失败,可以尝试的工作是(1)杀掉其他占用 KVM 的进程,比如Kata 容器。(2)重启机器。

$ vagrant destroy -f
$ vagrant up

通过 SSH 连接虚拟机。

# 如果你本地有一个 http 代理监听在 localhost:7890
# `-R` 是反向端口转发, guest 访问 localhost:7890 会被转发到 host 的 7890 端口;
# `-L` 是正向端口转发,host 访问 localhost:7890 -> guest:7890。
$ vagrant ssh -- -R 7890:localhost:7890
# 如果你不希望使用本机代理
$ vagrant ssh

构建测试环境

访问 workflows/ci-on-push.yaml 找到失败的 CI 测试项目,进入 "Summary" 页面,找到 "kata-static-tarball-amd64" 开头的文件,下载到本地并解压缩,最终获得 "kata-static.tar.xz" 文件。

将 tarball 上传到虚拟环境的 /vagrant,vagrant 自动建立了一个共享路径,host 上是 Vagrantfile 的目录,guest 上是 /vagrant。这一步的目的是把 kata-static-tarball-xxx.zip 文件解压缩获得 kata-static.tar.xz 文件,然后上传到 host 的当前虚拟机根目录($HOME/vagrant/kata-ubuntu-2304-small)。

# on macOS(这是我自己的方案)
$ unzip ~/Downloads/kata-static-tarball* -d ~/Downloads
$ scp ~/Downloads/kata-static.tar.xz devant:/home/nxw/vagrant/kata-ubuntu-2304-small
$ rm -rf ~/Downloads/kata-static-tarball* && rm -rf ~/Downloads/kata-static.tar.xz

初始化 git 仓库。

# on Guest
$ BRANCH=
$ git clone --branch $BRANCH https://github.com/justxuewei/kata-containers.git
$ cd kata-containers
$ git remote add upstream https://github.com/kata-containers/kata-containers
$ git remote update
$ git config --global user.email "you@example.com"
$ git config --global user.name "Your Name"
$ git rebase upstream/main

拷贝 tarball 到指定目录。

# on Guest
$ mkdir kata-artifacts
$ cp /vagrant/kata-static.tar.xz $(pwd)/kata-artifacts

编译并替换组件

安装 Golang

$ cd $HOME
$ wget https://go.dev/dl/go1.21.3.linux-amd64.tar.gz
$ sudo sh -c "rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.3.linux-amd64.tar.gz"
$ echo "export PATH=$PATH:/usr/local/go/bin" >> $HOME/.profile
$ source $HOME/.profile
$ go version

编译 Runtime-go

$ pushd src/runtime
$ make
$ sudo cp containerd-shim-kata-v2 /usr/local/bin
$ sudo mkdir -p /etc/kata-containers
$ sudo ln -s /opt/kata/share/defaults/kata-containers/configuration.toml /etc/kata-containers
$ popd

非 k8s 测试

不同的测试都需要下面两种 URL,这里以我自己的一个失败的 workflow 为例,请自行替换为自己需要的地址。

测试步骤记录在 run-docker-tests-on-garm.yaml

安装 Kata Containers 依赖

# 任选一种 hypervisor
$ export KATA_HYPERVISOR=qemu
$ export KATA_HYPERVISOR=clh
# runtime-rs
$ export KATA_HYPERVISOR=dragonball
$ export KATA_HYPERVISOR=cloud-hypervisor
# 安装 docker
$ sudo apt-get install docker-ce docker-ce-cli
$ sudo mkdir -p /etc/systemd/system/docker.service.d
$ sudo sh -c 'cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=$NO_PROXY,10.0.0.0/8,192.168.0.0/16"
EOF'
$ sudo systemctl daemon-reload \
&& sudo systemctl restart docker
$ bash tests/integration/docker/gha-run.sh install-dependencies
$ bash tests/integration/docker/gha-run.sh install-kata kata-artifacts

启动 debug 日志

# 启用 debug 日志
$ sudo chmod +w /opt/kata/share/defaults/kata-containers/runtime-rs/configuration.toml
$ sudo sed -i 's/#enable_debug = true/enable_debug = true/' /opt/kata/share/defaults/kata-containers/configuration.toml
$ sudo sh -c "echo '[debug]
level = \"debug\"' >> /etc/containerd/config.toml"
$ sudo systemctl restart containerd

开始调试。

$ bash tests/integration/docker/gha-run.sh run

导出 containerd 日志。

$ sudo journalctl -xe --unit containerd > /tmp/containerd.txt
$ vim /tmp/containerd.txt

导出 shim 日志。

$ sudo journalctl -t kata > /tmp/kata.txt
$ vim /tmp/kata.txt

清理

$ vagrant destroy -f
$ rm ./kata-static.tar.xz

K8s 测试

仅测试了 Kubernetes on AKS,其他的等待补充。

使用 kubeadm 创建单节点 k8s 集群。

# 安装 containerd
$ CONTAINERD_VERSION="1.6.8"
$ wget https://github.com/containerd/containerd/releases/download/v$CONTAINERD_VERSION/containerd-$CONTAINERD_VERSION-linux-amd64.tar.gz
$ sudo tar Cxzvf /usr/local containerd-$CONTAINERD_VERSION-linux-amd64.tar.gz
$ sudo sh -c "wget -qO - https://raw.githubusercontent.com/containerd/containerd/main/containerd.service > /lib/systemd/system/containerd.service"
$ sudo vim /lib/systemd/system/containerd.service
# 添加两行
# Environment="HTTP_PROXY=http://127.0.0.1:7890"
# Environment="HTTPS_PROXY=http://127.0.0.1:7890"
# Environment="NO_PROXY=localhost,127.0.0.1,.example.com,10.0.0.0/8,192.168.0.0/16"
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now containerd
$ sudo mkdir -p /etc/containerd
$ sudo sh -c "containerd config default > /etc/containerd/config.toml"
$ sudo systemctl restart containerd
# 安装 runc
$ sudo apt install runc
# 安装 kubectl & kubeadm
$ sudo apt-get install -y apt-transport-https ca-certificates curl gpg
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.26/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.26/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
# 设置网络
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
$ sudo modprobe -a overlay br_netfilter
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
$ sudo sysctl --system
# 降级 cgroup 版本至 v1
$ sudo vim /etc/default/grub
向 GRUB_CMDLINE_LINUX 插入一条 systemd.unified_cgroup_hierarchy=0
$ sudo update-grub
$ sudo reboot
$ cat /sys/fs/cgroup/cgroup.controllers
cat: /sys/fs/cgroup/cgroup.controllers: No such file or directory
# 安装 CNI
$ git clone https://github.com/containernetworking/plugins.git cni-plugins
$ cd cni-plugins
$ git checkout tags/v1.4.1 -b v1.4.1
$ ./build_linux.sh
$ sudo mkdir -p /opt/cni/bin
$ sudo cp bin/* /opt/cni/bin/
# CNI 配置
$ sudo mkdir -p /etc/cni/net.d
$ sudo sh -c 'cat > /etc/cni/net.d/10-mynet.conf <<EOF
{
"cniVersion": "0.2.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "172.19.0.0/24",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
EOF'

启动 k8s 集群

# 一定要进入 root 之后再设置 proxy
$ sudo su
$ export no_proxy="$no_proxy,10.0.0.0/8,192.168.0.0/16" \
&& export NO_PROXY="$NO_PROXY,10.0.0.0/8,192.168.0.0/16" \
&& echo "no_proxy: $no_proxy" \
&& echo "NO_PROXY=$NO_PROXY"
$ swapoff -a
$ kubeadm config images pull \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers
$ kubeadm init \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
--pod-network-cidr=192.168.0.0/16
# 设置 kubectl 配置
$ mkdir -p $HOME/.kube
$ cp -i /etc/kubernetes/admin.conf $HOME/.kube/config \
&& sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 清除污点
$ kubectl taint nodes --all node-role.kubernetes.io/control-plane-
# 安装 CNI
# calico
$ kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/tigera-operator.yaml
$ kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/custom-resources.yaml
# 等待所有 pod running
$ watch kubectl get pods -n calico-system
# 安装 yq(需要有代理的环境)
$ cd go/to/kata-containers
$ ./ci/install_yq.sh
# 安装 bats
$ sudo apt install bats

安装 Kata 环境

# 任选一种 hypervisor
$ export KATA_HYPERVISOR=qemu
$ export KATA_HYPERVISOR=clh
# runtime-rs
$ export KATA_HYPERVISOR=dragonball
$ export KATA_HYPERVISOR=cloud-hypervisor
$ cd go/to/kata-containers
# 记得修改 sleep 240s -> sleep 60s
$ bash tests/integration/kubernetes/gha-run.sh deploy-kata-aks

不知道为什么,有时候运行完脚本后 runtimeclass 不能够正确被配置,因此需要手动检查配置是否正确(以 cloud-hypervisor 为例)。

$ rm -rf /usr/local/bin/containerd-shim-kata-v2; \
rm -rf /usr/local/bin/containerd-shim-kata-runtime-rs-v2
$ ln -s /opt/kata/runtime-rs/bin/containerd-shim-kata-v2 \
/usr/local/bin/containerd-shim-kata-runtime-rs-v2; \
ln -s /opt/kata/bin/containerd-shim-kata-v2 \
/usr/local/bin/containerd-shim-kata-v2
$ kubectl get runtimeclass
NAME HANDLER AGE
kata kata-cloud-hypervisor 109m
kata-cloud-hypervisor kata-cloud-hypervisor 109m
$ kubectl describe runtimeclass kata
Handler: kata-cloud-hypervisor
$ kubectl describe runtimeclass kata-cloud-hypervisor
Handler: kata-cloud-hypervisor
$ vim /etc/containerd/config.toml
# 1. 搜索 "containerd.runtimes"
# 2. 如果没有 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-cloud-hypervisor] 就需要添加
# 3.添加以下东西:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-cloud-hypervisor]
runtime_type = "io.containerd.kata-runtime-rs.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-cloud-hypervisor.options]
ConfigPath = "/opt/kata/share/defaults/kata-containers/runtime-rs/configuration-cloud-hypervisor.toml"
# 最后重启 containerd 和 kubelet
$ sudo systemctl restart containerd && sudo systemctl restart kubelet

常见的集中 hypervisors 的配置项

  • cloud-hypervisor
    • runtime_type: io.containerd.kata-runtime-rs.v2
    • ConfigPath: /opt/kata/share/defaults/kata-containers/runtime-rs/configuration-cloud-hypervisor.toml

启动测试(需要根据 CI 测试执行什么命令来确定)

$ bash tests/integration/kubernetes/gha-run.sh run-tests

如果虚拟机重启需要做的事情

$ sudo su
# 关闭 swap
$ swapoff -a
$ systemctl restart kubelet
# 禁用 proxy envs
$ export no_proxy="$no_proxy,10.0.0.0/8,192.168.0.0/16" \
&& export NO_PROXY="$NO_PROXY,10.0.0.0/8,192.168.0.0/16" \
&& echo "no_proxy: $no_proxy" \
&& echo "NO_PROXY=$NO_PROXY"
# 任选一种 hypervisor
$ export KATA_HYPERVISOR=qemu
$ export KATA_HYPERVISOR=clh
# runtime-rs
$ export KATA_HYPERVISOR=dragonball
$ export KATA_HYPERVISOR=cloud-hypervisor