Kubernetes:Pod 升級、回滾

痴者工良發表於2021-12-03

本篇主要討論如何實現滾動更新和回滾,任意更換版本並且回滾以前的版本(版本更新),而下一章會討論到 Pod 縮放,根據機器資源自動擴充和收縮應用(自動擴容例項)。

本文為作者的 Kubernetes 系列電子書的一部分,電子書已經開源,歡迎關注,電子書瀏覽地址:

https://k8s.whuanle.cn【適合國內訪問】

https://ek8s.whuanle.cn 【gitbook】

滾動更新和回滾

部署應用

首先我們來部署 nginx,使用 nginx 作為練習的映象。

開啟 https://hub.docker.com/_/nginx 可以查詢 nginx 的映象版本,筆者這裡選擇三個版本:1.19.101.20.0latest,後續我們更新和回滾時,會在這幾個版本之間選擇。

[Info] 提示

需要讀者明確選擇nginx 的三個不同版本,我們後面的升級回滾練習會在這三個版本中來回切換。

1.19.10 -> 1.20.0 -> latest

首先,我們建立一個 Nginx 的 Deployment,副本數量為 3,首次部署的時候,跟之前的操作一致,不需要什麼特殊的命令。這裡我們使用舊一些的版本,筆者使用的是 1.19.0。

kubectl create deployment nginx --image=nginx:1.19.0 --replicas=3
# 或者
# kubectl create deployment nginx --image=nginx:1.19.0 --replicas=3 --record

注: 我們也可以加上 --record 標誌將所執行的命令寫入資源註解 kubernetes.io/change-cause 中。 這對於以後的檢查是有用的。例如,要檢視針對每個 Deployment 修訂版本所執行過的命令,對於這個引數的作用,我們後面再解釋。

執行 kubectl get podskubectl describe pods可以觀察到有有三個 Pod,每個 Pod 的 nginx 映象版本都是 1.19.0。

NAME                     READY   STATUS    RESTARTS   AGE
nginx-85b45874d9-7jlrv   1/1     Running   0          5s
nginx-85b45874d9-h22xv   1/1     Running   0          5s
nginx-85b45874d9-vthfb   1/1     Running   0          5s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  119s  default-scheduler  Successfully assigned default/nginx-85b45874d9-vthfb to instance-2
  Normal  Pulled     117s  kubelet            Container image "nginx:1.19.0" already present on machine
  Normal  Created    117s  kubelet            Created container nginx
  Normal  Started    117s  kubelet            Started container nginx

更新版本

其實更新 Pod 是非常簡單的,我們不需要控制每個 Pod 的更新,也不需要擔心會不會對業務產生影響,K8S 會自動控制這些過程。我們只需要觸發映象版本更新事件,K8S 會自動為我們更新所有 Pod 的。

kubectl set image 可以更新現有資源物件的容器映象,物件包括 PodDeploymentDaemonSetJobReplicaSet。在更新版本中,單個容器的 Pod,對於多個容器的 Pod 行為是差不多的,所以我們使用單容器 Pod 練習即可。

更新 Deployment 中的映象版本,觸發 Pod:

kubectl set image deployment nginx nginx=nginx:1.20.0

格式為:

kubectl set image deployment {deployment名稱} {映象名稱}:={映象名稱}:{版本}

此命令可以任意修改 Pod 中的其中一個容器的版本,只要某個容器的映象版本變化,整個 Pod 都會重新部署。如果映象版本沒有變化,即使是執行了 kubectl set image ,也不會產生影響。

我們可以檢視 Pod 的詳細資訊:

kubectl describe pods

找到 Events 描述:

... ...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  66s   default-scheduler  Successfully assigned default/nginx-7b87485749-rlmcx to instance-2
  Normal  Pulled     66s   kubelet            Container image "nginx:1.20.0" already present on machine
  Normal  Created    66s   kubelet            Created container nginx
  Normal  Started    65s   kubelet            Started container nginx

可以看到,現在現在建立的 Pod 例項為 1.20.0 版本。

更新過程中,會建立新版本的 Pod,舊的 Pod 會被逐漸移除。

我們在建立 Deployment 時,生成了三個 Pod ,而當我們觸發映象版本更新時,Pod 不會一次性更新,而是按照一定規則每次只重新部署一部分 Pod,Pod 更新替換過程類似下圖所示(實際上 Pod 數量可能大於 3個):

pod更新替換過程

另外,我們還可以通過 kubectl edit yaml 的方式方式更新 Pod。

執行:

kubectl edit deployment nginx

然後會彈出編輯 YAML 的介面,將 .spec.template.spec.containers[0].image 從 nginx:1.19.0 更改至 nginx:1.20.0,然後儲存即可。

為了記錄版本更新資訊,我們需要在 kubectl create deploymentkubectl set image 命令後面加上 --record

別忘記了 kubectl scale 命令也可以更改副本數量。

上線

僅當 Deployment Pod 模板(即 .spec.template)發生改變時,例如模板的標籤或容器映象被更新, 才會觸發 Deployment 上線。

其他更新(如對 Deployment 執行擴縮容的操作)不會觸發上線動作,Deployment 的上線動作可以為我們更新 Pod 的版本(Pod 中的容器版本)。

這裡提到的 上線/更新版本 是因為容器版本會發生變化,而更新一般指修改了 YAML 等,不一定會對容器產生影響。

當我們更新 Pod 版本時,K8S 會自動負載均衡,而不是把所有 pod 刪除,再重新建立新版本 Pod,它會以穩健的方式逐漸替換 Pod 副本,所以叫滾動更新

我們可以通過 kubectl rollout status 命令,檢視 Pod 的上線狀態,即 Pod 副本集的更替狀態:

kubectl rollout status deployment nginx

輸出結果一般有兩種:

# 已經完成時:
deployment "nginx-deployment" successfully rolled out
# 還在更新時:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...

我們也可以通過獲取 Deployment 資訊時,檢視已更新的 pod 數量:

kubectl get deployment
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   3/3     3            3           18m

UP-TO-DATE 欄位可以看到成功更新的 pod 數量。

還可以檢視 ReplicaSet 和 Pod:

kubectl get replicaset
kubectl get pods

輸出型別於:

NAME               DESIRED   CURRENT   READY   AGE
nginx-7b87485749   0         0         0       20m
nginx-85b45874d9   3         3         3       21m
NAME                     READY   STATUS    RESTARTS   AGE
nginx-85b45874d9-nrbg8   1/1     Running   0          12m
nginx-85b45874d9-qc7f2   1/1     Running   0          12m
nginx-85b45874d9-t48vw   1/1     Running   0          12m

可以看到有兩個 ReplicaSet,nginx-7b87485749 是 1.19.0 版本,已經被全部更新到 1.20.0 版本 了,所以前者的數量為 0,我們也可以看到 Pod 中,所有 Pod 都是以 nginx-85b45874d9 作為字首的。這幾個關鍵資訊,我們可以截圖,後面再次對照。

如何滾動更新

我們更新 映象版本時,舊的 Pod 版本會被替換,但是 ReplicaSet 副本記錄是不會被刪除的。實際上滾動更新就是控制副本數量,原本 1.19.0 的副本數量為 3,現在變成 0,1.20.0 的副本數量變成 3 。

如果我們的專案上線了,我們更新軟體版本,如果一次性更新所有容器或者 pod,那麼我們的軟體會有一段時間處於不可用狀態,直到所有 Pod 都完成更新。

Deployment 可確保在更新時僅關閉一定數量的 Pod,預設情況下,它確保至少所需 Pods 75% 處於執行狀態,也就是說正在被更新的 Pod 比例不超過 25%。當然,只有兩三個 pod 的 Deployment 不會按照這個比例限定。也就是說,Deployment 等處於滾動更新狀態時,其始終可以保證有可用的 Pod 提供服務。

如果我們的 Pod 數量足夠大,或者在更新 Deployment 時迅速輸出上線狀態,可以看到新舊的 Pod 數量加起來不一定就是 3 個,因為它不會殺死老 Pods,直到有足夠的數量新的 Pods 已經出現。 在足夠數量的舊 Pods 被殺死前並沒有建立新 Pods。當副本數量為3個時,它確保至少 2 個 Pod 可用,同時 最多總共 4 個 Pod 存在(不同版本)。

新舊pod

滾動更新過程如下圖所示:

滾動更新過程

Deployment 確保僅所建立 Pod 數量只可能比期望 Pods 數高一點點。 預設情況下,它可確保啟動的 Pod 個數比期望個數最多多出 25%(最大峰值 25%)所以在自動更新 Deployment 時,觀察到的 pod 可能為 4個,這是由 Deployment 的縮放配置決定的(下一章講解)。另外,在 Deployment 更新時,除了可以更改映象的版本,也可以更改 ReplicaSet 的數量。

執行 kubectl describe deployment nginx 檢視 Deployment 詳細資訊,我們檢視 Event 欄位,也可以觀察到新舊 Pod 的更替過程。

新舊pod建立過程

但是這些原理等知識我們都不需要記,也不需要深入,我們記得有這回事就行,有需要的時候也可以直接檢視文件的,後面的章節還會詳細介紹 ReplicaSet 的規則。

檢視上線記錄

預設情況下, Deployment 的上線記錄都會保留在系統中,以便可以隨時回滾,前面我們也提到了檢視 kubectl get replicasets 時出現的副本記錄。

我們檢視 Deployment 的上線歷史記錄:

kubectl rollout history deployment nginx
REVISION  CHANGE-CAUSE
1         
2         

# 帶 --record 的話,輸出是
REVISION  CHANGE-CAUSE
2       kubectl set image deployment nginx nginx=nginx:1.20.0 --record=true

可以看到有兩個版本,但是CHANGE-CAUSE 為 <none> 呢?這是因為筆者沒有使用 --record 引數記錄資訊,如果沒帶上 --record 的話,我們看著這個歷史記錄,完全分不出到底是什麼版本。

現在我們檢視 版本2 的詳細資訊:

kubectl rollout history deployment nginx --revision=2
deployment.apps/nginx with revision #2
Pod Template:
  Labels:    app=nginx
    pod-template-hash=85b45874d9
  Containers:
   nginx:
    Image:    nginx:1.20.0
    Port:    
    Host Port:    
    Environment:    
    Mounts:    
  Volumes:    

回滾

當部署的新版本程式發現嚴重 bug 影響平臺穩定性時,你可能需要將專案切換為上一個版本。目前介紹了幾個檢視 Deployment 上線的歷史記錄的命令,下面介紹如果將 Pod 換到舊的版本。

回滾到上一個版本的命令:

root@master:~# kubectl rollout undo deployment nginx
deployment.apps/nginx rolled back

例如當前是 版本2,那麼會回滾到 版本1。

再執行 kubectl rollout history deployment nginx 會發現 revision 變成 3 了。

root@master:~# kubectl rollout history deployment nginx
deployment.apps/nginx 
REVISION  CHANGE-CAUSE
2         
3         

revision 記錄的是部署記錄,與 Pod 的映象版本無關,每次更新版本或進行回滾等操作時, revision 會自動遞增 1。

如果版本數量多了,我們還可以指定回滾到特點的版本。

kubectl rollout undo deployment nginx --to-revision=2

這裡提一下 --record,在前面,我們建立和更新 Deployment 時,都沒有使用到這個引數,其實這個引數很有用的,接下來我們每次執行滾動更新時都要帶上這個引數才行。

更新映象到指定版本:

kubectl set image deployment nginx nginx=nginx:1.19.0 --record
kubectl rollout history deployment nginx

輸出:

REVISION  CHANGE-CAUSE
5         
6         kubectl set image deployment nginx nginx=nginx:1.19.0 --record=true

但是我們這裡目前來說,只有兩個記錄,我們明明提交了多次,雖然 revision 會變化,但是這裡查詢的只有兩條記錄,這時因為我們操作的時候,只用到了 1.19.0、1.20.0 兩個版本,所以也就只有這兩個版本的提交記錄。多用幾個版本,輸出結果:

REVISION  CHANGE-CAUSE
7         kubectl set image deployment nginx nginx=nginx:1.19.0 --record=true
8         kubectl set image deployment nginx nginx=nginx:1.20.0 --record=true
9         kubectl set image deployment nginx nginx=nginx:latest --record=true

REVISION 欄位的數字是會遞增的,當我們觸發上線動作(容器標籤、版本等)時,會產生新的上線記錄。

暫停上線

本小節需要水平縮放、比例縮放等知識,請先閱讀 3.6 章關於縮放的內容。

如果在上線過程中,發現機器不夠用了,或者需要調整一些配置等,可以暫停上線過程。

kubectl rollout pause 命令可以讓我們在 Deployment 的 Pod 版本時,暫停滾動更新。

命令:

kubectl rollout pause deployment nginx

在滾動更新過程中,會有一些現象需要我們留意。

先建立一個 Deployment 或者更新 Deployment 的 Pod 為 10 個副本。

kubectl create deployment --image=nginx:1.19.0 --replicas=10

我們執行 kubectl edit deployment nginx 修改縮放個數:

  strategy:
    rollingUpdate:
      maxSurge: 3
      maxUnavailable: 2
    type: RollingUpdate

設定了這個 maxSurge 和 maxUnavailable,可以讓 Deployment 替換 Pod 時慢一些。

之前我們已經使用了 1.19.01.20.0 兩個版本進行演示,這裡我們使用 latest 版本進行實踐。

複製以下兩條命令快速執行,可以快速卡住上線過程。我們暫停上線後,檢視一些狀態資訊。

kubectl set image deployment nginx nginx=nginx:latest
kubectl rollout pause deployment nginx

執行 kubectl get replicaset 檢視這些版本的數量。

NAME               DESIRED   CURRENT   READY   AGE
nginx-7b87485749   8         8         8       109m
nginx-85b45874d9   0         0         0       109m
nginx-bb957bbb5    5         5         5       52m

可以看到,所有的 Pod 加起來數量大於 10,舊容器以每次 2 個的數量減少;新容器以每次 3 個的數量建立;暫停上線後,多次執行 kubectl get replicaset ,會發現副本數量不會變化。

前面我們已經暫停了上線,如果我們執行上線命令換成別的版本:

kubectl set image deployment nginx nginx=nginx:1.19.0

會發現雖然提示更新了,但是實際上沒有變化。執行 kubectl rollout history deployment nginx 也查不到我們提交的 1.19.0 的請求。這是因為在已經暫停上線的控制器物件中,執行新的上線動作是無效的。

暫停的時候,我們可以更新一些配置,例如限制 Pod 中的 nginx 容器使用的 CPU 和 資源:

kubectl set resources deployment nginx -c=nginx --limits=cpu=200m,memory=512Mi

再恢復 Deployment 上線:

kubectl rollout resume deployment nginx

相關文章