kubernetes應用部署原理

Kubernetes應用部署模型解析(原理篇)
html


十多年來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-apiserverkube-schedulerkube-controller-manager ,它們能夠任意布屬,它們能夠部署在同一個節點上,也能夠部署在不一樣的節點上(目前版本好像不行)。 Kubernetes集羣依賴的第三方組件目前有 etcddocker兩個。前者提供狀態存儲,兩者用來管理容器。集羣還可使用分佈式存儲給容器提供存儲空間。下圖顯示了目前系統的組成部分:


Kubernetes代理節點Kubelet和Kube-proxy運行在代理節點上。他們監聽服務節點的信息來啓動容器和實現Kubernetes網絡和其它業務模型,好比Service、Pod等。固然每一個代理節點都運行Docker。Docker負責下載容器鏡像和運行容器。

Kubelet
node

Kubelet 組件管理 Pods 和它們的容器,鏡像和卷等信息。


Kube-ProxyKube-proxy是一個簡單的網絡代理和負載均衡器。它具體實現Service模型,每一個Service都會在全部的Kube-proxy節點上體現。根據Serviceselector所覆蓋的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 ServerController Manager Server負責全部其它的功能,好比endpoints控制器負責Endpoints對象的建立,更新。node控制器負責節點的發現,管理和監控。未來可能會把這些控制器拆分而且提供插件式的實現。

Kubernetes模型Kubernetes的偉大之處就在於它的應用部署模型,主要包括Pod、Replication controller、Label和Service。

PodKubernetes的最小部署單元是Pod而不是容器。做爲First class API公民,Pods能被建立,調度和管理。簡單地來講,像一個豌豆莢中的豌豆同樣,一個Pod中的應用容器同享同一個上下文:

nginx

  • 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。
git

在生命週期上講,複製控制器本身不會終止,可是跨度不會比Service強。Service可以橫跨多個複製控制器管理的Pods。並且在一個Service的生命週期內,複製控制器能被刪除和建立。Service和客戶端程序是不知道複製控制器的存在的。
複製控制器建立的Pods應該是能夠互相替換的和語義上相同的,這個對無狀態服務特別合適。
Pod是臨時性的對象,被建立和銷燬,並且不會恢復。複製器動態地建立和銷燬Pod。雖然Pod會分配到IP地址,可是這個IP地址都不是持久的。這樣就產生了一個疑問:外部如何消費Pod提供的服務呢?


ServiceService定義了一個Pod的邏輯集合和訪問這個集合的策略。集合是經過定義Service時提供的Label選擇器完成的。舉個例子,咱們假定有3個Pod的備份來完成一個圖像處理的後端。這些後端備份邏輯上是相同的,前端不關心哪一個後端在給它提供服務。雖然組成這個後端的實際Pod可能變化,前端客戶端不會意識到這個變化,也不會跟蹤後端。Service就是用來實現這種分離的抽象。
github

對於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。
web

注意:在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容器:
docker

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訪問服務了。
ubuntu

好比說咱們在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 IPPod 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。


外部訪問ServiceService對象在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 selectorLabel標籤在Kubernetes模型中佔着很是重要的做用。Label表現爲key/value對,附加到Kubernetes管理的對象上,典型的就是Pods。它們定義了這些對象的識別屬性,用來組織和選擇這些對象。Label能夠在對象建立時附加在對象上,也能夠對象存在時經過API管理對象的Label。

在定義了對象的Label後,其它模型能夠用Label 選擇器(selector)來定義其做用的對象。
Label選擇器有兩種,分別是 Equality-basedSet-based
好比以下 Equality-based選擇器樣例:


environment = productiontier != frontendenvironment = 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或者 qalabel附加的對象。第二條選擇具備 tier key,可是其值不是 frontendbackend。第三條選則具備 partition key的對象,不對 value進行校驗。
replication controller複製控制器和 Service都用 labellabel selctor來動態地配備做用對象。複製控制器在定義的時候就指定了其要建立 PodLabel和本身要匹配這個 PodselectorAPI服務器應該校驗這個定義。咱們能夠動態地修改 replication controller建立的 PodLabel用於調式,數據恢復等。一旦某個 Pod 因爲 Label 改變replication controller移出來後, replication controller會立刻啓動一個新的 Pod來確保複製池子中的份數。對於 ServiceLabel selector能夠用來選擇一個 Service的後端 Pods


一個簡單的應用

講了這麼多的原理和概念,本章咱們就部署一個簡單應用來感覺一下Kubernetes的部署模型。

部署Kubernetes集羣

kubernetes github站點上有數十種針對各類環境的部署文檔,本文選擇基於 ubuntu的集羣部署方案。在沒有使用本地 docker鏡像的狀況下,在部署過程當中須要確保可以訪問站點gcr.io。
基於 Ubuntu的集羣部署方案文檔寫得比較詳細,按照它的步驟幾乎不會出錯。在進行真正的部署以前,必定要確保:


  • 全部的節點安裝了docker version 1.2+ 和 bridge-utils
  • 若是沒有本地的docker registry, 要確保節點能訪問互聯網gcr.io
  • 確保管理節點可以ssh 訪問全部節點。好比ssh gongysh@192.168.0.201 ls


這裏咱們集羣將採用下圖顯示的結構。咱們將在管理節點上運行集羣管理命令。咱們將有一個服務和代理混合的節點,還有兩個純的代理節點。


首先咱們要下載kubernetes的代碼到管理節點上:


$ git clone https://github.com/GoogleCloudPlatform/kubernetes.git

而後進行本地構建:

cd kubernetes./build/run.sh hack/build-do.sh

修改config-default.sh定義集羣,本文使用的幾個關鍵配置以下 :

gongysh@fedora20:~/git/kubernetes/cluster/ubuntu$ cat config-default.sh#!/bin/bash# Define all your cluster nodes, MASTER node comes first"# And separated with blank space like <user_1@ip_1> <user_2@ip_2> <user_3@ip_3>export nodes="gongysh@192.168.0.201 gongysh@192.168.0.202 gongysh@192.168.0.203"# Define all your nodes role: a(master) or i(minion) or ai(both master and minion), must be the order sameexport roles=("ai" "i" "i")# Define minion numbersexport NUM_MINIONS=${NUM_MINIONS:-3}# define the IP range used for service portal.# according to rfc 1918 ref: https://tools.ietf.org/html/rfc1918 choose a private ip range here.export SERVICE_CLUSTER_IP_RANGE=192.168.3.0/24# define the IP range used for flannel overlay network, should not conflict with above SERVICE_CLUSTER_IP_RANGE rangeexport FLANNEL_NET=172.16.0.0/16....

最後運行集羣構建命令:


$ cd cluster$ KUBERNETES_PROVIDER=ubuntu ./kube-up.sh

當你看到:

Kubernetes cluster is running.  The master is running at:   http://192.168.0.201 ... calling validate-cluster Found 3 nodes.      1        NAME            LABELS    STATUS      2        192.168.0.201   <none>    Ready      3        192.168.0.202   <none>    Ready      4        192.168.0.203   <none>    Ready Validate output: Cluster validation succeeded Done, listing cluster services: Kubernetes master is running at http://192.168.0.201:8080

代表集羣構建成功。


部署nginx應用
咱們如下面的圖來安裝一個簡單的靜態內容的nginx應用:


首先,咱們用複製器啓動一個2個備份的nginx Pod。而後在前面掛Service,一個service只能被集羣內部訪問,一個能被集羣外的節點訪問。下面全部的命令都是在管理節點上運行的。

部署nginx pod 和複製器

以下表所示:


$ cat nginx-rc.yaml apiVersion: v1 kind: ReplicationController metadata:   name: nginx-controller spec:   replicas: 2   selector:     name: nginx   template:     metadata:       labels:         name: nginx     spec:       containers:         - name: nginx           image: nginx           ports:             - containerPort: 80

咱們定義了一個nginx pod複製器,複製份數爲2,咱們使用nginx docker鏡像。
執行下面的操做建立nginx pod複製器:


$  kubectl -s http://192.168.0.201:8080 create -f nginx-rc.yaml

因爲kubernetes要去gcr.io下載gcr.io/google_containers/pause鏡像,而後下載nginx鏡像,因此所建立的Pod須要等待一些時間才能處於running狀態。


$  kubectl -s http://192.168.0.201:8080 get podsNAME                     READY     REASON    RESTARTS   AGEnginx-controller-6zr34   1/1       Running   0          48mnginx-controller-njlgt   1/1       Running   0          48m

咱們可使用describe 命令查看pod所分到的節點:


$  $ kubectl -s http://192.168.0.201:8080 describe pod nginx-controller-6zr34 2>/dev/null | grep Node:Node:                                192.168.0.203/192.168.0.203$ kubectl -s http://192.168.0.201:8080 describe pod nginx-controller-njlgt 2>/dev/null | grep Node:Node:                                192.168.0.201/192.168.0.201

從上表能夠看出,這個複製器啓動了兩個Pod,分別運行在192.168.0.201和203代理節點主機上。

部署節點內部可訪問的nginx service

Service的type有ClusterIP和NodePort之分,缺省是ClusterIP,這種類型的Service只能在集羣內部訪問。下表是本文用的配置文件:


$ cat nginx-service-clusterip.yaml apiVersion: v1 kind: Service metadata:   name: nginx-service-clusterip spec:   ports:     - port: 8001       targetPort: 80       protocol: TCP   selector:     name: nginx

執行下面的命令建立service:


$ kubectl -s http://192.168.0.201:8080 create -f ./nginx-service-clusterip.yaml  services/nginx-service $ kubectl -s http://192.168.0.201:8080 get serviceNAME                      LABELS                                    SELECTOR     IP(S)           PORT(S)kubernetes                component=apiserver,provider=kubernetes   <none>       192.168.3.1     443/TCPnginx-service-clusterip   <none>                                    name=nginx   192.168.3.91   8001/TCP

驗證service的可訪問性:
上面的輸出告訴咱們這個Service的Cluster IP是192.168.3.91,端口是8001。下面咱們驗證這個PortalNet IP的工做狀況:


$ ssh 192.168.0.202 curl -s 192.168.3.91:8001  <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style>     body {         width: 35em;         margin: 0 auto;         font-family: Tahoma, Verdana, Arial, sans-serif;     } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>  <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p>  <p><em>Thank you for using nginx.</em></p> </body> </html>

從前面部署複製器的部分咱們知道nginx Pod運行在201和203節點上。上面咱們特地從202代理節點上訪問咱們的服務來體現Service Cluster IP在全部集羣代理節點的可到達性。

部署外部可訪問的nginx service

下面咱們建立NodePort類型的Service,這種類型的Service在集羣外部是能夠訪問。下表是本文用的配置文件:


$ cat nginx-service-nodeport.yaml apiVersion: v1 kind: Service metadata:   name: nginx-service-nodeport spec:   ports:     - port: 8000      targetPort: 80       protocol: TCP   type: NodePort  selector:     name: nginx

執行下面的命令建立service:


$ kubectl -s http://192.168.0.201:8080 create -f ./nginx-service-nodeport.yaml  services/nginx-service-nodeport $ kubectl -s http://192.168.0.201:8080 get serviceNAME                      LABELS                                    SELECTOR     IP(S)          PORT(S)kubernetes                component=apiserver,provider=kubernetes   <none>       192.168.3.1    443/TCPnginx-service-clusterip   <none>                                    name=nginx   192.168.3.91   8001/TCPnginx-service-nodeport    <none>                                    name=nginx   192.168.3.84   8000/TCP

使用下面的命令得到這個service的節點級別的端口:


$ kubectl -s http://192.168.0.201:8080 describe service nginx-service-nodeport 2>/dev/null | grep NodePortType:                        NodePortNodePort:                <unnamed>        32606/TCP

驗證service的可訪問性:
上面的輸出告訴咱們這個Service的節點級別端口是32606。下面咱們驗證這個Service的工做狀況:


$ curl 192.168.0.201:32606  <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style>     body {         width: 35em;         margin: 0 auto;         font-family: Tahoma, Verdana, Arial, sans-serif;     } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>  <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p>  <p><em>Thank you for using nginx.</em></p> </body> </html>
代理節點上的IP tables規則解析

下面的圖是IPTables中流量通過的table和chain。

能夠看出,Kubernetes在nat表中插入了下面四條chain:
1.  KUBE-PORTALS-CONTAINER
這個chain主要是處理全部service對象的cluster IP和port到kube-proxy本地端口的映射。好比下面規則:


-A KUBE-PORTALS-CONTAINER -d 192.168.3.84/32 -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 8000 -j REDIRECT --to-ports 43981

就是爲nginx-service-nodeport服務的Cluster IP準備的。其中192.168.3.84/32是該服務得到的Cluster IP,端口8000是其在定義文件中指定的spec.ports.port。43981則是kube-proxy爲這個service分配的本地端口。規則的意思是到192.168.3.84:8000的流量重定向到43981。
2.  KUBE-NODEPORT-CONTAINER
這條chain上則串連着類型爲NodePort的service的NodePort規則。好比下面規則:

-A KUBE-NODEPORT-CONTAINER -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 32606 -j REDIRECT --to-ports 43981

就是爲nginx-service-nodeport服務的NodePort 32606準備的。意思是訪問本地32606端口的流量從新定向到43981,後者是kube-proxy爲這個service分配的本地端口。
3.  KUBE-PORTALS-HOST
這條chain上也關聯着各個service的Cluster IP和Port的規則,好比:


-A KUBE-PORTALS-HOST -d 192.168.3.84/32 -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 8000 -j DNAT --to-destination 192.168.0.201:43981

這條規則是和KUBE-PORTALS-CONTAINER相似的,只不過流量來自於本地進程。
4.  KUBE-NODEPORT-HOST
這條chain上則關聯着類型爲NodePort的service的NodePort規則。好比下面規則:

-A KUBE-NODEPORT-HOST -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 30975 -j DNAT --to-destination 192.168.0.201:43981

這條規則是和KUBE-NODEPORT-CONTAINER相似的,只不過流量來自於本地進程。

總結

筆者認爲Docker已經不是僅表明容器自己,而是一組以應用部署爲中心的技術,產品和最佳實踐生態系統。Kubernetes以其出身,文檔的成熟度,社區的支持在這個生態系統中表現得比較突出。在部署Kubernetes時,咱們首先要理解Kubernetes的組件結構,它們有哪些角色,各個角色的做用是什麼和它們之接的通訊。在應用部署時,瞭解Kubernetes的應用模型是很是重要的。筆者認爲複製器和Service的概念是Kubernetes模型的核心,複製器和Service共同完成了應用的高可用性要求。最後本文以一個簡單的nginx服務來展現了複製器和Service的使用,特別經過對Service的cluster IP和NodePort的分析,使得讀者可以瞭解這個模型中的網絡特性。
最後就是容器技術的選型,本文使用Docker做爲容器,其實Kubernetes也支持CoreOS的rkt容器。kubelet的參數--container_runtime用於選擇使用的容器技術。(責編/周建丁)
做者簡介:龔永生,九州雲架構師。多年 Linux系統開發,J2EE產品和雲計算相關技術研發經驗。目前活躍在OpenStack社區的各個項目上,主要技術方向是虛擬網絡項目Neutron,是Neutron項目早期的主要貢獻者之一。
相關文章
相關標籤/搜索