☰
youki 的 Crates 架构:一个 Rust 编写的 OCI 容器运行时的模块化 crate 生态解析
2026/9/28 2:50:50 网站建设 项目流程
  • 容器运行时
  • 云原生

【免费下载链接】youki

A container runtime written in Rust

项目地址:https://gitcode.com/gh_mirrors/yo/youki
点击查看免费下载

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"等。

这样设计的直接好处是:

  1. 职责单一:每个 crate 只解决一个领域问题(cgroups、容器生命周期、CLI 解析、seccomp FFI);
  2. 可复用:你不需要引入整个 youki,就能在自己的项目里使用libcgroups管理 cgroups、或用liboci-cli解析 OCI 命令行参数;
  3. 可裁剪: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 声明了以下模块:

  • common
  • stats
  • systemd
  • test_manager
  • v1
  • v2

注意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-cliCLI 规范runccrunyouki
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 命令执行链路大致是:

  1. main.rs用liboci-cli解析create/start/run等子命令参数;
  2. 根据参数调用libcontainer构建容器:解析config.json生成YoukiConfig、准备 rootfs、配置 namespaces/capabilities/seccomp;
  3. 通过libcgroups的create_cgroup_manager探测系统 cgroup 版本并创建对应 manager,调用apply写入资源限制、add_task注册进程、stats上报统计;
  4. 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

项目地址:https://gitcode.com/gh_mirrors/yo/youki
点击查看免费下载

相关推荐

上一篇:BabelDOC技术评测:PDF文档翻译架构深度剖析与性能对比
下一篇:告别依赖混乱:用pipreqs为Apache Airflow工作流构建精准依赖清单的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询