kubernetes - day26

September 16, 2020

StatefulSet 簡單來說就是管理有狀態的應用程式,它使得每個應用程式都有不可替換的資源個體,其每個 POD 都有固定的 hostname 和專屬 volume,即時重新調度也不影響。

Stateful 和 Stateless

一個應用程式是否要紀錄前一次或多次連線中的內容訊息做為下一次連線的資訊,而該連線的對象可能是設備、用戶端或其它應用程式等。透過是否紀錄來分辨有狀態與無狀態,前者為需要記錄動作,後者則不用。

StatefulSet 與 ReplicaSet

StatefulSet 在文章開頭大致上將它的重點說明了。但我們可以比較它和 ReplicaSetReplicaSet 隨著被資源調度就會被新的資源所取代,因為其網路等資源被改變,StatefulSet 則是就算被重新調度,其源個體都會有著相同的資源。同樣的 StatefulSetReplicaSetreplicas 功能,但和 ReplicaSet 生成的 POD 副本卻是不一樣的,StatefulSet 中的 POD 會有獨立的 PV 也就是儲存空間,另一個是 POD 名稱,它會使用編號方式去命名,而 StatefulSet 也支持滾動更新,基於這些功能 StatefulSet 都會講求順序,在後面範例就會明白。

StatefulSet 特性

網路標識

通常一個 StatefulSet 會在建立一個 headless Service 資源,用來記錄每個 POD 的網路標識,就像先前文章講的,每一個 POD 都會有一個獨立的 DNS 紀錄,使得客戶端能夠透過 hostname 去找到服務。

資源調度

StatefulSet 下的 POD 發生故障時,會像 ReplicaSet 一樣將其重新建立,但是不一樣的是 StatefulSet 會讓該新 POD 擁有之前 PODhostname 等,因此透過該 hostname 進行訪問時還是會存取到一樣的 POD 資源。

擴展

前面有提到說 StatefulSetPOD 會有順序編號,該順序編號使得擴展能夠被預知。以 ResplicaSet 進行縮減的話則是以隨機方式,無法預知哪個 POD 會被終止。

個人儲存

竟然資源調度時網路標識能夠相同,那儲存想必也要。在定義 StatefulSet 的資源清單時,它可以定義多個 volumeClaimTemplates,而建立的儲存會在創建 POD 之前被建立,最後在綁定至 POD。因為 StatefulSet 可以像 ReplicaSet 一樣的去擴展,如果以縮減 POD 來看,並不會刪除儲存的部分,對於 StatefulSet 來說它是一個有狀態的應用,如果將其儲存刪除將會是一個災難,因此手動刪除確認是最好的。也因為不會刪除 PVC 的資源,在誤刪時在將縮減後的 POD 在擴展時 PV 會綁定在相同宣告的儲存上。

建立 StatefulSet

這邊使用 GKE 進行實驗。

定義 StorageClass 資源

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard-us-centrall-a-b-c
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
  - key: failure-domain.beta.kubernetes.io/zone
    values:
    - us-central1-a
    - us-central1-b
    - us-central1-c

定義 StatefulSetheadless 資源,StatefulSet 重要字段大致上是 serviceNamevolumeClaimTemplates。而 volumeClaimTemplates 就像是定義 POD 的模板一樣(template),因此他才能針對每個 POD 去做一個獨立的 PV

apiVersion: v1
kind: Service
metadata:
  name: sts-svc
  labels:
    app: sts-svc
spec:
  clusterIP: None
  selector:
    app: nginx-pod
  ports:
  - port: 80
    name: web
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-sts
  labels:
    app: nginx
spec:
  serviceName: sts-svc
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
        - image: nginx:1.18
          name: nginx
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: nginx-persistent-storage
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: nginx-persistent-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: standard-us-centrall-a-b-c # 關聯著我們定義的 StorageClass
      resources:
        requests:
          storage: 10Gi

使用 apply 布署,並使用 -w 觀察 POD,如下它們都是按照順序去建立 POD,並非像 ReplicaSet 可以並行,這種現象並非只有創建 POD 在刪除或是滾動更新都是呈現這模式。如果要使用並行需在 spec 下定義 podManagementPolicyParallel,預設效果就是下面結果。

$ kubectl get pods -w
NAME                                READY   STATUS    RESTARTS   AGE
...
nginx-sts-0                         0/1     Pending   0          0s
nginx-sts-0                         0/1     Pending   0          3s
nginx-sts-0                         0/1     ContainerCreating   0          3s
nginx-sts-0                         1/1     Running             0          14s
nginx-sts-1                         0/1     Pending             0          0s
nginx-sts-1                         0/1     Pending             0          3s
nginx-sts-1                         0/1     ContainerCreating   0          3s
nginx-sts-1                         1/1     Running             0          14s
nginx-sts-2                         0/1     Pending             0          0s
nginx-sts-2                         0/1     Pending             0          3s
nginx-sts-2                         0/1     ContainerCreating   0          3s
nginx-sts-2                         1/1     Running             0          23s

查看 StatefulSet 資源

$ kubectl get sts -o wide
NAME        READY   AGE     CONTAINERS   IMAGES
nginx-sts   3/3     7m57s   nginx        nginx:1.18

詳細查看某個 StatefulSet 資源訊息

$ kubectl describe sts nginx-sts

查看建立的 PVCPV,結果都是 Bound 狀態。

$ kubectl get pvc
NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                 AGE
nginx-persistent-storage-nginx-sts-0   Bound    pvc-4c81312a-38d3-4e0e-bd6b-9dcc9ff93ccb   10Gi       RWO            standard-us-centrall-a-b-c   12m
nginx-persistent-storage-nginx-sts-1   Bound    pvc-a9d9b751-dc74-4d00-ae87-1a878aec9c09   10Gi       RWO            standard-us-centrall-a-b-c   11m
nginx-persistent-storage-nginx-sts-2   Bound    pvc-fa583a17-5ca5-4634-87c4-52f85d211fb4   10Gi       RWO            standard-us-centrall-a-b-c   11m
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                          STORAGECLASS                 REASON   AGE
pvc-4c81312a-38d3-4e0e-bd6b-9dcc9ff93ccb   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-0   standard-us-centrall-a-b-c            12m
pvc-a9d9b751-dc74-4d00-ae87-1a878aec9c09   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-1   standard-us-centrall-a-b-c            12m
pvc-fa583a17-5ca5-4634-87c4-52f85d211fb4   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-2   standard-us-centrall-a-b-c            11m

這邊觀察其 PODhostname 與外部 POD 名稱相同,想像一下在 ReplicaSet 情況下它並非是以順序編號,而是隨機亂數。

$ for i in 0 1 2; do kubectl exec nginx-sts-$i --  sh -c 'hostname'; done
nginx-sts-0
nginx-sts-1
nginx-sts-2

驗證有狀態的 POD 是否會關聯同一個 PV

從以下結果來看,沒有改變其原先對應的儲存。

$ kubectl delete pod nginx-sts-1 nginx-sts-0 # 刪除 POD 
$ kubectl describe pod nginx-sts-0 # 觀察 ClaimName,並和 get pv 中的 RECLAIM 進行比對
...
Volumes:
  nginx-persistent-storage:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  nginx-persistent-storage-nginx-sts-0
...

更清楚的演示,寫入檔案至掛載目錄下,並使用另一個 POD 或任一節點使用 curl 驗證。這邊就不再演示了。

$ for i in 0 1 2; do kubectl exec nginx-sts-$i -- sh -c 'echo $(date), Hostname: $(hostname) > /usr/share/nginx/html/index.html'; done

擴展與縮減

$ kubectl edit sts nginx-sts # 修改 replicas 成 5
$ kubectl get pv # 新增兩個
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                          STORAGECLASS                 REASON   AGE
pvc-1d3ecb25-891d-4d6d-9708-2498148bcd92   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-3   standard-us-centrall-a-b-c            96s
pvc-4ae8113a-08f6-4d51-a01e-601a349b87e3   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-4   standard-us-centrall-a-b-c            73s
pvc-4c81312a-38d3-4e0e-bd6b-9dcc9ff93ccb   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-0   standard-us-centrall-a-b-c            43m
pvc-a9d9b751-dc74-4d00-ae87-1a878aec9c09   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-1   standard-us-centrall-a-b-c            43m
pvc-fa583a17-5ca5-4634-87c4-52f85d211fb4   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-2   standard-us-centrall-a-b-c            42m

$ kubectl edit sts nginx-sts # 修改 replicas 成 2
$ kubectl get pv # 依舊保留
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                          STORAGECLASS                 REASON   AGE
pvc-1d3ecb25-891d-4d6d-9708-2498148bcd92   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-3   standard-us-centrall-a-b-c            3m36s
pvc-4ae8113a-08f6-4d51-a01e-601a349b87e3   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-4   standard-us-centrall-a-b-c            3m13s
pvc-4c81312a-38d3-4e0e-bd6b-9dcc9ff93ccb   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-0   standard-us-centrall-a-b-c            45m
pvc-a9d9b751-dc74-4d00-ae87-1a878aec9c09   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-1   standard-us-centrall-a-b-c            45m
pvc-fa583a17-5ca5-4634-87c4-52f85d211fb4   10Gi       RWO            Delete           Bound    default/nginx-persistent-storage-nginx-sts-2   standard-us-centrall-a-b-c            44m
cchong0124@cloudshell:~/sc (sunny-catwalk-286908)$

滾動更新

編輯 yaml 檔並將其 nginx 版本變為 1.15,接著從新 apply

$ kubectl rollout status sts nginx-sts
Waiting for 1 pods to be ready...
partitioned roll out complete: 3 new pods have been updated...
$ kubectl get pods -l app=nginx-pod -o custom-columns=NAME:metadata.name,IMAGE:spec.containers[0].image
NAME          IMAGE
nginx-sts-0   nginx:1.15
nginx-sts-1   nginx:1.15
nginx-sts-2   nginx:1.15
$ kubectl rollout history sts nginx-sts
statefulset.apps/nginx-sts
REVISION
1
2

回滾,以下實驗可以透過 get pod -w 方式觀察 POD 的變動是否是按順序。

$ kubectl rollout undo  sts nginx-sts
statefulset.apps/nginx-sts rolled back
$ kubectl rollout status sts nginx-sts
Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 1 out of 3 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 2 out of 3 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 3 new pods have been updated...
$ kubectl get pods -l app=nginx-pod -o custom-columns=NAME:metadata.name,IMAGE:spec.containers[0].image
NAME          IMAGE
nginx-sts-0   nginx:1.18
nginx-sts-1   nginx:1.18
nginx-sts-2   nginx:1.18

DNS

這邊大致演示 DNS 部可搭配,前面擴縮來驗證是否會綁定。下面 ANSWER SECTION 顯示三個指向 headless service SRV 的資源紀錄,ADDITIONAL SECTION 部分則顯示每個 POD 的資源紀錄,沒有排序是正常的,因為它有著相同的優先權。

$ kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=Never -- dig SRV sts-svc.default.svc.cluster.local

; <<>> DiG 9.9.5-3ubuntu0.2-Ubuntu <<>> SRV sts-svc.default.svc.cluster.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10672
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 3

;; QUESTION SECTION:
;sts-svc.default.svc.cluster.local. IN  SRV

;; ANSWER SECTION:
sts-svc.default.svc.cluster.local. 30 IN SRV    10 33 0 nginx-sts-1.sts-svc.default.svc.cluster.local.
sts-svc.default.svc.cluster.local. 30 IN SRV    10 33 0 nginx-sts-0.sts-svc.default.svc.cluster.local.
sts-svc.default.svc.cluster.local. 30 IN SRV    10 33 0 nginx-sts-2.sts-svc.default.svc.cluster.local.

;; ADDITIONAL SECTION:
nginx-sts-1.sts-svc.default.svc.cluster.local. 30 IN A 10.4.1.45 
nginx-sts-0.sts-svc.default.svc.cluster.local. 30 IN A 10.4.2.84
nginx-sts-2.sts-svc.default.svc.cluster.local. 30 IN A 10.4.3.124

;; Query time: 71 msec
;; SERVER: 10.8.0.10#53(10.8.0.10)
;; WHEN: Tue Sep 22 09:46:06 UTC 2020
;; MSG SIZE  rcvd: 294

pod "srvlookup" deleted

結論

了解了 statefulSetReplicaSet 的差異像是回滾、PV 等。也明白 statefulSet 透過 DNS 方式來保持客戶端可見性。

參考資源