k8s編排之Deployment知識(shí)點(diǎn)詳解
Pod 復(fù)雜的API對象
Pod 這個(gè)看似復(fù)雜的 API 對象,實(shí)際上就是對容器的進(jìn)一步抽象和封裝而已。
說得更形象些,“容器”鏡像雖然好用,但是容器這樣一個(gè)“沙盒”的概念,對于描述應(yīng)用來說,還是太過簡單了。這就好比,集裝箱固然好用,但是如果它四面都光禿禿的,吊車還怎么把這個(gè)集裝箱吊起來并擺放好呢?
所以,Pod 對象,其實(shí)就是容器的升級(jí)版。它對容器進(jìn)行了組合,添加了更多的屬性和字段。這就好比給集裝箱四面安裝了吊環(huán),使得 Kubernetes 這架“吊車”,可以更輕松地操作它。
而 Kubernetes 操作這些“集裝箱”的邏輯,都由控制器(Controller)完成。 Deployment 這個(gè)最基本的控制器對象。
nginx-deployment
現(xiàn)在,我們一起來回顧一下這個(gè)名叫 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
這個(gè) Deployment 定義的編排動(dòng)作非常簡單,即:確保攜帶了 app=nginx 標(biāo)簽的 Pod 的個(gè)數(shù),永遠(yuǎn)等于 spec.replicas 指定的個(gè)數(shù),即 2 個(gè)。
這就意味著,如果在這個(gè)集群中,攜帶 app=nginx 標(biāo)簽的 Pod 的個(gè)數(shù)大于 2 的時(shí)候,就會(huì)有舊的 Pod 被刪除;反之,就會(huì)有新的 Pod 被創(chuàng)建。
這時(shí),你也許就會(huì)好奇:究竟是 Kubernetes 項(xiàng)目中的哪個(gè)組件,在執(zhí)行這些操作呢?
我在前面介紹 Kubernetes 架構(gòu)的時(shí)候,曾經(jīng)提到過一個(gè)叫作 kube-controller-manager 的組件。
實(shí)際上,這個(gè)組件,就是一系列控制器的集合。我們可以查看一下 Kubernetes 項(xiàng)目的 pkg/controller 目錄:
$ cd kubernetes/pkg/controller/ $ ls -d */ deployment/ job/ podautoscaler/ cloud/ disruption/ namespace/ replicaset/ serviceaccount/ volume/ cronjob/ garbagecollector/ nodelifecycle/ replication/
這個(gè)目錄下面的每一個(gè)控制器,都以獨(dú)有的方式負(fù)責(zé)某種編排功能。而我們的 Deployment,正是這些控制器中的一種。
實(shí)際上,這些控制器之所以被統(tǒng)一放在 pkg/controller 目錄下,就是因?yàn)樗鼈兌甲裱?Kubernetes 項(xiàng)目中的一個(gè)通用編排模式,即:控制循環(huán)(control loop)。
在具體實(shí)現(xiàn)中,實(shí)際狀態(tài)往往來自于 Kubernetes 集群本身。
比如,kubelet 通過心跳匯報(bào)的容器狀態(tài)和節(jié)點(diǎn)狀態(tài),或者監(jiān)控系統(tǒng)中保存的應(yīng)用監(jiān)控?cái)?shù)據(jù),或者控制器主動(dòng)收集的它自己感興趣的信息,這些都是常見的實(shí)際狀態(tài)的來源。
而期望狀態(tài),一般來自于用戶提交的 YAML 文件。
比如,Deployment 對象中 Replicas 字段的值。很明顯,這些信息往往都保存在 Etcd 中。
接下來,以 Deployment 為例,我和你簡單描述一下它對控制器模型的實(shí)現(xiàn):
- Deployment 控制器從 Etcd 中獲取到所有攜帶了“app: nginx”標(biāo)簽的 Pod,然后統(tǒng)計(jì)它們的數(shù)量,這就是實(shí)際狀態(tài);
- Deployment 對象的 Replicas 字段的值就是期望狀態(tài);
- Deployment 控制器將兩個(gè)狀態(tài)做比較,然后根據(jù)比較結(jié)果,確定是創(chuàng)建 Pod,還是刪除已有的 Pod
可以看到,一個(gè) Kubernetes 對象的主要編排邏輯,實(shí)際上是在第三步的“對比”階段完成的。
這個(gè)操作,通常被叫作調(diào)諧(Reconcile)。這個(gè)調(diào)諧的過程,則被稱作“Reconcile Loop”(調(diào)諧循環(huán))或者“Sync Loop”(同步循環(huán))。
所以,如果你以后在文檔或者社區(qū)中碰到這些詞,都不要擔(dān)心,它們其實(shí)指的都是同一個(gè)東西:控制循環(huán)。
而調(diào)諧的最終結(jié)果,往往都是對被控制對象的某種寫操作。
比如,增加 Pod,刪除已有的 Pod,或者更新 Pod 的某個(gè)字段。這也是 Kubernetes 項(xiàng)目“面向 API 對象編程”的一個(gè)直觀體現(xiàn)。
其實(shí),像 Deployment 這種控制器的設(shè)計(jì)原理,就是我們前面提到過的,“用一種對象管理另一種對象”的“藝術(shù)”。
其中,這個(gè)控制器對象本身,負(fù)責(zé)定義被管理對象的期望狀態(tài)。比如,Deployment 里的 replicas=2 這個(gè)字段。
而被控制對象的定義,則來自于一個(gè)“模板”。比如,Deployment 里的 template 字段。
可以看到,Deployment 這個(gè) template 字段里的內(nèi)容,跟一個(gè)標(biāo)準(zhǔn)的 Pod 對象的 API 定義,絲毫不差。而所有被這個(gè) Deployment 管理的 Pod 實(shí)例,其實(shí)都是根據(jù)這個(gè) template 字段的內(nèi)容創(chuàng)建出來的。
像 Deployment 定義的 template 字段,在 Kubernetes 項(xiàng)目中有一個(gè)專有的名字,叫作 PodTemplate(Pod 模板)。
這個(gè)概念非常重要,因?yàn)楹竺嫖乙v解到的大多數(shù)控制器,都會(huì)使用 PodTemplate 來統(tǒng)一定義它所要管理的 Pod。更有意思的是,我們還會(huì)看到其他類型的對象模板,比如 Volume 的模板。
Deployment 及類似控制器總結(jié)
至此,我們就可以對 Deployment 以及其他類似的控制器,做一個(gè)簡單總結(jié)了:

類似 Deployment 這樣的一個(gè)控制器,實(shí)際上都是由上半部分的控制器定義(包括期望狀態(tài)),加上下半部分的被控制對象的模板組成的。
這就是為什么,在所有 API 對象的 Metadata 里,都有一個(gè)字段叫作 ownerReference,用于保存當(dāng)前這個(gè) API 對象的擁有者(Owner)的信息。
那么,對于我們這個(gè) nginx-deployment 來說,它創(chuàng)建出來的 Pod 的 ownerReference 就是 nginx-deployment 嗎?或者說,nginx-deployment 所直接控制的,就是 Pod 對象么?
Deployment 看似簡單,但實(shí)際上,它實(shí)現(xiàn)了 Kubernetes 項(xiàng)目中一個(gè)非常重要的功能:Pod 的“水平擴(kuò)展 / 收縮”(horizontal scaling out/in)。這個(gè)功能,是從 PaaS 時(shí)代開始,一個(gè)平臺(tái)級(jí)項(xiàng)目就必須具備的編排能力。
舉個(gè)例子,如果你更新了 Deployment 的 Pod 模板(比如,修改了容器的鏡像),那么 Deployment 就需要遵循一種叫作“滾動(dòng)更新”(rolling update)的方式,來升級(jí)現(xiàn)有的容器。
而這個(gè)能力的實(shí)現(xiàn),依賴的是 Kubernetes 項(xiàng)目中的一個(gè)非常重要的概念(API 對象):ReplicaSet。
ReplicaSet 的結(jié)構(gòu)非常簡單,我們可以通過這個(gè) 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
從這個(gè) YAML 文件中,我們可以看到,一個(gè) ReplicaSet 對象,其實(shí)就是由副本數(shù)目的定義和一個(gè) Pod 模板組成的。不難發(fā)現(xiàn),它的定義其實(shí)是 Deployment 的一個(gè)子集。
更重要的是,Deployment 控制器實(shí)際操縱的,正是這樣的 ReplicaSet 對象,而不是 Pod 對象。
對于一個(gè) Deployment 所管理的 Pod,它的 ownerReference 是誰?這個(gè)問題的答案就是:ReplicaSet。
明白了這個(gè)原理,我再來和你一起分析一個(gè)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
可以看到,這就是一個(gè)我們常用的 nginx-deployment,它定義的 Pod 副本個(gè)數(shù)是 3(spec.replicas=3)。
那么,在具體的實(shí)現(xiàn)上,這個(gè) Deployment,與 ReplicaSet,以及 Pod 的關(guān)系是怎樣的呢?
我們可以用一張圖把它描述出來:

通過這張圖,我們就很清楚的看到,一個(gè)定義了 replicas=3 的 Deployment,與它的 ReplicaSet,以及 Pod 的關(guān)系,實(shí)際上是一種“層層控制”的關(guān)系。
其中,ReplicaSet 負(fù)責(zé)通過“控制器模式”,保證系統(tǒng)中 Pod 的個(gè)數(shù)永遠(yuǎn)等于指定的個(gè)數(shù)(比如,3 個(gè))。這也正是 Deployment 只允許容器的 restartPolicy=Always 的主要原因:只有在容器能保證自己始終是 Running 狀態(tài)的前提下,ReplicaSet 調(diào)整 Pod 的個(gè)數(shù)才有意義。
而在此基礎(chǔ)上,Deployment 同樣通過“控制器模式”,來操作 ReplicaSet 的個(gè)數(shù)和屬性,進(jìn)而實(shí)現(xiàn)“水平擴(kuò)展 / 收縮”和“滾動(dòng)更新”這兩個(gè)編排動(dòng)作。
其中,“水平擴(kuò)展 / 收縮”非常容易實(shí)現(xiàn),Deployment Controller 只需要修改它所控制的 ReplicaSet 的 Pod 副本個(gè)數(shù)就可以了。
比如,把這個(gè)值從 3 改成 4,那么 Deployment 所對應(yīng)的 ReplicaSet,就會(huì)根據(jù)修改后的值自動(dòng)創(chuàng)建一個(gè)新的 Pod。這就是“水平擴(kuò)展”了;“水平收縮”則反之。
而用戶想要執(zhí)行這個(gè)操作的指令也非常簡單,就是 kubectl scale,比如:
$ kubectl scale deployment nginx-deployment --replicas=4 deployment.apps/nginx-deployment scaled
那么,“滾動(dòng)更新”又是什么意思,是如何實(shí)現(xiàn)的呢?
接下來,我還以這個(gè) Deployment 為例,來為你講解“滾動(dòng)更新”的過程。
首先,我們來創(chuàng)建這個(gè) nginx-deployment:$ kubectl create -f nginx-deployment.yaml --record 注意,在這里,我額外加了一個(gè)–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é)果中,我們可以看到四個(gè)狀態(tài)字段,它們的含義如下所示。
- DESIRED:用戶期望的 Pod 副本個(gè)數(shù)(spec.replicas 的值);
- CURRENT:當(dāng)前處于 Running 狀態(tài)的 Pod 的個(gè)數(shù);
- UP-TO-DATE:當(dāng)前處于最新版本的 Pod 的個(gè)數(shù),所謂最新版本指的是 Pod 的 Spec 部分與 Deployment 里 Pod 模板里定義的完全一致;
- AVAILABLE:當(dāng)前已經(jīng)可用的 Pod 的個(gè)數(shù),即:既是 Running 狀態(tài),又是最新版本,并且已經(jīng)處于 Ready(健康檢查正確)狀態(tài)的 Pod 的個(gè)數(shù)。
可以看到,只有這個(gè) AVAILABLE 字段,描述的才是用戶所期望的最終狀態(tài)。
而 Kubernetes 項(xiàng)目還為我們提供了一條指令,讓我們可以實(shí)時(shí)查看 Deployment 對象的狀態(tài)變化。這個(gè)指令就是 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
在這個(gè)返回結(jié)果中,“2 out of 3 new replicas have been updated”意味著已經(jīng)有 2 個(gè) Pod 進(jìn)入了 UP-TO-DATE 狀態(tài)。
Deployment 所控制的 ReplicaSet查看
繼續(xù)等待一會(huì)兒,我們就能看到這個(gè) Deployment 的 3 個(gè) Pod,就進(jìn)入到了 AVAILABLE 狀態(tài)
此時(shí),你可以嘗試查看一下這個(gè) Deployment 所控制的 ReplicaSet:
$ kubectl get rs NAME DESIRED CURRENT READY AGE nginx-deployment-3167673210 3 3 3 20s
如上所示,在用戶提交了一個(gè) Deployment 對象后,Deployment Controller 就會(huì)立即創(chuàng)建一個(gè) Pod 副本個(gè)數(shù)為 3 的 ReplicaSet。這個(gè) ReplicaSet 的名字,則是由 Deployment 的名字和一個(gè)隨機(jī)字符串共同組成。
這個(gè)隨機(jī)字符串叫作 pod-template-hash,在我們這個(gè)例子里就是:3167673210。ReplicaSet 會(huì)把這個(gè)隨機(jī)字符串加在它所控制的所有 Pod 的標(biāo)簽里,從而保證這些 Pod 不會(huì)與集群里的其他 Pod 混淆。
而 ReplicaSet 的 DESIRED、CURRENT 和 READY 字段的含義,和 Deployment 中是一致的。所以,相比之下,Deployment 只是在 ReplicaSet 的基礎(chǔ)上,添加了 UP-TO-DATE 這個(gè)跟版本有關(guān)的狀態(tài)字段。
這個(gè)時(shí)候,如果我們修改了 Deployment 的 Pod 模板,“滾動(dòng)更新”就會(huì)被自動(dòng)觸發(fā)。
修改 Deployment 有很多方法。比如,我可以直接使用 kubectl edit 指令編輯 Etcd 里的 API 對象。kubectl edit deployment/nginx-deployment
這個(gè) kubectl edit 指令,會(huì)幫你直接打開 nginx-deployment 的 API 對象。然后,你就可以修改這里的 Pod 模板部分了。比如,在這里,我將 nginx 鏡像的版本升級(jí)到了 1.9.1。
kubectl edit 指令編輯完成后,保存退出,Kubernetes 就會(huì)立刻觸發(fā)“滾動(dòng)更新”的過程。你還可以通過 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
這時(shí),你可以通過查看 Deployment 的 Events,看到這個(gè)“滾動(dòng)更新”的流程: kubectl describe deployment nginx-deployment
可以看到,首先,當(dāng)你修改了 Deployment 里的 Pod 定義之后,Deployment Controller 會(huì)使用這個(gè)修改后的 Pod 模板,創(chuàng)建一個(gè)新的 ReplicaSet(hash=1764197365),這個(gè)新的 ReplicaSet 的初始 Pod 副本數(shù)是:0。
然后,在 Age=24 s 的位置,Deployment Controller 開始將這個(gè)新的 ReplicaSet 所控制的 Pod 副本數(shù)從 0 個(gè)變成 1 個(gè),即:“水平擴(kuò)展”出一個(gè)副本。
緊接著,在 Age=22 s 的位置,Deployment Controller 又將舊的 ReplicaSet(hash=3167673210)所控制的舊 Pod 副本數(shù)減少一個(gè),即:“水平收縮”成兩個(gè)副本。
如此交替進(jìn)行,新 ReplicaSet 管理的 Pod 副本數(shù),從 0 個(gè)變成 1 個(gè),再變成 2 個(gè),最后變成 3 個(gè)。而舊的 ReplicaSet 管理的 Pod 副本數(shù)則從 3 個(gè)變成 2 個(gè),再變成 1 個(gè),最后變成 0 個(gè)。這樣,就完成了這一組 Pod 的版本升級(jí)過程。
像這樣,將一個(gè)集群中正在運(yùn)行的多個(gè) Pod 版本,交替地逐一升級(jí)的過程,就是“滾動(dòng)更新”。
在這個(gè)“滾動(dòng)更新”過程完成之后,你可以查看一下新、舊兩個(gè) 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 個(gè)副本。
這種“滾動(dòng)更新”的好處是顯而易見的。 比如,在升級(jí)剛開始的時(shí)候,集群里只有 1 個(gè)新版本的 Pod。如果這時(shí),新版本 Pod 有問題啟動(dòng)不起來,那么“滾動(dòng)更新”就會(huì)停止,從而允許開發(fā)和運(yùn)維人員介入。而在這個(gè)過程中,由于應(yīng)用本身還有兩個(gè)舊版本的 Pod 在線,所以服務(wù)并不會(huì)受到太大的影響。
當(dāng)然,這也就要求你一定要使用 Pod 的 Health Check 機(jī)制檢查應(yīng)用的運(yùn)行狀態(tài),而不是簡單地依賴于容器的 Running 狀態(tài)。要不然的話,雖然容器已經(jīng)變成 Running 了,但服務(wù)很有可能尚未啟動(dòng),“滾動(dòng)更新”的效果也就達(dá)不到了。
而為了進(jìn)一步保證服務(wù)的連續(xù)性,Deployment Controller 還會(huì)確保,在任何時(shí)間窗口內(nèi),只有指定比例的 Pod 處于離線狀態(tài)。同時(shí),它也會(huì)確保,在任何時(shí)間窗口內(nèi),只有指定比例的新 Pod 被創(chuàng)建出來。這兩個(gè)比例的值都是可以配置的,默認(rèn)都是 DESIRED 值的 25%。
所以,在上面這個(gè) Deployment 的例子中,它有 3 個(gè) Pod 副本,那么控制器在“滾動(dòng)更新”的過程中永遠(yuǎn)都會(huì)確保至少有 2 個(gè) Pod 處于可用狀態(tài),至多只有 4 個(gè) Pod 同時(shí)存在于集群中。這個(gè)策略,是 Deployment 對象的一個(gè)字段,名叫 RollingUpdateStrategy,如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
...
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
在上面這個(gè) RollingUpdateStrategy 的配置中,maxSurge 指定的是除了 DESIRED 數(shù)量之外,在一次“滾動(dòng)”中,Deployment 控制器還可以創(chuàng)建多少個(gè)新 Pod;而 maxUnavailable 指的是,在一次“滾動(dòng)”中,Deployment 控制器可以刪除多少個(gè)舊 Pod。
同時(shí),這兩個(gè)配置還可以用前面我們介紹的百分比形式來表示,比如:maxUnavailable=50%,指的是我們最多可以一次刪除“50%*DESIRED 數(shù)量”個(gè) Pod。
結(jié)合以上講述,現(xiàn)在我們可以擴(kuò)展一下 Deployment、ReplicaSet 和 Pod 的關(guān)系圖了。

如上所示,Deployment 的控制器,實(shí)際上控制的是 ReplicaSet 的數(shù)目,以及每個(gè) ReplicaSet 的屬性。
而一個(gè)應(yīng)用的版本,對應(yīng)的正是一個(gè) ReplicaSet;這個(gè)版本應(yīng)用的 Pod 數(shù)量,則由 ReplicaSet 通過它自己的控制器(ReplicaSet Controller)來保證。
通過這樣的多個(gè) ReplicaSet 對象,Kubernetes 項(xiàng)目就實(shí)現(xiàn)了對多個(gè)“應(yīng)用版本”的描述。
以上就是k8s編排之Deployment知識(shí)點(diǎn)詳解的詳細(xì)內(nèi)容,更多關(guān)于k8s編排Deployment的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Rainbond云原生部署SpringCloud應(yīng)用架構(gòu)實(shí)踐
這篇文章主要為大家介紹了Rainbond云原生部署SpringCloud應(yīng)用架構(gòu)實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
Rainbond云原生部署開源社區(qū)Discourse的配置過程
這篇文章主要為大家介紹了Rainbond云原生部署開源社區(qū)Discourse配置過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
理解k8s控制器DaemonSet創(chuàng)建及使用場景
這篇文章主要為大家介紹了k8s控制器DaemonSet創(chuàng)建及使用場景詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
k8s自身原理service及實(shí)現(xiàn)圖文示例解析
這篇文章主要為大家介紹了k8s自身原理service圖文示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

