Kubernetes 理解筆記之容器持久化存儲以及CSI機制


本文詳細整理了自己對PV和PVC存儲體系、CSI 自定義存儲插件等容器持久化存儲方面的設計思想和知識理解,是在學習張磊老師Kubernetes課程中持久化存儲過程中的學習筆記。
【Kevin亓】



一、Kubernetes 本身的持久化存儲體系

1.1 容器的“持久化存儲”是什麼?

容器的存儲依賴容器 Volume 的掛載機制,將一個宿主機上的目錄,跟一個容器裏的目錄綁定掛載在了一起。

那持久化存儲就是“持久化 Volume”,指的就是這個宿主機上的目錄,具備“持久性”。即:這個目錄裏面的內容,既不會因爲容器的刪除而被清理掉,也不會跟當前的宿主機綁定,即可以被“遷移”到其他節點上。這樣,當容器被重啓或者在其他節點上重建出來之後,它仍然能夠通過掛載這個 Volume,訪問到這些內容。大多數情況下,持久化 Volume 的實現,往往依賴於一個遠程存儲服務,比如:遠程文件存儲(比如,NFS、GlusterFS)、遠程塊存儲(比如,公有云提供的遠程磁盤)等等。

所以,Kubernetes 需要做的持久化工作,就是爲容器準備一個宿主機目錄,將其綁定掛載一個持久化的遠程服務,使容器在這個目錄裏寫入的文件,都會保存在遠程存儲中,從而使得這個目錄具備了“持久性”。

1.2 在 Kubernetes 中容器如何實現持久化存儲?

核心:依賴 Persistent Volume(PV)和 Persistent Volume Claim(PVC)這套持久化存儲體系
PV和PVC體系注:這裏的 PV 是一個 API 對象,下面會對應 Volume 處理流程,得到一個持久化目錄

容器實現持久化存儲的過程:

(PV 和 PVC 的使用過程大體就是:運維人員維護一組 PV 對象,用戶使用時只需要定義 PVC 聲明想要的 Volume 屬性,然後創建 Pod 時聲明使用這個 PVC 就可以了。Kubernetes 會針對這些 API 對象進行一系列的控制循環工作,由 Kubernetes 中專門處理持久化存儲的控制器來實現,比如 PV 和 PVC 的綁定、將 PV 對象轉化爲真正的持久化存儲。)

1、事先創建 PV 對象

-創建方式:

(1)Static Provisioning(人工管理 PV 的方式),就是簡單的編寫提交 PersistentVolume 這個 API 對象。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 10.244.1.4
    path: "/"

(2)Dynamic Provisioning(自動創建 PV 的機制),會在一個 PVC 被創建時,自動創建對應 PV。原理是通過 StorageClass 這個 API 對象的描述來實現,StorageClass 其實就是創建 PV 的模板,包含 PV 的屬性和創建這種 PV 需要用到的 Provisioner(存儲插件)。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: block-service
provisioner: kubernetes.io/gce-pd # 指明存儲插件,這裏是 Kubernetes 內置的 GCE PD 存儲插件的名字
parameters: # parameters 字段,就是 PV 的參數。
  type: pd-ssd # type=pd-ssd,指的是這個 PV 的類型是“SSD 格式的 GCE 遠程磁盤”

-Kubernetes 中相關控制器操作:

(1)Static Provisioning 方式提交創建的 PV,還只是一個API 對象,需要經過“兩階段處理”變成宿主機上的“持久化 Volume”才真正有用。

一般分爲“兩個階段”:Attach 和 Mount。
第一階段(Attach) 爲虛擬機掛載遠程磁盤,爲了能夠使用這個遠程磁盤。由 Volume Controller 負責維護,作爲一個 Kubernetes 內置的控制器,運行在 Master 節點上。它的作用,就是不斷地檢查每一個 Pod 對應的 PV 和這個 Pod 所在宿主機之間的掛載情況,從而決定是否需要對這個 PV 進行 Attach(或者 Dettach)操作。
第二階段(Mount) 將磁盤設備格式化並掛載到宿主機上指定的掛載點,即 Volume 宿主機目錄。由於涉及到宿主機的指定目錄,必須發生在 Pod 對應的宿主機上,所以它必須是 kubelet 組件的一部分。

(2)Dynamic Provisioning 方式會首先創建 PV,然後進行 Volume 處理,一般使用 CSI 實現,可以參看下面內容。

2、使用人員:創建 PVC,在 Pod 中聲明所使用 PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: block-service
  resources:
    requests:
      storage: 30Gi

---

apiVersion: v1
kind: Pod
metadata:
  labels:
    role: web-frontend
spec:
  containers:
  - name: web
    image: nginx
    ports:
      - name: web
        containerPort: 80
    volumeMounts:
        - name: nfs
          mountPath: "/usr/share/nginx/html"
  volumes:
  - name: nfs
    persistentVolumeClaim:
      claimName: claim1

-Kubernetes 中相關控制器操作:

Kubernetes 會將 StorageClass 相同的 PVC 和 PV 綁定起來。

在 Kubernetes 中,實際上存在着一個專門處理持久化存儲的控制器,叫作 Volume Controller。這個 Volume Controller 維護着多個控制循環,其中有一個循環,扮演的就是撮合 PV 和 PVC 的“紅娘”的角色。它的名字叫作 PersistentVolumeController。

PersistentVolumeController 會不斷地查看當前每一個 PVC,是不是已經處於 Bound(已綁定)狀態。如果不是,那它就會遍歷所有的、可用的 PV,並嘗試將其與這個“單身”的 PVC 進行綁定。這樣,Kubernetes 就可以保證用戶提交的每一個 PVC,只要有合適的 PV 出現,它就能夠很快進入綁定狀態,從而結束“單身”之旅。

而所謂將一個 PV 與 PVC 進行“綁定”,其實就是將這個 PV 對象的名字,填在了 PVC 對象的 spec.volumeName 字段上。所以,接下來 Kubernetes 只要獲取到這個 PVC 對象,就一定能夠找到它所綁定的 PV。

二、CSI 機制與自定義存儲插件

2.1 存儲插件用在哪裏?

在 PV 對象被提交創建之後,Kubernetes 相關的控制器會自動進行 Volume 處理流程,也就是“兩階段處理”(“Attach 階段”和“Mount 階段”),在 Pod 所綁定的宿主機上完成這個 Volume 目錄的持久化過程。但其實這兩個操作實際上調用的,正是 Kubernetes 的 pkg/volume 目錄下的存儲插件(Volume Plugin),如下圖:

2.2 CSI 機制設計原理

CSI 插件體系

Kubernetes 會內置一些存儲插件,它們實際上擔任的角色,僅僅是 Volume 管理中的“Attach 階段”和“Mount 階段”的具體執行者。但除了存儲插件,還有像 Dynamic Provisioning (自動創建 PV 的機制)這樣的功能便是 Kubernetes 本身存儲管理功能的一部分。

CSI 插件體系的設計思想,就是把這個 Provision (自動創建 PV)階段,以及 Kubernetes 裏的一部分存儲管理功能,從主幹代碼裏剝離出來,做成了幾個單獨的組件。這些組件會通過 Watch API 監聽 Kubernetes 裏與存儲相關的事件變化,比如 PVC 的創建,來執行具體的存儲管理動作,而這些管理動作,比如“Attach 階段”和“Mount 階段”的具體操作,實際上就是通過調用 CSI 插件來完成的。這樣就可以更方便地自定義存儲插件,來擴展更多插件。

如下圖所示,中間是 Kubernetes 剝離出來的本身存儲管理部分,右邊是存儲插件

首先來看中間三個 External Components:

  • Driver Registrar 組件,負責將插件註冊到 kubelet 裏面(這可以類比爲,將可執行文件放在插件目錄下)。而在具體實現上,Driver Registrar 需要請求 CSI 插件的 Identity 服務來獲取插件信息。
  • External Provisioner 組件,負責的正是 Provision 階段。在具體實現上,External Provisioner 監聽(Watch)了 APIServer 裏的 PVC 對象。當一個 PVC 被創建時,它就會調用 CSI Controller 的 CreateVolume 方法,爲你創建對應 PV。此外,如果你使用的存儲是公有云提供的磁盤(或者塊設備)的話,這一步就需要調用公有云(或者塊設備服務)的 API 來創建這個 PV 所描述的磁盤(或者塊設備)了。
  • External Attacher 組件,負責的正是“Attach 階段”。在具體實現上,它監聽了 APIServer 裏 VolumeAttachment 對象的變化,一旦出現了 VolumeAttachment 對象,External Attacher 就會調用 CSI Controller 服務的 ControllerPublish 方法,完成它所對應的 Volume 的 Attach 階段。
    而 Volume 的“Mount 階段”,並不屬於 External Components 的職責。當 kubelet 的 VolumeManagerReconciler 控制循環檢查到它需要執行 Mount 操作的時候,會通過 pkg/volume/csi 包,直接調用 CSI Node 服務完成 Volume 的“Mount 階段”。

再來看右邊的存儲插件:

CSI 插件就獨立於 Kubernetes 之外的,只需要提供被調用能力就可以。所以 CSI 插件需要提供三個服務:CSI Identity、CSI Controller 和 CSI Node。

  • CSI Identity 服務,負責對外暴露這個插件本身的信息。
  • CSI Controller 服務,定義的則是對 CSI Volume(對應 Kubernetes 裏的 PV)的管理接口,比如:創建和刪除 CSI Volume、對 CSI Volume 進行 Attach/Dettach(在 CSI 裏,這個操作被叫作 Publish/Unpublish),以及對 CSI Volume 進行 Snapshot 等。這些服務的實際調用者是 External Provisioner 和 External Attacher。
  • CSI Node 服務包含了 CSI Volume 需要在宿主機上執行的全部操作,被 Kubelet 來調用。

在實際使用 CSI 插件的時候,我們會 External Components 作爲 sidecar 容器和 CSI 插件放置在同一個 Pod 中。

CSI 插件編寫:

我們編寫代碼來實現的 CSI 插件。一個 CSI 插件只需要提供一個二進制文件,但它會以 gRPC 的方式對外提供三個服務(gRPC Service),分別叫作:CSI Identity、CSI Controller 和 CSI Node

CSI 插件部署:

1、通過 DaemonSet 在每個節點上都啓動一個 CSI 插件,來爲 kubelet 提供 CSI Node 服務。

CSI Node 服務需要被 kubelet 直接調用,所以它要和 kubelet“一對一”地部署起來。此外,在上述 DaemonSet 的定義裏面,除了 CSI 插件,我們還以 sidecar 的方式運行着 driver-registrar 這個外部組件。它的作用,是向 kubelet 註冊這個 CSI 插件。這個註冊過程使用的插件信息,則通過訪問同一個 Pod 裏的 CSI 插件容器的 Identity 服務獲取到。
需要注意的是,由於 CSI 插件運行在一個容器裏,那麼 CSI Node 服務在“Mount 階段”執行的掛載操作,實際上是發生在這個容器的 Mount Namespace 裏的。可是,我們真正希望執行掛載操作的對象,都是宿主機 /var/lib/kubelet 目錄下的文件和目錄。所以,在定義 DaemonSet Pod 的時候,我們需要把宿主機的 /var/lib/kubelet 以 Volume 的方式掛載進 CSI 插件容器的同名目錄下,然後設置這個 Volume 的 mountPropagation=Bidirectional,即開啓雙向掛載傳播,從而將容器在這個目錄下進行的掛載操作“傳播”給宿主機,反之亦然。

2、通過 StatefulSet 在任意一個節點上再啓動一個 CSI 插件,爲 External Components 提供 CSI Controller 服務。

所以,作爲 CSI Controller 服務的調用者,External Provisioner 和 External Attacher 這兩個外部組件,就需要以 sidecar 的方式和這次部署的 CSI 插件定義在同一個 Pod 裏。

2.3 總結


基於 CSI 存儲插件的完整的持久化 Volume 的創建和掛載流程:

1、用戶提交 PVC ,系統自動創建出 PV
當用戶創建了一個 PVC 之後,前面部署的 StatefulSet 裏的 External Provisioner 容器,就會監聽到這個 PVC 的誕生,然後調用同一個 Pod 裏的 CSI 插件的 CSI Controller 服務的 CreateVolume 方法,爲你創建出對應的 PV。

2、系統將 PV 和 PVC 綁定
這時候,運行在 Kubernetes Master 節點上的 Volume Controller,就會通過 PersistentVolumeController 控制循環,發現這對新創建出來的 PV 和 PVC,並且看到它們聲明的是同一個 StorageClass。所以,它會把這一對 PV 和 PVC 綁定起來,使 PVC 進入 Bound 狀態。

3、用戶提交使用上述 PVC 的 Pod,系統將 Pod 調度到某宿主機
然後,用戶創建了一個聲明使用上述 PVC 的 Pod,並且這個 Pod 被調度器調度到了宿主機 A 上。這時候,Volume Controller 的 AttachDetachController 控制循環就會發現,上述 PVC 對應的 Volume,需要被 Attach 到宿主機 A 上。所以,AttachDetachController 會創建一個 VolumeAttachment 對象,這個對象攜帶了宿主機 A 和待處理的 Volume 的名字。

4、系統將 PVC 對應的 Volume 進行 Attach 到這臺宿主機
這樣,StatefulSet 裏的 External Attacher 容器,就會監聽到這個 VolumeAttachment 對象的誕生。於是,它就會使用這個對象裏的宿主機和 Volume 名字,調用同一個 Pod 裏的 CSI 插件的 CSI Controller 服務的 ControllerPublishVolume 方法,完成“Attach 階段”。

5、這臺宿主機上的 kubelet 完成這個 Volume 的“Mount 階段”
上述過程完成後,運行在宿主機 A 上的 kubelet,就會通過 VolumeManagerReconciler 控制循環,發現當前宿主機上有一個 Volume 對應的存儲設備(比如磁盤)已經被 Attach 到了某個設備目錄下。於是 kubelet 就會調用同一臺宿主機上的 CSI 插件的 CSI Node 服務的 NodeStageVolume 和 NodePublishVolume 方法,完成這個 Volume 的“Mount 階段”。


參考: 《深入剖析Kubernetes | 極客時間》張磊,Kubernetes容器持久化存儲

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