k8s負載資源StatefulSet工作解析


在k8s中工作負載資源StatefulSet用於管理有狀態應用。

什么是無狀態?

組成一個應用的pod是對等的,它們之前沒有關聯和依賴關系,不依賴外部存儲。

即我們上篇小作文中deployment創建的nginx pod ,他們是完全一樣的,任何一個pod 被移除后依然可以正常工作。由於不依賴外部存儲,它們可以被輕易的調度到任何 node 上。

什么是有狀態?

顯然無狀態的反面就是有狀態了,pod之間可能包含主從、主備的相互依賴關系,甚至對啟動順序也有要求。更關鍵的是這些pod 需要外部存儲,一旦pod被清除或調度后,怎么把pod 和原來的外部數據聯系起來?這就是StatefulSet厲害的地方。

StatefulSet將這些狀態應用進行記錄,在需要的時候恢復。


StatefulSet如何展開這些工作?

一、維護應用拓撲狀態

通過dns記錄為 pod 分配集群內唯一、穩定的網絡標識。即只要保證pod 的名稱不變,pod被調度到任何節點或者ip如何變更都能被找到。

在 k8s 中Service用來來將一組 Pod 暴露給外界訪問的一種機制。當創建的service 中clusterIP為None 時(headless 無頭服務), 不會進行負載均衡,也不會為該服務分配集群 IP。僅自動配置 DNS。

image-20210927121907615

這樣我們集群中的 一個pod 將被綁定到一條DNS記錄:

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

通過解析這個地址就能找到pod的IP 。

下面我們創建一個headless service,將clusterIP配置為 None

#headless-service.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-headless
spec:
  ports:
  - name: nginx-service-port
    port: 80
    targetPort: 9376
  clusterIP: None
  selector:
    app: nginx

這個service將會綁定 app=nginx標簽的pod,我們通過kubectl apply -f headless-service.yml應用service 並通過get 查看:

$ kubectl get service
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes       ClusterIP   10.96.0.1    <none>        443/TCP   18d
nginx-headless   ClusterIP   None         <none>        80/TCP    4h48m

nginx-headless 這個headless service創建成功了。接着我們創建一個StatefulSet:

#nginx-statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-statefulset
spec:
  serviceName: "nginx-headless"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx-web
        image: nginx:1.17
        ports:
        - containerPort: 82

nginx-statefulset 將會綁定我們前面的service nginx-headless並創建三個nginx pod。

我們查看創建的pod ,StatefulSet 中的每個 Pod 根據 StatefulSet 的名稱和 Pod 的序號派生出它的主機名。同時statefulset創建出來的pod 名稱以$(StatefulSet name)-$(order)開始編號。

$ kubectl get pod 
NAME                  READY   STATUS    RESTARTS   AGE
nginx-statefulset-0   1/1     Running   0          18s
nginx-statefulset-1   1/1     Running   0          15s
nginx-statefulset-2   1/1     Running   0          12s

$ kubectl exec nginx-statefulset-0 -- sh -c hostname
nginx-statefulset-0

其實他們的創建順序也是從0-2,當我們刪除這些pod時,statefulset 馬上重建出相同名稱的Pod 。

我們通過statefulset 的event可以觀測到這個過程:

$ kubectl describe  nginx-statefulset
Events:
  Type    Reason            Age    From                    Message
  ----    ------            ----   ----                    -------
  Normal  SuccessfulCreate  7m43s  statefulset-controller  create Pod nginx-statefulset-0 in StatefulSet nginx-statefulset successful
  Normal  SuccessfulCreate  7m40s  statefulset-controller  create Pod nginx-statefulset-1 in StatefulSet nginx-statefulset successful
  Normal  SuccessfulCreate  7m37s  statefulset-controller  create Pod nginx-statefulset-2 in StatefulSet nginx-statefulset successful

現在我們來看一下 pod 是否存在於 DNS 記錄中:

kubectl run -it --image busybox busybox --rm /bin/sh

運行一個一次性 pod busybox ,接着使用 ping 命令查詢之前提到的規則構建名稱nginx-statefulset-0.nginx-headless.default.svc.cluster.local

image-20210926164056933

解析的IP與如下nginx-statefulset-0相符。

image-20210926164906187

這樣我們使用pod名稱通過DNS就可以找到這個pod 再加上StatefulSet可以按順序創建出不變名稱的 pod ,即一個應用通過StatefulSet准確維護其拓撲狀態


二、維護應用存儲狀態

k8s為應對應用的數據存儲需求提供了卷的概念(volume)以及提供持久化存儲的PVC( PersistentVolumeClaim)PV( PersistentVolume)當一個pod 和 PVC綁定后,即使pod 被移除,PVC和PV仍然保留在集群中,pod 再次被創建后會自動綁定到之前的PVC。他們看起來是這樣的:

rs_pv_pvc

這里我們以討論statefulset持久化存儲為主,對於k8s存儲本身不了解的同學可以參考k8s官方文檔存儲章節storage

首先我們創建存儲目錄 /data/volumes/ 以及一個本地的local類型(使用節點上的文件或目錄來模擬網絡附加存儲)的PV:

#pv-local.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-local
spec:
  capacity:
    storage: 5Gi 
  volumeMode: Filesystem
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /data/volumes/ 
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - minikube

PV是集群中的一塊存儲,它聲明了后端使用的真實存儲,通常會由K8S管理員創建。我們在pv-local中聲明了后端存儲類型為local掛載到目錄 /data/volumes/ , 存儲卷類名為local-storage,1Gb容量,訪問模式ReadWriteMany -- 卷可以被多個個節點以讀寫方式掛載。親和的節點為minikube

我們通過get來查看這個PV:

$ kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS    REASON   AGE
pv-local   5Gi        RWX            Delete           Available                  local-storage            25m

此時PV的狀態為available,還未與任何PVC綁定。我們通過創建PV使集群得到了一塊存儲資源,但此時還不屬於你的應用,我們需要通過PVC去構建一個使用它的”通道“。

#app1-pvc.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app1-pvc
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

現在我們開辟好一個5Gb容量的存儲通道(PVC),此時PV和PVC已通過 storageClassName自動形成綁定。這樣PV和PVC的status 皆為Bound

$ kubectl get pv
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS    REASON   AGE
pv-local   5Gi        RWX            Delete           Bound    default/app-pvc   local-storage            25m

$ kubectl get pvc
NAME      STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
app-pvc   Bound    pv-local   5Gi        RWX            local-storage   27m

上面我們創建好通道,接下來要在我們statefuset中綁定這個通道,才能順利使用存儲。

# nginx-statefulset.yml

apiVersion: apps/v1            
kind: StatefulSet              
metadata:
  name: nginx-statefulset
spec:
  serviceName: "nginx-headless"
  replicas: 3
  selector:
    matchLabels:
      app: nginx               
  template:
    metadata:                  
      labels:
        app: nginx
    spec:
      nodeName: minikube
      volumes: 
        - name: app-storage
          persistentVolumeClaim:          
            claimName: app-pvc
            
      containers:
      - name: nginx-web
        image: nginx:1.17
        ports:
        - containerPort: 80
          name: nginx-port
        volumeMounts:
          - mountPath: /usr/share/nginx/html
            name: app-storage

與之前的statefulset相比我們在pod 模板中添加了volume 已經 volumeMounts,這樣使用這個statefulset 所創建的pod都將掛載 我們前面定義的PVC app-pvc,應用nginx-statefulset.yml后我們進入到pod 檢驗一下目錄是否被正確掛載。

$ kubectl exec -it nginx-statefulset-0 -- /bin/bash

root@nginx-statefulset-0:/# cat /usr/share/nginx/html/index.html
hello pv

查看本地目錄文件:

root@minikube:/# cat /data/volumes/index.html 
hello pv

接着我們在pod 中修改index.html內容為並將pod刪除,檢驗重載后的 pod 存儲數據是否能被找回。

root@nginx-statefulset-0:/# echo "pod data" > /usr/share/nginx/html/index.html

刪除帶有標簽app=nginx的pod ,由於statefulset的控制器使pod按順序被重建:

$ kubectl delete pod -l app=nginx
pod "nginx-statefulset-0" deleted
pod "nginx-statefulset-1" deleted
pod "nginx-statefulset-2" deleted

$ kubectl get pod 
NAME                  READY   STATUS              RESTARTS   AGE
nginx-statefulset-0   1/1     Running             0          9s
nginx-statefulset-1   1/1     Running             0          6s
nginx-statefulset-2   0/1     ContainerCreating   0          3s

毫無疑問,pod 數據完好無損:

$ kubectl exec -it nginx-statefulset-0 -- /bin/bash
root@nginx-statefulset-0:/# cat /usr/share/nginx/html/index.html
pod data

也就是說雖然我們的pod被刪除了,但是PV已經PV依然保留在集群中,當pod 被重建后,它依然會去找定義的claimName: app-pvc這個PVC,接着掛載到容器中。

這里我們一個PVC 綁定了多個節點,其實可以為每一個 statefulset中的pod 創建PVC,可以自行了解。

k8s存儲可操作性非常強,這里只在statefulset下做了簡單的演示。后續我們會對k8s存儲做更深入的了解。


三、總結

這篇小作文我們一起學習了k8s中工作負載資源StatefulSet是如何管理有狀態應用的,主要從維護應用拓撲狀態和存儲狀態兩個方面做了簡單介紹。這樣我們對statefulset這個工作資源有了大體了解:StatefulSet 與 Deployment 相比,它為每個管理的 Pod 都進行了編號,使Pod有一個穩定的啟動順序,並且是集群中唯一的網絡標識。有了標識后使用PV、PVC對存儲狀態進行維護。


希望小作文對你有些許幫助,如果內容有誤請指正。

您可以隨意轉載、修改、發布本文,無需經過本人同意。 通過博客閱讀:iqsing.github.io


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM