API 有新版本,怎麼把 v1 換成 v2?
上一篇學完擴縮容,這篇處理另一個現實問題:版本更新。
你的 API 跑 nginx 1.26,新版本 1.27 開發完了。要把線上 v1.26 換成 v1.27。怎麼做?
最土的方法:把舊 Pod 全砍 → 用新 Image 建一批。問題是舊的砍掉、新的還沒起來那段時間,使用者看到 502 Bad Gateway。電商網站幾秒空窗就是幾萬塊損失,金融系統幾秒可能整筆交易出錯。
生產環境不允許這樣做。K8s 用一個叫「滾動更新(Rolling Update)」的策略解這個問題。
滾動更新核心:逐步替換
四個字:逐步替換。不是全砍全建,是一個一個來。
3 個 Pod 跑 v1,要更新到 v2:
初始:[v1] [v1] [v1]
↓ 建 1 個 v2
[v1] [v1] [v1] [v2]
↓ 健康檢查通過 → 砍 1 個 v1
[v1] [v1] [v2]
↓ 建 1 個 v2,砍 1 個 v1
[v1] [v2] [v2]
↓ 建 1 個 v2,砍 1 個 v1
[v2] [v2] [v2]
任何時刻都有 Pod 在服務,使用者請求不會落空。像接力賽,下一棒跑起來上一棒才放手,沒有沒人跑的空檔。
背後機制:新舊 ReplicaSet 的蹺蹺板
Deployment 三層關係:Deployment → ReplicaSet → Pod。
滾動更新的秘密在 ReplicaSet 這層:
觸發更新時:
Deployment 建一個全新的 ReplicaSet
↓
舊 ReplicaSet(管 v1)副本數從 3 → 0
新 ReplicaSet(管 v2)副本數從 0 → 3
↓
蹺蹺板,一邊下去一邊上來
你 kubectl get rs 會看到兩個 ReplicaSet:一個 READY 是 3(新版)、另一個 READY 是 0(舊版)。
舊的不會被刪掉,副本數歸零還在 — 因為要留著給回滾用。
觸發滾動更新
最佳實踐:改 YAML 再 apply。
# 1. 取出現在的 YAML
kubectl get deployment my-nginx -o yaml > deployment.yaml
# 2. 改 image: nginx:1.26 → image: nginx:1.27(編輯器手動改)
# 3. apply 觸發更新
kubectl apply -f deployment.yaml
# 4. 看更新進度
kubectl rollout status deployment/my-nginx
# Waiting for deployment "my-nginx" rollout to finish: 1 out of 3 new replicas have been updated...
# Waiting for deployment "my-nginx" rollout to finish: 2 out of 3 new replicas have been updated...
# deployment "my-nginx" successfully rolled out
驗證:
# 看 Pod 名字(hash 變了,因為新 ReplicaSet)
kubectl get pods
# 看兩個 ReplicaSet
kubectl get rs
# my-nginx-aaa DESIRED 3 CURRENT 3 READY 3 ← 新版 1.27
# my-nginx-bbb DESIRED 0 CURRENT 0 READY 0 ← 舊版 1.26
# 看當前 Image
kubectl describe deployment my-nginx | grep Image
# Image: nginx:1.27
還有一個快捷方式:set image
kubectl set image deployment/my-nginx nginx=nginx:1.27
不用改檔案直接觸發更新。但生產環境推薦改 YAML 再 apply,因為:
- YAML 跟 Git 版本控制一致
- 團隊看檔案就知道現在跑什麼版本
set image改了不會反映到你的 YAML 檔
回滾:一行指令救命
v2 上線後使用者回報 bug,老闆衝過來說「趕快退回去」:
kubectl rollout undo deployment/my-nginx
Deployment 把舊 ReplicaSet 重新擴回 3、新的縮到 0。因為舊 ReplicaSet 還在、舊 Image 還在 Node 快取,回滾通常 30 秒搞定 — 不需要重新 build、不需要重推 Registry。
rollout history:看部署歷史
kubectl rollout history deployment/my-nginx
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# 3 <none>
每次部署都記一個 revision。要回到特定版本:
kubectl rollout undo deployment/my-nginx --to-revision=2
💡 K8s 預設保留最近 10 個版本(由 spec.revisionHistoryLimit 控制)。
實戰:故意推一個壞版本看 K8s 怎麼保護你
# YAML 把 image 改成 nginx:99.99(不存在)
kubectl apply -f deployment.yaml
kubectl get pods
# 新 Pod: ImagePullBackOff
# 舊 Pod: 還活著!
K8s 不會把所有舊 Pod 砍光才建新的,滾動更新的安全機制讓舊 Pod 保留。所以:
- 服務沒有完全更新成功
- 但也沒有完全掛掉
- 使用者依然能訪問到舊版
# 救命稻草
kubectl rollout undo deployment/my-nginx
舊 Pod 恢復、壞掉的新 Pod 砍掉,服務回正常。真實工作中超常見:開發人員打錯 tag、推了問題 Image、滾動更新卡住。別慌,rollout undo 一行搞定。
滾動更新指令速查
| 操作 | 指令 |
|---|---|
| 觸發更新 | 改 YAML → kubectl apply |
| 看進度 | kubectl rollout status deployment/xxx |
| 回上一版 | kubectl rollout undo deployment/xxx |
| 回特定版本 | kubectl rollout undo deployment/xxx --to-revision=N |
| 看歷史 | kubectl rollout history deployment/xxx |
| 暫停 | kubectl rollout pause deployment/xxx |
| 恢復 | kubectl rollout resume deployment/xxx |
對照 Docker:K8s 大幅領先
| 功能 | Docker Compose | K8s |
|---|---|---|
| 滾動更新 | 沒有 | 內建 |
| 健康檢查切換 | 沒有 | 內建 |
| 一行回滾 | 沒有 | rollout undo |
| 版本歷史 | 沒有 | rollout history |
up -d 換 Image 就是直接砍舊建新,有空窗期。Docker Swarm 有滾動更新但很多人不用。K8s 的滾動更新是生產環境的標配。
重點整理
- 滾動更新 = 逐步替換,零停機
- 背後是新舊 ReplicaSet 的蹺蹺板
- 觸發方式:改 YAML → apply(生產推薦)或
set image(快捷) - 舊 ReplicaSet 保留著,回滾就是把它擴回來
rollout status看進度、rollout history看歷史、rollout undo救命- 推壞 Image 不會把舊 Pod 砍光,滾動更新有保護機制
下一步
更新會了。但 Deployment 怎麼找到自己的 Pod?答案不是 Pod 名字,而是 labels。下一篇講Labels 與自我修復:K8s 怎麼認自己的 Pod,把 Day 4-5 的 Deployment 故事收尾。