回顧 ReplicaSet 控制器,該控制器是用來維護集群中運行的 Pod 數量的,但是往往在實際操作的時候,我們反而不會去直接使用 RS,而是會使用更上層的控制器,比如說 Deployment。
Deployment 一個非常重要的功能就是實現了 Pod 的滾動更新,比如我們應用更新了,我們只需要更新我們的容器鏡像,然后修改 Deployment 里面的 Pod 模板鏡像,那么 Deployment 就會用滾動更新(Rolling Update)的方式來升級現在的 Pod,這個能力是非常重要的,因為對于線上的服務我們需要做到不中斷服務,所以滾動更新就成了必須的一個功能。而 Deployment 這個能力的實現,依賴的就是ReplicaSet 這個資源對象。
線上應用建議使用 kubectl rollout restart 進行平滑重啟,避免 kubectl delete pod 造成短暫不可用。
回滾時,先用 kubectl rollout history 確認可用版本,然后執行 kubectl rollout undo。
更新過程中,可使用 kubectl rollout pause 和 resume 進行分階段部署。
Deployment 資源對象的格式和 ReplicaSet 幾乎一致,如下資源對象就是一個常見的 Deployment 資源類型。
創建下這個資源對象,查看 Pod 狀態:
$ kubectl apply -f - << EOF
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deploynamespace: default
spec:replicas: 3 # 期望的 Pod 副本數量,默認值為1selector: # Label Selector,必須匹配 Pod 模板中的標簽matchLabels:app: nginxtemplate: # Pod 模板metadata:labels:app: nginxspec:containers:- name: nginximage: nginxports:- containerPort: 80
EOF$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 3/3 3 3 58s$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deploy-85ff79dd56-7r76h 1/1 Running 0 41s
nginx-deploy-85ff79dd56-d5gjs 1/1 Running 0 41s
nginx-deploy-85ff79dd56-txc4h 1/1 Running 0 41s
到這里我們發現和之前的 RS 對象是否沒有什么兩樣,都是根據spec.replicas來維持的副本數量,我們隨意查看一個 Pod 的描述信息:
$ kubectl describe pod nginx-deploy-85ff79dd56-txc4h | grep Controlled
Controlled By: ReplicaSet/nginx-deploy-85ff79dd56
我們仔細查看其中有這樣一個信息Controlled By: ReplicaSet/nginx-deploy-85ff79dd56,什么意思?是不是表示當前我們這個 Pod 的控制器是一個 ReplicaSet 對象啊,我們不是創建的一個 Deployment 嗎?為什么 Pod 會被 RS 所控制呢?那我們再去看下這個對應的 RS 對象的詳細信息如何呢:
$ kubectl describe rs nginx-deploy-85ff79dd56
Name: nginx-deploy-85ff79dd56
Namespace: default
Selector: app=nginx,pod-template-hash=85ff79dd56
Labels: app=nginxpod-template-hash=85ff79dd56
Annotations: deployment.kubernetes.io/desired-replicas: 3deployment.kubernetes.io/max-replicas: 4deployment.kubernetes.io/revision: 1
Controlled By: Deployment/nginx-deploy
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
......
Events:Type Reason Age From Message---- ------ ---- ---- -------Normal SuccessfulCreate 4m52s replicaset-controller Created pod: nginx-deploy-85ff79dd56-7r76hNormal SuccessfulCreate 4m52s replicaset-controller Created pod: nginx-deploy-85ff79dd56-d5gjsNormal SuccessfulCreate 4m52s replicaset-controller Created pod: nginx-deploy-85ff79dd56-txc4h
其中有這樣的一個信息:Controlled By: Deployment/nginx-deploy,明白了吧?意思就是我們的 Pod 依賴的控制器 RS 實際上被我們的 Deployment 控制著呢,我們可以用下圖來說明 Pod、ReplicaSet、Deployment 三者之間的關系:
通過上圖我們可以很清楚的看到,定義了3個副本的 Deployment 與 ReplicaSet 和 Pod 的關系,就是一層一層進行控制的。ReplicaSet 作用和之前一樣還是來保證 Pod 的個數始終保存指定的數量,所以 Deployment 中的容器 restartPolicy只能是Always 就是這個原因,因為容器必須始終保證自己處于 Running 狀態,ReplicaSet 才可以去明確調整 Pod 的個數。而 Deployment 是通過管理 ReplicaSet 的數量和屬性來實現水平擴展/收縮以及滾動更新兩個功能的。
水平伸縮
水平擴展/收縮的功能比較簡單,因為 ReplicaSet 就可以實現,所以 Deployment 控制器只需要去修改它縮控制的 ReplicaSet 的 Pod 副本數量就可以了。比如現在我們把 Pod 的副本調整到 4 個,那么 Deployment 所對應的 ReplicaSet 就會自動創建一個新的 Pod 出來,這樣就水平擴展了,我們可以使用一個新的命令 kubectl scale 命令來完成這個操作:
$ kubectl scale deployment nginx-deploy --replicas=4
deployment.apps/nginx-deployment scaled
擴展完成后可以查看當前的 RS 對象:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deploy-85ff79dd56 4 4 3 40m
可以看到期望的 Pod 數量已經變成 4 了,只是 Pod 還沒準備完成,所以 READY 狀態數量還是 3,同樣查看 RS 的詳細信息:
$ kubectl describe rs nginx-deploy-85ff79dd56
Name: nginx-deploy-85ff79dd56
Namespace: default
Selector: app=nginx,pod-template-hash=85ff79dd56
......
Events:Type Reason Age From Message---- ------ ---- ---- -------Normal SuccessfulCreate 40m replicaset-controller Created pod: nginx-deploy-85ff79dd56-7r76hNormal SuccessfulCreate 40m replicaset-controller Created pod: nginx-deploy-85ff79dd56-d5gjsNormal SuccessfulCreate 40m replicaset-controller Created pod: nginx-deploy-85ff79dd56-txc4hNormal SuccessfulCreate 17s replicaset-controller Created pod: nginx-deploy-85ff79dd56-tph9g
可以看到 ReplicaSet 控制器增加了一個新的 Pod,同樣的 Deployment 資源對象的事件中也可以看到完成了擴容的操作:
$ kubectl describe deploy nginx-deploy
Name: nginx-deploy
Namespace: default
......
OldReplicaSets: <none>
NewReplicaSet: nginx-deploy-85ff79dd56 (4/4 replicas created)
Events:Type Reason Age From Message---- ------ ---- ---- -------Normal ScalingReplicaSet 43m deployment-controller Scaled up replica set nginx-deploy-85ff79dd56 to 3Normal ScalingReplicaSet 3m16s deployment-controller Scaled up replica set nginx-deploy-85ff79dd56 to 4
滾動更新
如果只是水平擴展/收縮這兩個功能,就完全沒必要設計 Deployment 這個資源對象了,Deployment 最突出的一個功能是支持滾動更新,比如現在我們需要把應用容器更改為 nginx:1.7.9 版本,修改后的資源清單文件如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deploynamespace: default
spec:replicas: 3 selector: matchLabels:app: nginxminReadySeconds: 5strategy: type: RollingUpdate # 指定更新策略:RollingUpdate和RecreaterollingUpdate:maxSurge: 1maxUnavailable: 1template: metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.7.9ports:- containerPort: 80
后前面相比較,除了更改了鏡像之外,我們還指定了更新策略:
minReadySeconds: 5
strategy:type: RollingUpdaterollingUpdate:maxSurge: 1maxUnavailable: 1
- minReadySeconds:表示 Kubernetes 在等待設置的時間后才進行升級,如果沒有設置該值,Kubernetes 會假設該容器啟動起來后就提供服務了,如果沒有設置該值,在某些極端情況下可能會造成服務不正常運行,默認值就是0。
- type=RollingUpdate:表示設置更新策略為滾動更新,可以設置為Recreate和RollingUpdate兩個值,Recreate表示全部重新創建,默認值就是RollingUpdate。
- maxSurge:表示升級過程中最多可以比原先設置多出的 Pod 數量,例如:maxSurage=1,replicas=5,就表示Kubernetes 會先啟動一個新的 Pod,然后才刪掉一個舊的 Pod,整個升級過程中最多會有5+1個 Pod。
- maxUnavaible:表示升級過程中最多有多少個 Pod 處于無法提供服務的狀態,例如:maxUnavaible=1,則表示 Kubernetes 整個升級過程中最多會有1個 Pod 處于無法服務的狀態。
? maxSurge 和 maxUnavailable 不能同時為 0,否則 Deployment 無法完成滾動更新。
? 至少有一個大于 0,否則 Pod 無法被替換。
? 推薦 maxSurge: 25%、maxUnavailable: 25%,兼顧可用性和更新速度。
? maxSurge: 1, maxUnavailable: 0 適用于高可用業務,確保無中斷更新。
? maxSurge: 0, maxUnavailable: 1 適用于資源受限場景,節省資源但更新更慢。
現在我們來直接更新上面的 Deployment 資源對象:
$ kubectl apply -f nginx-deploy.yaml
更新后,我們可以執行下面的 kubectl rollout status 命令來查看我們此次滾動更新的狀態:
$ kubectl rollout status deployment/nginx-deploy
Waiting for deployment "nginx-deploy" rollout to finish: 2 out of 3 new replicas have been updated...
從上面的信息可以看出我們的滾動更新已經有兩個 Pod 已經更新完成了,在滾動更新過程中,我們還可以執行如下的命令來暫停更新:
$ kubectl rollout pause deployment/nginx-deploy
deployment.apps/nginx-deploy paused
這個時候我們的滾動更新就暫停了,此時我們可以查看下 Deployment 的詳細信息:
$ kubectl describe deploy nginx-deploy
Name: nginx-deploy
Namespace: default
CreationTimestamp: Sat, 16 Nov 2019 16:01:24 +0800
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 2kubectl.kubernetes.io/last-applied-configuration:{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"nginx-deploy","namespace":"default"},"spec":{"minReadySec...
Selector: app=nginx
Replicas: 3 desired | 2 updated | 4 total | 4 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 5
RollingUpdateStrategy: 1 max unavailable, 1 max surge
......
OldReplicaSets: nginx-deploy-85ff79dd56 (2/2 replicas created)
NewReplicaSet: nginx-deploy-5b7b9ccb95 (2/2 replicas created)
Events:Type Reason Age From Message---- ------ ---- ---- -------Normal ScalingReplicaSet 26m deployment-controller Scaled up replica set nginx-deploy-85ff79dd56 to 4Normal ScalingReplicaSet 3m44s deployment-controller Scaled down replica set nginx-deploy-85ff79dd56 to 3Normal ScalingReplicaSet 3m44s deployment-controller Scaled up replica set nginx-deploy-5b7b9ccb95 to 1Normal ScalingReplicaSet 3m44s deployment-controller Scaled down replica set nginx-deploy-85ff79dd56 to 2Normal ScalingReplicaSet 3m44s deployment-controller Scaled up replica set nginx-deploy-5b7b9ccb95 to 2
我們仔細觀察 Events 事件區域的變化,上面我們用 kubectl scale 命令將 Pod 副本調整到了 4,現在我們更新的時候是不是聲明又變成 3 了,所以 Deployment 控制器首先是將之前控制的 nginx-deploy-85ff79dd56 這個 RS 資源對象進行縮容操作,然后滾動更新開始了,可以發現 Deployment 為一個新的 nginx-deploy-5b7b9ccb95 RS 資源對象首先新建了一個新的 Pod,然后將之前的 RS 對象縮容到 2 了,再然后新的 RS 對象擴容到 2,后面由于我們暫停滾動升級了,所以沒有后續的事件了,大家有看明白這個過程吧?這個過程就是滾動更新的過程,啟動一個新的 Pod,殺掉一個舊的 Pod,然后再啟動一個新的 Pod,這樣滾動更新下去,直到全都變成新的 Pod,這個時候系統中應該存在 4 個 Pod,因為我們設置的策略maxSurge=1,所以在升級過程中是允許的,而且是兩個新的 Pod,兩個舊的 Pod:
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deploy-5b7b9ccb95-k6pkh 1/1 Running 0 11m
nginx-deploy-5b7b9ccb95-l6lmx 1/1 Running 0 11m
nginx-deploy-85ff79dd56-7r76h 1/1 Running 0 75m
nginx-deploy-85ff79dd56-txc4h 1/1 Running 0 75m
查看 Deployment 的狀態也可以看到當前的 Pod 狀態:
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 4/3 2 4 75m
這個時候我們可以使用kubectl rollout resume來恢復我們的滾動更新:
$ kubectl rollout resume deployment/nginx-deploy
deployment.apps/nginx-deploy resumed
$ kubectl rollout status deployment/nginx-deploy
Waiting for deployment "nginx-deploy" rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deploy" successfully rolled out
看到上面的信息證明我們的滾動更新已經成功了,同樣可以查看下資源狀態:
$ kubectl get pod -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deploy-5b7b9ccb95-gmq7v 1/1 Running 0 115s
nginx-deploy-5b7b9ccb95-k6pkh 1/1 Running 0 15m
nginx-deploy-5b7b9ccb95-l6lmx 1/1 Running 0 15m
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 3/3 3 3 79m
這個時候我們查看 ReplicaSet 對象,可以發現會出現兩個:
$ kubectl get rs -l app=nginx
NAME DESIRED CURRENT READY AGE
nginx-deploy-5b7b9ccb95 3 3 3 18m
nginx-deploy-85ff79dd56 0 0 0 81m
從上面可以看出滾動更新之前我們使用的 RS 資源對象的 Pod 副本數已經變成 0 了,而滾動更新后的 RS 資源對象變成了 3 個副本,我們可以導出之前的 RS 對象查看:
$ kubectl get rs nginx-deploy-85ff79dd56 -o yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:annotations:deployment.kubernetes.io/desired-replicas: "3"deployment.kubernetes.io/max-replicas: "4"deployment.kubernetes.io/revision: "1"creationTimestamp: "2019-11-16T08:01:24Z"generation: 5labels:app: nginxpod-template-hash: 85ff79dd56name: nginx-deploy-85ff79dd56namespace: defaultownerReferences:- apiVersion: apps/v1blockOwnerDeletion: truecontroller: truekind: Deploymentname: nginx-deployuid: b0fc5614-ef58-496c-9111-740353bd90d4resourceVersion: "2140545"selfLink: /apis/apps/v1/namespaces/default/replicasets/nginx-deploy-85ff79dd56uid: 8eca2998-3610-4f80-9c21-5482ba579892
spec:replicas: 0selector:matchLabels:app: nginxpod-template-hash: 85ff79dd56template:metadata:creationTimestamp: nulllabels:app: nginxpod-template-hash: 85ff79dd56spec:containers:- image: nginximagePullPolicy: Alwaysname: nginxports:- containerPort: 80protocol: TCPresources: {}terminationMessagePath: /dev/termination-logterminationMessagePolicy: FilednsPolicy: ClusterFirstrestartPolicy: AlwaysschedulerName: default-schedulersecurityContext: {}terminationGracePeriodSeconds: 30
status:observedGeneration: 5replicas: 0
我們仔細觀察這個資源對象里面的描述信息除了副本數變成了replicas=0之外,和更新之前沒有什么區別吧?大家看到這里想到了什么?有了這個 RS 的記錄存在,是不是我們就可以回滾了啊?而且還可以回滾到前面的任意一個版本,這個版本是如何定義的呢?我們可以通過命令 rollout history 來獲取:
$ kubectl rollout history deployment nginx-deploy
deployment.apps/nginx-deploy
REVISION CHANGE-CAUSE
1 <none>
2 <none>
其實 rollout history 中記錄的 revision 是和 ReplicaSets 一一對應。如果我們手動刪除某個 ReplicaSet,對應的rollout history就會被刪除,也就是說你無法回滾到這個revison了,同樣我們還可以查看一個revison的詳細信息:
$ kubectl rollout history deployment nginx-deploy --revision=1
deployment.apps/nginx-deploy with revision #1
Pod Template:Labels: app=nginxpod-template-hash=85ff79dd56Containers:nginx:Image: nginxPort: 80/TCPHost Port: 0/TCPEnvironment: <none>Mounts: <none>Volumes: <none>
假如現在要直接回退到當前版本的前一個版本,我們可以直接使用如下命令進行操作:
$ kubectl rollout undo deployment nginx-deploy
當然也可以回退到指定的revision版本:
$ kubectl rollout undo deployment nginx-deploy --to-revision=1
deployment "nginx-deploy" rolled back
回滾的過程中我們同樣可以查看回滾狀態:
$ kubectl rollout status deployment/nginx-deploy
Waiting for deployment "nginx-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deploy" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deploy" rollout to finish: 2 of 3 updated replicas are available...
Waiting for deployment "nginx-deploy" rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deploy" successfully rolled out
這個時候查看對應的 RS 資源對象可以看到 Pod 副本已經回到之前的 RS 里面去了。
$ kubectl get rs -l app=nginx
NAME DESIRED CURRENT READY AGE
nginx-deploy-5b7b9ccb95 0 0 0 31m
nginx-deploy-85ff79dd56 3 3 3 95m
不過需要注意的是回滾的操作滾動的revision始終是遞增的:
$ kubectl rollout history deployment nginx-deploy
deployment.apps/nginx-deploy
REVISION CHANGE-CAUSE
2 <none>
3 <none>
保留舊版本
在很早之前的 Kubernetes 版本中,默認情況下會為我們暴露下所有滾動升級的歷史記錄,也就是 ReplicaSet 對象,但一般情況下沒必要保留所有的版本,畢竟會存在 etcd 中,我們可以通過配置 spec.revisionHistoryLimit 屬性來設置保留的歷史記錄數量,不過新版本中該值默認為 10,如果希望多保存幾個版本可以設置該字段。
總結
1.deployment的spec.template發生變更的時候,Deployment會創建一個新的ReplicaSet,然后滾動更新Pod. 而更改spec.replicas的數量不會創建一個新的ReplicaSet。
2.maxSurge 和 maxUnavailable 不能同時為 0,否則 Deployment 無法完成滾動更新。
3.當deployment的spec發生變更時,generation會升級。
4.deployment的restartPolicy只能是Always.
5.spec.revisionHistoryLimit設置保留ReplicaSet的歷史記錄數量
常用命令
調整副本數
kubectl scale deployment/<deployment-name> --replicas=4
查看滾動更新狀態
kubectl rollout status deployment/<deployment-name>
暫停滾動更新
kubectl rollout pause deployment/<deployment-name>
恢復滾動更新
kubectl rollout resume deployment/<deployment-name>
回滾到指定版本
kubectl rollout undo deployment/<deployment-name> --to-revision=1
回滾到上一個版本
kubectl rollout undo deployment/<deployment-name>
查看歷史版本
kubectl rollout history deployment/<deployment-name>
查看指定版本的詳情
kubectl rollout history deployment/<deployment-name> --revision=1
重新啟動 Deployment
kubectl rollout restart deployment/<deployment-name>