昨天把 todo-api 和 todo-app-frontend 都用 Deployment 跑起來了。今天來學 Rolling Update——在不停機的情況下更新服務版本 !
傳統的部署方式是「停掉舊版本、啟動新版本」,中間有一段服務中斷的時間。對生產環境來說,這段停機時間是無法接受的。
Rolling Update(滾動更新)的做法是:逐批替換舊版本的 Pod,每次只換一部分,確保隨時都有 Pod 在服務流量。
更新前:[v1] [v1] [v1]
更新中:[v2] [v1] [v1] ← 先換一個
[v2] [v2] [v1] ← 再換一個
[v2] [v2] [v2] ← 全部換完
更新後:[v2] [v2] [v2]
整個過程中服務從未中斷,使用者完全感覺不到版本在切換。
更新的時候,Deployment 不是直接修改現有的 ReplicaSet,而是:
更新前:
舊 ReplicaSet (v1) → [Pod] [Pod] [Pod]
新 ReplicaSet (v2) → (尚未建立)
更新中:
舊 ReplicaSet (v1) → [Pod] [Pod]
新 ReplicaSet (v2) → [Pod]
更新後:
舊 ReplicaSet (v1) → (保留但縮為 0,用於 rollback)
新 ReplicaSet (v2) → [Pod] [Pod] [Pod]
舊 ReplicaSet 保留下來是為了 rollback——萬一新版本有問題,可以直接切回舊的。
今天目標:幫 todo-api 設定滾動更新策略,推一個 v2 版本上去,親眼看 Pod 逐批替換的過程,並試試 rollback。
Todo-App/k8s/todo-api-deployment.yaml ,在 Deployment YAML 裡加上 strategy 欄位,控制滾動更新的行為:apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-api
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 更新期間最多多出幾個 Pod
maxUnavailable: 0 # 更新期間最多有幾個 Pod 不可用
selector:
matchLabels:
app: todo-api
template:
metadata:
labels:
app: todo-api
tier: backend
spec:
containers:
- name: api
image: yourname/todo-app-api:v1.0.0
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
value: "mysql+pymysql://user:password@mysql-service:3306/tododb"
兩個關鍵參數:
maxSurge: 1:更新期間允許多出 1 個 Pod。3 個副本 + maxSurge 1 = 最多同時存在 4 個 Pod。maxUnavailable: 0:更新期間不允許任何 Pod 不可用。這保證了「隨時都有足夠的 Pod 在服務流量」,適合對可用性要求高的服務。kubectl apply -f todo-api-deployment.yaml
docker buildx build --platform linux/amd64,linux/arm64 \
-t <你的Docker Hub帳號>/todo-app-api:v2.0.0 \
--push ./backend
更新 image 版本來觸發滾動更新:
# 方法一:直接用指令更新 image
kubectl set image deployment/todo-api api=yourname/todo-app-api:v2.0.0
# 方法二:修改 YAML 裡的 image tag,再 apply
# image: yourname/todo-app-api:v2.0.0
kubectl apply -f todo-api-deployment.yaml
# 即時觀察 Pod 的狀態變化
kubectl get pods -w
你會看到類似這樣的輸出:
NAME READY STATUS RESTARTS AGE
todo-api-6d4b9f8c7-k2xpq 1/1 Running 0 5m
todo-api-6d4b9f8c7-m9wvr 1/1 Running 0 5m
todo-api-6d4b9f8c7-x7bnp 1/1 Running 0 5m
todo-api-7f9c8d6b4-ab1cd 0/1 ContainerCreating 0 2s ← 新版本 Pod 建立
todo-api-7f9c8d6b4-ab1cd 1/1 Running 0 8s ← 新版本 Pod 就緒
todo-api-6d4b9f8c7-k2xpq 1/1 Terminating 0 5m ← 舊版本 Pod 開始終止
可能會看到 todo-frontend報錯,先暫時不理它,下一章節建立service就會修復了
確認更新狀態:
kubectl rollout status deployment/todo-api
Waiting for deployment "todo-api" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "todo-api" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "todo-api" rollout to finish: 1 old replicas are pending termination...
deployment "todo-api" successfully rolled out
kubectl rollout history deployment/todo-api

CHANGE-CAUSE 預設是空的。可以在 apply 的時候加上 annotation 記錄原因:
kubectl annotate deployment/todo-api kubernetes.io/change-cause="update api to v2.0.0"
之後再看 history 就會有記錄:

新版本上線後發現有問題,rollback 回上一個版本:
# 回到上一個版本
kubectl rollout undo deployment/todo-api
# 回到指定版本
kubectl rollout undo deployment/todo-api --to-revision=1
rollback 本身也是一次 Rolling Update,K8s 會用同樣的滾動方式把 Pod 換回舊版本,不會停機。
確認 rollback 完成:
kubectl rollout status deployment/todo-api
kubectl get pods
K8s 的 Deployment 支援兩種更新策略,RollingUpdate 是預設,另一種是 Recreate。
RollingUpdate(預設)
逐批替換 Pod,服務不中斷。適合大多數情況,特別是對可用性要求高的服務。
缺點是更新過程中會同時存在新舊兩個版本,如果新舊版本之間有不相容的資料庫 schema 變更,需要特別處理。
Recreate
先把所有舊版本 Pod 全部刪掉,再建立新版本 Pod。中間會有短暫停機。
strategy:
type: Recreate
什麼時候用 Recreate?當新舊版本完全不能同時存在的時候——例如資料庫 migration 必須在舊版本完全關閉後才能執行,或者服務用了不支援多副本的本地狀態。
大多數情況會用 RollingUpdate ,除非有明確的理由需要 Recreate。
今天學了 Rolling Update:
明天會學 Service,把 todo-api 和 todo-frontend 串起來,讓它們可以互相溝通,外部也能連進來。