跳到内容

第 7 卷 / 第 7 章 / 第 1 课

7.1 容器运行机制:镜像、隔离、资源与生命周期 ​

服务和实例不断增加后,靠人工复制目录、设置环境变量和启动进程已经无法保证结果一致。团队需要一种可重复交付进程及其依赖的方法,但“装进容器”是否等于得到一台轻量虚拟机?

本课讨论普通 Linux 容器:其中的进程受 namespaces、cgroups、capabilities 和文件系统视图约束,与承载它的 Linux 系统共享内核。Docker Desktop 中这个 Linux 宿主可能是一台虚拟机;容器与虚拟机可以分层使用。

镜像是不可变内容图 ​

OCI 镜像由 manifest、配置和只读 layers 组成。layer 按内容摘要寻址,可以在镜像之间复用。

下面的运行镜像示例要求构建上下文中已有可执行 app.jar。它只演示打包,不包含该项目的编译步骤。

dockerfile
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --chown=10001:10001 app.jar /app/app.jar
USER 10001:10001
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

示例基础镜像使用 tag 便于阅读,生产构建应固定审核过的基础镜像 digest,发布时也固定最终镜像 digest。以下省略号只是格式占位符,不能直接拉取:

text
registry.example/atlas@sha256:...

需要编译工具时,可用多阶段构建把工具链留在构建阶段;同时控制基础镜像内容、锁定依赖,并记录 SBOM 与签名。签名仍需校验可信签发者及策略,不能只检查“有签名”。删除密钥不会从旧 layer 中抹除它。排除构建上下文中的密钥;必须在构建时使用的凭据可通过 BuildKit secret mount 临时提供,避免写入 ARG、ENV 或输出文件。

Namespace 隔离视图 ​

PID、mount、network、UTS、IPC、user 等 namespace 让进程看到不同的系统资源视图。隔离不是安全边界的全部:内核漏洞、过宽 capability、宿主挂载和特权容器仍可能突破边界。

默认移除不需要的 Linux capabilities,使用 seccomp/AppArmor/SELinux、只读根文件系统和非 root 用户。不能因为进程在容器里就信任任意代码。

Cgroups 计量和限制资源 ​

  • 编排层 CPU request 用于调度资源核算,并可映射到 cgroup 的竞争权重;CPU quota/limit 则限制一个周期内可用 CPU 时间,可能触发 throttling;
  • cgroup v2 的 memory.max 是硬上限,回收仍不能满足分配时可能 OOM kill;memory.high 可通过回收和节制分配产生压力,但不能让超额内存像 CPU 时间一样轮流使用;
  • PID、IO 等也可以受控。

应用需要知道容器可用 CPU/内存,而不是宿主机总量。线程池、JVM heap 和缓存上限要与 cgroup 预算协调,在自身容器预算内留出 native memory、线程栈等空间。Sidecar 通常有独立容器预算,Pod 和节点层面还要计算所有容器的总需求。

Writable layer 是短暂状态 ​

可写层跟随容器对象:Docker 的 stop/start 或 restart 通常保留它,删除并重建容器才会换成新层。Kubernetes 重建容器时也不能依赖旧层中的文件。业务持久数据应写入有明确生命周期的 volume 或外部存储,日志通常输出 stdout/stderr。

Pod 的 emptyDir 可跨容器重启保留,但随 Pod 删除而消失;持久卷的保留又取决于 PVC/PV 与回收策略。容器本地会话、上传文件或队列可能在替换、迁移时丢失,也不会因扩容自动共享。只读根文件系统的应用若需要临时文件,应单独提供容量受限的临时挂载。

PID 1 与优雅终止 ​

容器的主进程需要处理终止信号;会派生子进程时,还需正确回收它们,必要时使用合适的 init 进程。上面的 exec-form ENTRYPOINT 让 Java 直接接收信号,避免多一层未正确转发信号的 shell。以 Kubernetes 正常终止为例:

  1. 记录 Pod 终止状态,异步更新端点,已有连接和缓存路由不会立刻消失;
  2. 若配置了 preStop,先执行它,耗时计入终止宽限期;
  3. 向容器主进程发送停止信号,通常为 SIGTERM,具体信号可由运行配置决定;
  4. 应用在剩余宽限期内排空并退出;
  5. 宽限期耗尽后仍未退出的进程会被强制终止。

应用应停止接新工作、排空在途请求、提交或放弃有明确语义的任务,然后退出。宽限期不是无限处理时间。

镜像启动成功不代表可运行 ​

上线前验证:

  • 以非 root 和只读文件系统运行;
  • CPU throttling、内存接近上限和 OOM 后的行为;
  • SIGTERM、连接排空和重复消息;
  • 基础镜像漏洞、签名和来源;
  • 临时目录、证书和时区等运行依赖;
  • 宿主架构和内核能力兼容性。

下一课把容器放进 Kubernetes 的声明式控制循环。

参考资料 ​

Built with VitePress | Software Systems Atlas