kubernetes - day14

September 4, 2020

Deployment 縮寫為 deploy,它被建構在 ReplicaSet 控制器之上的控制器。為 PODReplicaSet 提供聲明式更新,而 Deployment 基本上和 ReplicaSet 很相似,只不過多了這些特性

Deployment 的建立

其定義的字段與 Replica 很像,如 replicasselectortemplate 等。下面是本章節的實驗範例。

# deploy-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: myapp
      release: canary
  template:
    metadata:
     labels:
       app: myapp
       release: canary
    spec:
      containers:
      - name: myapp
        image: nginx:1.10
        ports:
        - name: http
          containerPort: 80

佈署該 yaml

$ kubectl apply -f deploy-demo.yaml

列出 Deployment 的相關訊息

$ kubectl get deploy myapp-deploy
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
myapp-deploy   5/5     5            5           14s

前面有提到說 DeploymentReplicaSet 之間關係,從下面的查看可以驗證,DeploymentReplicaSet 之上,在 NAME 方面 myapp-deploy 控制了 ReplicaSet 資源,因此會有此前綴。而在標籤選擇器上,ReplicaSet 使用的是 Deployment 資源清單所設定的標籤選擇器。

$ kubectl get rs -l app=myapp -o wide
NAME                      DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES       SELECTOR
myapp-deploy-5bc49f9bb9   5         5         5       4m15s   myapp        nginx:1.10   app=myapp,pod-template-hash=5bc49f9bb9,release=canary
$ kubectl describe rs myapp-deploy-5bc49f9bb9
Name:           myapp-deploy-5bc49f9bb9
Namespace:      default
Selector:       app=myapp,pod-template-hash=5bc49f9bb9,release=canary
Labels:         app=myapp
                pod-template-hash=5bc49f9bb9
                release=canary
Annotations:    deployment.kubernetes.io/desired-replicas: 5
                deployment.kubernetes.io/max-replicas: 7
                deployment.kubernetes.io/revision: 1
Controlled By:  Deployment/myapp-deploy # 這邊清楚的知道 Deployment 控制 ReplicaSet
Replicas:       5 current / 5 desired
Pods Status:    5 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=myapp
           pod-template-hash=5bc49f9bb9
           release=canary
  Containers:
   myapp:
    Image:        nginx:1.10
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  28m   replicaset-controller  Created pod: myapp-deploy-5bc49f9bb9-hcfbq
  Normal  SuccessfulCreate  28m   replicaset-controller  Created pod: myapp-deploy-5bc49f9bb9-bpcjp
  Normal  SuccessfulCreate  28m   replicaset-controller  Created pod: myapp-deploy-5bc49f9bb9-5hqgb
  Normal  SuccessfulCreate  28m   replicaset-controller  Created pod: myapp-deploy-5bc49f9bb9-75hxv
  Normal  SuccessfulCreate  28m   replicaset-controller  Created pod: myapp-deploy-5bc49f9bb9-lhnsz

接下來我們查看 POD 資源,我們觀察 NAME 的命名格式,它以上面的 ReplicaSet 的名稱為前綴,表示由 ReplicaSet 控制。這樣的標記方式讓管理者更直覺的知道這是什麼。

$ kubectl get pods -l app=myapp,release=canary
NAME                            READY   STATUS    RESTARTS   AGE
myapp-5t5qf                     1/1     Running   0          14h
myapp-deploy-5bc49f9bb9-5hqgb   1/1     Running   0          8m30s
myapp-deploy-5bc49f9bb9-75hxv   1/1     Running   0          8m30s
myapp-deploy-5bc49f9bb9-bpcjp   1/1     Running   0          8m30s
myapp-deploy-5bc49f9bb9-hcfbq   1/1     Running   0          8m30s
myapp-deploy-5bc49f9bb9-lhnsz   1/1     Running   0          8m30s
myapp-dnbf6                     1/1     Running   0          14h
myapp-flv45                     1/1     Running   0          22h
myapp-gjpp6                     1/1     Running   0          14h
myapp-t82p4                     1/1     Running   0          14h
$ kubectl describe pods myapp-deploy-5bc49f9bb9-75hxv
Name:           myapp-deploy-5bc49f9bb9-75hxv
Namespace:      default
Priority:       0
Node:           gke-cluster-1-default-pool-7dc8b11b-z2gx/10.128.0.20
Start Time:     Thu, 10 Sep 2020 06:53:22 +0000
Labels:         app=myapp
                pod-template-hash=5bc49f9bb9
                release=canary
Annotations:    kubernetes.io/limit-ranger: LimitRanger plugin set: cpu request for container myapp
Status:         Running
IP:             10.4.0.20
IPs:            <none>
Controlled By:  ReplicaSet/myapp-deploy-5bc49f9bb9 # 驗證說明此 POD 由 ReplicaSet 控制

藉由上面的範例,可以更清楚知道 DeploymentReplicaSet 之間關係,其操作的技巧都非常相似,但 Deployment 卻有著更高階的操作技巧。總體來說它門之間關係如下圖所示

更新策略

ReplicaSet 文章中,我們發現它對於更新操作需要人為介入,有人為操作就有操作錯誤的風險。而 Deployment 就移除了人為操作更新的介入,不論修改容器中鏡像版本或是更改 TemplateDeployment 會幫助我們完成。這邊使用 describe 觀察建立的 Deployment 預設更新策略,發現是 RollingUpdate

$ kubectl describe deploy myapp-deploy
Name:                   myapp-deploy
Namespace:              default
CreationTimestamp:      Thu, 10 Sep 2020 06:53:22 +0000
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=myapp,release=canary
Replicas:               5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType:           RollingUpdate # 更新策略
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=myapp
           release=canary
  Containers:
   myapp:
    Image:        nginx:1.10
    Port:         80/TCP
...

這邊在藉由 explain 來看更新策略是否是文章開頭的兩種,從下面來看確實是 RollingUpdateRecreate 兩種,而 RollingUpdate 是預設。前面有稍微提到 Recreate 的用法,一次終止所有 POD 在進行佈署,這依然存在有短暫時間無法存取的問題,感覺上適用於舊版與新版的兼容性很差時。RollingUpdate 它以小批次方式進行更新,刪除一些 POD 在創建新版的 POD。相較於 Recreate,會有要存取新版還是舊版 POD 的問題,但是不會有無法存取的問題。

$ kubectl explain deployment.spec.strategy
KIND:     Deployment
VERSION:  extensions/v1beta1

RESOURCE: strategy <Object>

DESCRIPTION:
     The deployment strategy to use to replace existing pods with new ones.

     DeploymentStrategy describes how to replace existing pods with new ones.

FIELDS:
   rollingUpdate        <Object>
     Rolling update config params. Present only if DeploymentStrategyType =
     RollingUpdate.

   type <string>
     Type of deployment. Can be "Recreate" or "RollingUpdate". Default is
     RollingUpdate.

DeploymentRollingUpdate 的操作非是同一個 ReplicaSet 來執行,而是會新建一個 ReplicaSet,這之間會將先前 ReplicaSet 控管的 POD 做刪除,同時間新的 ReplicaSet 會增加新版的 POD,停止條件簡單來說就是舊 ReplicaSet 要沒有 POD,新 ReplicaSet 要符合正確的 POD 數量,這過程用下圖表示,圖中可以看到舊版的 ReplicaSet 未被刪除,是因為它可用作回滾這個動作,revisionHistoryLimit 這字段用於控制可保存 ReplicaSet 的數量,預設是 10。

然而 Deployment 中有調整滾動更新細粒度的設置選項,分別是 maxSurgemaxUnavailable。透過 kubectl explain deployment.spec.strategy.rollingUpdate 可以查看其作用。這邊可以使用 describe 觀察 deployment 的設定其預設是 25% max unavailable, 25% max surge。這邊簡單的說明作用

下兩張圖為 Kubernetes in action 的圖,可以嘗試理解其作用。

Deployment 更新

這邊將要實驗 Deployment 的滾動更新,而我們要修改的是 image,這部分可以透過 applyeditpatchset image 等來實現。下面使用 set image 更換鏡像,再使用 rollout status 查看其更新的狀態,接著觀察 replicaSet,其保留舊版,而換新版的 replicaSet 控制 POD,而 PODNAME,將由新版 replicaSet 作為前綴。

$ kubectl set image deploy myapp-deploy myapp=nginx:1.15 
deployment.extensions/myapp-deploy image updated
$ kubectl rollout status deploy myapp-deploy # 滾動更新的狀態
deployment "myapp-deploy" successfully rolled out
$ kubectl get replicasets -l app=myapp
NAME                      DESIRED   CURRENT   READY   AGE
myapp-deploy-5bc49f9bb9   0         0         0       110m # 舊版的保留
myapp-deploy-8664dd475d   5         5         5       2m15s

回滾操作

可能因為佈署新版本時遇到鏡像無法拉取或是應用程式的 Bug 因此要將此佈署往之前的一個版本做佈署。我們可以使用 undo 關鍵字進行回滾

$ kubectl get pods -l app=myapp,release=canary -o custom-columns=Name:metadata.name,Image:spec.containers[0].image # 先確認鏡像是否更新至 1.15 
Name                            Image
...
myapp-deploy-8664dd475d-gn9lb   nginx:1.15
myapp-deploy-8664dd475d-hqwfq   nginx:1.15
myapp-deploy-8664dd475d-mvpvr   nginx:1.15
myapp-deploy-8664dd475d-plq8h   nginx:1.15
myapp-deploy-8664dd475d-tbtpq   nginx:1.15
...

這邊要將 nginx:1.15 回滾為 nginx:1.10

$ kubectl rollout undo deploy myapp-deploy
deployment.extensions/myapp-deploy rolled back
$ kubectl get pods -l app=myapp,release=canary -o custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name                            Image
...
myapp-deploy-5bc49f9bb9-2csm6   nginx:1.10 # 版本回滾,RS 資源回到上一個
myapp-deploy-5bc49f9bb9-9lvlp   nginx:1.10
myapp-deploy-5bc49f9bb9-bqxv8   nginx:1.10
myapp-deploy-5bc49f9bb9-ghp6h   nginx:1.10
myapp-deploy-5bc49f9bb9-h45cm   nginx:1.10
...

這邊我們藉由,rollout history 查看回滾紀錄。觸發回滾時其 REVISION 的紀錄訊息會變更,回滾操作會被視為一次滾動更新因此會被新增自歷史紀錄,我們上前面的回滾紀錄被記做是 3,被回滾的紀錄會被刪除。--to-revision 這關鍵字可用來要回滾的版本,這邊再新增一個 nginx:1.18 的更新紀錄,這樣就有 2、3 和 4 可選,預設會是以第一列回滾。這邊我們嘗試指定 3 來回滾(版本會回到 1.10)。

$ kubectl rollout history deploy myapp-deploy # 查看 REVISION 紀錄
deployment.extensions/myapp-deploy
REVISION  CHANGE-CAUSE
2         <none>
3         <none>
$ kubectl set image deploy myapp-deploy myapp=nginx:1.18
$ kubectl rollout history deploy myapp-deploy
deployment.extensions/myapp-deploy
REVISION  CHANGE-CAUSE
2         <none>
3         <none>
4         <none> # 剛才的更新配紀錄在這邊
$ kubectl get rs -l app=myapp
NAME                      DESIRED   CURRENT   READY   AGE
myapp-deploy-5bc49f9bb9   5         5         5       139m
myapp-deploy-8664dd475d   0         0         0       32m
myapp-deploy-cd5f457bf    0         0         0       3m22s
$ kubectl rollout undo deploy myapp-deploy --to-revision=3
$ kubectl get pods -l app=myapp,release=canary -o custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name                            Image
...
myapp-deploy-5bc49f9bb9-42w6g   nginx:1.10
myapp-deploy-5bc49f9bb9-dcnpp   nginx:1.10
myapp-deploy-5bc49f9bb9-hlk8r   nginx:1.10
myapp-deploy-5bc49f9bb9-k42bd   nginx:1.10
myapp-deploy-5bc49f9bb9-tnt9f   nginx:1.10
...
$ kubectl rollout history deployments myapp-deploy
deployment.extensions/myapp-deploy
REVISION  CHANGE-CAUSE
2         <none>
4         <none>
5         <none> # 剛回滾被記錄在這

pause 與 resume

這兩個元素,用於佈署時可以針對一小部分進行更新,並觀察期更新的 POD 運行結果,此時舊版 POD 依舊存在,當新的 POD 確認穩定後,再將剩下舊版的 POD 資源進行更新。

pause,只更新一個並讓資源進入暫停狀態。

$ kubectl set image deploy myapp-deploy myapp=nginx:1.14 && kubectl rollout pause deploy myapp-deploy
deployment.extensions/myapp-deploy image updated
deployment.extensions/myapp-deploy paused
$ kubectl rollout status deploy myapp-deploy # 監聽 rollout 狀態
Waiting for deployment "myapp-deploy" rollout to finish: 5 out of 10 new replicas have been updated...

resume,讓暫停的資源重新啟用並執行相對應動作

$ kubectl rollout resume deploy myapp-deploy
deployment.extensions/myapp-deploy resumed
$ kubectl rollout status deploy myapp-deploy
Waiting for deployment "myapp-deploy" rollout to finish: 5 out of 10 new replicas have been updated...
Waiting for deployment spec update to be observed...
Waiting for deployment spec update to be observed...
Waiting for deployment "myapp-deploy" rollout to finish: 5 out of 10 new replicas have been updated...
Waiting for deployment "myapp-deploy" rollout to finish: 5 out of 10 new replicas have been updated...
Waiting for deployment "myapp-deploy" rollout to finish: 3 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 3 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 3 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "myapp-deploy" rollout to finish: 8 of 10 updated replicas are available...
Waiting for deployment "myapp-deploy" rollout to finish: 9 of 10 updated replicas are available...
deployment "myapp-deploy" successfully rolled out```

伸縮 POD 數量

使用 patch 方式跟新 replicas 的副本數量,這邊將其擴充到 10 個,這邊不限制是 patch 可以是 editapply 等。然而因為 replicasTemplate 級別因此不會記錄在 rollout 中。

$ kubectl patch deploy myapp-deploy -p '{"spec":{"replicas": 10}}'
deployment.extensions/myapp-deploy patched
$ kubectl get deploy myapp-deploy # 驗證
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
myapp-deploy   10/10   10           10          146m

結論

Deployment 靈活性高於 ReplicaSet,尤其是在滾動更新方面,也介紹像是 rollingUpdateminReadySeconds 等字段應用。同時也嘗試使用不同更新佈署資源的方式像是 editpatch 等。之後會有一篇更好的 Deployment 應用,來說明一些模式的操作。上面提到的一些字段使用以下 yaml 作為範例。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
  namespace: default
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  minReadySeconds: 3
  selector:
    matchLabels:
      app: myapp
      release: canary
  template:
    metadata:
     labels:
       app: myapp
       release: canary
    spec:
      containers:
      - name: myapp
        image: nginx:1.10
        ports:
        - name: http
          containerPort: 80

參考資料