容器(Container)

定义

容器是宿主机上的一组普通进程。内核通过 namespace 限制这些进程可见的文件系统、网络栈、进程号与主机名,通过 cgroup 限制其 CPU、内存与设备用量。容器不拥有独立内核,系统调用直接进入宿主内核。

问题背景

软件部署需要同时满足三件事:依赖可复现、实例之间互不干扰、启动与资源开销足够低。早期常见做法有两类。

第一类是在每台机器上手工安装库与配置。结果依赖宿主发行版、路径与版本,难以复现。第二类是为每个应用启动完整虚拟机。边界清楚,但每个实例都要启动客户机内核并模拟硬件,冷启动以秒计,内存占用也高。

容器把「可复现环境」从整机下沉到进程:环境由镜像固化,隔离由内核提供,因而可以在一台机器上同时运行大量实例。

机制说明

理解容器,只需抓住三个对象。

进程。 操作系统调度的基本单位是进程。容器启动后,真正在运行的是镜像指定的入口进程(以及它拉起的子进程)。入口进程退出,容器生命周期即结束。所谓「容器在运行」,指的就是这组进程仍处于可调度状态。

视野隔离。 namespace 改变进程「看见什么」。例如 mount namespace 使容器拥有独立的挂载表,于是可以把rootfs 挂到 /,而不改写宿主根目录;PID namespace 使容器内的入口进程看起来像 PID 1,却不会与宿主 PID 1 冲突。隔离的是可见性,不是内核本身。

资源限制。 cgroup 改变进程「能用多少」。没有 cgroup,一个容器可以占满宿主 CPU 或内存,从而影响同机其他工作负载。

由此可以得到一条判断标准:若两个工作负载共用同一个内核,它们之间的隔离至多是命名空间与控制组级别的隔离。内核漏洞、错误的特权配置,或把敏感宿主路径 bind mount 进容器,都可以削弱甚至取消这层隔离。这也是 Box 默认选择 MicroVM 的原因:默认路径要求硬件虚拟化边界,而不是共享内核边界。

容器的一次完整生命周期可以写成:

  1. 解析镜像引用,取得清单与层;
  2. 将层展开或叠加入 rootfs
  3. 创建 namespace 与 cgroup;
  4. 在新的根文件系统中执行入口命令;
  5. 进程退出后回收命名空间、控制组与临时文件。

exec 不是启动第二个容器,而是在同一组 namespace 中再创建一个进程。因此 exec 看到的文件系统、网络与进程树,与入口进程相同。

在 Box 中的位置

  • Box 将一次本地工作负载实例称为 box,持久化记录类型为 BoxRecord,定义于 src/runtime/src/box_record.rs
  • 省略 --isolation 时,Box 并不走本词条描述的共享内核路径,而是启动 MicroVM。共享内核路径必须显式写作 --isolation sandbox,见 Sandbox(共享内核)
  • 在 Sandbox 路径上,创建 namespace、cgroup 并启动进程的权威实现是 A3S OCI Runtime 的 native Linux driver。Box 负责产品状态、镜像准备与策略,不直接实现内核隔离原语。

验证命令

下列命令在合格 Linux 主机上比较客户机内核版本与宿主内核版本。二者相同,说明工作负载使用的是共享内核。

a3s-box run --rm --isolation sandbox alpine:3.20 -- sh -lc 'uname -r'
uname -r

省略 --isolation 再运行同一命令时,uname -r 通常报告客户机内核版本,与宿主不同。这是观察隔离边界最直接的方法。

相关与易混