Kubernetes應用部署模型解析(原理篇)
【編者按】 Kubernetes可用來管理Linux容器集群,加速開發和簡化運維(即DevOps)。但目前網絡上關于Kubernetes的文章介紹性遠多于實 際使用。本系列文章著眼于實際部署,帶您快速掌握Kubernetes。本文為上篇,主要介紹部署之前需要了解的原理和概念,包括Kubernetes的 組件結構,以及各個組件角色的功能。
十多年來Google一直在生產環境中使用容器運行業務,負責管理其容器集群的系統就是Kubernetes的前身Borg。其實現在很多工作在 Kubernetes項目上的Google開發者先前就在Borg這個項目上工作。多數Kubernetes的應用部署模型的思想都起源于Borg,了解 這些模型是掌握Kubernetes的關鍵。Kubernetes的API版本目前是v1,本文以代碼 0.18.2版為基礎來介紹它的應用部署模型,最后我們用一個簡單的用例來說明部署過程。 在部署結束后,闡述了它是如何用Iptables規則來實現各種類型Service的。
Kubernetes架構
Kubernetes 集群包括 Kubernetes 代理 (agents ) 和 Kubernetes 服務 (master node) 兩種角色,代理角色的組件包括 Kube-proxy 和 Kubelet ,它們同時部署在一個節點上,這個節點也就是代理節點。服務角色的組件包括 kube-apiserver , kube-scheduler , kube-controller-manager ,它們 可以任意布屬,它們可以部署在同一個節點上,也可以部署在不同的節點上(目前版本好像不行)。 Kubernetes 集群依賴的第三方組件目前有 etcd 和 docker 兩個。前者提供狀態存儲,二者用來管理容器。集群還可以使用分布式存儲給容器提供存儲空間。下圖顯示了目前系統的組成部分:
Kubernetes代理節點
Kubelet和Kube-proxy運行在代理節點上。他們監聽服務節點的信息來啟動容器和實現Kubernetes網絡和其它業務模型,比如Service、Pod等。當然每個代理節點都運行Docker。Docker負責下載容器鏡像和運行容器。
Kubelet
Kubelet 組件管理 Pods 和它們的容器,鏡像和卷等信息。
Kube-Proxy
Kube-proxy 是一個簡單的網絡代理和負載均衡器。它具體實現 Service 模型,每個 Service 都會在所有的 Kube-proxy 節點上體現。根據 Service 的 selector 所覆蓋的 Pods, Kube-proxy 會對這些 Pods 做負載均衡來服務于 Service的訪問者。
Kubernetes服務節點
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_HOST h 和 {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 選擇器樣例:
environment = production tier != frontend environment = production,tier != frontend
對于上面的選擇器,第一條匹配L abel 具有 environment key 且等于 production 的對象,第二條匹配具有 tier key ,但是值不等于 frontend 的對象。由于 kubernetes 使用 AND 邏輯,第三條匹配 production 但不是 frontend 的對象。
Set-based 選擇器樣例:
environment in (production, qa)
tier notin (frontend, backend)
partition
第一條選擇具有 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 。
下篇預告: Kubernetes應用部署模型解析(部署篇)(責編/周建丁)
作者簡介: 龔永生, 九州云架構師。 多年Linux系統開發,J2EE產品和云計算相關技術研發經驗。目前活躍在OpenStack社區的各個項目上,主要技術方向是虛擬網絡項目Neutron,是Neutron項目早期的主要貢獻者之一。