Kubernetes — 做業副本與水平擴展

Deployment 看似簡單,但實際上,它實現了 Kubernetes 項目中一個很是重要的功能:Pod 的「水平擴展 / 收縮」(horizontal scaling out/in)。nginx

這個功能,是從 PaaS 時代開始,一個平臺 級項目就必須具有的編排能力。api

舉個例子,若是你更新了 Deployment 的 Pod 模板(好比,修改了容器的鏡像),那麼 Deployment 就須要遵循一種叫做「滾動更新」(rolling update)的方式,來升級現有的容器。 而這個能力的實現,依賴的是 Kubernetes 項目中的一個很是重要的概念(API 對象): ReplicaSet。 ReplicaSet 的結構很是簡單,咱們能夠經過這個 YAML 文件查看一下:session

 

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-set
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9

從這個 YAML 文件中,咱們能夠看到,一個 ReplicaSet 對象,其實就是由副本數目的定義和一個 Pod 模板組成的。不難發現,它的定義實際上是 Deployment 的一個子集。app

更重要的是,Deployment 控制器實際操縱的,正是這樣的 ReplicaSet 對象,而不是 Pod 對 象。運維

對於一個 Deployment 所管理的 Pod,它的 ownerReference 是誰? 因此,這個問題的答案就是:ReplicaSet。 明白了這個原理,我再來和你一塊兒分析一個以下所示的 Deployment:編輯器

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

能夠看到,這就是一個咱們經常使用的 nginx-deployment,它定義的 Pod 副本個數是 3(spec.replicas=3)。spa

那麼,在具體的實現上,這個 Deployment,與 ReplicaSet,以及 Pod 的關係是怎樣的呢?命令行

咱們能夠用一張圖把它描述出來:設計

經過這張圖,咱們就很清楚的看到,一個定義了 replicas=3 的 Deployment,與它的 ReplicaSet,以及 Pod 的關係,其實是一種「層層控制」的關係。版本控制

其中,ReplicaSet 負責經過「控制器模式」,保證系統中 Pod 的個數永遠等於指定的個數(好比, 3 個)。這也正是 Deployment 只容許容器的 restartPolicy=Always 的主要緣由:只有在容器能 保證本身始終是 Running 狀態的前提下,ReplicaSet 調整 Pod 的個數纔有意義。

而在此基礎上,Deployment 一樣經過「控制器模式」,來操做 ReplicaSet 的個數和屬性,進而實 現「水平擴展 / 收縮」和「滾動更新」這兩個編排動做。

其中,「水平擴展 / 收縮」很是容易實現,Deployment Controller 只須要修改它所控制的 ReplicaSet 的 Pod 副本個數就能夠了。

好比,把這個值從 3 改爲 4,那麼 Deployment 所對應的 ReplicaSet,就會根據修改後的值自動創 建一個新的 Pod。這就是「水平擴展」了;「水平收縮」則反之。 而用戶想要執行這個操做的指令也很是簡單,就是 kubectl scale,好比:

 

$ kubectl scale deployment nginx-deployment --replicas=4
deployment.apps/nginx-deployment scaled

  

 那麼,「滾動更新」又是什麼意思,是如何實現的呢?

接下來,我還以這個 Deployment 爲例,來爲你講解「滾動更新」的過程。 首先,咱們來建立這個 nginx-deployment:

 

$ kubectl create -f nginx-deployment.yaml --record

  

注意,在這裏,我額外加了一個–record 參數。

它的做用,是記錄下你每次操做所執行的命令,以 方便後面查看。 而後,咱們來檢查一下 nginx-deployment 建立後的狀態信息: 

 

$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         0         0            0           1s

  

在返回結果中,咱們能夠看到四個狀態字段,它們的含義以下所示。

  • 1. DESIRED:用戶指望的 Pod 副本個數(spec.replicas 的值);
  • 2. CURRENT:當前處於 Running 狀態的 Pod 的個數;
  • 3. UP-TO-DATE:當前處於最新版本的 Pod 的個數,所謂最新版本指的是 Pod 的 Spec 部分與 Deployment 裏 Pod 模板裏定義的徹底一致;
  • 4. AVAILABLE:當前已經可用的 Pod 的個數,即:既是 Running 狀態,又是最新版本,而且已經 處於 Ready(健康檢查正確)狀態的 Pod 的個數。

能夠看到,只有這個 AVAILABLE 字段,描述的纔是用戶所指望的最終狀態。 而 Kubernetes 項目還爲咱們提供了一條指令,讓咱們能夠實時查看 Deployment 對象的狀態變 化。這個指令就是 kubectl rollout status: 

 

$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment.apps/nginx-deployment successfully rolled out

  

 在這個返回結果中,「2 out of 3 new replicas have been updated」意味着已經有 2 個 Pod 進 入了 UP-TO-DATE 狀態。

繼續等待一下子,咱們就能看到這個 Deployment 的 3 個 Pod,就進入到了 AVAILABLE 狀態:

 

NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           20s

  

此時,你能夠嘗試查看一下這個 Deployment 所控制的 ReplicaSet: 

 

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-3167673210   3         3         3       20s

  

 如上所示,在用戶提交了一個 Deployment 對象後,Deployment Controller 就會當即建立一個 Pod 副本個數爲 3 的 ReplicaSet。這個 ReplicaSet 的名字,則是由 Deployment 的名字和一個隨 機字符串共同組成。

這個隨機字符串叫做 pod-template-hash,在咱們這個例子裏就是:3167673210。ReplicaSet 會 把這個隨機字符串加在它所控制的全部 Pod 的標籤裏,從而保證這些 Pod 不會與集羣裏的其餘 Pod 混淆。

而 ReplicaSet 的 DESIRED、CURRENT 和 READY 字段的含義,和 Deployment 中是一致的。所 以,相比之下,Deployment 只是在 ReplicaSet 的基礎上,添加了 UP-TO-DATE 這個跟版本有 關的狀態字段。

這個時候,若是咱們修改了 Deployment 的 Pod 模板,「滾動更新」就會被自動觸發。 修改 Deployment 有不少方法。好比,我能夠直接使用 kubectl edit 指令編輯 Etcd 裏的 API 對 象。

 

$ kubectl edit deployment/nginx-deployment
... 
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1 # 1.7.9 -> 1.9.1
        ports:
        - containerPort: 80
...
deployment.extensions/nginx-deployment edited

  

這個 kubectl edit 指令,會幫你直接打開 nginx-deployment 的 API 對象。而後,你就能夠修改 這裏的 Pod 模板部分了。

好比,在這裏,我將 nginx 鏡像的版本升級到了 1.9.1。 kubectl edit 指令編輯完成後,保存退出,Kubernetes 就會馬上觸發「滾動更新」的過程。

你還可 以經過 kubectl rollout status 指令查看 nginx-deployment 的狀態變化:

 

$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment.extensions/nginx-deployment successfully rolled out

  

這時,你能夠經過查看 Deployment 的 Events,看到這個「滾動更新」的流程:

$ kubectl describe deployment nginx-deployment
...
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
...
  Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1764197365 to 1
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-3167673210 to 2
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1764197365 to 2
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-3167673210 to 1
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1764197365 to 3
  Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-3167673210 to 0

  

能夠看到,首先,當你修改了 Deployment 裏的 Pod 定義以後,Deployment Controller 會使用 這個修改後的 Pod 模板,建立一個新的 ReplicaSet(hash=1764197365),這個新的 ReplicaSet 的初始 Pod 副本數是:0。

而後,在 Age=24 s 的位置,Deployment Controller 開始將這個新的 ReplicaSet 所控制的 Pod 副本數從 0 個變成 1 個,即:「水平擴展」出一個副本。 緊接着,在 Age=22 s 的位置,Deployment Controller 又將舊的 ReplicaSet(hash=3167673210)所控制的舊 Pod 副本數減小一個,即:「水平收縮」成兩個副 本。

如此交替進行,新 ReplicaSet 管理的 Pod 副本數,從 0 個變成 1 個,再變成 2 個,最後變成 3 個。而舊的 ReplicaSet 管理的 Pod 副本數則從 3 個變成 2 個,再變成 1 個,最後變成 0 個。

這 樣,就完成了這一組 Pod 的版本升級過程。 像這樣,將一個集羣中正在運行的多個 Pod 版本,交替地逐一升級的過程,就是「滾動更新」。 在這個「滾動更新」過程完成以後,你能夠查看一下新、舊兩個 ReplicaSet 的最終狀態:

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1764197365   3         3         3       6s
nginx-deployment-3167673210   0         0         0       30s

  

其中,舊 ReplicaSet(hash=3167673210)已經被「水平收縮」成了 0 個副本。 這種「滾動更新」的好處是顯而易見的。

好比,在升級剛開始的時候,集羣裏只有 1 個新版本的 Pod。若是這時,新版本 Pod 有問題啓動不 起來,那麼「滾動更新」就會中止,從而容許開發和運維人員介入。而在這個過程當中,因爲應用本 身還有兩個舊版本的 Pod 在線,因此服務並不會受到太大的影響。 固然,這也就要求你必定要使用 Pod 的 Health Check 機制檢查應用的運行狀態,而不是簡單地依 賴於容器的 Running 狀態。

要否則的話,雖然容器已經變成 Running 了,但服務頗有可能還沒有啓 動,「滾動更新」的效果也就達不到了。 而爲了進一步保證服務的連續性,Deployment Controller 還會確保,在任什麼時候間窗口內,只有指 定比例的 Pod 處於離線狀態。同時,它也會確保,在任什麼時候間窗口內,只有指定比例的新 Pod 被 建立出來。這兩個比例的值都是能夠配置的,默認都是 DESIRED 值的 25%。

因此,在上面這個 Deployment 的例子中,它有 3 個 Pod 副本,那麼控制器在「滾動更新」的過 程中永遠都會確保至少有 2 個 Pod 處於可用狀態,至多隻有 4 個 Pod 同時存在於集羣中。這個策 略,是 Deployment 對象的一個字段,名叫 RollingUpdateStrategy,以下所示:

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
...
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1

  

在上面這個 RollingUpdateStrategy 的配置中,maxSurge 指定的是除了 DESIRED 數量以外,在 一次「滾動」中,Deployment 控制器還能夠建立多少個新 Pod;

而 maxUnavailable 指的是,在 一次「滾動」中,Deployment 控制器能夠刪除多少箇舊 Pod。

同時,這兩個配置還能夠用前面咱們介紹的百分比形式來表示,好比:maxUnavailable=50%,指 的是咱們最多能夠一次刪除「50%*DESIRED 數量」個 Pod。

結合以上講述,如今咱們能夠擴展一下 Deployment、ReplicaSet 和 Pod 的關係圖了。

如上所示,Deployment 的控制器,實際上控制的是 ReplicaSet 的數目,以及每一個 ReplicaSet 的 屬性。

而一個應用的版本,對應的正是一個 ReplicaSet;這個版本應用的 Pod 數量,則由 ReplicaSet 通 過它本身的控制器(ReplicaSet Controller)來保證。

經過這樣的多個 ReplicaSet 對象,Kubernetes 項目就實現了對多個「應用版本」的描述。 而明白了「應用版本和 ReplicaSet 一一對應」的設計思想以後,我就能夠爲你講解一下 Deployment 對應用進行版本控制的具體原理了。

這一次,我會使用一個叫kubectl set image的指令,直接修改 nginx-deployment 所使用的鏡 像。這個命令的好處就是,你能夠不用像 kubectl edit 那樣須要打開編輯器。

不過這一次,我把這個鏡像名字修改爲爲了一個錯誤的名字,好比:nginx:1.91。這樣,這個 Deployment 就會出現一個升級失敗的版本。 咱們一塊兒來實踐一下:

 

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.91
deployment.extensions/nginx-deployment image updated

 

因爲這個 nginx:1.91 鏡像在 Docker Hub 中並不存在,因此這個 Deployment 的「滾動更新」被 觸發後,會馬上報錯並中止。

這時,咱們來檢查一下 ReplicaSet 的狀態,以下所示: 

 

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1764197365   2         2         2       24s
nginx-deployment-3167673210   0         0         0       35s
nginx-deployment-2156724341   2         2         0       7s

  

 經過這個返回結果,咱們能夠看到,新版本的 ReplicaSet(hash=2156724341)的「水平擴 展」已經中止。並且此時,它已經建立了兩個 Pod,可是它們都沒有進入 READY 狀態。

這固然是 由於這兩個 Pod 都拉取不到有效的鏡像。 與此同時,舊版本的 ReplicaSet(hash=1764197365)的「水平收縮」,也自動中止了。

此時, 已經有一箇舊 Pod 被刪除,還剩下兩個舊 Pod。 那麼問題來了, 咱們如何讓這個 Deployment 的 3 個 Pod,都回滾到之前的舊版本呢? 咱們只須要執行一條 kubectl rollout undo 命令,就能把整個 Deployment 回滾到上一個版本:

 

$ kubectl rollout undo deployment/nginx-deployment
deployment.extensions/nginx-deployment

  

很容易想到,在具體操做上,Deployment 的控制器,其實就是讓這個舊 ReplicaSet(hash=1764197365)再次「擴展」成 3 個 Pod,而讓新的 ReplicaSet(hash=2156724341)從新「收縮」到 0 個 Pod。

更進一步地,若是我想回滾到更早以前的版本,要怎麼辦呢?

首先,我須要使用 kubectl rollout history 命令,查看每次 Deployment 變動對應的版本。而由 於咱們在建立這個 Deployment 的時候,指定了–record 參數,因此咱們建立這些版本時執行的 kubectl 命令,都會被記錄下來。這個操做的輸出以下所示:

 

$ kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment"
REVISION    CHANGE-CAUSE
1           kubectl create -f nginx-deployment.yaml --record
2           kubectl edit deployment/nginx-deployment
3           kubectl set image deployment/nginx-deployment nginx=nginx:1.91

  

能夠看到,咱們前面執行的建立和更新操做,分別對應了版本 1 和版本 2,而那次失敗的更新操 做,則對應的是版本 3。

固然,你還能夠經過這個 kubectl rollout history 指令,看到每一個版本對應的 Deployment 的 API 對象的細節,具體命令以下所示:

 

$ kubectl rollout history deployment/nginx-deployment --revision=2

  

而後,咱們就能夠在 kubectl rollout undo 命令行最後,加上要回滾到的指定版本的版本號,就 能夠回滾到指定版本了。這個指令的用法以下:

$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
deployment.extensions/nginx-deployment

  

 這樣,Deployment Controller 還會按照「滾動更新」的方式,完成對 Deployment 的降級操做。

不過,你可能已經想到了一個問題:咱們對 Deployment 進行的每一次更新操做,都會生成一個新 的 ReplicaSet 對象,是否是有些多餘,甚至浪費資源呢? 沒錯。

因此,Kubernetes 項目還提供了一個指令,使得咱們對 Deployment 的屢次更新操做,最後 只生 成一個 ReplicaSet。 具體的作法是,在更新 Deployment 前,你要先執行一條 kubectl rollout pause 指令。它的用法 以下所示:

 

$ kubectl rollout pause deployment/nginx-deployment
deployment.extensions/nginx-deployment paused

  

 這個 kubectl rollout pause 的做用,是讓這個 Deployment 進入了一個「暫停」狀態。

因此接下來,你就能夠隨意使用 kubectl edit 或者 kubectl set image 指令,修改這個 Deployment 的內容了。 因爲此時 Deployment 正處於「暫停」狀態,因此咱們對 Deployment 的全部修改,都不會觸發 新的「滾動更新」,也不會建立新的 ReplicaSet。 而等到咱們對 Deployment 修改操做都完成以後,只須要再執行一條 kubectl rollout resume 指 令,就能夠把這個 Deployment「恢復」回來,以下所示:

 

$ kubectl rollout resume deploy/nginx-deployment
deployment.extensions/nginx-deployment resumed

  

而在這個 kubectl rollout resume 指令執行以前,在 kubectl rollout pause 指令以後的這段時間 裏,咱們對 Deployment 進行的全部修改,最後只會觸發一次「滾動更新」。

固然,咱們能夠經過檢查 ReplicaSet 狀態的變化,來驗證一下 kubectl rollout pause 和 kubectl rollout resume 指令的執行效果,以下所示: 

 

$ kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-1764197365   0         0         0         2m
nginx-3196763511   3         3         3         28s

  

 經過返回結果,咱們能夠看到,只有一個 hash=3196763511 的 ReplicaSet 被建立了出來。 不過,即便你像上面這樣當心翼翼地控制了 ReplicaSet 的生成數量,隨着應用版本的不斷增長, Kubernetes 中仍是會爲同一個 Deployment 保存不少不少不一樣的 ReplicaSet。

那麼,咱們又該如何控制這些「歷史」ReplicaSet 的數量呢?

很簡單,Deployment 對象有一個字段,叫做 spec.revisionHistoryLimit,就是 Kubernetes 爲 Deployment 保留的「歷史版本」個數。

因此,若是把它設置爲 0,你就不再能作回滾操做了。 總結 在今天這篇文章中,我爲你詳細講解了 Deployment 這個 Kubernetes 項目中最基本的編排控制器 的實現原理和使用方法。 經過這些講解,你應該瞭解到:Deployment 其實是一個兩層控制器。

首先,它經過ReplicaSet 的個數來描述應用的版本;而後,它再經過ReplicaSet 的屬性(好比 replicas 的值),來保證 Pod 的副本數量。 不過,相信你也可以感覺到,Kubernetes 項目對 Deployment 的設計,其實是代替咱們完成了 對「應用」的抽象,使得咱們可使用這個 Deployment 對象來描述應用,使用 kubectl rollout 命令控制應用的版本。

但是,在實際使用場景中,應用發佈的流程每每千差萬別,也可能有不少的定製化需求。好比,我 的應用可能有會話黏連(session sticky),這就意味着「滾動更新」的時候,哪一個 Pod 能下線, 是不能隨便選擇的。 這種場景,光靠 Deployment 本身就很難應對了。對於這種需求,我在專欄後續文章中重點介紹 的「自定義控制器」,就能夠幫咱們實現一個功能更增強大的 Deployment Controller。 固然,Kubernetes 項目自己,也提供了另一種抽象方式,幫咱們應對其餘一些用 Deployment 沒法處理的應用編排場景。

 

備忘

用戶修改pod的副本數

kubectl scale deployment deployment——name --replicas=想要修改的副本數

# 如
kubectl scale deployment nginx-deployment --replicas=4

  

–record 參數, 記錄下你每次操做所執行的命令,以 方便後面查看

kubectl create -f nginx-deployment.yaml --record

 

檢查  deployment 建立後的狀態信息  

kubectl get deployments

  

在返回結果中,咱們能夠看到四個狀態字段,它們的含義以下所示。

  • 1. DESIRED:用戶指望的 Pod 副本個數(spec.replicas 的值);
  • 2. CURRENT:當前處於 Running 狀態的 Pod 的個數;
  • 3. UP-TO-DATE:當前處於最新版本的 Pod 的個數,所謂最新版本指的是 Pod 的 Spec 部分與 Deployment 裏 Pod 模板裏定義的徹底一致;
  • 4. AVAILABLE:當前已經可用的 Pod 的個數,即:既是 Running 狀態,又是最新版本,而且已經 處於 Ready(健康檢查正確)狀態的 Pod 的個數。

 

實時查看 Deployment 對象的狀態變 化

kubectl rollout status deployment/nginx-deployment

  

 查看一下這個 Deployment 所控制的 ReplicaSet 的狀態

kubectl get rs 

 

 

直接使用 kubectl edit 指令編輯 Etcd 裏的 API 對 象

kubectl edit deployment/nginx-deployment

  

 查看 Deployment 的 Events,看到這個「滾動更新」的流程

kubectl describe deployment nginx-deployment
  • Normal ScalingReplicaSet 10m deployment-controller Scaled up replica set nginx-deployment-67594d6bf6 to 3
  • Normal ScalingReplicaSet 1m deployment-controller Scaled up replica set nginx-deployment-6fdbb596db to 1
  • Normal ScalingReplicaSet 40s deployment-controller Scaled down replica set nginx-deployment-67594d6bf6 to 2
  • Normal ScalingReplicaSet 40s deployment-controller Scaled up replica set nginx-deployment-6fdbb596db to 2
  • Normal ScalingReplicaSet 39s deployment-controller Scaled down replica set nginx-deployment-67594d6bf6 to 1
  • Normal ScalingReplicaSet 39s deployment-controller Scaled up replica set nginx-deployment-6fdbb596db to 3
  • Normal ScalingReplicaSet 36s deployment-controller Scaled down replica set nginx-deployment-67594d6bf6 to 0

 

 Deployment 的屢次更新操做,最後 只生 成一個 ReplicaSet。 具體的作法是,在更新 Deployment 前,你要先執行一條 kubectl rollout pause 指令。它的用法 以下所示

 

kubectl rollout pause deployment/deployment——name
如
kubectl rollout pause deployment/nginx-deployment

這個 kubectl rollout pause 的做用,是讓這個 Deployment 進入了一個「暫停」狀態。

因此接下來,你就能夠隨意使用 kubectl edit 或者 kubectl set image 指令,修改這個 Deployment 的內容了。 因爲此時 Deployment 正處於「暫停」狀態,因此咱們對 Deployment 的全部修改,都不會觸發 新的「滾動更新」,也不會建立新的 ReplicaSet。 

而等到咱們對 Deployment 修改操做都完成以後,只須要再執行一條 kubectl rollout resume 指 令,就能夠把這個 Deployment「恢復」回來,以下所示:  

 kubectl rollout resume deploy/nginx-deployment
相關文章
相關標籤/搜索