4、工作负载管理:Deployment滚动更新与回滚、StatefulSet有状态应用、DaemonSet守护进程、Job与CronJob
工作负载管理,说白了就是告诉Kubernetes:「你的应用该怎么跑、跑几个、出问题了怎么办」。这是日常运维中最常打交道的内容。我刚开始接触K8s时,总觉得这些概念差不多,直到线上出了几次事故才明白——每个控制器都有自己的脾气,用错了地方,后果很严重。
4.1 Deployment:滚动更新与回滚
Deployment是我们最常用的工作负载。它管理无状态应用,比如Web服务、API网关。你想想看,这类应用的特点是:任何一个Pod都可以处理请求,挂了一个,再拉一个起来就行。
滚动更新的策略
滚动更新是Deployment的默认更新方式。它不会一次性杀掉所有旧Pod,而是逐步替换。这样做的好处很明显——服务不中断。
我个人习惯在更新前先检查当前状态:
kubectl get deployment my-app -o yaml
滚动更新的核心参数有两个:
- maxSurge:更新时最多可以超出期望副本数的比例或数量。默认25%。
- maxUnavailable:更新时最多可以有多少个Pod不可用。默认25%。
举个例子,如果你的副本数是4,maxSurge=25%,maxUnavailable=25%。那么更新时,K8s会先启动1个新Pod(4*25%=1),同时允许1个旧Pod被干掉。整个过程就像「先建后拆」,保证总容量不降。
回滚操作
更新出问题怎么办?别慌,Deployment支持回滚。我曾经有一次更新了镜像版本,结果新版本有内存泄漏,Pod不断重启。当时我用了这个命令:
kubectl rollout undo deployment/my-app
如果想回滚到指定版本:
kubectl rollout undo deployment/my-app --to-revision=2
查看历史版本:
kubectl rollout history deployment/my-app
4.2 StatefulSet:有状态应用
StatefulSet是为有状态应用准备的。比如数据库、消息队列、缓存。这类应用的特点是:每个Pod都有独立的身份和存储。
你想想看,MySQL主从复制,主节点和从节点能互换吗?不能。每个节点都有自己的数据,不能随便杀掉重建。这就是StatefulSet存在的意义。
核心特性
- 稳定的网络标识:每个Pod有固定的主机名,比如mysql-0、mysql-1。Pod重建后名字不变。
- 稳定的存储:每个Pod绑定独立的PVC,Pod挂了,数据还在。
- 有序的部署和伸缩:Pod按顺序启动和停止,从0到N-1。
我记得有一次部署Elasticsearch集群,用的就是StatefulSet。每个节点有独立的数据卷,节点挂了重建后,数据自动挂载回来,集群自动恢复。这种体验,Deployment给不了。
配置示例
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
<volume-name>-<statefulset-name>-<ordinal>。
4.3 DaemonSet:守护进程
DaemonSet保证每个节点上运行一个Pod。适合做节点级别的任务,比如日志收集、监控代理、网络插件。
说白了,你不需要关心副本数,K8s会自动根据节点数量创建Pod。新节点加入集群,DaemonSet自动在新节点上启动Pod。节点被删除,Pod也会被清理。
我曾经用DaemonSet部署Fluentd收集日志。每个节点一个Pod,采集所有容器的日志。配置很简单:
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
4.4 Job与CronJob
Job和CronJob处理的是「一次性」或「定时」任务。比如数据迁移、批量处理、定时备份。
Job:一次性任务
Job保证Pod成功运行到完成。如果Pod失败,Job会自动重试。我常用Job做数据库迁移:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
template:
spec:
containers:
- name: migration
image: myapp/migration:1.0
command: ["/bin/sh", "-c", "python migrate.py"]
restartPolicy: Never
backoffLimit: 4
这里有个坑:restartPolicy必须设为Never或OnFailure。如果设为Always,Job会一直运行,永远不会完成。
CronJob:定时任务
CronJob就是在Job外面包了一层定时器。语法和Linux的crontab一样。我举个例子,每天凌晨2点备份数据库:
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: myapp/backup:1.0
command: ["/bin/sh", "-c", "pg_dump -h db -U admin mydb > /backup/db.sql"]
restartPolicy: OnFailure
concurrencyPolicy: Forbid来禁止并发。
总结
四种工作负载,各有各的用途:
| 控制器 | 适用场景 | 关键特性 |
|---|---|---|
| Deployment | 无状态应用 | 滚动更新、回滚、弹性伸缩 |
| StatefulSet | 有状态应用 | 稳定网络标识、持久化存储、有序部署 |
| DaemonSet | 节点级守护进程 | 每个节点一个Pod、自动扩缩 |
| Job/CronJob | 一次性/定时任务 | 任务完成即退出、定时触发 |
选对控制器,运维省一半力气。嗯,这就是我这些年总结出来的经验。