1、介紹
在Kubernetes中,大多數的Pod管理都是基於無狀態、一次性的理念。例如Replication Controller,它只是簡單的保證可提供服務的Pod數量。如果一個Pod被認定為不健康的,Kubernetes就會以對待牲畜的態度對待這個Pod——刪掉、重建。相比於牲畜應用,PetSet(寵物應用),是由一組有狀態的Pod組成,每個Pod有自己特殊且不可改變的ID,且每個Pod中都有自己獨一無二、不能刪除的資料。
眾所周知,相比於無狀態應用的管理,有狀態應用的管理是非常困難的。有狀態的應用需要固定的ID、有自己內部可不見的通訊邏輯、特別容器出現劇烈波動等。傳統意義上,對有狀態應用的管理一般思路都是:固定機器、靜態IP、持久化儲存等。Kubernetes利用PetSet這個資源,弱化有狀態Pet與具體物理設施之間的關聯。一個PetSet能夠保證在任意時刻,都有固定數量的Pet在執行,且每個Pet都有自己唯一的身份。一個“有身份”的Pet指的是該Pet中的Pod包含如下特性:
1) 靜態儲存;
2) 有固定的主機名,且DNS可定址(穩定的網路身份,這是通過一種叫 Headless Service 的特殊Service來實現的。 和普通Service相比,Headless Service沒有Cluster IP,用於為一個叢集內部的每個成員提供一個唯一的DNS名字,用於叢集內部成員之間通訊 。);
3) 一個有序的index(比如PetSet的名字叫mysql,那麼第一個啟起來的Pet就叫mysql-0,第二個叫mysql-1,如此下去。當一個Pet down掉後,新建立的Pet會被賦予跟原來Pet一樣的名字,通過這個名字就能匹配到原來的儲存,實現狀態儲存。)
應用舉例:
1) 資料庫應用,如Mysql、PostgreSQL,需要一個固定的ID(用於資料同步)以及外掛一塊NFS Volume(持久化儲存)。
2) 叢集軟體,如zookeeper、Etcd,需要固定的成員關係。
2、使用限制
1) 1.4新加功能,1.3及之前版本不可用;
2) DNS,要求使用1.4或1.4之後的DNS外掛,1.4之前的外掛只能解析Service對應的IP,無法解析Pod(HostName)對應的域名;
3) 日常運維,對於PetSet,唯一能夠更改的就是replicas;
4) 需要持久化資料卷(PV,若為nfs這種無法通過呼叫API來建立儲存的網路儲存,資料卷要在建立PetSet之前靜態建立;若為aws-ebs、vSphere、openstack Cinder這種可以通過API呼叫來動態建立儲存的虛擬儲存,資料卷除了可以通過靜態的方式建立以外,還可以通過StorageClass進行動態建立。需要注意的是,動態建立出來的PV,預設的回收策略是delete,及在刪除資料的同時,還會把虛擬儲存卷刪除);
5) 刪除或縮容PetSet不會刪除對應的持久化資料卷,這麼做是處於資料安全性的考慮;
6) 只能通過手動的方式升級PetSet。
3、PetSet示例
以下示例演示了建立兩個PV,建立兩個PetSet的過程。在PetSet建立完成之後,驗證了其hostName及直接Pod訪問。
3.1 建立PV
建立PV的時候,我兩個PV選擇了同一個NFS server,建立也是成功的。Kubernetes在建立PV的時候並不會校驗NFS server是否存在,是否能夠連線成功,也不會校驗storage設定的大小是否真實。所以說,PV的建立需要管理員在一開始就設定好,依賴人為的校驗。
[root@k8s-master pv]# cat nfs-pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv0001 spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle nfs: path: "/data/disk1" server: 192.168.20.47 readOnly: false [root@k8s-master pv]# cat nfs-pv2.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv0002 spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle nfs: path: "/data/disk1" server: 192.168.20.47 readOnly: false [root@k8s-master pv]# kubectl create -f nfs-pv.yaml persistentvolume "pv0001" created [root@k8s-master pv]# kubectl create -f nfs-pv2.yaml persistentvolume "pv0002" created [root@k8s-master pv]# kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE pv0001 5Gi RWX Recycle Available 8s pv0002 5Gi RWX Recycle Available 5s
3.2 建立PetSet
建立PetSet的時候需要先建立一個“headless”的Service,即service顯示的將ClusterIP設定為none。而使用者可以通過直接訪問PetSet中的Pod的IP(通過Pod的HostName解析得到),來訪問後端的服務的。Kubernetes在1.4之後的dns外掛之上才支援這種型別的DNS解析
[root@k8s-master pv]# cd ../petset/ [root@k8s-master petset]# cat test-petset.yaml # A headless service to create DNS records apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web # *.nginx.default.svc.cluster.local clusterIP: None selector: app: nginx --- apiVersion: apps/v1alpha1 kind: PetSet metadata: name: web spec: serviceName: "nginx" replicas: 2 template: metadata: labels: app: nginx annotations: pod.alpha.kubernetes.io/initialized: "true" spec: terminationGracePeriodSeconds: 0 containers: - name: nginx image: gcr.io/google_containers/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi [root@k8s-master petset]# kubectl create -f test-petset.yaml service "nginx" created petset "web" created [root@k8s-master petset]# kubectl get petset NAME DESIRED CURRENT AGE web 2 2 11s [root@k8s-master petset]# kubectl get pod NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 16s web-1 1/1 Running 0 12s [root@k8s-master petset]# kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE pv0001 5Gi RWX Recycle Bound default/www-web-0 1m pv0002 5Gi RWX Recycle Bound default/www-web-1 1m [root@k8s-master petset]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESSMODES AGE www-web-0 Bound pv0001 5Gi RWX 22s www-web-1 Bound pv0002 5Gi RWX 22s [root@k8s-master petset]# kubectl exec -ti web-0 /bin/bash root@web-0:/# cd /usr/share/nginx/html root@web-0:/usr/share/nginx/html# ls root@web-0:/usr/share/nginx/html# touch 1.out root@web-0:/usr/share/nginx/html# exit exit [root@k8s-master petset]# ssh 192.168.20.47 #登入到NFS主機 root@192.168.20.47's password: Last login: Tue Mar 28 11:54:58 2017 from 10.0.251.145 [root@localhost ~]# echo "123456">> /data/disk1/1.out [root@localhost ~]# exit 登出 Connection to 192.168.20.47 closed. [root@k8s-master petset]# kubectl exec -ti web-0 /bin/bash root@web-0:/# cat /usr/share/nginx/html/1.out 123456 root@web-0:/# exit exit [root@k8s-master petset]# kubectl exec -ti web-1 /bin/bash root@web-1:/# cat /usr/share/nginx/html/1.out 123456 root@web-1:/# exit exit [root@k8s-master petset]#
3.3 驗證域名解析
檢查PetSet中的Pod的hostName
[root@k8s-master ~]# for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done web-0 web-1
檢查通過hostName解析IP
[root@k8s-master dns14]# kubectl get svc nginx NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx None <none> 80/TCP 5h [root@k8s-master dns14]# kubectl describe svc nginx Name: nginx Namespace: default Labels: app=nginx Selector: app=nginx Type: ClusterIP IP: None Port: web 80/TCP Endpoints: 10.0.28.3:80,10.0.82.6:80 Session Affinity: None No events. [root@k8s-master ~]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE web-0 1/1 Running 0 42m 10.0.28.3 k8s-node-1 web-1 1/1 Running 0 42m 10.0.82.6 k8s-node-4 [root@k8s-master ~]# kubectl exec -i -t frontend-service-1988680557-xuysd /bin/bash #進入叢集中的一個pod [root@frontend-service-1988680557-xuysd /]# nslookup web-0.nginx Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-0.nginx.default.svc.cluster.local Address: 10.0.28.3 [root@frontend-service-1988680557-xuysd /]# nslookup web-1.nginx Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-1.nginx.default.svc.cluster.local Address: 10.0.82.6 <680557-xuysd /]# nslookup web-1.nginx.default.svc.cluster.local #通常情況下,直接解析web-1.nginx即可得到對應的IP,但再一些容器內發現只有解析全部的名稱“web-1.nginx.default.svc.cluster.local”才能得到IP,這個地方需要研究下區別在哪。 Server: 10.254.10.2 Address: 10.254.10.2#53 Non-authoritative answer: Name: web-1.nginx.default.svc.cluster.local Address: 10.0.82.6
3.4 驗證Pet重建
#為web-0新增一個index頁面,內容為它自己的HostName,注意該目錄我們將其外掛到了PV之上,是一個NFS路徑 [root@k8s-master dns14]# kubectl exec web-0 -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html' #在其它Pod上獲取web-0的歡迎頁 [root@k8s-master dns14]# kubectl exec -it web-1 -- curl web-0.nginx web-0 [root@k8s-master dns14]# kubectl exec -it web-0 -- curl web-0.nginx web-0 #查詢web-0的存活時間 [root@k8s-master dns14]# kubectl get pod | grep web web-0 1/1 Running 0 5h web-1 1/1 Running 0 5h [root@k8s-master dns14]# kubectl delete pod web-0 #刪除web-0 pod "web-0" deleted [root@k8s-master dns14]# kubectl get pod | grep web #可以看到,web-0被刪除後幾乎立即又被重建了 web-0 1/1 Running 0 3s web-1 1/1 Running 0 5h [root@k8s-master dns14]# kubectl get pod | grep web web-0 1/1 Running 0 5s web-1 1/1 Running 0 5h #檢視web-0中的歡迎頁是否還在 [root@k8s-master dns14]# kubectl exec -it web-0 -- curl web-0.nginx web-0 [root@k8s-master dns14]# kubectl exec -it web-1 -- curl web-0.nginx web-0
4、運維
4.1 Pet互相發現
通常,Pets需要互相知道對方的存在,在之前的示例中,我們演示了操作員“告訴”一個Pet,它有多少個同伴,但這顯然是不夠的。一種方法是,在Pod內部呼叫Kubectl的api來獲取該Pet對應的PetSet中的其他成員,但並不推薦這麼做。這樣做的話,就會使得你的Pod可以反過來操控外部的元件。另一種方法是通過DNS解析,利用工具nslookup工具可以將nginx(上文定義的headless service)中的所有endPoint都查詢出來。具體見下:
# apt-get update && apt-get install -y dnsutils ... # nslookup -type=srv nginx Server: 10.254.10.2 Address: 10.254.10.2#53 nginx.default.svc.cluster.local service = 10 50 0 web-1.nginx.default.svc.cluster.local. nginx.default.svc.cluster.local service = 10 50 0 web-0.nginx.default.svc.cluster.local. # nslookup web-1.nginx.default.svc.cluster.local Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-1.nginx.default.svc.cluster.local Address: 10.0.82.6 # nslookup web-0.nginx.default.svc.cluster.local Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-0.nginx.default.svc.cluster.local Address: 10.0.28.3
4.2 更新及擴縮容
如之前使用限制(3)中講的,對於PetSet,Kubernetes能幫我們自動做的僅有“replicas”,即副本數量。對於擴充副本數量來說,Kubernetes每次會按照順序,一個個的建立Pod,且在前一個沒有running或ready之前,不會建立下一個;對於縮減副本數量來說,Kubernetes每次會按照順序,一個個的刪除Pod。且在前一個沒有真正的shutdown之前,不會刪除下一個。
需要注意的是,縮容時,雖然刪除了一些Pod,但Pod對於的持久化儲存PVC—PV是不會被刪除的。例如,我們一開始建立了3個Pet,pod-0、pod-1、pod-2,掛載了pvc-0——pv-0、pvc-1——pv-1、pvc-2——pv-2,在縮容到2個副本的時候,最後一個pod-2會被刪除,但pvc-2——pv-2則不會被刪除,裡面的資料還是安全的。PV的最終刪除就像它一開始建立一樣,是由管理員統一管理的。
4.3 映象更新
有時需要更新映象到新的版本,那該如何操作呢?雖然Kubernetes沒有給我們提供一些自動更新整個Pet叢集的功能,但通過它提供的edit和set image功能也基本上夠我們用的了。更新Pet中的映象功能示例如下:
[root@k8s-master ~]# kubectl get po web-0 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' gcr.io/google_containers/nginx-slim:0.8 [root@k8s-master ~]# kubectl exec -i -t frontend-service-1988680557-xuysd /bin/bash #進入叢集中的一個pod [root@frontend-service-1988680557-xuysd /]# nslookup web-0.nginx Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-0.nginx.default.svc.cluster.local Address: 10.0.28.3 [root@k8s-master ~]# kubectl edit petset/web #執行之後就像開啟一個vi介面,修改對應的映象名稱或版本,儲存退出 petset "web" edited #也可以用set image的方式進行更改 [root@k8s-master ~]# kubectl set image petset/web nginx=gcr.io/google_containers/nginx-slim:0.7 petset "web" image updated [root@k8s-master ~]# kubectl delete po web-0 #手動刪除第一個Pod,PetSet會自動給我們再起一個 pod "web-0" deleted [root@k8s-master ~]# kubectl get po web-0 -o wide #檢視Pod的IP已經更改了 NAME READY STATUS RESTARTS AGE IP NODE web-0 1/1 Running 0 34s 10.0.62.4 k8s-node-2 [root@k8s-master ~]# kubectl get po web-0 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' gcr.io/google_containers/nginx-slim:0.7 #檢視映象版本資訊,版本也已經更改了
之後可以一個個的將PetSet中的Pod刪除,PetSet會自動按照新版本的映象幫我們啟動起來。
5、後續新功能
官方給出後續將會逐步推出的新功能如下:
1) 資料安全與本地資料儲存(目前僅支援網路共享儲存卷,在IO高的情況下網路儲存卷可能會出現效能瓶頸,後續Kubernetes將會推出支援本地儲存的PetSet)
2) 豐富通知實踐
3) 公網的網路身份
4) 廣域網叢集釋出(跨多個可用區、region、雲服務提供商)
5) 更多的自動運維手段(包括映象升級等)