containerd 虚拟机集成测试指南:基于 Lima 在 Fedora/EL 上运行完整测试套件
【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd
本文以 script/vm/README.md 为核心,系统讲解 containerd 项目如何在虚拟机(Fedora 或 EL 发行版,默认 SELinux Enforcing)中运行集成测试,涵盖虚拟机准备、环境初始化(Go、runc、CNI 插件、cri-tools、containerd 本体)、三套测试套件(test-integration.sh、test-cri-integration.sh、test-cri.sh)的执行方式与全部可调环境变量。读完本文,你将掌握与 CI 中integration-vm任务完全一致的本机复现方法,并能深入理解每个脚本在源码层的实现细节。
一、这套 VM 脚本解决什么问题
containerd 的集成测试对运行环境有较高要求:需要内核模块、SELinux 策略、runc/crun 运行时、CNI 网络插件、CRI 工具链等一整套依赖。直接在宿主机上运行既不安全也不可复现。script/vm/目录下的脚本正是为此设计——它们在一个全新的虚拟机中完成全链路环境搭建与测试执行,保证测试结果与 CI 一致。
这些脚本被 .github/workflows/ci.yml 中的integration-vm任务使用,但不依赖 Lima 本身,同样可以配合其他 VM 环境(如 QEMU、VirtualBox 或云主机)使用。脚本约定的目标操作系统是Fedora 或 EL 发行版(RHEL/CentOS/AlmaLinux 等),默认在SELinux Enforcing模式下运行,这是它区别于一般测试脚本的最大特点——确保 containerd 在强制 SELinux 策略下依然行为正确。
script/vm/目录共包含 4 个脚本,职责划分如下:
| 脚本 | 职责 |
|---|---|
| provision.sh | 在客户机内初始化环境(软件包、Go、runc、CNI、cri-tools、containerd、gotestsum、failpoint 二进制) |
| test-integration.sh | 运行 containerd 核心集成测试(make integration) |
| test-cri-integration.sh | 运行 CRI 插件集成测试(cri-integration.test) |
| test-cri.sh | 以 systemd 单元方式启动 containerd,运行critest一致性测试 |
二、使用 Lima 在本机复现
Lima 是 macOS/Linux 上常用的虚拟机管理工具。下面的流程摘自 script/vm/README.md,可完整复现 CI 行为。
1. 启动一台干净的虚拟机
# Boot a plain VM. template:almalinux-8, -9, and -10 are tested too. # --plain keeps the guest pristine (no guest agent, no mounts, no port forwards). limactl start --plain --name=default --cpus=2 --memory=4 --disk=60 template:fedora-44关键点说明:
--plain:保持客户机"原始"状态——不安装 guest agent、不做目录挂载、不做端口转发。这正是测试所需的隔离环境。--cpus=2 --memory=4 --disk=60:CI 中同样采用此配置(见 ci.yml)。- 官方测试过的模板:
template:fedora-44、template:almalinux-8、-9、-10。其中 AlmaLinux 8 保留了旧版 glibc、cgroup 与内核,用于验证兼容性(CI 注释明确说明 "We have to keep EL8 to test old glibc, cgroup, kernel, etc.",见 ci.yml)。
2. 拷贝源码树到客户机
# Copy the source tree into the guest. limactl cp -r . default:containerd lima bash -c 'sudo mkdir -p /go/src/github.com/containerd && sudo mv ~/containerd /go/src/github.com/containerd/containerd' export LIMA_WORKDIR=/go/src/github.com/containerd/containerd把源码放到/go/src/github.com/containerd/containerd并非随意为之——provision 脚本会建立$GOPATH/src/github.com/containerd/containerd的软链接(见下文 provision.sh 分析),使仓库位于 Go 模块的标准导入路径上。LIMA_WORKDIR变量供后续lima命令切换到该目录使用。
3. 初始化客户机
# Provision the guest (packages, Go, runc, CNI plugins, cri-tools, containerd, ...). lima sudo script/vm/provision.sh该步骤耗时较长,需要联网下载软件包、Go 工具链、runc 源码与各测试工具,详见第三节。
4. 运行测试套件
# Run the test suites. lima sudo script/vm/test-integration.sh lima sudo CGROUP_DRIVER=systemd script/vm/test-cri-integration.sh lima sudo CGROUP_DRIVER=systemd script/vm/test-cri.sh三行命令依次对应三类测试:核心集成测试、CRI 集成测试、CRI 一致性测试(critest)。CGROUP_DRIVER=systemd表示以 systemd cgroup 驱动运行——在采用 systemd 作为 init 的发行版上,这是生产环境的典型配置,与 cgroupfs 驱动分别覆盖两条路径(CI 矩阵对两者都会测试,见 ci.yml)。
5. 清理
limactl delete -f default三、环境变量一览
原文档给出了各脚本支持的全部环境变量,此处完整保留并补充默认值(均来自脚本源码):
| 脚本 | 环境变量 | 取值/默认值 | 作用 |
|---|---|---|---|
| provision.sh | GO_VERSION | 默认1.27.1 | 要安装的 Go 工具链版本 |
RUNC_FLAVOR | runc或crun,默认runc | 安装哪种低层运行时(crun 会被安装为runc同名二进制) | |
SELINUX | Enforcing/Permissive/Disabled,默认Enforcing | 客户机的 SELinux 模式 | |
INSTALL_PACKAGES | 默认空 | 追加安装的额外软件包(空格分隔,会拼入 dnf install 命令) | |
| test-integration.sh | RUNC_FLAVOR | runc或crun,默认runc | 指定测试使用的运行时 flavor |
| test-cri-integration.sh | CGROUP_DRIVER | 空(cgroupfs)或systemd | CRI 插件的 cgroup 驱动 |
RUNC_FLAVOR | runc或crun,默认runc | 指定测试使用的运行时 flavor | |
| test-cri.sh | CGROUP_DRIVER | 空(cgroupfs)或systemd | CRI 插件的 cgroup 驱动 |
REPORT_DIR | 默认空 | 非空时critest会额外生成测试报告到该目录 |
另有几个脚本内部使用的变量(源码中: "${VAR:=default}"形式声明):GOTEST(默认go test,test-integration.sh 与 test-cri-integration.sh)、GOPATH(默认/go)、GITHUB_WORKSPACE(test-cri-integration.sh 中转发给内部脚本,默认空)。
四、深入 provision.sh:客户机初始化全流程
provision.sh 是整套流程的基础,必须在客户机内以root身份执行(脚本开头显式检查id -u,非 root 直接报错退出,见 provision.sh)。它依次完成以下工作:
1. 安装系统软件包
通过dnf安装测试所需依赖(provision.sh):
dnf -y install \ container-selinux \ curl gcc git iptables \ libseccomp-devel libselinux-devel \ lsof make strace which \ "kernel-modules-extra-$(uname -r)" \ ${INSTALL_PACKAGES} modprobe xt_comment几个值得注意的细节:
container-selinux提供容器运行所需的 SELinux 策略模块;libseccomp-devel与libselinux-devel是编译 containerd(seccomp/selinux build tag)的开发头文件。kernel-modules-extra-$(uname -r)按当前内核版本精确匹配安装,为测试提供额外内核模块。modprobe xt_comment加载 iptables 的 comment 匹配模块,供 CNI 网络插件使用。${INSTALL_PACKAGES}未加引号展开(# shellcheck disable=SC2086注释了该意图),便于传入以空格分隔的多个包名。
2. 安装 Go 工具链
GOARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')" curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-${GOARCH}.tar.gz" | tar Cxz /usr/local从uname -m映射出GOARCH(x86_64→amd64,aarch64→arm64),下载对应架构的官方二进制包解压到/usr/local。
3. 处理源码树属主与 SELinux 标签
源码是从宿主机拷贝进来的,属主可能是非 root 用户,且文件可能带有宿主机目录的 SELinux 上下文(provision.sh):
git config --system --add safe.directory "${containerd_dir}" if type -p restorecon > /dev/null; then restorecon -R "${containerd_dir}" figit config --system --add safe.directory:把源码目录标记为 git 安全目录,避免"dubious ownership"错误。restorecon -R:递归恢复文件的默认 SELinux 上下文,防止后续执行时被策略拦截。
4. 建立 GOPATH 布局
mkdir -p "${GOPATH}/src/github.com/containerd" if [[ "${containerd_dir}" != "${GOPATH}/src/github.com/containerd/containerd" ]]; then ln -fnsv "${containerd_dir}" "${GOPATH}/src/github.com/containerd/containerd" fi若源码不在标准位置,则在/go/src/github.com/containerd/containerd建立指向实际源码的软链接,保证 Go 构建工具能找到正确的导入路径。
5. 安装低层运行时(runc/crun)
RUNC_FLAVOR="${RUNC_FLAVOR}" script/setup/install-runc type runc runc --version type -ap runc | sort -u | xargs chcon -v -t container_runtime_exec_tinstall-runc 根据RUNC_FLAVOR二选一:
runc:从runc-version文件读取固定版本(脚本头部注释说明该版本与 go.mod 中 runc 模块保持一致),git clone 后以BUILDTAGS='seccomp'编译并make install;crun:直接下载对应版本的预编译 crun 二进制,保存为/usr/local/sbin/runc(即以 runc 之名充当 runc 使用)。
安装后执行chcon -t container_runtime_exec_t为二进制打上容器运行时执行标签,这是 SELinux Enforcing 下容器能够启动的前提之一。
6. 安装 CNI 插件与 cri-tools
script/setup/install-cni CNI_BINARIES="bridge dhcp flannel host-device host-local ipvlan loopback macvlan portmap ptp tuning vlan" PATH="/opt/cni/bin:${PATH}" type ${CNI_BINARIES} || true GOBIN=/usr/local/bin script/setup/install-critools type crictl critest critest --version- CNI 插件安装到
/opt/cni/bin,type逐一确认 11 个标准插件存在(|| true允许校验不中断脚本)。 - cri-tools 提供
crictl(CRI 调试客户端)与critest(CRI 一致性测试工具),critest --version确认版本。
7. 编译安装 containerd 本体
make BUILDTAGS="seccomp selinux no_btrfs no_devmapper no_zfs" binaries install type containerd containerd --version chcon -v -t container_runtime_exec_t /usr/local/bin/{containerd,containerd-shim*}- 采用
seccomp selinux开启安全特性,用no_btrfs no_devmapper no_zfs关闭不需要的快照器,缩减构建面。 - 对
containerd及所有containerd-shim*二进制设置container_runtime_exec_tSELinux 标签。
8. 安装 gotestsum 与 failpoint 二进制
script/setup/install-gotestsum cp "${GOPATH}/bin/gotestsum" /usr/local/bin/ script/setup/install-failpoint-binaries type -ap containerd-shim-runc-fp-v1 | sort -u | xargs chcon -v -t container_runtime_exec_t containerd-shim-runc-fp-v1 -vgotestsum用于格式化测试输出;containerd-shim-runc-fp-v1是带 failpoint 注入能力的 shim,供故障注入类集成测试使用(对应integration/failpoint/目录)。
9. 配置 SELinux 与 containerd
SELINUX="${SELINUX}" script/setup/config-selinux script/setup/config-containerdconfig-selinux 按SELINUX变量配置模式:
Disabled:若/sys/fs/selinux已挂载则setenforce 0并卸载;Enforcing:挂载 selinuxfs 并setenforce 1;Permissive:挂载 selinuxfs 并setenforce 0;- 其他值直接报错退出。
config-containerd 生成/etc/containerd/config.toml:
version = 2 [plugins."io.containerd.snapshotter.v1.overlayfs"] slow_chown = true [plugins] [plugins."io.containerd.grpc.v1.cri"] enable_selinux = true # 仅当 SELinux 非 Disabled 时为 true两个配置点均有明确目的:
slow_chown = true:规避内核 < 5.19 时 overlayfs 快照器不支持 idmap mount 的问题(报错信息为 "snapshotter "overlayfs" doesn't support idmap mounts on this host, configureslow_chownto allow a slower and expensive fallback"),对内核 ≥ 5.19 可安全忽略(该注释与关联 issue 见 config-containerd)。enable_selinux:动态由getenforce的结果决定,SELinux 处于 Enforcing/Permissive 时为true,Disabled 时为false。
五、三套测试脚本的源码级解析
1. test-integration.sh:核心集成测试
test-integration.sh 执行两步(L37-L40):
rm -rf /var/lib/containerd-test /run/containerd-test cd "${containerd_dir}" go test -v -count=1 -race ./core/metrics/cgroups make integration EXTRA_TESTFLAGS="-timeout 15m -no-criu -test.v" TEST_RUNTIME=io.containerd.runc.v2 RUNC_FLAVOR="${RUNC_FLAVOR}"- 先清理可能残留的测试数据目录,保证干净起点。
- 单独运行
go test -race对core/metrics/cgroups包做竞态检测,覆盖 cgroups 指标采集路径。 make integration是主入口,EXTRA_TESTFLAGS传入-timeout 15m(超时上限)、-no-criu(跳过 CRIU 检查点/恢复相关测试)、-test.v(详细输出);TEST_RUNTIME=io.containerd.runc.v2指定使用 runc v2 shim 运行时。
2. test-cri-integration.sh:CRI 插件集成测试
test-cri-integration.sh 聚焦 containerd 内置 CRI 插件(L43-L48):
cleanup cd "${containerd_dir}" make BUILDTAGS="seccomp selinux no_btrfs no_devmapper no_zfs" binaries bin/cri-integration.test chcon -v -t container_runtime_exec_t ./bin/{containerd,containerd-shim*} CONTAINERD_RUNTIME=io.containerd.runc.v2 ./script/test/cri-integration.sh cleanupbin/cri-integration.test是编译出的 CRI 集成测试二进制;脚本注释特别说明该测试从./bin目录执行 containerd,而不是从$PATH(test-cri-integration.sh),因此必须先在仓库内完成make binaries。cleanup()函数会清空/var/lib/containerd*、/run/containerd*、/tmp/containerd*、/tmp/test*、/tmp/failpoint*、/tmp/nri*,确保测试间互不污染(L39-L41)。
3. test-cri.sh:CRI 一致性测试(critest)
test-cri.sh 把 containerd 作为systemd 单元启动,再运行critest(L37-L71):
systemctl disable --now containerd || true rm -rf /var/lib/containerd /run/containerd ... systemctl enable --now "${containerd_dir}/containerd.service" ... critest --parallel=$(($(nproc) + 2)) --ginkgo.skip="${skip_test_args}"值得注意的实现细节:
- 先
systemctl disable --now containerd停掉系统自带的 containerd,再用仓库根目录的 containerd.service 启动测试版本,保证测试对象是本次构建的二进制。 - SELinux 处理:
getenforce为 Enforcing 时,先setenforce 0启动服务、再恢复setenforce 1,让服务进程能在启动过程中正常初始化(L46-L53)。 trap cleanup EXIT:无论成败,退出前都会通过journalctl -u containerd导出完整日志到/tmp/containerd.log并打印,便于排障(L40-L44)。- 跳过列表(
skip_tests,L57-L62):HostIpc is true始终跳过;当CGROUP_DRIVER=systemd时额外跳过 "should terminate with exitCode 137 and reason OOMKilled"(systemd cgroup 驱动下 OOM 行为与 cgroupfs 存在差异)。 - 并行度设为
nproc + 2,最大化利用客户机 CPU;REPORT_DIR非空时通过--report-dir输出测试报告。
六、CI 中的矩阵化使用
.github/workflows/ci.yml中的integration-vm任务(ci.yml)展示了这些脚本的完整组合方式。矩阵维度包括:
- 模板:
fedora-44(主测版本)、almalinux-8(验证旧 glibc/cgroup/内核)、almalinux-9、almalinux-10; - cgroup 驱动:
cgroupfs与systemd两条路径全覆盖; - 运行时:
runc与crun。
任务流水线与本地流程一一对应:limactl start --plain启动 →limactl cp -r .拷贝源码 → 按LIMA_TEMPLATE、CGROUP_DRIVER、RUNC_FLAVOR三个环境变量注入矩阵值 → 依次执行provision.sh、test-integration.sh、test-cri-integration.sh、test-cri.sh,最后无论成败都执行dmesg -T -f kern导出内核日志用于诊断(ci.yml)。
七、常见问题与使用建议
- 必须以 root 执行:四个脚本开头都有
id -u != 0检查。在 Lima 下用lima sudo调用;在其他 VM 环境请先切换为 root。 - SELinux 相关报错:若容器无法启动,优先检查二进制 SELinux 标签(
chcon -t container_runtime_exec_t)与enable_selinux配置;临时弱化可设SELINUX=Permissive或SELINUX=Disabled重新运行 provision。 - 更换运行时:设置
RUNC_FLAVOR=crun即可用 crun 替代 runc 验证另一条 OCI 运行时路径;注意 crun 会被安装为/usr/local/sbin/runc名称。 - 更换 Go 版本:通过
GO_VERSION环境变量控制,当前默认值为1.27.1(provision.sh)。 - 追加系统包:
INSTALL_PACKAGES="pkg1 pkg2"会被直接拼入dnf install,适合按需补充内核模块或调试工具。 - 获取测试日志:
test-cri.sh的 containerd 日志始终被导出到/tmp/containerd.log;集成测试失败时配合 CI 的dmesg -T -f kern可快速定位内核层面问题。
八、小结
script/vm/这套脚本把 containerd 集成测试的"环境准备 + 测试执行"完整自动化,从零开始依次完成软件包安装、Go 工具链、runc/crun、CNI 插件、cri-tools、containerd 构建安装、SELinux 与 containerd 配置,再到核心集成测试、CRI 集成测试与critest一致性测试的串行执行。得益于与 CIintegration-vm任务完全一致的脚本与参数,开发者可以在本地 Lima 虚拟机中 1:1 复现 CI 测试矩阵,是提交 PR 前自测与排查 CI 失败的有力工具。若需调整环境细节,环境变量一览 一节给出了全部可调入口。
【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考