天天看點

深入淺出 Kubernetes:StatefulSet 概念了解與實踐深入淺出 Kubernetes:StatefulSet 概念了解與實踐

深入淺出 Kubernetes:StatefulSet 概念了解與實踐

一 背景知識及相關概念

StatefulSet 的設計其實非常容易了解。它把真實世界裡的應用狀态,抽象為了兩種情況:

拓撲狀态。這種情況意味着,應用的多個執行個體之間不是完全對等的關系。這些應用執行個體,必須按照某些順序啟動,比如應用的主節點 A 要先于從節點 B 啟動。而如果你把 A 和 B 兩個 Pod 删除掉,它們再次被建立出來時也必須嚴格按照這個順序才行。并且,新建立出來的 Pod,必須和原來 Pod 的網絡辨別一樣,這樣原先的通路者才能使用同樣的方法,通路到這個新 Pod。

存儲狀态。這種情況意味着,應用的多個執行個體分别綁定了不同的存儲資料。對于這些應用執行個體來說,Pod A 第一次讀取到的資料,和隔了十分鐘之後再次讀取到的資料,應該是同一份,哪怕在此期間 Pod A 被重新建立過。這種情況最典型的例子,就是一個資料庫應用的多個存儲執行個體。

StatefulSet 的核心功能,就是通過某種方式記錄這些狀态,然後在 Pod 被重新建立時,能夠為新 Pod 恢複這些狀态。

這個 Service 又是如何被通路的呢?

第一種方式,是以 Service 的 VIP(Virtual IP,即:虛拟 IP)方式。比如:當我通路 172.20.25.3 這個 Service 的 IP 位址時,172.20.25.3 其實就是一個 VIP,它會把請求轉發到該 Service 所代理的某一個 Pod 上。

第二種方式,就是以 Service 的 DNS 方式。比如:這時候,隻要我通路“my-svc.my-namespace.svc.cluster.local”這條 DNS 記錄,就可以通路到名叫 my-svc 的 Service 所代理的某一個 Pod。

二 StatefulSet 的兩種結構

2.1 拓撲結構

讓我們來看一下以下例子:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      name: web
  clusterIP: None           
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web-server-gysl
  labels:
    app: nginx
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      restartPolicy: Always
      containers:
        - name: web-server
          image: nginx:1.16.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
              name: web-port           

這些 Pod 的建立,也是嚴格按照編号順序進行的。比如,在 web-server-gysl-0 進入到 Running 狀态、并且細分狀态(Conditions)成為 Ready 之前,web-server-gysl-1 會一直處于 Pending 狀态。

使用以下指令測試:

kubectl run -i --tty  --image toolkit:v1.0.0821 dns-test --restart=Never --rm /bin/bash           
[root@dns-test /]# nslookup web-server-gysl-0.nginx
Server:         10.0.0.2
Address:        10.0.0.2#53

Name:   web-server-gysl-0.nginx.default.svc.cluster.local
Address: 172.20.25.3

[root@dns-test /]# nslookup web-server-gysl-1.nginx
Server:         10.0.0.2
Address:        10.0.0.2#53

Name:   web-server-gysl-1.nginx.default.svc.cluster.local
Address: 172.20.72.7

[root@dns-test /]# nslookup nginx
Server:         10.0.0.2
Address:        10.0.0.2#53

Name:   nginx.default.svc.cluster.local
Address: 172.20.72.7
Name:   nginx.default.svc.cluster.local
Address: 172.20.25.3           

由于最近版本的 busybox 有坑,我自己制作了一個 DNS 測試工具,Dockerfile 如下:

FROM centos:7.6.1810
RUN  yum -y install bind-utils
CMD  ["/bin/bash","-c","while true;do sleep 60000;done"]           

回到 Master 節點看一下:

$ kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES
web-server-gysl-0   1/1     Running   0          43m   172.20.25.3   172.31.2.12   <none>           <none>
web-server-gysl-1   1/1     Running   0          42m   172.20.72.7   172.31.2.11   <none>           <none>
$ kubectl get svc -o wide
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
nginx        ClusterIP   None         <none>        80/TCP    43m   app=nginx           

當我們在叢集内部分别 ping 域名 web-server-gysl-0.nginx.default.svc.cluster.local 和 web-server-gysl-1.nginx.default.svc.cluster.local 時,正常傳回了對應的 Pod IP, 在 ping 域名 nginx.default.svc.cluster.local 時,則随機傳回2個 Pod IP 中的一個。完全印證了上文所述内容。

在上述操作過程中,我随機删除了這些 Pod 中的某一個或幾個,稍後再次來檢視的時候,新建立的 Pod 依然按照之前的編号進行了編排。

此外,我将 StatefulSet 的一個 Pod 所在的叢集内節點下線,再次檢視 Pod 的情況,系統在其他節點上以原 Pod 的名稱迅速建立了新的 Pod。編号都是從 0 開始累加,與 StatefulSet 的每個 Pod 執行個體一一對應,絕不重複。

2.2 存儲結構

由于測試環境資源有限,原計劃使用 rook-ceph 來進行實驗的,無奈使用 NFS 來進行實驗。 Ceph 建立 PV 的相關 yaml 如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-gysl
  labels:
    type: local
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteOnce
  rbd:
    monitors:
      - '172.31.2.11:6789'
      - '172.31.2.12:6789'
    pool: data
    image: data
    fsType: xfs
    readOnly: true
    user: admin
    keyring: /etc/ceph/keyrin           

NFS 實驗相關 yaml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-gysl-0
  labels:
    environment: test
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /data-0
    server: 172.31.2.10
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-gysl-1
  labels:
    environment: test
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /data-1
    server: 172.31.2.10
---
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: nfs
provisioner: fuseim.pri/ifs
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: statefulset-pvc-gysl
spec:
  replicas: 2
  serviceName: "gysl-web"
  selector:
    matchLabels:
      app: pod-gysl
  template:
    metadata:
      name: web-pod
      labels:
        app: pod-gysl
    spec:
      containers:
        - name: nginx
          image: nginx
          imagePullPolicy: IfNotPresent
          ports:
            - name: web-port
              containerPort: 80
          volumeMounts:
            - name: www-vct
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: www-vct
        annotations:
          volume.beta.kubernetes.io/storage-class: "nfs"
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: nfs
---
apiVersion: v1
kind: Service
metadata:
  name: gysl-web
spec:
  type: NodePort
  selector:
    app: pod-gysl
  ports:
    - name: web-svc
      protocol: TCP
      nodePort: 31688
      port: 8080
      targetPort: 80           

通過以下指令向相關 Pod 寫入驗證内容:

for node in 0 1;do kubectl exec statefulset-pvc-gysl-$node -- sh -c "echo \<h1\>Node: ${node}\</h1\>>/usr/share/nginx/html/index.html";done           

觀察實驗結果:

$ kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES
statefulset-pvc-gysl-0   1/1     Running   0          51m   172.20.85.2   172.31.2.11   <none>           <none>
statefulset-pvc-gysl-1   1/1     Running   0          32m   172.20.65.4   172.31.2.12   <none>           <none>
$ kubectl get pvc
NAME                             STATUS   VOLUME          CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-vct-statefulset-pvc-gysl-0   Bound    pv-nfs-gysl-0   1Gi        RWO            nfs            51m
www-vct-statefulset-pvc-gysl-1   Bound    pv-nfs-gysl-1   1Gi        RWO            nfs            49m
$ kubectl get pv
NAME            CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                    STORAGECLASS   REASON   AGE
pv-nfs-gysl-0   1Gi        RWO            Recycle          Bound    default/www-vct-statefulset-pvc-gysl-0   nfs                     51m
pv-nfs-gysl-1   1Gi        RWO            Recycle          Bound    default/www-vct-statefulset-pvc-gysl-1   nfs                     51m
$ cat /data-0/index.html
<h1>Node: 0</h1>
$ cat /data-1/index.html
<h1>Node: 1</h1>
$ curl 172.31.2.11:31688
<h1>Node: 0</h1>
$ curl 172.31.2.11:31688
<h1>Node: 1</h1>
$ curl 172.31.2.12:31688
<h1>Node: 1</h1>
$ curl 172.31.2.12:31688
<h1>Node: 0</h1>           
kubectl run -i --tty  --image toolkit:v1.0.0821 test --restart=Never --rm /bin/bash           
[root@test /]# curl statefulset-pvc-gysl-0.gysl-web
<h1>Node: 0</h1>
[root@test /]# curl statefulset-pvc-gysl-0.gysl-web
<h1>Node: 0</h1>
[root@test /]# curl statefulset-pvc-gysl-1.gysl-web
<h1>Node: 1</h1>
[root@test /]# curl statefulset-pvc-gysl-1.gysl-web
<h1>Node: 1</h1>
[root@test /]# curl gysl-web:8080
<h1>Node: 1</h1>
[root@test /]# curl gysl-web:8080
<h1>Node: 1</h1>
[root@test /]# curl gysl-web:8080
<h1>Node: 0</h1>
[root@test /]# curl gysl-web:8080
<h1>Node: 0</h1>           

從實驗結果中我們可以看出 Pod 與 PV、PVC 的對應關系,結合上文中的 yaml 我們不難發現:

  1. Pod 與對應的 PV 存儲是一一對應的,在創 Pod 的同時, StatefulSet根據對應的規建立了相應的 PVC,PVC 選擇符合條件的 PV 進綁定。當 Pod 被删除之後,資料依然儲存在 PV 中,當被删除的 Pod 再次被建立時, 該 Pod 依然會立即與原來的 Pod 進行綁定,保持原有的對應關系。
  2. 在叢集内部,可以通過 pod 名加對應的服務名通路指定的 Pod 及其綁定的 PV。 如果通過服務名來通路 StatefulSet ,那麼服務名的功能類似于 VIP 的功能。

繼續閱讀