目錄
- Pod 復(fù)雜的API對象
- nginx-deployment
- Deployment 及類似控制器總結(jié)
- Deployment 所控制的 ReplicaSet查看
Pod 復(fù)雜的API對象
Pod 這個看似復(fù)雜的 API 對象,實際上就是對容器的進一步抽象和封裝而已。
說得更形象些,“容器”鏡像雖然好用,但是容器這樣一個“沙盒”的概念,對于描述應(yīng)用來說,還是太過簡單了。這就好比,集裝箱固然好用,但是如果它四面都光禿禿的,吊車還怎么把這個集裝箱吊起來并擺放好呢?
所以,Pod 對象,其實就是容器的升級版。它對容器進行了組合,添加了更多的屬性和字段。這就好比給集裝箱四面安裝了吊環(huán),使得 Kubernetes 這架“吊車”,可以更輕松地操作它。
而 Kubernetes 操作這些“集裝箱”的邏輯,都由控制器(Controller)完成。 Deployment 這個最基本的控制器對象。
nginx-deployment
現(xiàn)在,我們一起來回顧一下這個名叫 nginx-deployment 的例子:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
這個 Deployment 定義的編排動作非常簡單,即:確保攜帶了 app=nginx 標(biāo)簽的 Pod 的個數(shù),永遠(yuǎn)等于 spec.replicas 指定的個數(shù),即 2 個。
這就意味著,如果在這個集群中,攜帶 app=nginx 標(biāo)簽的 Pod 的個數(shù)大于 2 的時候,就會有舊的 Pod 被刪除;反之,就會有新的 Pod 被創(chuàng)建。
這時,你也許就會好奇:究竟是 Kubernetes 項目中的哪個組件,在執(zhí)行這些操作呢?
我在前面介紹 Kubernetes 架構(gòu)的時候,曾經(jīng)提到過一個叫作 kube-controller-manager 的組件。
實際上,這個組件,就是一系列控制器的集合。我們可以查看一下 Kubernetes 項目的 pkg/controller 目錄:
$ cd kubernetes/pkg/controller/ $ ls -d */ deployment/ job/ podautoscaler/ cloud/ disruption/ namespace/ replicaset/ serviceaccount/ volume/ cronjob/ garbagecollector/ nodelifecycle/ replication/
這個目錄下面的每一個控制器,都以獨有的方式負(fù)責(zé)某種編排功能。而我們的 Deployment,正是這些控制器中的一種。
實際上,這些控制器之所以被統(tǒng)一放在 pkg/controller 目錄下,就是因為它們都遵循 Kubernetes 項目中的一個通用編排模式,即:控制循環(huán)(control loop)。
在具體實現(xiàn)中,實際狀態(tài)往往來自于 Kubernetes 集群本身。
比如,kubelet 通過心跳匯報的容器狀態(tài)和節(jié)點狀態(tài),或者監(jiān)控系統(tǒng)中保存的應(yīng)用監(jiān)控數(shù)據(jù),或者控制器主動收集的它自己感興趣的信息,這些都是常見的實際狀態(tài)的來源。
而期望狀態(tài),一般來自于用戶提交的 YAML 文件。
比如,Deployment 對象中 Replicas 字段的值。很明顯,這些信息往往都保存在 Etcd 中。
接下來,以 Deployment 為例,我和你簡單描述一下它對控制器模型的實現(xiàn):
- Deployment 控制器從 Etcd 中獲取到所有攜帶了“app: nginx”標(biāo)簽的 Pod,然后統(tǒng)計它們的數(shù)量,這就是實際狀態(tài);
- Deployment 對象的 Replicas 字段的值就是期望狀態(tài);
- Deployment 控制器將兩個狀態(tài)做比較,然后根據(jù)比較結(jié)果,確定是創(chuàng)建 Pod,還是刪除已有的 Pod
可以看到,一個 Kubernetes 對象的主要編排邏輯,實際上是在第三步的“對比”階段完成的。
這個操作,通常被叫作調(diào)諧(Reconcile)。這個調(diào)諧的過程,則被稱作“Reconcile Loop”(調(diào)諧循環(huán))或者“Sync Loop”(同步循環(huán))。
所以,如果你以后在文檔或者社區(qū)中碰到這些詞,都不要擔(dān)心,它們其實指的都是同一個東西:控制循環(huán)。
而調(diào)諧的最終結(jié)果,往往都是對被控制對象的某種寫操作。
比如,增加 Pod,刪除已有的 Pod,或者更新 Pod 的某個字段。這也是 Kubernetes 項目“面向 API 對象編程”的一個直觀體現(xiàn)。
其實,像 Deployment 這種控制器的設(shè)計原理,就是我們前面提到過的,“用一種對象管理另一種對象”的“藝術(shù)”。
其中,這個控制器對象本身,負(fù)責(zé)定義被管理對象的期望狀態(tài)。比如,Deployment 里的 replicas=2 這個字段。
而被控制對象的定義,則來自于一個“模板”。比如,Deployment 里的 template 字段。
可以看到,Deployment 這個 template 字段里的內(nèi)容,跟一個標(biāo)準(zhǔn)的 Pod 對象的 API 定義,絲毫不差。而所有被這個 Deployment 管理的 Pod 實例,其實都是根據(jù)這個 template 字段的內(nèi)容創(chuàng)建出來的。
像 Deployment 定義的 template 字段,在 Kubernetes 項目中有一個專有的名字,叫作 PodTemplate(Pod 模板)。
這個概念非常重要,因為后面我要講解到的大多數(shù)控制器,都會使用 PodTemplate 來統(tǒng)一定義它所要管理的 Pod。更有意思的是,我們還會看到其他類型的對象模板,比如 Volume 的模板。
Deployment 及類似控制器總結(jié)
至此,我們就可以對 Deployment 以及其他類似的控制器,做一個簡單總結(jié)了:
類似 Deployment 這樣的一個控制器,實際上都是由上半部分的控制器定義(包括期望狀態(tài)),加上下半部分的被控制對象的模板組成的。
這就是為什么,在所有 API 對象的 Metadata 里,都有一個字段叫作 ownerReference,用于保存當(dāng)前這個 API 對象的擁有者(Owner)的信息。
那么,對于我們這個 nginx-deployment 來說,它創(chuàng)建出來的 Pod 的 ownerReference 就是 nginx-deployment 嗎?或者說,nginx-deployment 所直接控制的,就是 Pod 對象么?
Deployment 看似簡單,但實際上,它實現(xiàn)了 Kubernetes 項目中一個非常重要的功能:Pod 的“水平擴展 / 收縮”(horizontal scaling out/in)。這個功能,是從 PaaS 時代開始,一個平臺級項目就必須具備的編排能力。
舉個例子,如果你更新了 Deployment 的 Pod 模板(比如,修改了容器的鏡像),那么 Deployment 就需要遵循一種叫作“滾動更新”(rolling update)的方式,來升級現(xiàn)有的容器。
而這個能力的實現(xiàn),依賴的是 Kubernetes 項目中的一個非常重要的概念(API 對象):ReplicaSet。
ReplicaSet 的結(jié)構(gòu)非常簡單,我們可以通過這個 YAML 文件查看一下:
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 對象,其實就是由副本數(shù)目的定義和一個 Pod 模板組成的。不難發(fā)現(xiàn),它的定義其實是 Deployment 的一個子集。
更重要的是,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 副本個數(shù)是 3(spec.replicas=3)。
那么,在具體的實現(xiàn)上,這個 Deployment,與 ReplicaSet,以及 Pod 的關(guān)系是怎樣的呢?
我們可以用一張圖把它描述出來:
通過這張圖,我們就很清楚的看到,一個定義了 replicas=3 的 Deployment,與它的 ReplicaSet,以及 Pod 的關(guān)系,實際上是一種“層層控制”的關(guān)系。
其中,ReplicaSet 負(fù)責(zé)通過“控制器模式”,保證系統(tǒng)中 Pod 的個數(shù)永遠(yuǎn)等于指定的個數(shù)(比如,3 個)。這也正是 Deployment 只允許容器的 restartPolicy=Always 的主要原因:只有在容器能保證自己始終是 Running 狀態(tài)的前提下,ReplicaSet 調(diào)整 Pod 的個數(shù)才有意義。
而在此基礎(chǔ)上,Deployment 同樣通過“控制器模式”,來操作 ReplicaSet 的個數(shù)和屬性,進而實現(xiàn)“水平擴展 / 收縮”和“滾動更新”這兩個編排動作。
其中,“水平擴展 / 收縮”非常容易實現(xiàn),Deployment Controller 只需要修改它所控制的 ReplicaSet 的 Pod 副本個數(shù)就可以了。
比如,把這個值從 3 改成 4,那么 Deployment 所對應(yīng)的 ReplicaSet,就會根據(jù)修改后的值自動創(chuàng)建一個新的 Pod。這就是“水平擴展”了;“水平收縮”則反之。
而用戶想要執(zhí)行這個操作的指令也非常簡單,就是 kubectl scale,比如:
$ kubectl scale deployment nginx-deployment --replicas=4 deployment.apps/nginx-deployment scaled
那么,“滾動更新”又是什么意思,是如何實現(xiàn)的呢?
接下來,我還以這個 Deployment 為例,來為你講解“滾動更新”的過程。
首先,我們來創(chuàng)建這個 nginx-deployment:$ kubectl create -f nginx-deployment.yaml --record
注意,在這里,我額外加了一個–record 參數(shù)。它的作用,是記錄下你每次操作所執(zhí)行的命令,以方便后面查看。
然后,我們來檢查一下 nginx-deployment 創(chuàng)建后的狀態(tài)信息:
$ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx-deployment 3 0 0 0 1s
在返回結(jié)果中,我們可以看到四個狀態(tài)字段,它們的含義如下所示。
- DESIRED:用戶期望的 Pod 副本個數(shù)(spec.replicas 的值);
- CURRENT:當(dāng)前處于 Running 狀態(tài)的 Pod 的個數(shù);
- UP-TO-DATE:當(dāng)前處于最新版本的 Pod 的個數(shù),所謂最新版本指的是 Pod 的 Spec 部分與 Deployment 里 Pod 模板里定義的完全一致;
- AVAILABLE:當(dāng)前已經(jīng)可用的 Pod 的個數(shù),即:既是 Running 狀態(tài),又是最新版本,并且已經(jīng)處于 Ready(健康檢查正確)狀態(tài)的 Pod 的個數(shù)。
可以看到,只有這個 AVAILABLE 字段,描述的才是用戶所期望的最終狀態(tài)。
而 Kubernetes 項目還為我們提供了一條指令,讓我們可以實時查看 Deployment 對象的狀態(tài)變化。這個指令就是 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
在這個返回結(jié)果中,“2 out of 3 new replicas have been updated”意味著已經(jīng)有 2 個 Pod 進入了 UP-TO-DATE 狀態(tài)。
Deployment 所控制的 ReplicaSet查看
繼續(xù)等待一會兒,我們就能看到這個 Deployment 的 3 個 Pod,就進入到了 AVAILABLE 狀態(tài)
此時,你可以嘗試查看一下這個 Deployment 所控制的 ReplicaSet:
$ kubectl get rs NAME DESIRED CURRENT READY AGE nginx-deployment-3167673210 3 3 3 20s
如上所示,在用戶提交了一個 Deployment 對象后,Deployment Controller 就會立即創(chuàng)建一個 Pod 副本個數(shù)為 3 的 ReplicaSet。這個 ReplicaSet 的名字,則是由 Deployment 的名字和一個隨機字符串共同組成。
這個隨機字符串叫作 pod-template-hash,在我們這個例子里就是:3167673210。ReplicaSet 會把這個隨機字符串加在它所控制的所有 Pod 的標(biāo)簽里,從而保證這些 Pod 不會與集群里的其他 Pod 混淆。
而 ReplicaSet 的 DESIRED、CURRENT 和 READY 字段的含義,和 Deployment 中是一致的。所以,相比之下,Deployment 只是在 ReplicaSet 的基礎(chǔ)上,添加了 UP-TO-DATE 這個跟版本有關(guān)的狀態(tài)字段。
這個時候,如果我們修改了 Deployment 的 Pod 模板,“滾動更新”就會被自動觸發(fā)。
修改 Deployment 有很多方法。比如,我可以直接使用 kubectl edit 指令編輯 Etcd 里的 API 對象。kubectl edit deployment/nginx-deployment
這個 kubectl edit 指令,會幫你直接打開 nginx-deployment 的 API 對象。然后,你就可以修改這里的 Pod 模板部分了。比如,在這里,我將 nginx 鏡像的版本升級到了 1.9.1。
kubectl edit 指令編輯完成后,保存退出,Kubernetes 就會立刻觸發(fā)“滾動更新”的過程。你還可以通過 kubectl rollout status 指令查看 nginx-deployment 的狀態(tài)變化:
$ 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
可以看到,首先,當(dāng)你修改了 Deployment 里的 Pod 定義之后,Deployment Controller 會使用這個修改后的 Pod 模板,創(chuàng)建一個新的 ReplicaSet(hash=1764197365),這個新的 ReplicaSet 的初始 Pod 副本數(shù)是:0。
然后,在 Age=24 s 的位置,Deployment Controller 開始將這個新的 ReplicaSet 所控制的 Pod 副本數(shù)從 0 個變成 1 個,即:“水平擴展”出一個副本。
緊接著,在 Age=22 s 的位置,Deployment Controller 又將舊的 ReplicaSet(hash=3167673210)所控制的舊 Pod 副本數(shù)減少一個,即:“水平收縮”成兩個副本。
如此交替進行,新 ReplicaSet 管理的 Pod 副本數(shù),從 0 個變成 1 個,再變成 2 個,最后變成 3 個。而舊的 ReplicaSet 管理的 Pod 副本數(shù)則從 3 個變成 2 個,再變成 1 個,最后變成 0 個。這樣,就完成了這一組 Pod 的版本升級過程。
像這樣,將一個集群中正在運行的多個 Pod 版本,交替地逐一升級的過程,就是“滾動更新”。
在這個“滾動更新”過程完成之后,你可以查看一下新、舊兩個 ReplicaSet 的最終狀態(tài):
$ 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)已經(jīng)被“水平收縮”成了 0 個副本。
這種“滾動更新”的好處是顯而易見的。 比如,在升級剛開始的時候,集群里只有 1 個新版本的 Pod。如果這時,新版本 Pod 有問題啟動不起來,那么“滾動更新”就會停止,從而允許開發(fā)和運維人員介入。而在這個過程中,由于應(yīng)用本身還有兩個舊版本的 Pod 在線,所以服務(wù)并不會受到太大的影響。
當(dāng)然,這也就要求你一定要使用 Pod 的 Health Check 機制檢查應(yīng)用的運行狀態(tài),而不是簡單地依賴于容器的 Running 狀態(tài)。要不然的話,雖然容器已經(jīng)變成 Running 了,但服務(wù)很有可能尚未啟動,“滾動更新”的效果也就達(dá)不到了。
而為了進一步保證服務(wù)的連續(xù)性,Deployment Controller 還會確保,在任何時間窗口內(nèi),只有指定比例的 Pod 處于離線狀態(tài)。同時,它也會確保,在任何時間窗口內(nèi),只有指定比例的新 Pod 被創(chuàng)建出來。這兩個比例的值都是可以配置的,默認(rèn)都是 DESIRED 值的 25%。
所以,在上面這個 Deployment 的例子中,它有 3 個 Pod 副本,那么控制器在“滾動更新”的過程中永遠(yuǎn)都會確保至少有 2 個 Pod 處于可用狀態(tài),至多只有 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 數(shù)量之外,在一次“滾動”中,Deployment 控制器還可以創(chuàng)建多少個新 Pod;而 maxUnavailable 指的是,在一次“滾動”中,Deployment 控制器可以刪除多少個舊 Pod。
同時,這兩個配置還可以用前面我們介紹的百分比形式來表示,比如:maxUnavailable=50%,指的是我們最多可以一次刪除“50%*DESIRED 數(shù)量”個 Pod。
結(jié)合以上講述,現(xiàn)在我們可以擴展一下 Deployment、ReplicaSet 和 Pod 的關(guān)系圖了。
如上所示,Deployment 的控制器,實際上控制的是 ReplicaSet 的數(shù)目,以及每個 ReplicaSet 的屬性。
而一個應(yīng)用的版本,對應(yīng)的正是一個 ReplicaSet;這個版本應(yīng)用的 Pod 數(shù)量,則由 ReplicaSet 通過它自己的控制器(ReplicaSet Controller)來保證。
通過這樣的多個 ReplicaSet 對象,Kubernetes 項目就實現(xiàn)了對多個“應(yīng)用版本”的描述。
以上就是k8s編排之Deployment知識點詳解的詳細(xì)內(nèi)容,更多關(guān)于k8s編排Deployment的資料請關(guān)注其它相關(guān)文章!