Kubernetes 理解筆記之 StatefulSet


本文是對“有狀態應用”的多副本控制器 StatefulSet 的理解筆記。StatefulSet 是 Kubernetes 中用來處理“有狀態應用”的,所以理解過程從“什麼是有狀態應用”到“Kubernetes的解決方案”順序展開,着重在記錄 Kubernetes 在處理擁有拓撲狀態和存儲狀態的“有狀態應用”的控制管理上的的解決方案。大部分都是按照自己的理解記錄,所以若有錯誤歡迎指正。



“使用”一句話:StatefulSet 是 Kubernetes 中用來處理“多實例的有狀態應用”的。
“原理”一句話:StatefulSet 直接管理 Pod,併爲每一個 Pod 編號命名。


一. 有狀態應用

1.1 這個“有沒有狀態”到底代表什麼?

這裏首先要講一個自己的理解:”應用“ 和 ”實例“
之前從容器到Pod理解的時候,往往說 Kubernetes 通過 Pod 來對“應用”進行描述,但準確地說應該是 Pod YAML 來描述,而通過這個 Pod YAML 建立運行起來的 Pod ,在這裏就稱爲一個“實例”。
所以在 Kubernetes 中 Pod 描述的其實應該是”單實例應用“,但是應用的部署爲了高可用和方便擴展伸縮,往往都是多實例部署,也就是”多實例應用“,之前理解的 Deployment 便是一種描述,多副本可伸縮控制器。
這裏就會有一個困惑:每一個 Pod 都會有自己的網絡地址、數據依賴的,要不然也不會有 Volume 這種設置了。爲什麼還會出現”無狀態應用“呢 ?
其實,這裏的”應用狀態“是對於”多實例應用“而言的,並不是對於 Pod 這種”單實例應用“而言。如果一個”多實例應用“的各個實例承擔不同的責任相互配合,並各自有着不同的外部數據依賴就是”有狀態應用“,如果多個實例僅是單純的副本擴展則是”無狀態應用“了。

1.2 “有狀態應用”的狀態抽象

Kubernetes 將“多實例應用”的有狀態抽象爲了兩種情況:

  1. 有拓撲狀態:一個應用的多個實例之間有拓撲狀態
    場景:應用多個實例必須按照某些順序啓動,比如應用的主節點 A 要先於從節點 B 啓動。而如果你把 A 和 B 兩個 Pod 刪除掉,它們再次被創建出來時也必須嚴格按照這個順序才行。並且,新創建出來的 Pod,必須和原來 Pod 的網絡標識一樣,這樣原先的訪問者才能使用同樣的方法,訪問到這個新 Pod。
  2. 有存儲狀態:一個應用的每個實例有各自不同的存儲狀態
    場景:一個數據庫應用的多個存儲實例,往往都會在本地磁盤上保存一份數據,這樣每個實例關聯的數據依賴就不同了,同時這些實例哪怕被重建過,每個實例與各自數據之間的對應關係也應該是不變的。

二. Kubernetes 如何處理有狀態應用:StatefulSet

如何處理好有狀態應用,就需要有方法維護好各個實例的狀態,這也是 StatefulSet 的核心功能,其實就是:通過某些方法記錄這些狀態,然後在 Pod 被重新創建時能夠爲新的 Pod 恢復這些狀態。

2.1 針對拓撲狀態

拓撲狀態如圖,可以看到各個實例之間有拓撲狀態的應用有2個點:(1)不同實例扮演不同角色承擔不同任務。(2)每個實例需要知道其他實例的訪問地址,實例之間需要互通。

首先解答第一個點:
【問】都是通過同一個 Pod 模板建立的 Pod,StatefulSet 如何讓它們扮演不同角色承擔不同任務?
【答】每個實例雖然通過相同的鏡像創建,但啓動 Pod 的命令和初始化流程可以完全不一樣。StatefulSet 實現的一個基礎點是爲創建的每一個 Pod 提供了一個命名編號(假設 nginx 就是從nginx-1 到 nginx-N),這樣之後就可以根據 Pod 的不同命名來設定不同命令和流程來讓特定 Pod 承擔特定任務(可以給 Pod1-name 和其他 Pod 設定不同配置從而區分 master 和 slave )。Pod 重建也會按照命名編號重建,這樣假設 Pod-N 重建產生新的 Pod,名字不會變,還是叫 Pod-N,也還是會按照 Pod-N 原來的命令和配置重新設置, 在拓撲中扮演的角色也就不會變。

然後解答第二個點:
【問】Pod 重建後 IP 會發生改變,網絡標識不穩定,StatefulSet 如何爲每一個 Pod 提供一個穩定的網絡訪問點從而維持拓撲狀態不變?
【答】固定的網絡標識一般使用 IP 地址表示,也就是每個 Pod 的 IP地址,但因爲 Pod 會出現刪除重建等操作,IP 會變,無法固定,所以需要另一種方式。而 StatefulSet 創建或重建的一組 Pod 有一個東西是固定的,就是 Pod 的編號命名(pod-name),比如10個實例的應用,Pod 命名就是從 pod1 到 pod10,即使重建也會按順序,而且不會出現 pod11。
另外,再借助另一對象 Service (Service 是 Kubernetes 項目中用來將一組 Pod 暴露給外界訪問的一種機制)的 Headless Service 方式,它爲所代理的每一個 Pod 綁定一個固定格式的 DNS 記錄作爲“可解析身份”,核心由 pod-name 和 service-name 標識:

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

這樣針對固定 pod-name 的 Pod 就有了唯一的網絡訪問地址了。所以想要用 StatefulSet 部署有狀態應用,也要配合部署一個 Service(Headless Service) ,爲每個 Pod 提供DNS記錄及解析。

如圖,Pod 的唯一標識過程:
Pod 的唯一標識過程
接下來任憑 Pod 重建 IP 如何變化,StatefulSet 會維護特定角色的 Pod 的 pod-name 不變,而 Service 會更新 DNS 對 Pod 的解析,這樣由 pod-name 和 svc-name 組成的“特定格式地址”是一直不需要變化的,什麼時候通過這一 DNS 記錄中的“特定格式地址”都可以訪問到擔任特定角色的 Pod,就實現了 Pod 網絡標識的穩定。換句話說,DNS 記錄中的“特定格式地址”是一直不變的,但地址到 Pod IP 的解析會隨着 Pod 重建而更新,不過我們使用的僅僅就是這一“特定格式地址”就夠了。

如圖,Pod2重建過程:
Pod2重建過程
圖中過程解釋:一共3個 Pod,當 Pod2 重建時,因爲 Pod 創建是有序的,所以 Pod3 也會刪除,然後重新按順序創建 Pod2、Pod3。這時候原有 DNS 記錄中的“特定格式地址”(圖中藍色部分)是不需要變化的,更新的是 DNS 對新 Pod IP 的解析,這樣訪問 Pod 的入口地址(圖中藍色部分)一直就是穩定的了。

一句話總結:
StatefulSet 爲管理的每個 Pod 實例提供編號命名;Headless Service 根據 Pod 命名綁定固定格式的 DNS 記錄,然後不斷自動更新 DNS 記錄到 Pod IP 解析;從而達到由 pod-name 和 service-name 組成的“固定格式地址”來作爲每一個 Pod 穩定的網絡標識的效果。

2.2 針對存儲狀態

1、首先,補充一下 StatefulSet 使用到的 PVC 和 PV 機制。

Pod 中直接配置 Volume,對開發人員會有字段使用複雜以及過度暴露存儲系統細節的風險,如下:

apiVersion: v1
kind: Pod
metadata:
  name: rbd
spec:
  containers:
    - image: kubernetes/pause
      name: rbd-rw
      volumeMounts:
      - name: rbdpd
        mountPath: /mnt/rbd
  volumes:
    - name: rbdpd
      rbd:
        monitors:
        - '10.16.154.78:6789'
        - '10.16.154.82:6789'
        - '10.16.154.83:6789'
        pool: kube
        image: foo
        fsType: ext4
        readOnly: true
        user: admin
        keyring: /etc/ceph/keyring
        imageformat: "2"
        imagefeatures: "layering"

所以 Kubernetes 項目引入了一組叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 對象。Kubernetes 中 PVC 和 PV 的設計,實際上類似於“接口”和“實現”的思想。開發者只要知道並會使用“接口”,即:PVC;而運維人員則負責給“接口”綁定具體的實現,即:PV。
運維人員維護一組 PV 對象,開發人員使用時只需要定義 PVC 聲明想要的 Volume 屬性,然後創建 Pod 時指明使用這個 PVC 就可以了。

2、然後,看看 StatefulSet 實現每個 Pod 實例配置各自 Volume 的方式

(1) 運維人員已經在系統裏創建好了符合條件的 PV,這也是一個前提。例如:

kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv-volume
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  rbd:
    monitors:
    - '10.16.154.78:6789'
    - '10.16.154.82:6789'
    - '10.16.154.83:6789'
    pool: kube
    image: foo
    fsType: ext4
    readOnly: true
    user: admin
    keyring: /etc/ceph/keyring
    imageformat: "2"
    imagefeatures: "layering"

(2) 利用 StatefulSet 的 volumeClaimTemplates 字段,爲創建的每一個 Pod 聲明一個對應的 PVC(這個 PVC 的名字,會被分配一個與這個 Pod 完全一致的編號)。

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
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

(3) Kubernetes 自動通過 Persistent Volume 機制爲每個 PVC 綁定上對應的 PV。

所以,即使 Pod 刪除,原來聲明的 PVC 及其後面綁定的 PV是沒影響的,重建之後因爲 Pod 編號命名沒變,按照名字依然可以直接找到同名 PVC 實現綁定。這樣新的 Pod 就可以掛載到舊 Pod 對應的那個 Volume,並且獲取到保存在 Volume 裏的數據。

總結

  1. StatefulSet 是 Kubernetes 中用來處理“有狀態應用”的。這個應用是指“多實例應用”。多實例應用的”狀態“可以抽象爲”實例之間的拓撲狀態“和”實例各自的存儲狀態“ 。
  2. 爲了區分“有狀態應用”的各個 Pod 實例:StatefulSet 會爲創建的 Pod 在名字裏加上事先約定好的順序編號。
  3. 爲了創建穩定網絡標識維護各個實例拓撲網絡的“有狀態”:Kubernetes 通過 Headless Service,爲這些有編號的 Pod,在 DNS 服務器中生成帶有同樣編號的 DNS 記錄。只要 StatefulSet 能夠保證這些 Pod 名字裏的編號不變,那麼 Service 裏類似於 web-0.nginx.default.svc.cluster.local 這樣的 DNS 記錄也就不會變,而這條記錄解析出來的 Pod 的 IP 地址,則會隨着後端 Pod 的刪除和再創建而自動更新。這當然是 Service 機制本身的能力,不需要 StatefulSet 操心。
  4. 爲了從而維護各個實例數據存儲的“有狀態”:StatefulSet 爲每一個 Pod 分配並創建一個同樣編號的 PVC。這樣,Kubernetes 就可以通過 Persistent Volume 機制爲這個 PVC 綁定上對應的 PV,從而保證了每一個 Pod 都擁有一個獨立的 Volume。

參考:
《深入剖析Kubernetes》張磊,極客時間,容器編排和Kubernetes作業管理 18 19 20

發佈了106 篇原創文章 · 獲贊 111 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章