CSI架構和原理

2023-06-07 21:00:31

CSI

CSI簡介

CSI的誕生背景

K8s 原生支援一些儲存型別的 PV,如 iSCSI、NFS、CephFS 等等,這些 in-tree 型別的儲存程式碼放在 Kubernetes 程式碼倉庫中。這裡帶來的問題是 K8s 程式碼與三方儲存廠商的程式碼強耦合:

  1. 更改 in-tree 型別的儲存程式碼,使用者必須更新 K8s 元件,成本較高

  2. in-tree 儲存程式碼中的 bug 會引發 K8s 元件不穩定

  3. K8s 社群需要負責維護及測試 in-tree 型別的儲存功能

  4. in-tree 儲存外掛享有與 K8s 核心元件同等的特權,存在安全隱患

  5. 三方儲存開發者必須遵循 K8s 社群的規則開發 in-tree 型別儲存程式碼

CSI 容器儲存介面標準的出現解決了上述問題,將三方儲存程式碼與 K8s 程式碼解耦,使得三方儲存廠商研發人員只需實現 CSI 介面即可(無需關注容器平臺是 K8s 還是 Swarm 等)。

Pod掛載volume的過程

  1. 使用者建立一個包含PVC的Pod(使用動態儲存卷);

  2. PV Controller發現這個PVC處於待繫結狀態,呼叫Volume Plugin(in-tree或者out-of-tree)建立儲存卷,並建立PV物件,然後將建立的PV與PVC繫結;

  3. Scheduler根據Pod的設定、節點狀態、PV設定等資訊,把Pod排程刀worker節點Node上;

  4. AD Controller發現Pod和PVC處於待掛載狀態,呼叫Volume Plugin(in-tree或者out-of-tree)實現裝置掛載到目標節點(/dev/vdb);

  5. 在worker節點上,kubelet(Volume Manager)等待裝置掛載完成,通過Volume Plugin將裝置掛載到指定目錄:/var/lib/kubelet/pods/646154cf-dc72-11e9-b200-00163e007d53/volumes/alicloud~disk/pv-disk;

  6. kubelet在被告知掛載目錄準備好後,啟動Pod中的containers,用Docker -v方式(bind)將已經掛載到原生的卷對映到容器中;

 

CSI工作原理

CSI的部署模式

CSI 主要包含兩個部分:CSI Controller Server 與 CSI Node Server,分別對應Controller Server Pod和Node Server Pod

Controller Server

只需要部署一個 Controller Server,如果是多備份的,可以部署兩個。

Controller Server 主要是通過多個外部外掛來實現的,一個 Pod 中可以定義多個 sideCar形式的External Container 和一個包含 CSI Controller Server 的 Container,這時候不同的 External 元件會和 Controller Server 組成不同的功能。

互動過程
External Provisioner + Controller Server 建立、刪除資料卷
External Attacher + Controller Server 執行資料卷的掛載、解除安裝操作
Volume Manager + Volume Plugin + Node Server 執行資料卷的Mount、Umount操作
AD Controller + VolumePlugin 建立、刪除VolumeAttachment物件
External Resizer + Controller Server 執行資料卷的擴容操作
ExternalSnapshotter+ControllerServer 執行資料卷的備份操作
Driver Registrar + VolumeManager + Node Server 註冊CSI外掛,建立CSINode物件

 

Node Server

Node Server Pod 是個 DaemonSet,它會在每個節點上進行註冊。

Kubelet 會直接通過 Socket 的方式直接和 CSI Node Server 進行通訊、呼叫 Attach/Detach/Mount/Unmount 等。

 

Driver Registrar

Driver Registrar 只是做一個註冊的功能,會在每個節點上進行部署。

 

CSI的工作原理

CSI Controller

CSI Controller,它是以 deployment 的形式執行在叢集裡面,主要負責 provision 和 attach 工作。

attach並不是不是每一個儲存都會用到的,而 provision 就是在使用 StorageClass 的時候會動態建立 PV 的過程, CSI Controller 在實現 provision 這個功能的時候,是 external-provisioner 這個 SideCar 去配合實現的,在實現 attach 功能的時候是external-attacher 這個SideCar配合它一起完成的。

注意:動態建立pv才會走到provision流程,靜態並不會

CSI Node & CSI Identity

CSI Node 和 CSI Identity 通常是部署在一個容器裡面的,它們是以 daemonset 的形式執行在叢集裡面,保證每一個節點會有一個 Pod 部署出來,這兩個元件會和 CSI Controller 一起完成 volume 的 mount 操作。

CSI Identity 是用來告訴 Controller,我現在是哪一個 CSI 外掛,它實現的介面會被 node-driver-registrar 呼叫給 Controller 去註冊自己。

CSI Node 會實現一些 publish volume 和 unpublished volume 的介面,Controller 會去呼叫來完成 volume 的 mount 的操作,我們只需要實現這幾個外掛的介面就可以了。

 

CSI物件

volumeAttachment

volumeAttachment描述了一個volume卷掛載、解除安裝相關的資訊,包含:卷的名字、掛載的節點、使用的CSIDriver外掛、當前狀態等資訊,代表一個掛載操作的期望;

  • AD Controller在執行掛載一個CSI PV的時候,會呼叫csi-attacher(in-tree)建立一個volume Attachment

  • External-attacher通過watch volume attachment,發現有需要掛載的資料卷,呼叫csi-plugin的controllerPublishVolume方法,執行attach操作

  • volume Attachment是由AD Controller呼叫csi-attacher刪除

❯ kubectl get volumeattachments -o wide
NAME     ATTACHER             PV                                   NODE           ATTACHED   AGE
csi-1ac8 udisk.csi.ucloud.cn   pvc-fc6b4beb-3766-488e-a261-792a25   10.13.34.201    true       103m
csi-87fe udisk.csi.ucloud.cn   pvc-65bb1aa1-b15e-45eb-82ce-29b2f7   10.13.136.103   true       103m

CSIDriver(驅動程式)

CSIDriver 用於定義和設定 CSI 驅動程式的屬性和行為,是叢集範圍的資源物件。它描述了叢集中所部署的 CSI Plugin 列表,需要管理員根據外掛型別進行建立。CSIDriver 物件是叢集範圍的,即在整個叢集中共用和使用;

可以通過 kuberctl get csidriver 可以看到叢集裡面建立的各種型別的 CSI Driver

❯ kubectl get csidrivers.csi.storage.k8s.io
NAME                 AGE
udisk.csi.ucloud.cn   4h9m

CSINode(節點)

CSINode 用於將 CSI 驅動程式繫結到節點上,表示節點上的 CSI 驅動程式外掛,是節點級別的資源物件。它是叢集中的節點資訊,在 Node Driver Registrar 元件向 Kubelet 註冊完畢後,Kubelet 會建立該資源,故不需要顯式建立 CSINode 資源。它的作用是每一個新的 CSI Plugin 註冊後,都會在 CSINode 列表裡新增一個 CSINode 資訊。

  • CSINode 物件用於告知 Kubernetes 叢集該節點上可用的 CSI 驅動程式,以便在排程 Pod 時進行選擇和匹配;

  • CSINode 物件是節點級別的,每個節點上都需要建立一個對應的 CSINode 物件;

將 Kubernetes 中 Node 資源名稱與三方儲存系統中節點名稱(nodeID)一一對應。此處Kubelet會呼叫外部 CSI 外掛NodeServer 的 GetNodeInfo 函數獲取 nodeID。

CSINode 中 topologyKeys 用來表示儲存節點的拓撲資訊,卷拓撲資訊會使得Scheduler在 Pod 排程時選擇合適的儲存節點。

❯ kubectl get csinodes.storage.k8s.io -o wide   # 叢集一共有5個節點,對應5個csinode
NAME           DRIVERS   AGE
10.13.109.116   1         4h13m
10.13.116.114   1         4h13m
10.13.136.103   1         4h12m
10.13.170.186   1         4h13m
10.13.34.201    1         4h12m

 

CSI核心流程

K8s 中的 Pod 在掛載儲存卷時需經歷三個的階段

  1. Provision/Delete(創盤/刪盤)

  2. Attach/Detach(掛接/摘除)

  3. Mount/Unmount(掛載/解除安裝)

Provisioning Volumes

  • 創盤由External Provisioner來完成

  1. 叢集管理員建立 StorageClass 資源,該 StorageClass 中包含 CSI 外掛名稱;

  2. 使用者建立 PVC 資源,PVC 指定儲存大小及 StorageClass;

  3. 卷控制器(PV Controller)觀察到叢集中新建立的 PVC 沒有與之匹配的 PV,且其使用的儲存型別為 out-of-tree,於是為 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 外掛名稱]

  4. External Provisioner 元件觀察到 PVC 的 annotation 中包含 volume.beta.kubernetes.io/storage-provisioner且其 value 是自己,於是開始創盤流程:

    1. 獲取相關 StorageClass 資源並從中獲取引數,用於後面 CSI 函數呼叫

    2. 通過 unix domain socket 呼叫外部 CSI 外掛的CreateVolume 函數

  5. 外部 CSI 外掛返回成功後表示盤建立完成,此時External Provisioner 元件會在叢集建立一個 PersistentVolume 資源。

  6. 卷控制器會將 PV 與 PVC 進行繫結。

Attaching Volumes

  • 掛接由External Attacher完成

  1. AD 控制器(AttachDetachController)觀察到使用 CSI 型別 PV 的 Pod 被排程到某一節點,此時AD 控制器會呼叫內部 in-tree CSI 外掛(csiAttacher)的 Attach 函數;

  2. 內部 in-tree CSI 外掛(csiAttacher)會建立一個 VolumeAttachment 物件到叢集中;

  3. External Attacher 觀察到該 VolumeAttachment 物件,並呼叫外部 CSI外掛的ControllerPublish 函數以將卷掛接到對應節點上。當外部 CSI 外掛掛載成功後,External Attacher會更新相關 VolumeAttachment 物件的 .Status.Attached 為 true;

  4. AD 控制器內部 in-tree CSI 外掛(csiAttacher)觀察到 VolumeAttachment 物件的 .Status.Attached 設定為 true,於是更新AD 控制器內部狀態(ActualStateOfWorld),該狀態會顯示在 Node 資源的 .Status.VolumesAttached 上;

❯ kubectl get node 10.13.136.103 -o yaml | tail -n 20
...
volumesAttached:
- devicePath: ""
  name: kubernetes.io/csi/udisk.csi.ucloud.cn^bsm-jw6svmajfjn
volumesInUse:
- kubernetes.io/csi/udisk.csi.ucloud.cn^bsm-jw6svmajfjn

Mounting Volumes

  • 掛載由kubelet來完成

  1. Volume Manager(Kubelet 元件)觀察到有新的使用 CSI 型別 PV 的 Pod 排程到本節點上,於是呼叫內部 in-tree CSI 外掛(csiAttacher)的 WaitForAttach 函數;

  2. 內部 in-tree CSI 外掛(csiAttacher)等待叢集中 VolumeAttachment 物件狀態 .Status.Attached 變為 true;

  3. in-tree CSI 外掛(csiAttacher)呼叫 MountDevice 函數,該函數內部通過 unix domain socket 呼叫外部 CSI 外掛的NodeStageVolume 函數;之後外掛(csiAttacher)呼叫內部 in-tree CSI 外掛(csiMountMgr)的 SetUp 函數,該函數內部會通過 unix domain socket 呼叫外部 CSI 外掛的NodePublishVolume 函數;

Unmounting Volumes

  1. 使用者刪除相關 Pod;

  2. Volume Manager(Kubelet 元件)觀察到包含 CSI 儲存卷的 Pod 被刪除,於是呼叫內部 in-tree CSI 外掛(csiMountMgr)的 TearDown 函數,該函數內部會通過 unix domain socket 呼叫外部 CSI 外掛的 NodeUnpublishVolume 函數;

  3. Volume Manager(Kubelet 元件)呼叫內部 in-tree CSI 外掛(csiAttacher)的 UnmountDevice 函數,該函數內部會通過 unix domain socket 呼叫外部 CSI 外掛的 NodeUnpublishVolume 函數。

Detaching Volumes

  1. AD 控制器觀察到包含 CSI 儲存卷的 Pod 被刪除,此時該控制器會呼叫內部 in-tree CSI 外掛(csiAttacher)的 Detach 函數;

  2. csiAttacher會刪除叢集中相關 VolumeAttachment 物件(但由於存在 finalizer,va 物件不會立即刪除);

  3. External Attacher觀察到叢集中 VolumeAttachment 物件的 DeletionTimestamp 非空,於是呼叫外部 CSI 外掛的ControllerUnpublish 函數以將卷從對應節點上摘除。外部 CSI 外掛摘除成功後,External Attacher會移除相關 VolumeAttachment 物件的 finalizer 欄位,此時 VolumeAttachment 物件被徹底刪除;

  4. AD 控制器中內部 in-tree CSI 外掛(csiAttacher)觀察到 VolumeAttachment 物件已刪除,於是更新AD 控制器中的內部狀態;同時AD 控制器更新 Node 資源,此時 Node 資源的 .Status.VolumesAttached 上已沒有相關掛接資訊;

Deleting Volumes

  1. 使用者刪除相關 PVC;

  2. External Provisioner 元件觀察到 PVC 刪除事件,根據 PVC 的回收策略(Reclaim)執行不同操作:

    • Delete:呼叫外部 CSI 外掛的DeleteVolume 函數以刪除卷;一旦捲成功刪除,Provisioner會刪除叢集中對應 PV 物件

    • Retain:Provisioner不執行卷刪除操作;

     

CSI 元件

sidecar元件由Kubernetes官方維護

Cluster Driver Registrar

功能

Cluster Driver Registrar 負責自動註冊 CSI 驅動程式到 Kubernetes 叢集中

註冊範圍:Cluster Driver Registrar 在整個叢集範圍內工作,負責叢集中所有節點上的 CSI 驅動程式的註冊

註冊過程

  • 註冊過程:它會監聽 Kubernetes API 中的 CSI 驅動程式設定物件(CSIDriver 物件),當有新的設定建立或更新時,Cluster Driver Registrar 將相應的 CSI 驅動程式註冊到叢集中。

  • 功能擴充套件:除了註冊驅動程式,Cluster Driver Registrar 還負責更新驅動程式的資訊到 Kubernetes API 中的其他物件,如 CSINode 和 CSIDriver 物件。

Cluster Driver Registrar & Node Driver Registrar

  • Cluster Driver Registrar 在整個叢集範圍內工作,負責自動註冊和更新 CSI 驅動程式的資訊。

  • Node Driver Registrar 在每個節點上執行,負責啟動和管理該節點上的 CSI 驅動程式,並處理與儲存卷附加和解除安裝相關的操作。

Node Driver Registrar

功能

Node-Driver-Registrar 元件會將外部 CSI 外掛註冊到Kubelet,從而使Kubelet通過特定的 Unix Domain Socket 來呼叫外部 CSI 外掛函數

Kubelet 會呼叫外部 CSI 外掛的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函數

註冊成功後的操作

  1. Kubelet為本節點 Node 資源打 annotation:Kubelet呼叫外部 CSI 外掛的NodeGetInfo 函數,其返回值 [nodeID]、[driverName] 將作為值用於 「csi.volume.kubernetes.io/nodeid」 鍵;

  2. Kubelet更新 Node Label:將NodeGetInfo 函數返回的 [AccessibleTopology] 值用於節點的 Label;

  3. Kubelet更新 Node Status:將NodeGetInfo 函數返回的 maxAttachLimit(節點最大可掛載卷數量)更新到 Node 資源的 Status.Allocatable:attachable-volumes-csi-[driverName]=[maxAttachLimit]

  4. Kubelet更新 CSINode 資源(沒有則建立):將 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓撲僅保留 Key 值);

External Provisioner

功能

External Provisioner用於建立/刪除實際的儲存卷,以及代表儲存卷的 PV 資源

External-Provisioner在啟動時需指定引數 — provisioner,該引數指定 Provisioner 名稱,與 StorageClass 中的 provisioner 欄位對應。

watch叢集的PVC和PV資源

External-Provisioner啟動後會 watch 叢集中的 PVC 和 PV 資源:

1)對於PVC資源:

  1. 判斷 PVC 是否需要動態建立儲存卷,標準如下:

    • PVC 的 annotation 中是否包含 「volume.beta.kubernetes.io/storage-provisioner」 鍵(由卷控制器建立)並且其值是否與 Provisioner 名稱相等

    • PVC 對應 StorageClass 的 VolumeBindingMode 欄位:

      • 若為 WaitForFirstConsumer,則 PVC 的 annotation 中必須包含 「volume.kubernetes.io/selected-node」 鍵,且其值不為空;

      • 若為 Immediate 則表示需要 Provisioner 立即提供動態儲存卷;

  2. 通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 CreateVolume 函數;

  3. 建立 PV 資源,PV 名稱為 [Provisioner 指定的 PV 字首] – [PVC uuid]

2)對於PV資源:

  1. 判斷 PV 是否需要刪除,標準如下:

    • 判斷其 .Status.Phase 是否為 Release;

    • 判斷其 .Spec.PersistentVolumeReclaimPolicy 是否為 Delete;

    • 判斷其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否為自己;

  2. 通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 DeleteVolume 介面;

  3. 刪除叢集中的 PV 資源;

 

External Attacher

功能

External Attacher用於掛接/摘除儲存卷

watch叢集的VA和PV資源

External-Attacher 內部會時刻 watch 叢集中的 VolumeAttachment 資源和 PersistentVolume 資源

1)對於VolumeAttachment資源:

  1. 從 VolumeAttachment 資源中獲得 PV 的所有資訊,如 volume ID、node ID、掛載 Secret 等

  2. 判斷 VolumeAttachment 的 DeletionTimestamp 欄位是否為空來判斷其為卷掛接或卷摘除;

    • 若為卷掛接則通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的ControllerPublishVolume 介面;

    • 若為卷摘除則通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的ControllerUnpublishVolume 介面;

2)對於PV資源:

  1. 在掛接時為相關 PV 打上 Finalizer:external-attacher/[driver 名稱]

  2. 當 PV 處於刪除狀態時(DeletionTimestamp 非空),刪除 Finalizer:external-attacher/[driver 名稱]

 

External Resizer

功能

External Resizer用於擴容儲存卷

watch叢集的PVC資源

External-Resizer內部會 watch 叢集中的 PersistentVolumeClaim 資源

判斷 PersistentVolumeClaim 資源是否需要擴容:PVC 狀態需要是 Bound 且 .Status.Capacity 與 .Spec.Resources.Requests 不等

  1. 更新 PVC 的 .Status.Conditions,表明此時處於 Resizing 狀態

  2. 通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 ControllerExpandVolume 介面

  3. 更新 PV 的 .Spec.Capacity

  4. 若 CSI 支援檔案系統線上擴容,ControllerExpandVolume 介面返回值中 NodeExpansionRequired 欄位為 true,External-Resizer更新 PVC 的 .Status.Conditions 為 FileSystemResizePending 狀態;

  5. 若不支援則擴容成功,External-Resizer更新 PVC 的 .Status.Conditions 為空,且更新 PVC 的 .Status.Capacity

Volume Manager(Kubelet 元件)觀察到儲存卷需線上擴容,於是通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的NodeExpandVolume 介面實現檔案系統擴容

 

livenessprobe

livenessprobe用於檢查CSI外掛是否正常

通過對外暴露一個 / healthz HTTP 埠以服務 kubelet 的探針探測器,內部是通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 Probe 介面

 

第三方廠商要實現的CSI介面

IdentityServer

IdentityServer 主要用於認證 CSI 外掛的身份資訊

ControllerServer

ControllerServer 主要負責儲存卷及快照的建立/刪除以及掛接/摘除操作

NodeServer

NodeServer 主要負責儲存卷掛載/解除安裝操作