大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Deployment 与滚动更新
这一课你将能做到
- 写出带
replicas、selector、Pod 模板的 Deployment,并验证「删 Pod 自动补齐」的自愈能力 - 用
kubectl set image或改 YAML 完成一次滚动更新,用 rollout status/history 观察全程 - 执行
rollout undo回滚到上一版本,并说清 maxUnavailable / maxSurge 的含义
为什么要学
无状态 API(本站 backend、platform 前端)在 K8s 里的标准载体是 Deployment:你声明「要 N 个这样的 Pod」,控制器负责维持健康副本,并内置滚动更新与回滚。
对照现网:以前换 jar 要停服、传包、重启,用户感知中断;K8s 的滚动更新是「起新的、验就绪、停旧的」逐步替换,全程服务不断,出问题一条命令回滚。这是 K8s 最打动业务方的能力,也是面试最高频的考点(「滚动更新怎么实现」「发布如何不中断」)。
先建立图像
Deployment(你操作的层)
└─ ReplicaSet(版本世代:v1 一组、v2 一组)
└─ Pod × N(同一模板的实例)
发布 v1 → v2 的滚动过程:
新建 RS(v2) → 逐步加 v2 Pod、减 v1 Pod → 旧 RS 缩到 0(保留以便回滚)
出问题 → rollout undo → 旧 RS 重新扩容、新 RS 缩到 0
| 你的操作 | 实际发生 |
|---|---|
| apply Deployment | 创建/更新 ReplicaSet 与 Pod |
| 删一个 Pod | 控制器立刻补一个 |
| 改 template 里的镜像/探针 | 生成新 RS,触发滚动替换 |
| 只改 replicas | 仅扩缩容,不算发版 |
跟着做
步骤 1:写 Deployment YAML
deploy-backend.yaml(先用 nginx 占位,逻辑与本站 backend 完全一致):
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
labels:
app: backend
spec:
replicas: 2
revisionHistoryLimit: 5 # 保留最近 5 个版本供回滚
selector:
matchLabels:
app: backend # 必须与 template.labels 匹配
template:
metadata:
labels:
app: backend
spec:
containers:
- name: api
image: nginx:1.27-alpine
ports:
- containerPort: 80
硬规则:spec.selector.matchLabels 必须与 template.metadata.labels 匹配,且创建后 selector 不可再改——写错直接 apply 失败或抓错 Pod。
步骤 2:apply 并验证自愈
kubectl apply -f deploy-backend.yaml
kubectl get deploy backend
kubectl get rs -l app=backend
kubectl get pods -l app=backend
看到 1 个 Deployment、1 个 ReplicaSet、2 个 Pod(名字形如 backend-7d9f8b6c5-xxxxx)。现在杀掉一个 Pod:
kubectl delete pod <某个pod名>
kubectl get pods -l app=backend -w
几秒内会出现一个新 Pod 接替它——ReplicaSet 控制器发现副本数低于期望,自动重建。这就是「自愈」。
步骤 3:扩缩容
kubectl scale deployment/backend --replicas=4
kubectl get pods -l app=backend
改回 2 也一样。工程习惯:改 YAML 文件里的 replicas 再 apply(声明式、可追溯),scale 命令适合应急。
步骤 4:滚动更新镜像
kubectl set image deployment/backend api=nginx:1.25-alpine
kubectl rollout status deployment/backend
rollout status 会实时打印 Waiting for deployment "backend" rollout to finish... 直到完成。期间另开终端 kubectl get pods -l app=backend -w,能看到新旧 Pod 此消彼长:新 Pod 先起、Ready 后旧 Pod 才退。对本站即:your-registry/backend:1.0.0 → 1.1.0,用户全程无感。
kubectl rollout history deployment/backend
应看到 REVISION 列表——每次模板变更生成一个修订版本。
步骤 5:回滚
kubectl rollout undo deployment/backend
kubectl rollout status deployment/backend
kubectl get rs -l app=backend
应回到上一版本(旧 RS 重新扩容,新 RS 缩到 0)。指定回到某一版:
kubectl rollout undo deployment/backend --to-revision=1
人话:发布前确认能 undo,比「祈祷不出事」可靠一万倍。
步骤 6:理解更新策略参数
在 Deployment 里显式写策略(默认就是 RollingUpdate,默认值 25%/25%):
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 滚动期间最多允许几个副本不可用:0 = 不牺牲容量
maxSurge: 1 # 滚动期间最多允许多起几个临时副本
小集群/低副本数建议 maxUnavailable: 0, maxSurge: 1——先补齐再替换,容量不缩水。注意:必须配合 readiness 探针,否则「新 Pod 起来了」不等于「新 Pod 能接流量」,策略形同虚设。改回文件里的镜像 tag 后 kubectl apply -f deploy-backend.yaml 收尾,保持「文件是真相」。
讲清楚要点
- Deployment 管 RS、RS 管 Pod:日常别手搓 ReplicaSet——会和控制器打架;也解释了为什么删 Deployment 才是「真卸载」。
- 只有改 Pod 模板才触发滚动:改镜像、环境变量、探针算发版;只改 replicas 是扩缩。版本历史按「模板变更」计。
- 滚动更新靠 readiness 保障不中断:新 Pod 未 Ready 就不摘旧 Pod。没配 readiness 的滚动发布 = 「用户打到启动中的实例」,是发布事故最常见根因。
- maxUnavailable 与 maxSurge 决定节奏:不可用上限保护容量,超出上限控制资源消耗;两者都以「期望副本数」的百分比或绝对数表示。
- 镜像 tag 必须可追溯:本站镜像来自 CI 固定 tag;用
:latest滚动可能「以为更新了其实没变」(本地缓存旧层),也回滚不了明确版本。 - 回滚依赖 revisionHistoryLimit:默认保留 10 个;设 0 会失去回滚能力。频繁无意义的改动会挤掉历史。
你可能会懵的地方
-
spec.selector能改吗? 创建后不可变。要换标签体系通常得新建 Deployment 迁移——所以命名空间和标签规范要一开始就想好。 -
滚动更新卡住不动?
rollout status超时常见于:镜像拉不下来(describe 新 Pod 看 Events)、探针一直失败、节点资源不足。处理完根因滚动会自动继续。 -
和 Compose 换镜像比差在哪?
docker compose up -d常常整容器重建,中间有真空期;K8s 按策略逐步替换并保留回滚点,且由 readiness 保证流量只打健康实例。 -
有状态应用也用 Deployment? 数据库类优先考虑托管服务或 StatefulSet(第 8 课);Deployment 擅长无状态服务。MySQL 的多个副本绝不该用一个 Deployment 硬凑。
-
k8s 里还有别的发布方式吗? 蓝绿、金丝雀可以在 Deployment 层面做(双 Deployment 切流量),更标准的做法靠 Ingress/服务网格按权重分流(第 9 课铺垫)。本课先把「滚动 + undo」练成肌肉记忆。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 手动删 ReplicaSet「清理」 | 破坏版本历史,回滚失效 | 用 rollout undo / 改 Deployment |
| selector 与 template 标签不一致 | apply 被拒或 Pod 失管 | 两处严格一致 |
| 无 readiness 就滚动 | 流量进未就绪实例 | 探针必配(上一课) |
| 生产直接改集群 live 对象、不入 Git | 无法复现、无法审计 | YAML 进仓库,apply 都有据可查 |
| 把一次性批处理写成 Deployment | 任务永不结束或被反复重启 | 用 Job/CronJob(按需自查) |
卡点急救
- 滚动更新卡住,rollout status 一直等:
kubectl describe pod -l app=backend看新 Pod 的 Events——多数是镜像拉取失败或探针不通过;kubectl rollout undo可立即止损。 - undo 提示没有可回滚版本:revisionHistoryLimit 设成了 0,或这是第一个版本。预防:保留默认 10,别乱设 0。
- 副本数与期望不符:
kubectl describe deployment backend看 Conditions;Pod 起不来多因资源不足(Pending,第 10 课)或镜像错误,个别旧 Pod 处于 Terminating 属正常。
面试常问
-
滚动更新的原理是什么? 修改 Pod 模板后 Deployment 创建新 ReplicaSet,按 maxSurge 逐步扩新 RS、按 maxUnavailable 逐步缩旧 RS,此消彼长直至新 RS 达到期望副本、旧 RS 缩到 0(但保留对象以备回滚)。每个新 Pod 必须 readiness 通过才继续下一步,配合探针实现发布全程服务不中断。rolling update 的每一步都可被 rollout pause/resume 暂停,用于灰度观察。
-
maxUnavailable 和 maxSurge 怎么理解? 都是相对期望副本数的比例或绝对数。maxUnavailable:滚动过程中允许同时有多少个副本「不可用」,控制容量下限;maxSurge:允许超出期望副本数多少个,控制资源上限。例:期望 4、maxUnavailable=0、maxSurge=1,则滚动期间始终 ≥4 个可用副本,最多瞬时 5 个 Pod。要求「容量永不缩水」就设 maxUnavailable: 0。
-
线上发新版本发现异常,怎么回滚?
kubectl rollout undo deployment/<名>回到上一 revision(或--to-revision=N指定版本),随后rollout status确认完成。前提:镜像 tag 可追溯、revisionHistoryLimit 未被清零。更完善的方案把「回滚」做成 CI/CD 里的标准动作(重新发布上一个稳定 tag)。
自测三问
- 删一个 Pod 和删 Deployment 的后果分别是什么?
- 如何确认一次滚动更新真正完成(而不是「看起来完成」)?
- 本站 backend 发版时,镜像 tag 与 rollout undo 各扮演什么角色?
小结一句话
Deployment 用「副本维持 + 滚动策略 + 版本历史」把上线变成可观察、可回滚的常规操作——下一课用 Service 给这些随时会变的 Pod 一个稳定访问入口。
延伸
- Deployments
- Performing a Rolling Update
- 下一课预告:Service 与网络——ClusterIP/NodePort/LoadBalancer 三种类型、DNS 服务发现与 Endpoints 排查。