三次替换的 Pod 使用同一镜像,依次选择蓝色、绿色和保留的蓝色配置。

ConfigMap 把配置与镜像分开

ConfigMap 把非敏感配置放在镜像之外,让环境相关设置发生变化时,不必重新构建应用产物。真正有用的边界需要明确:镜像提供代码,工作负载定义则指定这些代码应该读取哪份配置。因此,一次发布不仅是一个镜像标签,配置引用同样属于正在交付的行为。只记录镜像版本,可能无法解释两个环境为什么表现不同。 给配置一个可审阅的身份 ConfigMap 的 data 值是字符串,也可以通过 binaryData 表达二进制内容及其 API 编码形式。它适合小规模配置,数据大小有一 MiB 限制,不是通用文件仓库,更不是保护秘密的机制。API 能检查对象结构,却不知道某个应用参数在业务上是否合理。因此,结构合法和业务正确仍需分别验证,不能以创建成功代替配置审查。 Pod 可以通过环境变量引用或挂载文件消费配置。普通 Pod 引用要求 ConfigMap 位于这个 Pod 所在的命名空间。名称、必需键和应用解释方式都应明确。Deployment 对象被 API 接受,并不证明它引用的每个键都存在,也不证明新容器能够带着这些值成功启动。发布检查必须继续观察真实工作负载的结果。 单位、允许值与默认值应属于应用配置规范。某个字符串看起来像布尔值或数字,并不说明接收方会采用什么转换规则。在不同环境之间推广配置之前,需要结合真实消费者检查这些规则,避免同一份文本被解释成不同业务含义。 版本化的不可变 ConfigMap 更容易表达发布身份。settings-v2 可以与 settings-v1 同时保留,而 immutable 会禁止修改当前对象的数据。但这项保护不阻止有权限的操作者删除对象,再用同名重新创建另一份内容。对象生命周期权限和发布记录,应与希望实现的不可变承诺一致,不能把一个字段理解成所有可能路径都被永久封闭。 坏掉的样子 修改配置与替换进程是两种不同动作。已经注入运行中容器的环境值,不会因为 ConfigMap 更新而被改写。普通投射文件可能最终更新,但应用仍要重新读取;subPath 挂载和已经打开的文件又有不同生命周期。因此,API 中出现了新值,并不能直接宣布应用已经完成配置切换,还需要查看消费者实际读到了什么。 把 Deployment 的 Pod 模板改为引用新的 ConfigMap,是明确的滚动替换触发条件。仅修改 ConfigMap 本身,不会自动改写这个模板。同样,校验和 annotation 只有在某个工具真正把它更新到 Pod 模板中时才起作用。Kubernetes 不会自行发现任意外部配置变化,并替应用生成期望的校验和;这部分自动化需要有真实实现。 ...

八月 20, 2027 · 3 分钟 · XB Field Notes
让控制器把正在运行的副本数量,推向你所声明的目标。

Kubernetes Deployment 描述期望状态

与其描述步骤,不如描述终态。这条思想支撑起副本、滚动更新与回滚的一切机制。 声明规范胜过过程脚本 一份 Deployment 是「终态」的陈述,而不是一张命令清单:多少副本、哪个镜像、哪条 tag、什么标签。操作者写下「要什么」,控制器这位 reconcile 循环决定「怎么要」。它与一条命令的本质差别在于:一条 kubectl run 是命令一样「去执行一下」,而 Deployment 说「这个状态要一直保持」。命令被中途打断可能留下半截结果;控制器则会反复收敛,直到与现实相符。这正是把启动、升级这些动作,从一锤子买卖变成系统持续盯守的事情的能力来源。 由控制器填补差距 reconcile 循环正是 Deployment 值得信任的原因。每次控制面看到现实与声明不相同——副本少了、Pod 被驱逐、节点丢失——它就做一步修正,再把现实推回声明。不是靠人每隔一会去手工补一个副本,而是控制器自己每几秒对一次账。这是它与「打一条命令」的根本差异:漂移不是你发现之后去修,而是系统自行察觉并回位。也正因此,被驱逐的、崩掉的 Pod 会自己回来,而不需要有人在半夜醒着盯。 一次次发布都是经过步骤的 变化也以同样的方式被描述:一次滚动更新就是换一份新的 Deployment,控制器用新副本逐步接管、尊重 ready 状态,让整支队伍在受控的节奏里切换。因为每一次改动都会生成一个新的 Revision 并留下记录,所以发布过程本身是可回溯的;回滚,不过就是再指向上一版 spec,而不是写一段「不知道自己在做什么」的脚本。这让集群在大规模下依然可以维护:意图只写一次,由控制器永久去追索,而每一次改动都把历史留下。 坏掉的样子 不可变的选择器会把一次随手的标签改动变成长期漂移。选择器在创建之后就不能再改,如果只改模板标签而不改选择器,新起的副本就会落在选择器之外:副本集一边报告缺副本,一边却有游离的副本在旁边空转。对称的错误是两份选择器重叠的负载,各自的控制器都把对方的副本算成自己的观测状态,轮流扩缩,谁也收敛不了。解法朴素而严格:把选择器加模板标签当成一份原子契约,对着两者逐行比对,一分钟就能定位漂移;凡是必须换选择器,就新建一份负载,而不是原地修补旧对象。 手改观测对象注定被循环打回。直接改下层副本集的副本数、顺手删一个副本省资源,或者只在集群里扩容却不改声明里的期望副本,都只能生效几秒,控制器下一次同步就会把副本数和模板按声明重写回去。上了版本同步的集群下一次对账就会直接覆盖回去,手工扩容看起来像是没有执行过。表象像是接口没有生效,看事件会发现控制器每轮都在补同一个差。如果紧急扩容是真的,就去改声明与版本库,而不是改眼前的观测对象。 历史版本也会从审计变成负担。每次改动都会留下一份旧副本集,保留多少由历史上限决定,持续集成的流水线每次提交都下发一次,若把上限设到几十,集群里很快攒下几十份僵尸版本,拖慢查询也撑大键值存储。用查询旧版本的数量与存储增长来验证清理效果,而不是只看发布成功。反过来把上限设成零,则连可回滚的目标都不剩。没有实测理由就保留默认的十份,把真正的审计交给版本库,而不是堆在集群里。 快速验证 把下面程序存成 desired_state_loop.py,运行 python3 desired_state_loop.py。它只用标准库,每轮比较期望与观测,差一个就补一个动作,直到差距归零,既演示节点丢失后的补齐,也演示手改多出的副本如何被收回。 """期望态收敛循环模型(仅标准库):期望 vs 观测,控制器补差直到归零。""" def reconcile_step(desired, observed): if observed < desired: return "create-one-pod" if observed > desired: return "delete-one-pod" return "in-sync" def converge(desired, observed, log): while observed != desired: action = reconcile_step(desired, observed) log.append(action) if action == "create-one-pod": observed += 1 else: observed -= 1 log.append("in-sync") return observed def main(): desired = 3 observed = 1 # 节点丢失后只剩一个 actions = [] observed = converge(desired, observed, actions) assert observed == desired assert actions == ["create-one-pod", "create-one-pod", "in-sync"], actions # 手工漂移:期望未变,观测多了两个。 observed = 5 drift = [] observed = converge(desired, observed, drift) assert observed == desired assert drift == ["delete-one-pod", "delete-one-pod", "in-sync"], drift # 标签错配不会自愈:选择器之外的模板标签永远不计入观测。 selector = {"app": "api"} template_labels = {"app": "api-v2"} # 故意错配,否定对照 matched = all(template_labels.get(k) == v for k, v in selector.items()) assert not matched, "mismatched labels must not count as observed" print(f"desired={desired} final-observed={observed}") print(f"recovery-actions={actions}") print(f"manual-scale-reverted={drift}") print("PASS: controller closes the desired-vs-observed gap; label mismatch stays visible") if __name__ == "__main__": main() 两次恢复都以同步结尾,而标签错配是否定对照:选择器之外的副本永远不计入观测,循环不会自作主张把契约错误也治好,而是让它一直可见。跑通后会打印两次补差动作序列与最终同步,手工多出的副本被删回期望数。若把期望改成你的真实副本数再跑一遍,收敛结论不变,数字跟着变。 ...

八月 19, 2027 · 1 分钟 · XB Field Notes