Kubernetes應用部署模型解析(原理篇)
十多年來Google一直在生產環境中使用容器執行業務,負責管理其容器叢集的系統就是Kubernetes的前身Borg。其實現在很多工作在Kubernetes專案上的Google開發者先前就在Borg這個專案上工作。多數Kubernetes的應用部署模型的思想都起源於Borg,瞭解這些模型是掌握Kubernetes的關鍵。Kubernetes的API版本目前是v1,本文以程式碼0.18.2版為基礎來介紹它的應用部署模型,最後我們用一個簡單的用例來說明部署過程。在部署結束後,闡述了它是如何用Iptables規則來實現各種型別Service的。
Kubernetes架構
Kubernetes
Kubernetes代理節點
Kubelet和Kube-proxy執行在代理節點上。他們監聽服務節點的資訊來啟動容器和實現Kubernetes網路和其它業務模型,比如Service、Pod等。當然每個代理節點都執行Docker。Docker負責下載容器映象和執行容器。
Kubelet
Kubelet元件管理Pods和它們的容器,映象和卷等資訊。
Kube-Proxy
Kube-proxy是一個簡單的網路代理和負載均衡器。它具體實現Service模型,每個Service都會在所有的Kube-proxy節點上體現。根據Service的selector所覆蓋的Pods, Kube-proxyKubernetes服務節點
Kubernetes服務元件形成了Kubernetes的控制平面,目前他們執行在單一節點上,但是將來會分開來部署,以支援高可用性。etcd
所有的永續性狀態都儲存在etcd中。Etcd同時支援watch,這樣元件很容易得到系統狀態的變化,從而快速響應和協調工作。Kubernetes API Server
這個元件提供對API的支援,響應REST操作,驗證API模型和更新etcd中的相應物件。Scheduler
通過訪問Kubernetes中/binding API, Scheduler負責Pods在各個節點上的分配。Scheduler是外掛式的,Kubernetes將來可以支援使用者自定義的scheduler。Kubernetes Controller Manager Server
Controller Manager Server負責所有其它的功能,比如endpoints控制器負責Endpoints物件的建立,更新。node控制器負責節點的發現,管理和監控。將來可能會把這些控制器拆分並且提供外掛式的實現。Kubernetes模型
Kubernetes的偉大之處就在於它的應用部署模型,主要包括Pod、Replication controller、Label和Service。Pod
Kubernetes的最小部署單元是Pod而不是容器。作為First class API公民,Pods能被建立,排程和管理。簡單地來說,像一個豌豆莢中的豌豆一樣,一個Pod中的應用容器同享同一個上下文:- PID 名字空間。但是在docker中不支援
- 網路名字空間,在同一Pod中的多個容器訪問同一個IP和埠空間。
- IPC名字空間,同一個Pod中的應用能夠使用SystemV IPC和POSIX訊息佇列進行通訊。
- UTS名字空間,同一個Pod中的應用共享一個主機名。
- Pod中的各個容器應用還可以訪問Pod級別定義的共享卷。
從生命週期來說,Pod應該是短暫的而不是長久的應用。 Pods被排程到節點,保持在這個節點上直到被銷燬。當節點死亡時,分配到這個節點的Pods將會被刪掉。將來可能會實現Pod的遷移特性。在實際使用時,我們一般不直接建立Pods, 我們通過replication controller來負責Pods的建立,複製,監控和銷燬。一個Pod可以包括多個容器,他們直接往往相互協作完成一個應用功能。
Replication controller
複製控制器確保Pod的一定數量的份數(replica)在執行。如果超過這個數量,控制器會殺死一些,如果少了,控制器會啟動一些。控制器也會在節點失效、維護的時候來保證這個數量。所以強烈建議即使我們的份數是1,也要使用複製控制器,而不是直接建立Pod。在生命週期上講,複製控制器自己不會終止,但是跨度不會比Service強。Service能夠橫跨多個複製控制器管理的Pods。而且在一個Service的生命週期內,複製控制器能被刪除和建立。Service和客戶端程式是不知道複製控制器的存在的。
複製控制器建立的Pods應該是可以互相替換的和語義上相同的,這個對無狀態服務特別合適。
Pod是臨時性的物件,被建立和銷燬,而且不會恢復。複製器動態地建立和銷燬Pod。雖然Pod會分配到IP地址,但是這個IP地址都不是持久的。這樣就產生了一個疑問:外部如何消費Pod提供的服務呢?
Service
Service定義了一個Pod的邏輯集合和訪問這個集合的策略。集合是通過定義Service時提供的Label選擇器完成的。舉個例子,我們假定有3個Pod的備份來完成一個影象處理的後端。這些後端備份邏輯上是相同的,前端不關心哪個後端在給它提供服務。雖然組成這個後端的實際Pod可能變化,前端客戶端不會意識到這個變化,也不會跟蹤後端。Service就是用來實現這種分離的抽象。對於Service,我們還可以定義Endpoint,Endpoint把Service和Pod動態地連線起來。
Service Cluster IP和 kuber proxy
每個代理節點都運行了一個kube-proxy程序。這個程序從服務程序那邊拿到Service和Endpoint物件的變化。 對每一個Service, 它在本地開啟一個埠。 到這個埠的任意連線都會代理到後端Pod集合中的一個Pod IP和埠。在建立了服務後,服務Endpoint模型會體現後端Pod的 IP和埠列表,kube-proxy就是從這個endpoint維護的列表中選擇服務後端的。另外Service物件的sessionAffinity屬性也會幫助kube-proxy來選擇哪個具體的後端。預設情況下,後端Pod的選擇是隨機的。可以設定service.spec.sessionAffinity 成"ClientIP"來指定同一個ClientIP的流量代理到同一個後端。在實現上,kube-proxy會用IPtables規則把訪問Service的Cluster IP和埠的流量重定向到這個本地埠。下面的部分會講什麼是service的Cluster IP。注意:在0.18以前的版本中Cluster IP叫PortalNet IP。
內部使用者的服務發現
Kubernetes在一個叢集內建立的物件或者在代理叢集節點上發出訪問的客戶端我們稱之為內部使用者。要把服務暴露給內部使用者,Kubernetes支援兩種方式:環境變數和DNS。環境變數
當kubelet在某個節點上啟動一個Pod時,它會給這個Pod的容器為當前執行的Service設定一系列環境變數,這樣Pod就可以訪問這些Service了。一般地情況是{SVCNAME}_SERVICE_HOSTh和{SVCNAME}_SERVICE_PORT變數, 其中{SVCNAME}是Service名字變成大寫,中劃線變成下劃線。比如Service "redis-master",它的埠是 TCP 6379,分配到的Cluster IP地址是 10.0.0.11,kubelet可能會產生下面的變數給新建立的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
注意,只有在某個Service後建立的Pod才會有這個Service的環境變數。
DNS
一個可選的Kubernetes附件(強烈建議使用者使用)是DNS服務。它跟蹤叢集中Service物件,為每個Service物件建立DNS記錄。這樣所有的Pod就可以通過DNS訪問服務了。比如說我們在Kubernetes 名字空間"my-ns"中有個叫my-service的服務,DNS服務會建立一條"my-service.my-ns"的DNS記錄。同在這個名稱空間的Pod就可以通過"my-service"來得到這個Service分配到的Cluster IP,在其它名稱空間的Pod則可以用全限定名"my-service.my-ns"來獲得這個Service的地址。
Pod IP and Service Cluster IP
Pod IP 地址是實際存在於某個網絡卡(可以是虛擬裝置)上的,但Service Cluster IP就不一樣了,沒有網路裝置為這個地址負責。它是由kube-proxy使用Iptables規則重新定向到其本地埠,再均衡到後端Pod的。我們前面說的Service環境變數和DNS都使用Service的Cluster IP和埠。就拿上面我們提到的影象處理程式為例。當我們的Service被建立時,Kubernetes給它分配一個地址10.0.0.1。這個地址從我們啟動API的service-cluster-ip-range引數(舊版本為portal_net引數)指定的地址池中分配,比如--service-cluster-ip-range=10.0.0.0/16。假設這個Service的埠是1234。叢集內的所有kube-proxy都會注意到這個Service。當proxy發現一個新的service後,它會在本地節點開啟一個任意埠,建相應的iptables規則,重定向服務的IP和port到這個新建的埠,開始接受到達這個服務的連線。
當一個客戶端訪問這個service時,這些iptable規則就開始起作用,客戶端的流量被重定向到kube-proxy為這個service開啟的埠上,kube-proxy隨機選擇一個後端pod來服務客戶。這個流程如下圖所示:
根據Kubernetes的網路模型,使用Service Cluster IP和Port訪問Service的客戶端可以坐落在任意代理節點上。外部要訪問Service,我們就需要給Service外部訪問IP。
外部訪問Service
Service物件在Cluster IP range池中分配到的IP只能在內部訪問,如果服務作為一個應用程式內部的層次,還是很合適的。如果這個Service作為前端服務,準備為叢集外的客戶提供業務,我們就需要給這個服務提供公共IP了。外部訪問者是訪問叢集代理節點的訪問者。為這些訪問者提供服務,我們可以在定義Service時指定其spec.publicIPs,一般情況下publicIP 是代理節點的物理IP地址。和先前的Cluster IP range上分配到的虛擬的IP一樣,kube-proxy同樣會為這些publicIP提供Iptables 重定向規則,把流量轉發到後端的Pod上。有了publicIP,我們就可以使用load balancer等常用的網際網路技術來組織外部對服務的訪問了。
spec.publicIPs在新的版本中標記為過時了,代替它的是spec.type=NodePort,這個型別的service,系統會給它在叢集的各個代理節點上分配一個節點級別的埠,能訪問到代理節點的客戶端都能訪問這個埠,從而訪問到服務。
Label和Label selector
Label標籤在Kubernetes模型中佔著非常重要的作用。Label表現為key/value對,附加到Kubernetes管理的物件上,典型的就是Pods。它們定義了這些物件的識別屬性,用來組織和選擇這些物件。Label可以在物件建立時附加在物件上,也可以物件存在時通過API管理物件的Label。在定義了物件的Label後,其它模型可以用Label 選擇器(selector)來定義其作用的物件。
Label選擇器有兩種,分別是Equality-based和Set-based。
比如如下Equality-based選擇器樣例:
對於上面的選擇器,第一條匹配Label具有environment key且等於production的物件,第二條匹配具有tier key,但是值不等於frontend的物件。由於kubernetes使用AND邏輯,第三條匹配production但不是frontend的物件。
Set-based選擇器樣例:
第一條選擇具有environment key,而且值是production或者qa的label附加的物件。第二條選擇具有tier key,但是其值不是frontend和backend。第三條選則具有partition key的物件,不對value進行校驗。
replication controller複製控制器和Service都用label和label selctor來動態地配備作用物件。複製控制器在定義的時候就指定了其要建立Pod的Label和自己要匹配這個Pod的selector, API伺服器應該校驗這個定義。我們可以動態地修改replication controller建立的Pod的Label用於調式,資料恢復等。一旦某個Pod由於Label改變從replication controller移出來後,replication controller會馬上啟動一個新的Pod來確保複製池子中的份數。對於Service,Label selector可以用來選擇一個Service的後端Pods。
作者簡介:龔永生,九州雲架構師。多年Linux系統開發,J2EE產品和雲端計算相關技術研發經驗。目前活躍在OpenStack社群的各個專案上,主要技術方向是虛擬網路專案Neutron,是Neutron專案早期的主要貢獻者之一。