从一个 Pod 到工作负载控制器
一句话总结
Pod 不是部署单元,而是调度单元。人们几乎不会直接创建它,实际通常由控制器创建。 选择哪一种控制器,取决于“这项工作何时结束”。
为什么需要它
如果容器是唯一的最小单元,日志收集器、代理等辅助进程该放在哪里就会变得棘手。 把它们塞进同一镜像会破坏 12-factor 的“一个进程只负责一个关注点”;放在别的机器上, 又无法通过 localhost 通信。
Pod 是介于两者之间的答案:共享网络命名空间(相同 IP 和端口空间)与卷的一组容器。
因此 sidecar 能通过 localhost 连接应用,应用与 sidecar 也总会一起调度到同一个节点。
但 Pod 本身不会让自己复活。节点死亡,其上的 Pod 也会一同消失。 因此,Pod 上层还需要控制器。
它如何运作
所有权关系链
Deployment --(소유)--> ReplicaSet --(소유)--> Pod
创建 Deployment 后,controller manager 会创建 ReplicaSet,再由 ReplicaSet 创建 Pod。
每个对象的 metadata.ownerReferences 都记录父对象,这个字段会成为垃圾回收的依据。
删除 Deployment 后,垃圾回收会沿 ownerReferences 连锁清理 ReplicaSet 和 Pod。
为什么 rollout Deployment 后会出现多个 ReplicaSet?因为旧版本的 ReplicaSet 会被保留。
所以才能使用 kubectl rollout undo。所谓回滚,不过是再次提高旧 ReplicaSet 的 replicas。
选择工作负载控制器
| 控制器 | 何时使用 | 是否结束 |
|---|---|---|
| Deployment | 无状态服务。Web、API | 不结束 |
| StatefulSet | 需要稳定名称、顺序和专用存储的服务。数据库、队列 | 不结束 |
| DaemonSet | 每个节点一个。日志收集器、CNI、节点 exporter | 不结束 |
| Job | 运行一次后结束的工作。迁移、批处理 | 结束 |
| CronJob | 在指定时刻创建 Job | Job 会结束 |
核心判断式是:**“这个进程会自行结束吗?”**如果为会结束的工作使用 Deployment,
容器每次退出都会被重新启动,形成无限循环。因此 Job 的 Pod 模板不能把 restartPolicy 设为 Always。
DaemonSet 没有 replicas 字段。数量不由人决定,节点数就是实例数。 增加节点后,会自动多出一个实例。
自愈不是魔法
删除 Pod 后又出现新 Pod,是因为 ReplicaSet 控制器不断执行以下循环。
- 当前有多少 Pod 符合我的 selector?
- spec.replicas 是多少?
- 数量不足就创建,多余就删除
**返回的不是同名 Pod,而是创建了新 Pod。**名称每次都会不同。 因此,依赖 Pod 名称的设计永远会失效。
探针
- livenessProbe——失败时重启容器。“已经死了,重新启动”
- readinessProbe——失败时从 Service endpoint 移除。“还活着,但现在不适合接收流量”
- startupProbe——为启动缓慢的应用延后 liveness/readiness 的开始时间
如果不设置 readiness,流量会进入仍在启动的 Pod;如果 liveness 设置得过于激进, 短暂变慢的应用会不断重启,使情况更加恶化。
在实际工作中会遇到的情况
作者的家庭实验室安装 Cilium 后,hubble-relay 与 hubble-ui Pod 一直处于 Pending。
事件内容是 0/1 nodes are available: 1 node(s) had untolerated taint(s)。
原因在于控制器类型不同。控制平面节点带有
node-role.kubernetes.io/control-plane:NoSchedule taint,**而 hubble 组件不是 DaemonSet,
而是 Deployment,所以没有容忍该 taint。**同一时间 CoreDNS 能正常启动,
是因为 CoreDNS 默认具有 control-plane toleration。
这不是错误,而是正常行为;worker 节点加入后,问题立即消失。
还有一个例子。同一集群扩展到 7 个节点后,有 4 台 GPU worker(RTX 3090 24GB、
RTX 5090 32GB,以及两台 RTX 4070 Laptop 8GB)。如果 Pod 只请求 nvidia.com/gpu: 1,
**需要 32GB 5090 的训练任务可能会被放到 8GB 的笔记本 GPU 上。**在 Kubernetes 看来,
两者都是“1 个 GPU”。最终只能自行添加 gpu.homelab/tier=xlarge|large|small 这样的语义标签,
再通过 nodeSelector 选择。
这个案例说明:资源名称相同并不代表资源相同,而标签正是填补这种差距的工具。
后续实验要做什么
在后续实验中启动第一个 Pod,创建 Deployment 并扩缩容,再亲自查看 ReplicaSet 的 ownerReferences。 逐一创建 Job、CronJob 与 DaemonSet,最后故意删除 Pod, 通过比较删除前后的列表,证明自愈确实发生。