电子说
这是今年开源之夏活动中,陈轶阳同学参加 runk 项目的总结文档,主要介绍了 runk 的由来,以及如何基于现有 kata agent 组件来实现一个标准的 OCI runtime。
1. runk背景
kata-agent是在虚拟机 (VM) 中运行的进程,作为管理容器和在这些容器中运行的进程的主管。换句话说,kata-agent 是 VM 内部的一种“低级”容器运行时,因为agent根据 OCI 运行时规范生成和运行容器。但是,kata-agent 没有运行时规范中定义的 OCI 命令行界面 (CLI)。 kata-runtime 提供了 Kata Containers 运行时组件的 CLI 部分,但 kata-runtime是一个容器运行时,用于创建在主机上运行的硬件虚拟化容器。
ManaSugi[1]发起了实验性质的 runk 项目。runk 是一个基于 Rust 的标准 OCI 容器运行时,它管理传统的容器。 runk 旨在成为现有 OCI 兼容容器运行时的替代方案之一。 kata-agent具有容器运行时所需的大部分功能,并且由于使用 Rust 语言实现,因此具有高性能和低内存占用的特点。runk 利用 kata-agent 的机制来避免重新发明轮子。
目前,runk仍然是实验性质的工具,有一部分功能还有待实现。根据 ManaSugi 提交的 Proposal[2],截止到目前,runk支持的功能如下:
Feature | Status |
crictl | |
Docker | |
Podman | |
OCI commands (state/create/start/kill/delete) | |
run command | |
spec command | |
exec command | |
list command | |
ps command | |
pause/resume commands | |
update command | |
events command | |
init command | |
checkpoint/restore commands (CRIU) | |
Foreground terminal mode | See therunc modes[3]. |
Cgroups v1 | |
Cgroups v2 | |
Systemd Cgroups | |
Namespaces | |
no pivot_root | kata-agent 支持该功能,但是runk还不支持。 |
Capabilities | |
Seccomp | |
AppArmor | WIP on#2227[4] |
SELinux | |
Rlimit | |
Readonly path | |
Masked path | |
Hooks | |
Rootless |
我在这次CCF开源夏令营中新增实现的功能有list/ps/exec/pause/resume子命令。
2. 新增子命令的作用和实现
2.1 list
list子命令用于列出当前运行的容器,列出的基本信息包括容器ID、进程PID、状态、Bundle、创建时间、所有者。运行效果如下:
$ sudo runk list container1 ID PID STATUS BUNDLE CREATED OWNER k1 0 stopped /home/cyyzero/workspace/test/runk/bundle 2022-11-04 07:41:47.489394784 UTC root
实现方式是遍历root目录下的所有子目录,然后读取state.json文件,解析出容器的基本信息并打印输出。
2.2 ps
ps子命令用于列出容器内的进程信息。运行效果如下:
$ sudo runk ps container1 PID TTY TIME CMD 1 ? 00:00:00 sh 2 ? 00:00:00 sleep 3 pts/0 00:00:00 ps
实现方式是首先通过cgroup来获取容器内所有的pid;然后利用ps -ef命令搜集操作系统上所有进程的信息。最后通过pid的比对,将容器进程相关的信息打印出来。
2.3 exec
exec子命令用于在容器内执行一个新的进程,它允许指定启动进程的命令行参数、环境变量、cwd等信息。新进程会通过setns系统调用来加入到容器的namesapce中,同时也会加入容器所在的cgroup进程集合。
它与已实现的create和run命令类似,依赖rustjail包里的LinuxContainer类。LinuxContainer类是agent启动容器的核心类,针对每个容器会生成一个LinuxContainer实例,并通过这个对象来管理整个容器的生命周期,包括创建、启动、停止、删除等一系列操作。runk也依赖LinuxContainer类来启动容器进程。目前只在两个场景下使用:创建初始容器进程(对应create/run命令)和在已创建的容器中再启动额外进程(对应exec命令)。针对这两种启动进程的方式,我抽象出了两个类,InitContainer和ActivatedContainer,它们能够生成ContainerLauncher类的对象来启动进程。
运行方式如下:
# --pid-file 用于输出启动进程的进程号,--env用于指定环境变量,--cwd用于指定工作目录 $ sudo runk exec --pid-file container1.pid --env ENV1=test --cwd / container1 ls -l
2.4 pause/resume
pause/resume利用了cgroup的freezer子系统,可以挂起或者恢复cgroup集合中的进程。
在cgroup_rs包中,已经封装好了对于freezer子系统的操作,目前支持cgroup v1和cgroup v2。核心代码如下,可以很简单地改变容器的freezer状态。
pub fn freeze(cgroup: &cgroups::Cgroup, state: FreezerState) -> Result<()> { let freezer_controller: &FreezerController = cgroup .controller_of() .ok_or_else(|| anyhow!("failed to get freezer controller"))?; match state { FreezerState::Frozen => { freezer_controller.freeze()?; } FreezerState::Thawed => { freezer_controller.thaw()?; } _ => return Err(anyhow!("invalid freezer state")), } Ok(()) }
3. 遇到的特殊问题
在cgroup v1,处于frozen状态的进程无法处理信号,所以对于kill命令,需要先将容器解除frozen状态,然后再发送信号。详情可以参考 runc 仓库的讨论[5]。
4. 测试
目前,runk除了rust自带的单元测试外,还添加了集成测试。集成测试的目的是验证runk的功能是否正常,以及runk与containerd的交互是否正常。集成测试的代码在kata-containers/tests仓库的integration/containerd/runk/runk-tests.sh文件。测试会利用containerd自带的调试工具ctr来调用runk,比如典型的容器启动命令如下:
# --runc-binary 用于指定runk的路径,从而使用runk而非默认的runc作为 OCI runtime sudo ctr run --pid-file ${PID_FILE} --rm -d --runc-binary ${RUNK_BIN_PATH} ${CONTAINER_ID}
5. 总结展望
在runk开发的过程中,我学习到了安全容器的基本架构,阅读了一些容器相关的源码(kata agent/runc/youki),并辅以动手编码,加深了对 OCI runtime 细节的了解。在参与 ·kata· 社区的定期周会以及 GitHub issue 讨论中,我学习到了开源社区的工作模式,也体验到了开源社区的友好氛围。在未来,我希望能够继续参与 kata 社区的开发,为 kata 社区的发展做出贡献。短期目标来看,我会继续专注 runk ,补全其他特性的开发,并持续跟进 runk 的测试,最终让 runk 成为一个完善的 OCI runtime。
最后,感谢一直以来给予指导和review代码的刘斌导师(@liubin)和Manabu Sugimoto(@ManaSugi)。
6. 个人介绍
我是来自中国科学院计算机网络信息中心的研究生陈轶阳,研究方向是超算环境的容器应用。机缘巧合下从隔壁软件所举办的开源之夏活动中知道了kata社区,并最终参加了GLCC开源夏令营,并做了一点微小的工作。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !