- 容器运行时
- 云原生
【免费下载链接】youki
A container runtime written in Rust
本篇指南面向想要深入 youki 源码并参与开发的开发者。youki 是一个用 Rust 编写的低层(low-level)容器运行时,本文将从"低层运行时在容器生态中的定位"讲起,带你梳理开发 youki 前需要具备的 Rust 基础与系统环境,并以仓库根目录的 justfile 为入口,完整拆解单元测试、OCI 集成测试与 Rust 移植集成测试三级验证体系的具体执行方式与底层实现。读完后你将能独立搭建开发环境、编译 youki、跑通全部测试,并知道如何借助 OCI 规范与 Linux man pages 继续深入理解源码。
前置要求:扎实的 Rust 基础
youki 完全使用 Rust 编写,因此在动手阅读或修改任何模块之前,建议先掌握一定的 Rust 语言基础,例如所有权与借用、trait、错误处理、unsafe代码块的使用等。如果你尚未入门,可以访问 Rust 官方站点的 Learn 板块系统学习(本文不展开语言教学,官方文档已足够详尽)。
仓库根目录的 rust-toolchain.toml 固定了工具链版本,如果你通过 rustup 安装 Rust,rustup会自动按照该文件切换到正确的编译器版本,无需手工干预;同时 rustfmt.toml 定义了全仓统一的代码格式化风格。这些文件意味着"克隆即构建、开箱即编译",是后续所有开发工作的前提。
Youki 是什么:低层容器运行时的定位
youki 是一个低层容器运行时(low-level container runtime),负责 Linux 容器的创建与管理。与它同类的低层运行时还有 runc(Go 编写)和 crun(C 编写)。这类运行时通常不会直接面向普通用户,而是被 Docker、Podman 等高层(high-level)运行时调用——高层运行时负责镜像拉取、网络、存储卷等用户友好的编排能力,真正"创建并管理容器进程"的脏活累活则委托给低层运行时完成。
从本仓库的 crate 结构可以清晰地印证这一定位:
- crates/youki/src/main.rs:可执行程序入口,解析并分发
create、start、run、delete、state、kill、exec等命令,这些命令正是 OCI Runtime Specification 定义的标准接口; - crates/libcontainer:核心实现库,包含容器构建(builder)、进程初始化(init)、rootfs 与挂载(mount)、命名空间(namespaces)、seccomp 等关键子系统;
- crates/libcgroups:cgroups v1/v2 控制器的抽象与实现,包括 cpu、memory、pids、devices(基于 BPF)等;
- crates/liboci-cli:与 OCI 命令行规范对应的 CLI 参数结构定义。
正是由于 youki、runc、crun、Docker 都遵循 OCI 规范、暴露标准接口,youki 可以直接顶替 runc 的角色(例如作为 Docker 的运行时后端),而无需高层运行时做任何额外适配。OCI 规范是这一切互操作性的基石,这一话题将在后文"资源"一节详细展开。
开发前的准备:先读用户文档、再搭环境
在开始开发之前,建议先通读 用户文档导读,它明确了 youki 的运行需求与基本安装方式。随后按照 基础安装(Basic Setup) 和 基础使用(Basic Usage) 两篇完成环境准备。要点包括:
- youki仅支持 Linux 平台,且当前依赖 systemd 作为 init 系统,在其他平台或非 systemd 系统上需要借助虚拟化(仓库提供了 Vagrantfile 一键搭建 VM);
- 构建依赖需要安装
pkg-config、libsystemd-dev、libseccomp-dev、libelf-dev、libclang-dev、libssl-dev等系统库(Debian/Ubuntu 系,Fedora/CentOS/RHEL 系有对应的 dnf 包名); - 使用
just youki-dev(调试构建)或just youki-release(release 构建)即可编译出根目录下的youki可执行文件;跨架构编译可用 cross-rs,目标列表见 Cross.toml; - 基础使用文档还演示了两种典型用法:通过
/etc/docker/daemon.json将 youki 注册为 Docker 运行时;以及脱离高层运行时、用youki spec+youki create/start/state/list/delete手工管理容器(含 rootless 模式)。
开发中的测试:编译、验证与三级测试体系
开发过程中,你需要频繁编译并验证改动是否破坏既有功能。目前 youki 用两类测试来兜底:
- 单元测试(Unit tests):针对 youki 内部各个独立组件;
- 集成测试(Integration tests):从命令入口到最终效果的全链路验证。
早期的开发者文档(本文所基于的 basics.md)提到根目录的 makefile 提供了便捷入口;当前仓库已将这一职责交给根目录的 justfile(由just命令驱动)。它暴露了三个核心测试目标,与原文档中三个目标一一对应:
| 原文档目标 | 当前 justfile 目标 | 作用 |
|---|---|---|
test | test-unit | 运行 Rust 单元测试 |
oci-integration-test | test-oci | 运行 OCI 官方提供的 runtime-tools 集成测试,验证 OCI 合规性 |
integration-test | test-contest | 运行 OCI runtime 测试的 Rust 移植版(contest),弥补 Go 版测试在本地运行上的问题 |
三个目标可以一次性执行just test-all(当前等价于test-basic test-features test-oci containerd-test的组合,其中test-basic又由test-unit与test-doc组成),也可以按需单独运行其中任意一个。下面逐一拆解每一级的实现与原理。
第一级:单元测试(test-unit)
justfile 中test-unit的实际命令是:
scripts/cargo.sh test --lib --bins --all --all-targets --all-features --no-fail-fast -- --test-threads=1注意两个关键点:
--test-threads=1串行执行:容器运行时涉及大量系统级副作用(cgroup 目录、挂载点、命名空间等),串行能避免并发测试互相干扰;- 经由 scripts/cargo.sh 调用 cargo:该包装脚本会读取
CARGO_BUILD_TARGET环境变量——当目标与宿主机一致时直接用cargo,否则自动切换到cross(在容器中交叉编译),并且在使用 cross 时设置CROSS_CONTAINER_OPTS,以特权模式挂载/sys/fs/cgroup、/run、/tmp等路径,保证 cgroups、dbus 相关测试在容器内也能真实运行。
单元测试分布在各个 crate 的源码文件中(例如crates/libcgroups/src/v2/、crates/libcontainer/src/下的各模块),遵循 Rust 惯例的#[cfg(test)] mod tests组织方式。此外test-doc目标会运行cargo test --doc校验文档示例,test-features则通过 scripts/features_test.sh 对 feature 组合做排列编译验证,防止条件编译出现遗漏。
第二级:OCI 官方集成测试(test-oci)
test-oci调用 scripts/oci_integration_tests.sh,其测试素材来自仓库内嵌的 OCI runtime-tools 验证套件(位于tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools,需要时用make runtimetest validation-executables构建)。脚本的核心逻辑:
- 维护一个
test_cases数组,逐条执行create/create.t、default/default.t、hooks_stdin、killsig、linux_cgroups_*、mounts、prestart/poststart/poststop(含_fail变体)、process_capabilities、state等测试用例; - 每个用例以
sudo RUST_BACKTRACE=1 RUNTIME=... validation/<case>方式运行,输出写入log目录,并检查日志中是否出现not ok判定失败; - 脚本对宿主机环境做前置检查:例如 memory/hugetlb 用例要求存在
/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes,delete_only_create_resources要求存在/sys/fs/cgroup/pids/cgrouptest/tasks,不满足则跳过并说明原因; - 部分用例被注释掉并留有明确注释:如
linux_cgroups_relative_blkio(内核 5.0 移除相关特性,runc 也不通过)、linux_process_apparmor_profile(需要预装特定 AppArmor profile)、misc_props(runc 同样失败)等;而delete、hooks、kill、seccomp等用例则注明"已在 contest 中实现",避免重复执行。
这套 Go 测试是当前检验 youki 是否符合 OCI Runtime Spec 的标准手段。
第三级:Rust 移植的集成测试(contest)
由于 OCI 原版集成测试用 Go 编写,开发者需要同时维护 Rust 与 Go 两套语言环境,且其输出解析还曾依赖 Node.js;更麻烦的是部分用例在特定本地系统上运行困难。因此 youki 团队将这些测试移植到了 Rust,形成contest套件,其说明文档见 tests/contest/contest/README.md。
test-contest目标会先构建youki-release与contest两个二进制,再调用 scripts/contest.sh。该脚本会:
- 根据
uname -m挑选架构对应的 OCI bundle(tests/contest/contest/bundle-<arch>.tar.gz,找不到则回退 x86_64 的bundle.tar.gz),bundle 内包含运行测试所需的 rootfs 与默认 config.json; - 执行
${ROOT}/contest run --runtime <runtime> --runtimetest ${ROOT}/runtimetest运行全部测试;也可以追加-t test-grp-1::test-1,test-2 ...只跑指定测试组/用例; - 从
test.log中统计not ok判定失败。
测试框架:contest构建在仓库自研的轻量测试框架之上(详见 tests/contest/test_framework/README.md),核心抽象包括:
TestResult:类似Result的枚举,Ok无关联值,另有Skip变体表示跳过;- trait
Testable(最小测试单元)与TestableGroup(测试组),分别要求实现get_name/can_run/run与get_name/run_all/run_selected; - 预置结构体
Test、ConditionalTest(带条件判断的无状态测试)、TestGroup(无状态测试组)与TestManager(统一调度与打印结果)。默认结构体并发运行测试,因此适用于 HugeTLB 这类无状态用例;而 lifecycle、create 等有状态用例则通过自定义结构体实现TestableGroup来控制执行顺序(create 必须先于 start 等)。
两个实战陷阱(详细分析见 e2e/integration_test.md):
create_container必须wait()而非wait_with_output():youki create会 fork 出子进程,该子进程等待后续youki start发来启动信号后才 exec 容器程序;如果只创建而不启动,对create进程调用wait_with_output()会永久挂起。正确做法是保留返回的Child,在调用 start 之后再wait_with_output()获取容器内进程的 stdout/stderr(这与 runc 的 detached pass-through 模式一致);- 容器内断言靠
test_inside_container+ runtimetest:需要验证容器内部约束时,先把runtimetest二进制设为容器进程(见 tests/contest/runtimetest),测试函数等待其结束后检查 stderr 是否为空——非空即视为失败。
更上层的端到端测试
除上述三级之外,仓库还维护了面向容器生态的端到端测试,概览见 e2e/e2e_tests.md,并在 justfile 中提供了对应目标:
containerd-test:让 containerd 的集成测试跑在 youki 之上(Vagrant + 预置脚本);test-runc-comp:验证 youki 与 runc 的兼容性;test-rootless-podman:验证 Podman rootless 模式下 youki 可用;test-kind/test-kind-deploy:用 Kind 拉起 Kubernetes 集群,验证 youki 作为 k8s 容器运行时的表现(tests/k8s 下提供了 runtimeclass 与部署清单)。
资源:理解底层原理的两大知识库
OCI:容器互操作的标准来源
Open Containers Initiative(OCI)是一个致力于为操作系统级虚拟化提供标准化规范的项目。只要组件遵循 OCI 规范(尤其是 Runtime Specification),彼此就能无缝互操作,新应用的开发也因此变得容易——youki 能在 Docker 中替代 runc,正是因为 Docker、runc、youki 三者都实现了同一套标准接口。规范的核心文档是 runtime.md(描述容器生命周期、状态机与运行时命令),建议在动手开发前通读。开发中涉及某个命令或字段语义不明确时,回到 OCI 规范核对定义,是最稳妥的做法。
Linux man pages:内核接口的权威参考
youki 与 Linux 内核的底层编程接口打了大量交道——命名空间(clone、unshare)、cgroups、mount(pivot_root、mount)、seccomp(seccomp(2))、信号与进程管理等。在线 man pages 项目(man7.org)提供了这些内核特性的详尽说明:包括接口的行为、用法及设计缘由。无论是阅读 youki 源码时遇到陌生系统调用,还是调试容器行为异常,都可以用man <feature-name>快速查阅(例如man 2 clone、man 2 pivot_root)。对照 man pages 理解"为什么这样做",是吃透容器底层原理最有效的路径。
结语:从文档到代码的进阶路线
本文对应开发者文档的 basics.md,属于 youki 开发的"公共基础"章节。以此为起点,可以进一步阅读 unwritten_rules.md 了解社区沉淀的开发约定,通过 good_places_to_start.md 找到适合新手的切入方向(文档注释补充、集成测试移植等都是不错的起点),并借助 crate_specific_information.md 深入每个 crate 的专属资料。开发流程闭环是:just youki-dev编译 →just test-unit快速自检 →just test-contest <用例>定向验证 → 全量just test-all。祝开发顺利!
- 容器运行时
- 云原生
【免费下载链接】youki
A container runtime written in Rust
相关推荐
secrets-gradle-plugin完全解析:从安装到高级配置的7个实用技巧
secrets gradle plugin完全解析:从安装到高级配置的7个实用技巧 secrets gradle plugin是一款专为Android项目设计的
Ingress NGINX Controller 开发入门:从环境搭建到测试运行全指南
Ingress NGINX Controller 开发入门:从环境搭建到测试运行全指南 本文是一篇面向开发者的实战入门指南,讲解如何在本地为 Kubernete
后端API网关负载均衡云原生youki 各 Crate 详解:容器运行时工作区中的模块分工、核心原理与开发指南
youki 各 Crate 详解:容器运行时工作区中的模块分工、核心原理与开发指南 本文基于 youki 开发者文档中的 Crate Specific Info
容器运行时云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考