← 系统设计与交付 · 第 2 篇

从容器到集群,哪些责任发生了变化

以无状态资料 API 区分镜像、实例、探针、调度资源和基础设施状态,检查容器与集群交付中的责任边界。

学完这一篇,你能做到

写出可丢弃实例的交付约束,验证资源预算、故障探针与最小云权限。

一个资料预览 API 需要读取对象存储里的文件,并返回可公开展示的摘要。它没有本地持久数据,适合练习容器化。把程序放进镜像只完成了交付的一部分:运行权限、进程健康、流量入口、资源预算与云资源变更仍需要明确负责人。

案例中的机器有八个 CPU 预算单位和十六个内存预算单位,两组实例分别申请不同资源。这里的数值是简化的教学单位,不代表某种云机器规格,也没有做吞吐量测试。

镜像与运行实例承担不同责任

镜像包含用户空间的程序、依赖和默认启动方式。容器是从镜像启动的受隔离进程,通常共享宿主机内核;虚拟机则有自己的操作系统内核。镜像可以减少依赖差异,但 CPU 架构、内核能力、运行配置和外部服务仍会影响实际行为。

镜像 tag 可以被重新指向另一份内容。需要追踪发布时,记录内容 digest、源代码提交、构建环境和依赖锁定结果。旧 tag 叫作 stable 并不能证明它还指向昨天验证过的镜像。尽量让测试和正式环境使用同一份已经构建的产物。

不要把密钥复制进构建目录,再在后一层删除。早期镜像层和构建日志仍可能保留内容。运行时通过受控方式注入配置与凭据,镜像只放公开可分发的程序。使用非 root 进程、减少可写目录和多余能力,可以缩小被利用后的影响范围;容器隔离不能替代完整安全设计。

无状态要求可以丢弃实例

资料服务需要能在任意新实例上继续运行。上传对象、任务结果和数据库记录要放在明确的外部持久系统里,本地临时文件只用于可以重建的中间过程。服务必须解释重启后临时文件消失的行为,而不能依赖某个实例一直活着。

请求结束、连接关闭和进程退出也有顺序。停止接收新请求后,应给正在处理的工作留出有限时间,再退出。长任务更适合转成有持久状态的后台任务;否则滚动发布会让用户不知道预览是否完成。外部存储需要单独的访问权限、备份策略和失败处理。

先在单个容器层面检查启动参数、配置缺失、端口绑定、日志输出和退出码。只在默认配置下启动成功,不能说明生产环境的网络与权限已经可用。本文的离线代码不启动 Docker,也不验证真实镜像。

编排器持续协调期望状态

Kubernetes 控制器观察实际状态,并尝试让它接近声明的期望状态,例如保持指定数量的 Pod。Pod 是调度与运行的基本单元;Service 提供稳定的访问方式,将流量导向符合条件的后端。容器重启、Pod 重建和迁移到另一节点是不同事件,不能把它们统称为同一种恢复。

readiness 表达实例当前是否应接流量,失败时应从可用后端中移出。liveness 用于发现需要重启才能恢复的状态。startup probe 适用于启动较慢的进程,避免尚未完成初始化就被其他探针反复判坏。探针接口自身也要便宜、可靠,并有合理的超时与失败阈值。

如果数据库短暂不可达就让所有实例 liveness 失败,重启并不能修复数据库,反而增加启动流量。应区分进程死锁、依赖异常和业务拒绝。readiness 失败也可能让全部实例退出服务,必须观察并设计降级行为,不能把探针当成无风险开关。

资源申请影响调度,限制影响运行

为服务估算 CPU 与内存请求,再选择限制和副本数量。请求表达调度时需要预留的资源;限制约束运行时的资源使用,两者含义不同。内存不足可能导致进程被终止,CPU 限制可能造成处理延迟。具体行为要结合运行环境测试。

下面只比较申请总量与一个总预算,不使用真实的 Kubernetes 调度器。它假定每个实例的资源字段完整,且数值非负。

capacity = {"cpu": 8, "memory": 16}
pods = [{"cpu": 2, "memory": 4}, {"cpu": 3, "memory": 6}]
def fits(items):
    return all(sum(p[resource] for p in items) <= maximum
               for resource, maximum in capacity.items())
assert fits(pods)
assert not fits(pods + [{"cpu": 4, "memory": 2}])
print(fits(pods))

实际输出是 True。已有两个实例共申请五个 CPU、十个内存单位,可以放进总预算。再加入申请四个 CPU 的实例,总 CPU 达到九,断言确认它超过八个单位。

总量满足只是必要检查。真实调度还涉及每个节点能否单独容纳某个 Pod、节点保留资源、污点、亲和性、可用区与存储约束。集群总内存充足,仍可能没有任何单个节点放得下一个大 Pod。扩容也要先检查下游容量,否则增加实例会扩大数据库压力。

基础设施代码需要状态与权限

Terraform 配置声明希望存在的资源,plan 展示预计变更,state 记录配置与实际资源之间的映射。配置保存在 Git 中便于审查,但 state 可能包含敏感信息,不能把它当成普通源码公开。状态存储要控制访问,并根据后端能力配置并发锁,防止多个操作者同时修改。

审查 plan 时逐项看创建、修改和销毁,确认环境、资源标识与依赖。应用层回滚不自动恢复已经删除的云资源。控制台手工修改会造成漂移,后续 plan 需要解释差异,再决定纳入配置还是恢复原状态。

云 IAM 则决定部署程序和运行程序分别能操作哪些资源。资料 API 只需要读取指定环境的对象范围;创建集群与修改权限的能力属于交付流程。优先使用可管理的工作负载身份和短期凭据,并确认具体平台支持的方式。一个通用管理员密钥会把多个责任边界合并。

练习:检验责任是否落到了具体机制

写一份资料 API 的交付说明,列出镜像 digest、启动参数、临时目录、外部存储、三类探针语义、资源请求与限制,以及运行身份可访问的范围。加入实例重建、依赖不可达、配置缺失和预算不足四个失败分支。

验收时,新实例应能仅靠镜像与受控配置启动;重建后不丢失已完成业务数据;依赖故障不会触发无限重启;未获授权的存储操作被拒绝。给离线预算函数增加缺失字段、负数和单节点约束检查,并记录它仍无法覆盖的调度条件。若随后实施真实部署,另外记录平台、配置和观测结果,不把离线断言算作集群验证。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。