- 容器运行时
- 云原生
【免费下载链接】youki
A container runtime written in Rust
youki 本身是一个 Cargo workspace,它把 OCI 容器运行时所需的底层能力拆分成多个可独立复用的子 crate——libcgroups、libcontainer、liboci-cli、libseccomp等,最终由 youki 二进制将其组装成完整的容器运行时。本文以 docs/src/user/crates.md 为骨架,逐一剖析这些 crate 的模块划分、核心接口与底层实现,并说明如何在自己的项目中把 youki 的子 crate 当作依赖直接使用。
从一个 Cargo workspace 说起
从仓库根目录的 Cargo.toml 可以看到,youki 采用标准 Cargo workspace 组织方式:
[workspace] resolver = "2" members = ["crates/*", "tests/contest/*", "tools/*"] exclude = ["experiment/seccomp", "experiment/selinux", "tools/youki-deploy"]workspace 成员分为三类:
crates/*:核心运行时库,即本文的主角libcgroups、libcontainer、liboci-cli与libseccomp;tests/contest/*:集成测试与测试框架(如contest、runtimetest、test_framework);tools/*:辅助工具(如wasm-sample、youki-deploy)。
被exclude的experiment/目录是独立实验项目(seccomp、selinux),不参与主 workspace 构建。[workspace.dependencies]则统一管理各 crate 共享的第三方依赖版本,例如oci-spec = "0.10.0"(带runtimefeature)、clap = "4.5.13"、nix = "0.31.3"等。
这样设计的直接好处是:
- 职责单一:每个 crate 只解决一个领域问题(cgroups、容器生命周期、CLI 解析、seccomp FFI);
- 可复用:你不需要引入整个 youki,就能在自己的项目里使用
libcgroups管理 cgroups、或用liboci-cli解析 OCI 命令行参数; - 可裁剪:
libcgroups的 v1/v2/systemd 能力均通过 feature 开关启用(见后文),你可以按需组合。
关于如何把某个子 crate 添加为自己的依赖,可参考 Basic Usage;更完整的入门流程见 Basic Setup。
libcgroups:cgroups 的统一操作层
libcgroups是处理 Linux cgroups 的 crate,它封装了读写 cgroup 文件的底层细节,并提供一系列表示 cgroup 数据的结构体,对外暴露一个易用的操作接口。它对应的源码位于 crates/libcgroups,入口 crates/libcgroups/src/lib.rs 声明了以下模块:
commonstatssystemdtest_managerv1v2
注意lib.rs中 v1/v2/systemd 三个模块都通过#[cfg(feature)]条件编译:启用 feature 时使用真实实现,未启用时则映射到 crates/libcgroups/src/stub 目录下的 stub 实现,这样即便你不编译 v1 或 systemd 支持,crate 依然能通过编译。
common:通用能力与 manager 抽象
common模块(crates/libcgroups/src/common.rs)提供与具体 cgroup 版本无关的通用功能。
首先是CgroupManagertrait,它是所有 cgroup manager 的统一接口,定义了六个核心操作(见源码第 34–54 行):
add_task(pid):把指定 pid 的任务加入 cgroup;apply(controller_opt):应用资源限制;remove():删除 cgroup;freeze(state):控制 freezer cgroup 状态(FreezeState枚举:Undefined/Frozen/Thawed);stats():获取 cgroup 统计信息;get_all_pids():获取属于该 cgroup 的所有 pid。
围绕 trait,common还提供了一组文件读写与系统探测函数:
write_cgroup_file_str/write_cgroup_file:向 cgroup 文件写入数据(字符串或任意ToString类型);read_cgroup_file:读取指定 cgroup 文件内容;get_cgroup_setup_with_root(root_path)/get_cgroup_setup():探测系统是 cgroup v1(Legacy)、v2(Unified)还是混合(Hybrid)模式。前者接受自定义 cgroup 根路径,后者使用默认根路径/sys/fs/cgroup(常量DEFAULT_CGROUP_ROOT,见源码第 19 行);create_cgroup_manager_with_root(root_path, config)/create_cgroup_manager(config):根据探测结果返回对应的 cgroup manager,root_path传None时行为与create_cgroup_manager一致(默认/sys/fs/cgroup)。
从源码可以看到 manager 的创建逻辑(第 352–383 行):系统为 Legacy/Hybrid 时创建 v1 manager;Unified 时如果 cgroup 路径是绝对路径或未启用 systemd cgroup,则创建 v2 manager,否则创建 systemd manager。返回值统一包装为AnyCgroupManager枚举(Systemd/V1/V2三态,见第 67–71 行),它也实现了CgroupManager,通过 match 分派到具体实现——这是典型的「统一接口 + 具体分派」设计,让上层调用方完全不需要关心底层是哪种 cgroup。
stats:统计信息模型与解析工具
stats模块(crates/libcgroups/src/stats.rs)定义 cgroup 统计数据及其解析函数,核心是Stats结构体,内部包含:
CpuStats:CPU 使用量与 throttling(节流)信息;MemoryStats:内存、swap、内存+swap 合计、内核内存、内核 TCP 内存及其他内存统计;PidStats:当前活跃 pid 数与允许的 pid 数;BlkioStats:块设备 IO 相关统计,如设备间传输字节数、IO 操作次数、设备访问与队列信息;HugeTlbStats:HugeTLB 统计,如 usage、max_usage 与 fail count。
此外还提供supported_page_size()(返回系统支持的 hugepage 大小)以及一组 cgroup 文件解析工具:
parse_single_value:读取只含单个值的文件并返回值;parse_flat_keyed_data:解析 flat keyed 格式(key value)数据;parse_nested_keyed_data:解析嵌套 keyed 格式(key + value 列表)数据;parse_device_number:解析设备的 major/minor 号。
这些解析函数是后面 v1/v2 各控制器(cpu、memory、blkio 等)读取统计文件的基础。
systemd:与 systemd 深度集成
systemd模块(crates/libcgroups/src/systemd)是 youki 与 systemd 交互的通道:
booted():检测系统是否由 systemd 引导;controller_type子模块:ControllerType枚举,用于标记系统可用的 cgroup 控制器;manager子模块:Manager结构体是 systemd 场景下的 cgroup manager,内部保存 cgroup 根路径、特定 cgroup 路径、与 systemd 通信的 client 等信息,同样实现CgroupManagertrait;dbus-native子模块:dbus 连接的原生实现,用于 rootless(无 root)模式下与 systemd 交互。
test_manager:测试替身
test_manager模块暴露TestManager结构体(crates/libcgroups/src/test_manager.rs),同样实现CgroupManager,作为 cgroup 测试的 dummy 实现,方便在单元测试中模拟 cgroup 行为而无需真正操作系统 cgroup。
v1 与 v2:不同 cgroup 版本的专属实现
v1(crates/libcgroups/src/v1)与v2(crates/libcgroups/src/v2)分别包含 cgroup v1 与 v2 的实现,都提供各自的 manager(v1::manager::Manager、v2::manager::Manager)以及本版本的实用函数:
- v1:
get_mount_points、get_subsystem_mount_points等,并包含 blkio、cpu、cpuset、devices、memory、pids 等控制器实现; - v2:
get_mount_points、get_available_controllers等,并包含 cpu、cpuset、io、memory、pids 等控制器实现。
此外,v2模块还包含devices子模块(crates/libcgroups/src/v2/devices),提供与 eBPF 相关的设备控制功能:加载 bpf 程序、查询 bpf 程序信息、把 bpf 程序 attach/detach 到 cgroup 等。这是 cgroup v2 设备控制(cgroupsv2_devicesfeature)的实现基础,对应的libbpf-sys、rbpf依赖声明在 Cargo.toml 的 workspace dependencies 中。
libcontainer:容器生命周期与运行时的核心
libcontainer(crates/libcontainer)提供创建与管理容器的功能,youki 自身正是用它来管理和控制容器的。其入口 crates/libcontainer/src/lib.rs 声明了 20 余个模块,按职责可分为几大类。
生命周期与核心流程
container:整个容器模块的核心,包含创建、启动、停止、删除等生命周期相关的子模块与结构体,对应源码在 crates/libcontainer/src/container,如builder.rs、container_start.rs、container_delete.rs等;process:与进程相关的模块,包括 fork 进程、设置 namespace、以正确 namespace 启动容器进程,源码见 crates/libcontainer/src/process;rootfs:处理 rootfs——容器获得的最小文件系统,见 crates/libcontainer/src/rootfs。
隔离与安全
namespaces:Namespaces结构体,处理 namespace 的创建与应用;user_ns:在新建 user namespace 中运行容器,rootless 容器(无需 root 权限运行)通常使用它;seccomp:为容器进程配置 seccomp 过滤器,内部使用libseccompcrate 实现,该模块通过libseccompfeature 启用(见 lib.rs 第 13–14 行);apparmor:与 AppArmor(Linux 内核安全模块,按程序 profile 控制能力)相关的函数;capabilities:设置/重置特定 capability、为容器进程剥离多余特权的函数。
配置、通信与抽象层
config:暴露YoukiConfig结构体,它只保存config.json中启动/管理容器所需的子集。相比反复解析和传递整个config.json,传递更小的YoukiConfig性能更好;hooks:run_hooks函数,按 oci-spec 定义运行各类容器生命周期 hook;notify_socket:NotifyListener结构体,内部用于 youki 主进程与 fork 出的容器进程之间通信;signal:unix 信号的简单封装,方便从信号名或信号号解析信号;syscall:提供Syscalltrait,抽象需要调用 libc 函数的功能,让库的其他部分无需关心底层实现细节;tty:为容器进程设置 tty;utils:工具函数集合,如parse_env(解析环境变量)、get_cgroups_path、create_dir_all_with_mode等。
另外,lib.rs 第 24–28 行有一个值得注意的设计:由于libcontainer的 API 使用了位于其他 crate 的oci_spec,它选择pub use oci_spec;重新导出自己依赖的 oci_spec 版本,避免使用者因 oci_spec 版本不一致导致类型不匹配。
从 crates/libcontainer/Cargo.toml 可以看出,它还包含 network(网络设备与地址管理,基于 netlink)、workload(wasm 工作负载执行)等模块;workload 下的 executor 与 wasmer/wasmtime/wasmedge 支持使得 youki 能运行 WASM 负载,详见 Webassembly。
liboci-cli:OCI 运行时命令行参数的结构化定义
liboci-cli(crates/liboci-cli)为 OCI 容器运行时提供命令行参数的结构体定义,完全遵循 OCI Runtime Command Line Interface 规范。所有暴露的结构体都 derive 自clap::Parser,可以直接用于解析 OCI 命令行参数。入口 crates/liboci-cli/src/lib.rs 把子命令分成两组:
StandardCmd(标准命令,OCI CLI 规范明确规定):create、start、state、kill、delete;CommonCmd(规范未定义但 runc/crun 等运行时普遍支持的命令):checkpoint、events、exec、features、list、pause、ps、resume、run、update、spec。
此外还定义了GlobalOpts结构体(全局选项),包括:
--log/-l:指定日志文件(默认/dev/stderr);--debug:把日志级别改为 debug(--log-level优先级更高);--log-format:日志格式(text(默认)或json);--root/-r:存放容器状态的根目录;--systemd-cgroup:启用 systemd cgroup manager 而非直接使用 cgroupfs。
youki 的二进制入口(crates/youki/src/main.rs)正是基于这些结构体完成参数解析后分派到各命令实现(crates/youki/src/commands)。
各子命令在主流运行时中的支持情况
原文档给出了一张对照表,反映各子命令在 liboci-cli、OCI CLI 规范以及 runc、crun、youki 中的支持情况(✅ 表示支持,空白表示未实现):
| 命令 | liboci-cli | CLI 规范 | runc | crun | youki |
|---|---|---|---|---|---|
| create | ✅ | ✅ | ✅ | ✅ | ✅ |
| start | ✅ | ✅ | ✅ | ✅ | ✅ |
| state | ✅ | ✅ | ✅ | ✅ | ✅ |
| kill | ✅ | ✅ | ✅ | ✅ | ✅ |
| delete | ✅ | ✅ | ✅ | ✅ | ✅ |
| checkpoint | ✅ | ✅ | |||
| events | ✅ | ✅ | ✅ | ||
| exec | ✅ | ✅ | ✅ | ✅ | |
| list | ✅ | ✅ | ✅ | ✅ | |
| pause | ✅ | ✅ | ✅ | ✅ | |
| ps | ✅ | ✅ | ✅ | ✅ | |
| restore | ✅ | ✅ | |||
| resume | ✅ | ✅ | ✅ | ✅ | |
| run | ✅ | ✅ | ✅ | ✅ | |
| spec | ✅ | ✅ | ✅ | ✅ | |
| update | ✅ | ✅ |
从源码看,liboci-cli/src/lib.rs中StandardCmd只包含规范明确要求的 5 个命令,其余命令归入CommonCmd,与表格中「CLI 规范」一列恰好对应。值得注意的是,youki 当前未实现checkpoint/restore/update,尽管 crates/youki/src/commands 中存在对应的源码文件——这说明 youki 的命令能力仍在持续补齐中。
libseccomp:libseccomp 的 Rust FFI 绑定
libseccomp(crates/libseccomp)提供对 libseccomp C 库的 Rust FFI 绑定。它基于 rust-bindgen 从 C 头文件生成,并手工修复了 rust-bindgen 处理 C 函数宏时产生的一些问题。这个 crate 主要被libcontainer的 seccomp 模块使用,用于为容器进程构建 seccomp 过滤器;libcontainer的seccomp模块与seccomp_listener进程模块共同支撑了 seccomp 通知(notify)机制,相关测试与 fixture 配置见 crates/libcontainer/src/seccomp/fixture/config.json。
各 crate 如何组装成 youki 二进制
要理解这些 crate 的分工,最直接的方式是看 youki 二进制的依赖声明(crates/youki/Cargo.toml):
[dependencies] libcgroups = { path = "../libcgroups", default-features = false, version = "0.7.0" } libcontainer = { path = "../libcontainer", default-features = false, version = "0.7.0" } liboci-cli = { workspace = true } libseccomp = "0.4.0" # 经 libcontainer 的 libseccomp feature 引入youki 的 feature 定义把各子 crate 的能力透传出来:
[features] systemd = ["libcgroups/systemd", "libcontainer/systemd", "v2"] v2 = ["libcgroups/v2", "libcontainer/v2"] v1 = ["libcgroups/v1", "libcontainer/v1"] cgroupsv2_devices = ["libcgroups/cgroupsv2_devices", "libcontainer/cgroupsv2_devices"] seccomp = ["libcontainer/libseccomp"]一条典型的 youki 命令执行链路大致是:
main.rs用liboci-cli解析create/start/run等子命令参数;- 根据参数调用
libcontainer构建容器:解析config.json生成YoukiConfig、准备 rootfs、配置 namespaces/capabilities/seccomp; - 通过
libcgroups的create_cgroup_manager探测系统 cgroup 版本并创建对应 manager,调用apply写入资源限制、add_task注册进程、stats上报统计; libseccomp为容器进程安装 seccomp 过滤器。
这也解释了为什么用户文档 Basic Usage 中的日志级别说明与liboci-cli的GlobalOpts一一对应:youki在 release 构建下默认日志级别为error,debug 构建下为debug,可用--log-level覆盖;为兼容 runc/crun 提供了--debug标志(若同时设置--log-level则忽略--debug)。
在你的项目中使用这些子 crate
crates.md明确说明:youki 二进制依赖这些子 crate 提供底层能力,你也可以把其中任何一个作为自己项目的依赖。以在Cargo.toml中引入libcgroups为例:
[dependencies] libcgroups = { path = "/path/to/youki/crates/libcgroups", version = "0.7.0" }按需启用 feature:
[dependencies] libcgroups = { path = "/path/to/youki/crates/libcgroups", version = "0.7.0", features = ["v2", "systemd"] }结合前文可以形成如下选型建议:
- 需要读写 cgroups 文件、管理容器资源限制与统计 → 使用
libcgroups,通过CgroupManagertrait 与create_cgroup_manager获得与系统 cgroup 版本无关的统一入口; - 需要实现完整的 OCI 容器生命周期 → 使用
libcontainer,并注意它 re-export 的oci_spec版本需要与你的代码一致(lib.rs 第 26–28 行的设计正是为了避免版本错配); - 需要开发一个符合 OCI CLI 规范的运行时命令前端 → 直接使用
liboci-cli的StandardCmd/CommonCmd枚举与GlobalOpts,配合clap即可完成参数解析; - 需要与 libseccomp 交互(如自研安全沙箱)→ 使用
libseccomp。
更完整的独立使用 youki 的方式(含 rootful/rootless 两种模式、与 Docker 集成的daemon.json配置、以及youki spec生成默认config.json的流程)请参见 Basic Usage;各 crate 更深层的源码导读见开发者文档 Crate Specific Information。
小结
youki 的模块化 crate 设计遵循了清晰的「分层 + 接口抽象」思路:liboci-cli解决命令行入口、libcontainer解决容器生命周期、libcgroups通过CgroupManagertrait 屏蔽 v1/v2/systemd 差异、libseccomp提供安全过滤的 FFI 绑定。这套设计不仅让 youki 二进制本身结构清晰,也让开发者能够按需复用其中的任何一层。正如crates.md所述,目前各 crate 最权威的细节说明仍在源码本身,本文所引用的模块、接口与实现位置可以作为你深入源码的起点。
- 容器运行时
- 云原生
【免费下载链接】youki
A container runtime written in Rust
相关推荐
youki架构深度解析:理解Rust容器运行时的核心设计
youki架构深度解析:理解Rust容器运行时的核心设计 youki是用Rust语言实现的OCI容器运行时,它遵循开放容器倡议标准,提供了高效、安全的容器管理能
容器运行时云原生如何利用CSYuTuiMian2024精准把握100+院校申请节点?保研小白速成指南
如何利用CSYuTuiMian2024精准把握100+院校申请节点?保研小白速成指南 CSYuTuiMian2024是2024年计算机保研预推免通知的汇总平台,
liboci-cli 深度解析:youki 容器运行时的 OCI 命令行参数解析层
liboci cli 深度解析:youki 容器运行时的 OCI 命令行参数解析层 youki 是一个用 Rust 编写的 OCI 容器运行时。在它的多 cra
容器运行时云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考