Kubernetes應(yīng)用部署模型原理解析
Kubernetes可用來(lái)管理Linux容器集群,加速開(kāi)發(fā)和簡(jiǎn)化運(yùn)維(即DevOps)。但目前網(wǎng)絡(luò)上關(guān)于Kubernetes的文章介紹性遠(yuǎn) 多于實(shí) 際使用。本系列文章著眼于實(shí)際部署,帶您快速掌握Kubernetes。本文為上篇,主要介紹部署之前需要了解的原理和概念,包括Kubernetes的 組件結(jié)構(gòu),以及各個(gè)組件角色的功能。
十多年來(lái)Google一直在生產(chǎn)環(huán)境中使用容器運(yùn)行業(yè)務(wù),負(fù)責(zé)管理其容器集群的系統(tǒng)就是 Kubernetes的前身Borg。其實(shí)現(xiàn)在很多工作在 Kubernetes項(xiàng)目上的Google開(kāi)發(fā)者先前就在Borg這個(gè)項(xiàng)目上工作。多數(shù)Kubernetes的應(yīng)用部署模型的思想都起源于Borg,了解 這些模型是掌握Kubernetes的關(guān)鍵。Kubernetes的API版本目前是v1,本文以代碼 0.18.2版為基礎(chǔ)來(lái)介紹它的應(yīng)用部署模型,***我們用一個(gè)簡(jiǎn)單的用例來(lái)說(shuō)明部署過(guò)程。 在部署結(jié)束后,闡述了它是如何用Iptables規(guī)則來(lái)實(shí)現(xiàn)各種類型Service的。
Kubernetes架構(gòu)
Kubernetes 集群包括 Kubernetes 代理 (agents ) 和 Kubernetes 服務(wù) (master node) 兩種角色,代理角色的組件包括 Kube-proxy 和 Kubelet ,它們同時(shí)部署在一個(gè)節(jié)點(diǎn)上,這個(gè)節(jié)點(diǎn)也就是代理節(jié)點(diǎn)。服務(wù)角色的組件包括 kube-apiserver , kube-scheduler , kube-controller-manager ,它們 可以任意布屬,它們可以部署在同一個(gè)節(jié)點(diǎn)上,也可以部署在不同的節(jié)點(diǎn)上(目前版本好像不行)。 Kubernetes 集群依賴的第三方組件目前有 etcd 和 docker 兩個(gè)。前者提供狀態(tài)存儲(chǔ),二者用來(lái)管理容器。集群還可以使用分布式存儲(chǔ)給容器提供存儲(chǔ)空間。下圖顯示了目前系統(tǒng)的組成部分:
Kubernetes代理節(jié)點(diǎn)
Kubelet和Kube-proxy運(yùn)行在代理節(jié)點(diǎn)上。他們監(jiān)聽(tīng)服務(wù)節(jié)點(diǎn)的信息來(lái)啟動(dòng)容器和實(shí)現(xiàn)Kubernetes網(wǎng)絡(luò)和其它業(yè)務(wù)模型,比如Service、Pod等。當(dāng)然每個(gè)代理節(jié)點(diǎn)都運(yùn)行Docker。Docker負(fù)責(zé)下載容器鏡像和運(yùn)行容器。
Kubelet
Kubelet 組件管理 Pods 和它們的容器,鏡像和卷等信息。
Kube-Proxy
Kube-proxy 是一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)代理和負(fù)載均衡器。它具體實(shí)現(xiàn) Service 模型,每個(gè) Service 都會(huì)在所有的 Kube-proxy 節(jié)點(diǎn)上體現(xiàn)。根據(jù) Service 的 selector 所覆蓋的 Pods, Kube-proxy 會(huì)對(duì)這些 Pods 做負(fù)載均衡來(lái)服務(wù)于 Service
的訪問(wèn)者。
Kubernetes服務(wù)節(jié)點(diǎn)
Kubernetes 服務(wù)組件形成了 Kubernetes的控制平面,目前他們運(yùn)行在單一節(jié)點(diǎn)上,但是將來(lái)會(huì)分開(kāi)來(lái)部署,以支持高可用性。
etcd
所有的持久性狀態(tài)都保存在etcd中。Etcd同時(shí)支持watch,這樣組件很容易得到系統(tǒng)狀態(tài)的變化,從而快速響應(yīng)和協(xié)調(diào)工作。
Kubernetes API Server
這個(gè)組件提供對(duì)API的支持,響應(yīng)REST操作,驗(yàn)證API模型和更新etcd中的相應(yīng)對(duì)象。
Scheduler
通過(guò)訪問(wèn)Kubernetes中/binding API, Scheduler負(fù)責(zé)Pods在各個(gè)節(jié)點(diǎn)上的分配。Scheduler是插件式的,Kubernetes將來(lái)可以支持用戶自定義的scheduler。
Kubernetes Controller Manager Server
Controller Manager Server負(fù)責(zé)所有其它的功能,比如endpoints控制器負(fù)責(zé)Endpoints對(duì)象的創(chuàng)建,更新。node控制器負(fù)責(zé)節(jié)點(diǎn)的發(fā)現(xiàn),管理和監(jiān)控。將來(lái)可能會(huì)把這些控制器拆分并且提供插件式的實(shí)現(xiàn)。
#p#
Kubernetes模型
Kubernetes的偉大之處就在于它的應(yīng)用部署模型,主要包括Pod、Replication controller、Label和Service。
Pod
Kubernetes的最小部署單元是Pod而不是容器。作為First class API公民,Pods能被創(chuàng)建,調(diào)度和管理。簡(jiǎn)單地來(lái)說(shuō),像一個(gè)豌豆莢中的豌豆一樣,一個(gè)Pod中的應(yīng)用容器同享同一個(gè)上下文:
- PID 名字空間。但是在docker中不支持
- 網(wǎng)絡(luò)名字空間,在同一Pod中的多個(gè)容器訪問(wèn)同一個(gè)IP和端口空間。
- IPC名字空間,同一個(gè)Pod中的應(yīng)用能夠使用SystemV IPC和POSIX消息隊(duì)列進(jìn)行通信。
- UTS名字空間,同一個(gè)Pod中的應(yīng)用共享一個(gè)主機(jī)名。
- Pod中的各個(gè)容器應(yīng)用還可以訪問(wèn)Pod級(jí)別定義的共享卷。
從 生命周期來(lái)說(shuō),Pod應(yīng)該是短暫的而不是長(zhǎng)久的應(yīng)用。 Pods被調(diào)度到節(jié)點(diǎn),保持在這個(gè)節(jié)點(diǎn)上直到被銷毀。當(dāng)節(jié)點(diǎn)死亡時(shí),分配到這個(gè)節(jié)點(diǎn)的Pods將會(huì)被刪掉。將來(lái)可能會(huì)實(shí)現(xiàn)Pod的遷移特性。在實(shí)際使用 時(shí),我們一般不直接創(chuàng)建Pods, 我們通過(guò)replication controller來(lái)負(fù)責(zé)Pods的創(chuàng)建,復(fù)制,監(jiān)控和銷毀。一個(gè)Pod可以包括多個(gè)容器,他們直接往往相互協(xié)作完成一個(gè)應(yīng)用功能。
Replication controller
復(fù)制控制器確保Pod的一定數(shù)量的份數(shù)(replica)在運(yùn)行。如果超過(guò)這個(gè)數(shù)量,控制器會(huì)殺死一些,如果少了,控制器會(huì)啟動(dòng)一些??刂破饕矔?huì)在節(jié)點(diǎn)失效、維護(hù)的時(shí)候來(lái)保證這個(gè)數(shù)量。所以強(qiáng)烈建議即使我們的份數(shù)是1,也要使用復(fù)制控制器,而不是直接創(chuàng)建Pod。
在生命周期上講,復(fù)制控制器自己不會(huì)終止,但是跨度不會(huì)比Service強(qiáng)。Service能夠橫跨多個(gè)復(fù)制控制器管理的Pods。而且在一個(gè)Service的生命周期內(nèi),復(fù)制控制器能被刪除和創(chuàng)建。Service和客戶端程序是不知道復(fù)制控制器的存在的。
復(fù)制控制器創(chuàng)建的Pods應(yīng)該是可以互相替換的和語(yǔ)義上相同的,這個(gè)對(duì)無(wú)狀態(tài)服務(wù)特別合適。
Pod是臨時(shí)性的對(duì)象,被創(chuàng)建和銷毀,而且不會(huì)恢復(fù)。復(fù)制器動(dòng)態(tài)地創(chuàng)建和銷毀Pod。雖然Pod會(huì)分配到IP地址,但是這個(gè)IP地址都不是持久的。這樣就產(chǎn)生了一個(gè)疑問(wèn):外部如何消費(fèi)Pod提供的服務(wù)呢?
Service
Service 定義了一個(gè)Pod的邏輯集合和訪問(wèn)這個(gè)集合的策略。集合是通過(guò)定義Service時(shí)提供的Label選擇器完成的。舉個(gè)例子,我們 假定有3個(gè)Pod的備份來(lái)完成一個(gè)圖像處理的后端。這些后端備份邏輯上是相同的,前端不關(guān)心哪個(gè)后端在給它提供服務(wù)。雖然組成這個(gè)后端的實(shí)際Pod可能變 化,前端客戶端不會(huì)意識(shí)到這個(gè)變化,也不會(huì)跟蹤后端。Service就是用來(lái)實(shí)現(xiàn)這種分離的抽象。
對(duì)于Service,我們還可以定義Endpoint,Endpoint把Service和Pod動(dòng)態(tài)地連接起來(lái)。
Service Cluster IP和 kuber proxy
每 個(gè)代理節(jié)點(diǎn)都運(yùn)行了一個(gè)kube-proxy進(jìn)程。這個(gè)進(jìn)程從服務(wù)進(jìn)程那邊拿到Service和Endpoint對(duì)象的變化。 對(duì)每一個(gè)Service, 它在本地打開(kāi)一個(gè)端口。 到這個(gè)端口的任意連接都會(huì)代理到后端Pod集合中的一個(gè)Pod IP和端口。在創(chuàng)建了服務(wù)后,服務(wù)Endpoint模型會(huì)體現(xiàn)后端Pod的 IP和端口列表,kube-proxy就是從這個(gè)endpoint維護(hù)的列表中選擇服務(wù)后端的。另外Service對(duì)象的sessionAffinity 屬性也會(huì)幫助kube-proxy來(lái)選擇哪個(gè)具體的后端。缺省情況下,后端Pod的選擇是隨機(jī)的??梢栽O(shè)置 service.spec.sessionAffinity 成 "ClientIP" 來(lái)指定同一個(gè) ClientIP 的流量代理到同一個(gè)后端。
在實(shí)現(xiàn)上,kube-proxy會(huì)用IPtables規(guī)則把訪問(wèn)Service的Cluster IP和端口的流量重定向到這個(gè)本地端口。下面的部分會(huì)講什么是service的Cluster IP。
注意:在0.18以前的版本中Cluster IP叫PortalNet IP。
內(nèi)部使用者的服務(wù)發(fā)現(xiàn)
Kubernetes
在一個(gè)集群內(nèi)創(chuàng)建的對(duì)象或者在代理集群節(jié)點(diǎn)上發(fā)出訪問(wèn)的客戶端我們稱之為內(nèi)部使用者。
要把服務(wù)暴露給內(nèi)部使用者,Kubernetes支持兩種方式:環(huán)境變量和DNS。
環(huán)境變量
當(dāng) kubelet在某個(gè)節(jié)點(diǎn)上啟動(dòng)一個(gè)Pod時(shí),它會(huì)給這個(gè)Pod的容器為當(dāng)前運(yùn)行的Service設(shè)置一系列環(huán)境變量,這樣Pod就可以訪問(wèn)這些 Service了。一般地情況是 {SVCNAME}_SERVICE_HOST h 和 {SVCNAME}_SERVICE_PORT 變量 , 其中 {SVCNAME} 是 Service 名字變成大寫,中劃線變成下劃線。
比如
Service "redis-master",它的端口是 TCP 6379,分配到的Cluster IP地址是 10.0.0.11,kubelet可能會(huì)產(chǎn)生下面的變量給新創(chuàng)建的Pod容器:
- REDIS_MASTER_SERVICE_HOST= 10.0.0.11
- REDIS_MASTER_SERVICE_PORT=6379
- REDIS_MASTER_PORT=tcp://10.0.0.11:6379
- REDIS_MASTER_PORT_6379_TCP=tcp:// 10.0.0.11 :6379
- REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
- REDIS_MASTER_PORT_6379_TCP_PORT=6379
- REDIS_MASTER_PORT_6379_TCP_ADDR=
- 10.0.0.11
注意,只有在某個(gè)Service后創(chuàng)建的Pod才會(huì)有這個(gè)Service的環(huán)境變量。
#p#
DNS
一個(gè)可選的Kubernetes附件(強(qiáng)烈建議用戶使用)是DNS服務(wù)。它跟蹤集群中Service對(duì)象,為每個(gè)Service對(duì)象創(chuàng)建DNS記錄。這樣所有的Pod就可以通過(guò)DNS訪問(wèn)服務(wù)了。
比 如說(shuō)我們?cè)贙ubernetes 名字空間"my-ns"中有個(gè)叫my-service的服務(wù),DNS服務(wù)會(huì)創(chuàng)建一條"my-service.my-ns"的DNS記錄。同在這個(gè)命名空間 的Pod就可以通過(guò)"my-service"來(lái)得到這個(gè)Service分配到的Cluster IP,在其它命名空間的Pod則可以用全限定名"my-service.my-ns"來(lái)獲得這個(gè)Service的地址。
Pod IP and Service Cluster IP
Pod IP 地址是實(shí)際存在于某個(gè)網(wǎng)卡(可以是虛擬設(shè)備)上的,但Service Cluster IP就不一樣了,沒(méi)有網(wǎng)絡(luò)設(shè)備為這個(gè)地址負(fù)責(zé)。它是由kube-proxy使用Iptables規(guī)則重新定向到其本地端口,再均衡到后端Pod的。我們前 面說(shuō)的Service環(huán)境變量和DNS都使用Service的Cluster IP和端口。
就拿上面我們提到的圖像處理程序?yàn)槔.?dāng)我們 的Service被創(chuàng)建時(shí),Kubernetes給它分配一個(gè)地址10.0.0.1。這個(gè)地址從我們啟動(dòng) API的service-cluster-ip-range參數(shù)(舊版本為portal_net參數(shù))指定的地址池中分配,比如 -- service-cluster-ip-range =10.0.0.0/16 。假設(shè)這個(gè)Service的端口是1234。集群內(nèi)的所有kube-proxy都會(huì)注意到這個(gè)Service。當(dāng)proxy發(fā)現(xiàn)一個(gè)新的service 后,它會(huì)在本地節(jié)點(diǎn)打開(kāi)一個(gè)任意端口,建相應(yīng)的iptables規(guī)則,重定向服務(wù)的IP和port到這個(gè)新建的端口,開(kāi)始接受到達(dá)這個(gè)服務(wù)的連接。
當(dāng)一個(gè)客戶端訪問(wèn)這個(gè)service時(shí),這些iptable規(guī)則就開(kāi)始起作用,客戶端的流量被重定向到kube-proxy為這個(gè)service打開(kāi)的端口上,kube-proxy隨機(jī)選擇一個(gè)后端pod來(lái)服務(wù)客戶。這個(gè)流程如下圖所示:
根據(jù) Kubernetes 的網(wǎng)絡(luò)模型,使用 Service Cluster IP 和 Port 訪問(wèn) Service 的客戶端可以坐落在任意代理節(jié)點(diǎn)上。外部要訪問(wèn) Service ,我們就需要給 Service 外部訪問(wèn) IP 。
外部訪問(wèn)Service
Service對(duì)象在Cluster IP range池中分配到的IP只能在內(nèi)部訪問(wèn),如果服務(wù)作為一個(gè)應(yīng)用程序內(nèi)部的層次,還是很合適的。如果這個(gè)Service作為前端服務(wù),準(zhǔn)備為集群外的客戶提供業(yè)務(wù),我們就需要給這個(gè)服務(wù)提供公共IP了。
外 部訪問(wèn)者是訪問(wèn)集群代理節(jié)點(diǎn)的訪問(wèn)者。為這些訪問(wèn)者提供服務(wù),我們可以在定義Service時(shí)指定其spec.publicIPs,一般情況下 publicIP 是代理節(jié)點(diǎn)的物理IP地址。和先前的Cluster IP range上分配到的虛擬的IP一樣,kube-proxy同樣會(huì)為這些publicIP提供Iptables 重定向規(guī)則,把流量轉(zhuǎn)發(fā)到后端的Pod上。有了publicIP,我們就可以使用load balancer等常用的互聯(lián)網(wǎng)技術(shù)來(lái)組織外部對(duì)服務(wù)的訪問(wèn)了。
spec.publicIPs在新的版本中標(biāo)記為過(guò)時(shí)了,代替它的是spec.type=NodePort,這個(gè)類型的service,系統(tǒng)會(huì)給它在集群的各個(gè)代理節(jié)點(diǎn)上分配一個(gè)節(jié)點(diǎn)級(jí)別的端口,能訪問(wèn)到代理節(jié)點(diǎn)的客戶端都能訪問(wèn)這個(gè)端口,從而訪問(wèn)到服務(wù)。
Label和Label selector
Label 標(biāo)簽在 Kubernetes
模 型中占著非常重要的作用。Label表現(xiàn)為key/value對(duì),附加到Kubernetes管理的對(duì)象上,典型的就是Pods。它們定義了這 些對(duì)象的識(shí)別屬性,用來(lái)組織和選擇這些對(duì)象。Label可以在對(duì)象創(chuàng)建時(shí)附加在對(duì)象上,也可以對(duì)象存在時(shí)通過(guò)API管理對(duì)象的Label。
在定義了對(duì)象的Label后,其它模型可以用Label 選擇器(selector)來(lái)定義其作用的對(duì)象。
Label 選擇器有兩種,分別是 Equality-based 和 Set-based 。
比如如下 Equality-based 選擇器樣例:
- environment = production
- tier != frontend
- environment = production,tier != frontend
對(duì) 于上面的選擇器,***條匹配L abel 具有 environment key 且等于 production 的對(duì)象,第二條匹配具有 tier key ,但是值不等于 frontend 的對(duì)象。由于 kubernetes 使用 AND 邏輯,第三條匹配 production 但不是 frontend 的對(duì)象。
Set-based 選擇器樣例:
- environment in (production, qa)
- tier notin (frontend, backend)
- partition
***條選擇具有 environment key ,而且值是 production 或者 qa 的 label 附加的對(duì)象。第二條選擇具有 tier key ,但是其值不是 frontend 和 backend 。第三條選則具有 partition key 的對(duì)象,不對(duì) value 進(jìn)行校驗(yàn)。
replication controller 復(fù)制控制器和 Service 都用 label 和 label selctor 來(lái)動(dòng)態(tài)地配備作用對(duì)象。復(fù)制控制器在定義的時(shí)候就指定了其要?jiǎng)?chuàng)建 Pod 的 Label 和自己要匹配這個(gè) Pod 的 selector , API 服務(wù)器應(yīng)該校驗(yàn)這個(gè)定義。我們可以動(dòng)態(tài)地修改 replication controller 創(chuàng)建的 Pod 的 Label 用于調(diào)式,數(shù)據(jù)恢復(fù)等。一旦某個(gè) Pod 由于 Label 改變 從 replication controller 移出來(lái)后, replication controller 會(huì)馬上啟動(dòng)一個(gè)新的 Pod 來(lái)確保復(fù)制池子中的份數(shù)。對(duì)于 Service , Label selector 可以用來(lái)選擇一個(gè) Service 的后端 Pods 。