从零开始构建一个 Linux Container Runtime:Styrolite 介绍
Styrolite 介绍:从零开始构建一个 Linux Container Runtime
2025年3月26日
Edera Protect 是一套解决方案,旨在弥合现代云原生计算和基于虚拟化的安全技术之间的差距。为了支持这个平台,我们构建了自己的 container runtime,它被设计成一个微服务,允许以完全编程的方式运行容器,类似于 Kubernetes Container Runtime Interface (CRI) 如何通过微服务实现容器管理。
今天,我们很高兴地宣布,我们将开源我们的编程底层 container runtime——Styrolite,以便其他人可以从我们的工作中受益。
为什么要构建一个新的底层 container runtime?
将底层 container runtime 关注点分离到其自身的工具或微服务中的想法并不新鲜。除了 Kubernetes CRI(它将容器生命周期管理呈现为可插拔的微服务)之外,还有一些更简单的工具也提供了底层 container runtime,例如 util-linux 中的 unshare 实用程序,以及另一个名为 Bubblewrap 的工具。
但这些工具要么过于高级(如 Kubernetes CRI),要么旨在通过 shell 脚本使用:Bubblewrap 具有很高的可配置性,但只能通过非常复杂的 CLI 访问,并且很容易出错;而 util-linux 的 unshare 具有基本功能,但也隐藏在 CLI 之后。虽然 CLI 允许快速迭代,但我们需要一些不同的东西用于 Edera Protect:一个丰富的编程接口,用于精确地生成和管理容器。Styrolite 提供了两全其美的解决方案——一个可以直接从 Rust 使用的干净的 API,同时保留了 CLI 的快速迭代能力。
Styrolite 如何工作:Linux 容器的内部原理
重要的是,我们在设计 Styrolite 时充分意识到 Linux namespace 从未打算作为硬安全边界——这一事实解释了为什么容器逃逸漏洞不断出现。我们的方法承认这些限制,同时提供更强大的基础。
Linux 中的容器——与其他 容器化 努力 无关 ——建立在 Linux namespace 之上。Linux 中的 namespace 允许对给定类型的资源进行替代视图,例如:mount namespace 允许对挂载表进行替代视图,从而允许对根文件系统进行替代视图,而 PID namespace 允许对系统进程集进行替代视图。Container runtime 组合这些 namespace 以提供容器化环境,从而以更高的复杂性为代价实现了极大的灵活性。
所有 container runtime 的根源是 Linux unshare(2) syscall。这允许当前正在运行的进程从其一个或多个当前 namespace 解除关联到可以修改的 namespace 的 fork 版本中。通常,container runtime 默认情况下会 unshare 大多数 namespace,因为有必要这样做:
- 从主机 unshare Mount namespace,以便我们可以透视到容器镜像的可修改副本中,作为容器的根文件系统。
- 从主机 unshare PID namespace,以便容器化的工作负载无法查看容器外部的进程。
- 从主机 unshare IPC namespace,以便容器化的工作负载无法查看或操作与容器化的工作负载无关的 System V IPC 资源。
- 从主机 unshare User namespace,以便容器内部的 UID 和 GID 重新映射到主机 namespace 中更安全的 UID 和 GID。换句话说,在 user namespace 中运行时,容器内部的 root 通常以容器环境之外的非特权 UID 运行。
- 可以从主机 unshare Time namespace 以修改可见的系统正常运行时间。
- 可以从主机 unshare UTS namespace 以更改可见的系统主机名。
- 可以从主机 unshare Network namespace 以强制容器化的工作负载使用备用网络路径。
在 Edera Protect 平台上,所有这些 namespace 都在堆栈中的不同级别上 unshare。在 Edera Protect 平台内部使用时,Styrolite 主要负责处理 Mount、PID、IPC、User、Time 和 UTS namespace,而网络在平台的其他地方处理。
简单、可编程的接口
以下是 Styrolite 与传统方法相比如何简化容器创建的一个例子:
// 使用 Styrolite 创建一个基本容器let mut request = CreateRequestBuilder::new()
.with_rootfs("/path/to/container/rootfs")
.set_executable(“/bin/sh”)
.set_arguments(vec![“-i”])
.set_working_directory(“/”)
.push_namespace(Namespace::User)
.push_namespace(Namespace::Mount)
.push_namespace(Namespace::Pid)
.to_request();
// 在容器中启动一个进程let runner = Runner::new(“styrolite”);
runner.run(request)?;
这种简洁的接口使容器创建和管理更易于维护,并且比复杂的 CLI 命令或 shell 脚本更不容易出错。
实际应用
Styrolite 为几个重要的用例提供支持:
- 安全微服务: 在 Edera Protect 中,Styrolite 能够对安全关键型工作负载进行细粒度的容器隔离
- 应用程序沙盒: 我们的配套工具 styrojail 可帮助 Linux 用户限制资源消耗并提高 Web 浏览器等处理不受信任的输入的应用程序的安全性
- 自定义 CI/CD 环境: 开发人员可以使用 Styrolite 创建具有精确资源控制的隔离构建环境
性能与安全
Styrolite 旨在以最小的开销运行,提供与传统 CLI 方法相当或更快的容器初始化时间,同时提供关于容器状态的更强的编程保证。
我们的安全优先设计承认了 Linux namespace 的固有局限性,同时通过仔细的默认设置和显式安全控制提供了更强大的基础。
加入社区
当我们决定构建一个底层容器化工具供我们在 Edera Protect 中使用时,我们立即知道它会对更大的 OSS 社区有其他好处。我们欢迎社区的贡献:
- 在 GitHub 上给项目点赞并 fork
- 试用 Styrojail 进行应用程序沙箱处理
- 报告问题或提出改进建议
- 贡献代码或文档
我们致力于 Styrolite 的持续开发,并期待看到社区如何使用它进行构建!
作者
Ariadne Conill