上一课的结尾我们看到,裸 Pod 被删掉就没了。真实的服务需要:始终保持 N 个副本在跑,挂了自动补;升级版本时不中断服务;新版本有问题能一键回退。这就是 ReplicaSet 和 Deployment 的工作。
学完这一课,你能用 Deployment 部署一个多副本应用,控制滚动更新的节奏,查看发布历史并回滚,并理解 Recreate、蓝绿、金丝雀这几种发布策略在 Kubernetes 里怎么实现。
ReplicaSet:保证副本数
它做什么
ReplicaSet 只做一件事:让匹配选择器的 Pod 数量始终等于 replicas。少了就按模板创建,多了就删除。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: web-rs
spec:
replicas: 3
selector: # 我管理哪些 Pod
matchLabels:
app: web-rs
template: # 不够时按这个模板创建 Pod
metadata:
labels:
app: web-rs # 必须能被上面的 selector 匹配
spec:
containers:
- name: nginx
image: nginx:1.27kubectl apply -f web-rs.yaml
kubectl get rs,pods -l app=web-rsNAME DESIRED CURRENT READY AGE
replicaset.apps/web-rs 3 3 3 12s
NAME READY STATUS RESTARTS AGE
pod/web-rs-7xk2p 1/1 Running 0 12s
pod/web-rs-h9dqc 1/1 Running 0 12s
pod/web-rs-tl4mz 1/1 Running 0 12s自愈与标签归属
删掉一个 Pod,立刻会有新的补上:
kubectl delete pod web-rs-7xk2p
kubectl get pods -l app=web-rsNAME READY STATUS RESTARTS AGE
web-rs-h9dqc 1/1 Running 0 1m
web-rs-q5v8n 0/1 ContainerCreating 0 1s
web-rs-tl4mz 1/1 Running 0 1mReplicaSet 靠标签认领 Pod。把某个 Pod 的标签改掉,它就"脱离"了 ReplicaSet,后者会再补一个新的:
kubectl label pod web-rs-h9dqc app=debug --overwrite
kubectl get pods -L appNAME READY STATUS RESTARTS AGE APP
web-rs-h9dqc 1/1 Running 0 2m debug
web-rs-q5v8n 1/1 Running 0 40s web-rs
web-rs-tl4mz 1/1 Running 0 2m web-rs
web-rs-zz8wd 1/1 Running 0 2s web-rsReplicaSet 的局限在于不会更新已有 Pod:修改模板里的镜像只影响之后新建的 Pod。所以我们几乎不直接使用它,而是用更上层的 Deployment。
kubectl delete rs web-rs
kubectl delete pod -l app=debugDeployment:管理 ReplicaSet 的控制器
层级关系
Deployment (web)
│ 管理多个版本的 ReplicaSet,负责滚动切换
├── ReplicaSet (web-5d8f7c9b6) ← 当前版本,replicas=3
│ ├── Pod web-5d8f7c9b6-abcde
│ ├── Pod web-5d8f7c9b6-fghij
│ └── Pod web-5d8f7c9b6-klmno
└── ReplicaSet (web-7c4b9d8f5) ← 旧版本,replicas=0,留作回滚每次修改 Pod 模板(镜像、环境变量、资源等),Deployment 会创建一个新 ReplicaSet,逐步把新的调大、旧的调小。ReplicaSet 名字里的哈希来自 Pod 模板,对应 Pod 上的 pod-template-hash 标签。
创建 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 4
revisionHistoryLimit: 10 # 保留多少个旧 ReplicaSet 用于回滚(默认 10)
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新时最多比 replicas 多出几个 Pod
maxUnavailable: 0 # 更新时最多允许几个 Pod 不可用
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.26
ports:
- containerPort: 80kubectl apply -f web-deploy.yaml
kubectl get deploy,rs,pods -l app=webNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web 4/4 4 4 20s
NAME DESIRED CURRENT READY AGE
replicaset.apps/web-6c9d5bb8f4 4 4 4 20s
NAME READY STATUS RESTARTS AGE
pod/web-6c9d5bb8f4-2kq9x 1/1 Running 0 20s
pod/web-6c9d5bb8f4-8wmtz 1/1 Running 0 20s
pod/web-6c9d5bb8f4-fp7bn 1/1 Running 0 20s
pod/web-6c9d5bb8f4-rx5lc 1/1 Running 0 20sREADY 是就绪数/期望数,UP-TO-DATE 是已更新到最新模板的副本数,AVAILABLE 是可对外服务的副本数。
扩缩容只需要改 replicas 再 apply;临时实验也可以 kubectl scale deploy web --replicas=6。
滚动更新
maxSurge 与 maxUnavailable
RollingUpdate(默认策略)用两个参数控制节奏,可以写整数或百分比,默认都是 25%:
| 参数 | 含义 | 调大的效果 |
|---|---|---|
maxSurge | 更新过程中,Pod 总数最多可以超出 replicas 多少 | 更快,但临时占用更多资源 |
maxUnavailable | 更新过程中,最多有多少副本可以不可用 | 更快,但服务容量下降 |
两者不能同时为 0。常见组合:
maxSurge: 1, maxUnavailable: 0:先起一个新的、就绪后再删一个旧的,容量始终不低于期望值,最稳妥。maxSurge: 0, maxUnavailable: 1:先删再建,不额外占资源,适合资源紧张的集群。maxSurge: 100%, maxUnavailable: 0:一次性起齐新版本再切,接近蓝绿,资源翻倍。
触发一次更新
先在另一个终端观察 Pod 变化:
kubectl get pods -l app=web -w然后把镜像改为 nginx:1.27,并用注解记录变更原因:
kubectl set image deploy/web nginx=nginx:1.27
kubectl annotate deploy/web kubernetes.io/change-cause="升级到 nginx 1.27"
kubectl rollout status deploy/webWaiting for deployment "web" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
deployment "web" successfully rolled out更新完成后 kubectl get rs -l app=web 会看到两个 ReplicaSet:新的 web-84b5f6d7c9 有 4 个副本,旧的 web-6c9d5bb8f4 副本数为 0,留作回滚。
rollout status 会阻塞到发布完成或失败,常用在 CI/CD 里作为发布门禁。如果新 Pod 迟迟无法就绪,超过 progressDeadlineSeconds(默认 600 秒)后 Deployment 会被标记为 ProgressDeadlineExceeded,rollout status 以非零退出码返回。注意 Kubernetes 本身不会自动回滚。
发布历史与回滚
查看历史
kubectl rollout history deploy/webdeployment.apps/web
REVISION CHANGE-CAUSE
1 <none>
2 升级到 nginx 1.27加 --revision=1 可以查看某个版本的 Pod 模板。
模拟一次失败的发布
kubectl set image deploy/web nginx=nginx:9.9.9-not-exist
kubectl annotate deploy/web kubernetes.io/change-cause="错误的镜像" --overwrite
kubectl get pods -l app=webNAME READY STATUS RESTARTS AGE
web-5f7d9c8b6d-xq2lp 0/1 ImagePullBackOff 0 30s
web-84b5f6d7c9-4gk8s 1/1 Running 0 3m
web-84b5f6d7c9-9mnbv 1/1 Running 0 3m
web-84b5f6d7c9-ct7wd 1/1 Running 0 3m
web-84b5f6d7c9-lz2rq 1/1 Running 0 3m因为 maxUnavailable: 0,新 Pod 起不来时旧 Pod 一个都不会被删,服务完全不受影响,这就是保守参数的价值。
回滚
kubectl rollout undo deploy/web # 回到上一个版本
kubectl rollout undo deploy/web --to-revision=1 # 回到指定版本
kubectl rollout status deploy/web回滚本质上是把旧版本的 Pod 模板重新应用一次,会产生一个新的 revision 号,旧 ReplicaSet 被重新扩容。
暂停与恢复
需要连续修改多处(镜像、环境变量、资源)但只想触发一次滚动时:
kubectl rollout pause deploy/web
# ……多次修改……
kubectl rollout resume deploy/webkubectl rollout restart deploy/web 则会在不改配置的情况下重建所有 Pod(常用于让 Pod 重新读取挂载的配置)。
Recreate 策略
有些应用不允许新旧版本同时运行,例如会做不兼容数据库迁移的单实例服务,或者要独占某个存储卷。这时用 Recreate:先把旧 Pod 全部删掉,再创建新的。
spec:
strategy:
type: Recreate代价是更新期间服务完全中断。
蓝绿与金丝雀
Deployment 原生只支持滚动和重建两种策略,但借助标签和 Service 的选择器,可以组合出更多发布方式。
蓝绿发布(Blue/Green)
同时运行两套完整的 Deployment,Service 只指向其中一套,切换时改 Service 的选择器:
selector: app=shop, version=blue
Service shop ───────────────▶ Deployment shop-blue (v1, 4 副本)
Deployment shop-green (v2, 4 副本,已就绪待命)
切换:把 Service 的 selector 改成 version=green,瞬间全量切换;
回退:改回 version=blue。优点是切换和回退都是瞬时的;缺点是资源翻倍。
金丝雀发布(Canary)
两个 Deployment 共享 Service 选择器用的标签,按副本数比例分流:
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-stable
spec:
replicas: 9
selector:
matchLabels: { app: shop, track: stable }
template:
metadata:
labels: { app: shop, track: stable }
spec:
containers:
- name: nginx
image: nginx:1.26
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-canary
spec:
replicas: 1
selector:
matchLabels: { app: shop, track: canary }
template:
metadata:
labels: { app: shop, track: canary }
spec:
containers:
- name: nginx
image: nginx:1.27Service 只用 app: shop 选择,于是大约 10% 的请求会落到金丝雀版本。观察指标没问题后,逐步调大 canary、调小 stable,最后把 stable 升级到新版本。
清理:
kubectl delete deploy web shop-stable shop-canary --ignore-not-found动手练习
- 创建
web-rs.yaml,删除一个 Pod 观察自愈,再通过修改标签把一个 Pod 从 ReplicaSet 里"摘出来"。 - 部署
web-deploy.yaml,分别用maxSurge: 1, maxUnavailable: 0和maxSurge: 0, maxUnavailable: 1做一次升级,用kubectl get pods -w对比 Pod 数量的变化过程。 - 发布一个不存在的镜像版本,确认服务仍有 4 个可用副本,然后用
rollout undo回滚,并查看rollout history的 revision 变化。 - 把策略改为
Recreate再升级一次,观察是否出现所有 Pod 同时Terminating的时刻。 - 部署
canary.yaml,把shop-canary扩到 3、shop-stable缩到 7,思考流量比例的变化。
自测
Deployment、ReplicaSet、Pod 三者是什么关系?
Deployment 管理 ReplicaSet,每个 Pod 模板版本对应一个 ReplicaSet;ReplicaSet 通过标签选择器管理 Pod,保证副本数。滚动更新就是 Deployment 调大新 ReplicaSet、调小旧 ReplicaSet 的过程。
replicas: 4, maxSurge: 25%, maxUnavailable: 25% 时,更新过程中 Pod 数量的上下限是多少?
maxSurge 向上取整为 1,maxUnavailable 向下取整为 1。所以总 Pod 数最多 5 个,可用 Pod 最少 3 个。
发布卡住了,Kubernetes 会自动回滚吗?
不会。超过 progressDeadlineSeconds 后只会把 Deployment 的 Progressing 条件标记为 False(原因 ProgressDeadlineExceeded),需要人工或 CI/CD 工具执行回滚。
什么情况下应该用 Recreate 而不是 RollingUpdate?
当新旧版本不能同时运行时:例如不兼容的数据结构迁移、单实例独占的存储卷或许可证。代价是更新期间服务中断。