【K8S】Kubernetes-StatefulSet

StatefulSet

簡介

實際場景中,尤其是分佈式應用,多個實例之間,往往有依賴關係,比如:主從關係、主備關係。對於數據存儲類應用,它的多個實例,往往都會在本地磁盤保存一份數據。導致這些實例一旦被殺掉,即便重建出來,實例與數據之間的對應關係也已經丟失,從而導致應用創建失敗。

這種實例之間有不對等關係,以及實例對外部數據有依賴關係的應用,稱爲“有狀態應用”

StatefulSet將應用狀態抽象成了兩種情況:

  • 拓撲狀態。應用實例必須按照某種順序啓動。新創建的Pod必須和原來Pod的網絡標識一樣
  • 存儲狀態。應用的多個實例分別綁定了不同存儲數據。

Headless Service

Service是Kubernetes項目用來將一組Pod暴露給外界訪問的一種機制。

Service被訪問的方式:

  • Service的VIP(Virtual IP)
  • Service的DNS方式,只要訪問“my-svc.my-namespace.svc.cluster.local”這條DNS記錄,就可以訪問到名爲my-svc的Service所代理的某一個Pod

在第二種Service DNS的方式下,具體還可以分爲兩種處理方法:

第一種處理方法,是Normal Service。這種情況下,訪問“my-svc.my-namespace.svc.cluster.local”解析到的,正是my-svc這個Service 的VIP,後面的流程和VIP一致

第二種處理方法,是Headless Service。這種情況下,訪問“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是my-svc代理的某一個Pod的IP地址。這裏的區別在於,Headless Service不需要分配一個VIP,而是直接以DNS記錄的方式解析出被代理Pod的IP地址。

Headless Service的作用

所謂的Headless Service仍是一個標準的Service的YAML文件,只不過spec.clusterIP的值爲None,即這個Service沒有一個VIP做爲頭。這個Service被創建後並不會分配一個VIP,而是以DNS記錄的方式暴露它所代理的Pod

當按照這樣的方式創建了一個Headless Service後,它所代理的所有Pod的IP地址,都會被綁定一個如下格式的DNS記錄,如下:

<pod-name>.<svc-name>.<namespace>.svc.cluster.local

這條DNS記錄,是Kubernetes爲Pod分配的唯一“可解析身份”

StatefulSet如何通過Headless Service維持Pod的拓撲狀態

apiVersion: v1
kind: Service
metadata:
 name: nginx
 labels:
  app: nginx
spec:
 ports:
 - port: 80
   name: web
 clusterIP: None
 selector:
  app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
 name: web
spec:
 serviceName: "nginx"
 replicas: 2
 selector:
  matchLabels:
   app: nginx
 template:
  metadata:
   labels:
    app: nginx
  spec:
   containers:
   - name: nginx
     image: nginx:1.9.1
     ports:
     - containerPort: 80
       name: web

然後我們創建這個service和statefulset

[root@host1 statefulset]# kubectl create -f nginx-statefulset.yaml 
service/nginx created
statefulset.apps/web created
[root@host1 statefulset]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23h
nginx        ClusterIP   None         <none>        80/TCP    9s
[root@host1 statefulset]# kubectl get statefulset
NAME   READY   AGE
web    2/2     22s
[root@host1 statefulset]# kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
web-0                   1/1     Running   0          104s
web-1                   1/1     Running   0          103s

StatefulSet給它所管理的所有的Pod的名字進行了編號,編號規則是:-;並且這些編號都是從0開始累加,與StatefulSet的每個Pod實例一一對應;這些Pod的創建也是嚴格按照編號順序進行的。比如在web-0進入到running狀態,並且Conditions爲Ready之前,web-1一直會處於pending狀態

使用kubectl exec命令進入到容器內部查看它們的hostname:

[root@host1 statefulset]# kubectl exec web-0 -- sh -c 'hostname'
web-0
[root@host1 statefulset]# kubectl exec web-1 -- sh -c 'hostname'
web-1

並且,就算Pod被刪除後重建,重建Pod的網絡標識也不會改變,通過這種方式,Kubernetes就可以成功地將Pod的拓撲狀態按照Pod的“名字+編號”的方式固定下來,並且Kubernetes爲每個Pod提供了一個固定且唯一的訪問入口(這個Pod對應的DNS記錄)。

StatefulSet控制器的主要作用之一,就是使用Pod模板創建Pod的時候,對它們進行編號,並且按照編號順序逐一完成創建工作。當StatefulSet的控制循環發現Pod的實際狀態和期望狀態不一致的時候,需要新建或刪除Pod進行調諧的時候,它會嚴格按照這些Pod編號的順序,逐一完成這些操作。任何Pod的變化都會觸發statefulset的滾動更新

存儲管理

Kubernetes中PV和PVC的設計,類似於“接口”和“實現”的思想。PV和PVC的設計,使得StatefulSet對存儲狀態的管理成爲了可能。

kind: StatefulSet
metadata:
 name: web
spec:
 serviceName: "nginx"
 replicas: 2
 selector:
  matchLabels:
   app: nginx
 template:
  metadata:
   labels:
    app: nginx
  spec:
   containers:
   - name: nginx
     image: nginx:1.9.1
     ports:
     - containerPort: 80
       name: web
     volumeMounts:
     - name: www
       mountPath: /usr/share/nginx/html
 volumeClaimTemplates:
  - metadata:
     name: www
    spec:
     storageClassName: rook-ceph-block
     accessModes:
     - ReadWriteOnce
     resources:
      requests:
       storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
 name: nginx
 labels:
  app: nginx
spec:
 ports:
 - port: 80
   name: web
 clusterIP: None
 selector:
  app: nginx

這裏我們使用了Rook的ceph Flex Driver,關於rook的配置可以參考官方文檔 https://rook.io/docs/rook/v1.1/ceph-block.html

然後我們創建這個Statefulset

[root@host1 statefulset]# kubectl create -f nginx-statefulset.yaml 
statefulset.apps/web created
service/nginx created
[root@host1 statefulset]# kubectl get statefulset
NAME   READY   AGE
web    2/2     8s
[root@host1 statefulset]# kubectl get statefulset
NAME   READY   AGE
web    2/2     10s
[root@host1 statefulset]# kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
test-projected-volume   1/1     Running   1          25h
web-0                   1/1     Running   0          13s
web-1                   1/1     Running   0          11s
[root@host1 statefulset]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
www-web-0   Bound    pvc-167033a6-f56a-11e9-9319-000c296c79f3   1Gi        RWO            rook-ceph-block   2m19s
www-web-1   Bound    pvc-17bc7e66-f56a-11e9-9319-000c296c79f3   1Gi        RWO            rook-ceph-block   2m17s

當我們進入上述創建的Pod,在/usr/share/nginx/html中寫入內容,即使Pod被刪除重建,Volume中的內容仍不會丟失。這是怎麼做到的?

StatefulSet控制器恢復Pod的過程分析:

首先,當你把一個Pod,比如web-0刪除之後,這個Pod對應的PV和PVC並不會被刪除,而這個Volume中寫入的數據,也依然存儲在遠程存儲服務中。

此時,StatefulSet控制器發現,一個名叫web-0的Pod消失了。所以控制器會重新創建一個新的、名字還是叫做web-0的Pod,來進行調諧。

並且這個新Pod對象的定義中,它聲明使用的PVC的名字,仍是www-web-0。所以這個新的web-0 Pod被創建出來之後,Kubernetes爲它查找名叫www-web-0的PVC時,就會將舊Pod遺留的PVC,進而找到和這個PVC綁定在一起的PV

StatefulSet工作原理

首先,StatefulSet的控制器直接管理的是Pod。

其次,Kubernetes通過Headless Serrvice,爲那些有編號的Pod,在DNS服務器中生成帶有同樣編號的DNS記錄。只要StatefulSet能夠保證這些Pod名字中的編號不變,那麼Service中類似於web-0.nginx.default.svc.cluster.clocal這樣的DNS記錄也不會變,這條記錄解析出來的Pod的IP地址,隨着後端Pod的刪除和創建而自動更新。

最後,StatefulSet還會爲每一個Pod分配並創建一個同樣編號的PVC。這樣,kubernetes就可以通過Persistent Volume機制爲這個PVC綁定對應的PV,從而保證每一個Pod都擁有一個獨立的Volume。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章