4、Pod与工作负载:Pod生命周期、Deployment控制器、StatefulSet、DaemonSet
4.1 Pod生命周期——从生到死的完整旅程
Pod是Kubernetes里最小的调度单元。说白了,它就是一个或多个容器的"家"。我刚开始接触K8s时,总觉得Pod就是个包装壳,后来踩了坑才明白——Pod的生命周期管理,才是整个集群稳定性的基石。
Pod从创建到销毁,会经历几个阶段:
- Pending:Pod刚提交,还在等调度。可能是镜像没拉完,也可能是节点资源不够。
- Running:所有容器都跑起来了。注意,Running不代表服务正常,只是进程活着。
- Succeeded:所有容器正常退出。一般用于Job任务。
- Failed:至少一个容器异常退出。嗯,这时候就该排查日志了。
- Unknown:节点失联了。我遇到过网络分区导致的状态卡死,很头疼。
核心要点:Pod的restartPolicy决定了容器退出后的行为。Always、OnFailure、Never,选错了会出大问题。
Pod内部还有两个重要的钩子:postStart和preStop。postStart在容器启动后执行,preStop在容器终止前执行。我曾经在preStop里写了个优雅关闭脚本,结果脚本执行时间太长,Pod被强制杀了——后来加了terminationGracePeriodSeconds才搞定。
4.2 Deployment控制器——无状态应用的标配
Deployment是我用得最多的控制器。它管理ReplicaSet,ReplicaSet管理Pod。你想想看,如果直接操作Pod,哪天节点挂了,Pod就真没了。但Deployment会帮你重新拉起。
Deployment的核心能力:
- 滚动更新:逐步替换旧Pod,保证服务不中断。
- 回滚:新版本出问题,一键回到上一个版本。
- 扩缩容:调整副本数,应对流量变化。
来看一个典型的Deployment YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
我个人习惯把replicas设成奇数,比如3、5、7。为什么?因为配合PodDisruptionBudget做节点维护时,奇数副本更容易保证多数可用。当然,这只是经验之谈。
小技巧:滚动更新时,设置maxSurge和maxUnavailable可以控制更新速度。生产环境我一般用maxSurge: 25%和maxUnavailable: 25%,既快又稳。
4.3 StatefulSet——有状态应用的救星
Deployment很好,但它管不了有状态应用。比如数据库、消息队列,每个Pod都需要稳定的网络标识和持久化存储。这时候就得用StatefulSet。
StatefulSet和Deployment最大的区别:
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod名称 | 随机后缀 | 有序编号(如web-0, web-1) |
| 网络标识 | 不固定 | 稳定的DNS名称 |
| 存储 | 共享卷 | 每个Pod独立PVC |
| 启停顺序 | 无序 | 有序(从0到N-1) |
我记得有一次部署Elasticsearch集群,用了StatefulSet。每个Pod都有自己的数据卷,Pod挂了重建后,数据还在。但要注意——StatefulSet的缩容是从编号最大的Pod开始删。如果你不小心把编号0的Pod删了,集群可能会脑裂。
避坑指南:我曾经在StatefulSet里忘记设置serviceName,结果Pod之间无法通过DNS互相发现。StatefulSet必须配合Headless Service使用,否则网络标识不生效。
4.4 DaemonSet——每个节点一个"哨兵"
DaemonSet保证每个节点上运行一个Pod副本。新节点加入集群,DaemonSet自动部署Pod;节点被删除,Pod也跟着清理。说白了,它就是集群的"哨兵"。
常见的DaemonSet场景:
- 日志采集:比如Fluentd、Filebeat,每个节点都要跑一个。
- 监控代理:比如Prometheus Node Exporter,采集节点指标。
- 网络插件:比如Calico、Flannel的节点代理。
- 存储插件:比如Ceph的RBD客户端。
DaemonSet的YAML和Deployment很像,只是kind不同:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
spec:
selector:
matchLabels:
name: fluentd
template:
metadata:
labels:
name: fluentd
spec:
containers:
- name: fluentd
image: fluent/fluentd:v1.14
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/log
你想想看,如果集群有100个节点,DaemonSet就会跑100个Pod。但有时候你不想在所有节点都部署——比如GPU节点只跑AI任务,不需要日志采集。这时候可以用nodeSelector或affinity来过滤。
个人经验:DaemonSet的Pod默认使用hostNetwork: true,可以直接用节点IP访问。但要注意端口冲突,我遇到过两个DaemonSet都用同一个端口,结果Pod起不来。
4.5 工作负载的选择策略
说了这么多,到底该用哪个?我总结了一个简单的判断逻辑:
- 无状态应用(Web服务、API网关)→ Deployment
- 有状态应用(数据库、缓存)→ StatefulSet
- 节点级服务(日志、监控、网络)→ DaemonSet
- 一次性任务(批处理、数据迁移)→ Job/CronJob
嗯,这里要注意——StatefulSet不是万能的。它的有序启停机制在扩缩容时比较慢。如果业务对性能要求高,可以考虑用Operator来管理有状态应用,比如Prometheus Operator、Elasticsearch Operator。
最后说一句:工作负载的选择没有绝对的对错。我在项目中见过有人用Deployment跑数据库,也见过用StatefulSet跑无状态服务。只要你能理解每种控制器的特性,并且做好对应的容错和运维方案,怎么选都行。