Appearance
源码实证:Deployment controller 如何收敛
专题索引 / 上一篇:源码阅读:从行为问题追到架构边界
本页固定阅读 Kubernetes v1.31.0。问题只有一个:Deployment 的 spec.replicas 改为 5 后,controller 怎样从事件到状态收敛,而不是在 API 请求返回时假装已经完成。
text
Deployment / ReplicaSet / Pod 事件
-> informer handler
-> workqueue 中的 namespace/name key
-> worker
-> syncDeployment(key)
-> 读取当前 Deployment、ReplicaSet、Pod
-> 按状态选择 scale、rollout 或 status 路径
-> API 写入引发下一轮事件Deployment controller 并不直接把某个进程启动成容器。它修改 ReplicaSet 与 Deployment 状态;ReplicaSet controller、scheduler、kubelet 再各自完成后续收敛。因此 API 写入成功、ReplicaSet 创建成功、Pod 创建成功、Pod Ready、Deployment available 是不同完成点。
1. 组装:controller manager 把依赖注入 controller
cmd/kube-controller-manager/app/apps.go 注册 Deployment controller,并在 startDeploymentController 中传入三份 informer:Deployment、ReplicaSet、Pod,以及专用 client。随后以配置的 ConcurrentDeploymentSyncs 启动 dc.Run。
text
controller manager
-> NewDeploymentController(deployment informer, rs informer, pod informer, client)
-> dc.Run(ctx, worker count)这处组装就是架构边界:controller 不自己创建 watch 连接,也不自行管理 API 客户端配置;它接收已组装的缓存和客户端,因此更容易测试、替换和统一治理。
2. 事件:不是把对象交给业务逻辑
在 deployment_controller.go,构造函数创建名为 deployment 的 rate-limiting queue,为三份 informer 注册 handler,并保存 lister 与 cache-sync 函数。
Deployment 的 add/update/delete handler 都调用 enqueueDeployment。enqueue 只用 controller.KeyFunc 生成 namespace/name key 并执行 queue.Add(key),源码见 400-408 行。
text
对象变化 -> key "namespace/name" -> queue这里不把 Deployment 对象副本塞进队列。处理时对象可能早已更新,正确动作是由 key 重新取当前状态。ReplicaSet 与 Pod 的事件也会反向 enqueue 所属 Deployment:controller 不假定只有 Deployment 自身变化才需要重算。
3. worker:同一 key 的串行、不同 key 的并发
Run 先等待三份 informer cache 同步,才启动多个 worker。worker 与 processNextWorkItem 从队列取 key,调用 syncHandler(ctx, key),然后依据错误结果 Forget 或 AddRateLimited。
源码注释明确:同一 key 不会并发调用 syncHandler。多个 worker 仍能同时处理不同 Deployment,因此这个设计同时避免单对象竞态,又不把整个集群串行化。
text
Get(key)
-> syncHandler(key)
-> Done(key)
-> 成功:Forget(key)
-> 可重试失败:AddRateLimited(key)
-> 超过重试预算:记录错误后 Forget(key)“超过重试预算后移出队列”不是代表系统已经正确;它代表这次自动重试停止。后续对象事件、人工修改或控制器重启仍可能再次触发收敛。告警与 status/事件是把这种未收敛状态暴露给运维的必要补充。
4. syncDeployment:从当前事实选择分支
syncDeployment 先拆 key,从 dLister 读取当前 Deployment;对象不存在时正常结束。读出的 cache 对象会 DeepCopy,避免 controller 修改 informer cache。
随后它做两类事实收集:
text
getReplicaSetsForDeployment
-> 根据 ControllerRef 管理、adopt 或 orphan ReplicaSet
getPodMapForDeployment
-> 读取匹配 selector 的 Pod
-> 依 ReplicaSet UID 分组getReplicaSetsForDeployment 在需要 adopt 前会做一次 uncached recheck,防止基于过期 cache 接管一个已经删除或重建的 Deployment。这说明“从 lister 读当前状态”不是盲信 cache;关键生命周期操作会补足更强的一致性检查。
主要分支如下:
| 条件 | 处理 |
|---|---|
| Deployment 正在删除 | 仅同步 status |
spec.paused=true | 更新暂停相关 condition,再同步状态 |
| 有 rollback 请求 | 先执行 rollback,等待后续 enqueue |
| 识别为 scaling event | 同步 ReplicaSet 副本与状态 |
Recreate 策略 | rolloutRecreate |
RollingUpdate 策略 | rolloutRolling |
这张分支表比“Deployment 会滚动发布”更接近真实架构:controller 的核心不是固定创建 Pod,而是对当前对象状态选择下一步能安全缩小差异的动作。
5. 一个 scale=5 的时间线
text
t0 用户更新 Deployment spec.replicas=5
t1 API Server 持久化并 watch 通知 informer
t2 handler 将 Deployment key 入队
t3 worker 从 cache 读 Deployment、RS、Pod
t4 controller 判断为 scaling event,更新受管 ReplicaSet
t5 ReplicaSet controller 创建/删除 Pod 对象
t6 scheduler 绑定 Node,kubelet 启动容器
t7 Pod Ready 变化触发相关 controller 再次同步
t8 Deployment status 反映 observed generation、可用副本和 conditiont4 以后由不同控制器和 Node 承担。若 Pod 因镜像、配额、存储、网络或探针失败,Deployment controller 的正确行为不是绕过这些边界直接重试“启动容器”,而是从后续对象状态重新判断 rollout/scale 与 status。
6. 这段源码给设计者的规则
- 事件只携带足以定位对象的 key;工作时重新读取事实。
- 一个资源键需要串行化,资源之间应允许并行。
- 先等 cache 初始同步,再开始基于 cache 做决策。
- ownership 不只是删除便利,它决定谁可以安全 adopt、更新和收敛关联资源。
- 把全局资源准备、对象控制和 Node 执行拆给不同组件,完成状态必须逐层观察。
可复现阅读与验证
text
git clone --branch v1.31.0 --depth 1 https://github.com/kubernetes/kubernetes.git
rg -n "NewDeploymentController|syncDeployment|processNextWorkItem|enqueue\(" \
pkg/controller/deployment/deployment_controller.go
rg -n "startDeploymentController" cmd/kube-controller-manager/app/apps.go源码阅读完成后,应把这个静态链与真实集群证据对齐:Deployment/ReplicaSet/Pod 的 generation、observedGeneration、conditions、Events、controller 日志和最终 Pod Ready。只看源码不能证明某个集群的 CNI、CSI、镜像仓库或 Node 是否健康。
资料
- Kubernetes v1.31.0
deployment_controller.go,访问于 2026-08-03。 - Kubernetes v1.31.0 controller-manager registration,访问于 2026-08-03。
- Kubernetes Controllers 概念文档,访问于 2026-08-03。
本文的函数和行号固定到 v1.31.0;升级到其他版本前,应重新确认注册路径、queue 类型、同步分支和 API 状态语义。