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内部还有两个重要的钩子:postStartpreStop。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做节点维护时,奇数副本更容易保证多数可用。当然,这只是经验之谈。

小技巧:滚动更新时,设置maxSurgemaxUnavailable可以控制更新速度。生产环境我一般用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任务,不需要日志采集。这时候可以用nodeSelectoraffinity来过滤。

个人经验:DaemonSet的Pod默认使用hostNetwork: true,可以直接用节点IP访问。但要注意端口冲突,我遇到过两个DaemonSet都用同一个端口,结果Pod起不来。

4.5 工作负载的选择策略

说了这么多,到底该用哪个?我总结了一个简单的判断逻辑:

  1. 无状态应用(Web服务、API网关)→ Deployment
  2. 有状态应用(数据库、缓存)→ StatefulSet
  3. 节点级服务(日志、监控、网络)→ DaemonSet
  4. 一次性任务(批处理、数据迁移)→ Job/CronJob

嗯,这里要注意——StatefulSet不是万能的。它的有序启停机制在扩缩容时比较慢。如果业务对性能要求高,可以考虑用Operator来管理有状态应用,比如Prometheus Operator、Elasticsearch Operator。

最后说一句:工作负载的选择没有绝对的对错。我在项目中见过有人用Deployment跑数据库,也见过用StatefulSet跑无状态服务。只要你能理解每种控制器的特性,并且做好对应的容错和运维方案,怎么选都行。