K8s 原生支援一些儲存型別的 PV,如 iSCSI、NFS、CephFS 等等,這些 in-tree 型別的儲存程式碼放在 Kubernetes 程式碼倉庫中。這裡帶來的問題是 K8s 程式碼與三方儲存廠商的程式碼強耦合:
更改 in-tree 型別的儲存程式碼,使用者必須更新 K8s 元件,成本較高
in-tree 儲存程式碼中的 bug 會引發 K8s 元件不穩定
K8s 社群需要負責維護及測試 in-tree 型別的儲存功能
in-tree 儲存外掛享有與 K8s 核心元件同等的特權,存在安全隱患
三方儲存開發者必須遵循 K8s 社群的規則開發 in-tree 型別儲存程式碼
CSI 容器儲存介面標準的出現解決了上述問題,將三方儲存程式碼與 K8s 程式碼解耦,使得三方儲存廠商研發人員只需實現 CSI 介面即可(無需關注容器平臺是 K8s 還是 Swarm 等)。
使用者建立一個包含PVC的Pod(使用動態儲存卷);
PV Controller發現這個PVC處於待繫結狀態,呼叫Volume Plugin(in-tree或者out-of-tree)建立儲存卷,並建立PV物件,然後將建立的PV與PVC繫結;
Scheduler根據Pod的設定、節點狀態、PV設定等資訊,把Pod排程刀worker節點Node上;
AD Controller發現Pod和PVC處於待掛載狀態,呼叫Volume Plugin(in-tree或者out-of-tree)實現裝置掛載到目標節點(/dev/vdb);
在worker節點上,kubelet(Volume Manager)等待裝置掛載完成,通過Volume Plugin將裝置掛載到指定目錄:/var/lib/kubelet/pods/646154cf-dc72-11e9-b200-00163e007d53/volumes/alicloud~disk/pv-disk;
kubelet在被告知掛載目錄準備好後,啟動Pod中的containers,用Docker -v方式(bind)將已經掛載到原生的卷對映到容器中;
CSI 主要包含兩個部分:CSI Controller Server 與 CSI Node Server,分別對應Controller Server Pod和Node Server Pod
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 Pod 是個 DaemonSet,它會在每個節點上進行註冊。
Kubelet 會直接通過 Socket 的方式直接和 CSI Node Server 進行通訊、呼叫 Attach/Detach/Mount/Unmount 等。
Driver Registrar 只是做一個註冊的功能,會在每個節點上進行部署。
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 通常是部署在一個容器裡面的,它們是以 daemonset 的形式執行在叢集裡面,保證每一個節點會有一個 Pod 部署出來,這兩個元件會和 CSI Controller 一起完成 volume 的 mount 操作。
CSI Identity 是用來告訴 Controller,我現在是哪一個 CSI 外掛,它實現的介面會被 node-driver-registrar 呼叫給 Controller 去註冊自己。
CSI Node 會實現一些 publish volume 和 unpublished volume 的介面,Controller 會去呼叫來完成 volume 的 mount 的操作,我們只需要實現這幾個外掛的介面就可以了。
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 用於定義和設定 CSI 驅動程式的屬性和行為,是叢集範圍的資源物件。它描述了叢集中所部署的 CSI Plugin 列表,需要管理員根據外掛型別進行建立。CSIDriver 物件是叢集範圍的,即在整個叢集中共用和使用;
可以通過 kuberctl get csidriver
可以看到叢集裡面建立的各種型別的 CSI Driver
❯ kubectl get csidrivers.csi.storage.k8s.io
NAME AGE
udisk.csi.ucloud.cn 4h9m
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
K8s 中的 Pod 在掛載儲存卷時需經歷三個的階段
Provision/Delete(創盤/刪盤)
Attach/Detach(掛接/摘除)
Mount/Unmount(掛載/解除安裝)
創盤由External Provisioner來完成
叢集管理員建立 StorageClass 資源,該 StorageClass 中包含 CSI 外掛名稱;
使用者建立 PVC 資源,PVC 指定儲存大小及 StorageClass;
卷控制器(PV Controller)觀察到叢集中新建立的 PVC 沒有與之匹配的 PV,且其使用的儲存型別為 out-of-tree,於是為 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 外掛名稱]
External Provisioner 元件觀察到 PVC 的 annotation 中包含 volume.beta.kubernetes.io/storage-provisioner
且其 value 是自己,於是開始創盤流程:
獲取相關 StorageClass 資源並從中獲取引數,用於後面 CSI 函數呼叫
通過 unix domain socket 呼叫外部 CSI 外掛的CreateVolume 函數
外部 CSI 外掛返回成功後表示盤建立完成,此時External Provisioner 元件會在叢集建立一個 PersistentVolume 資源。
卷控制器會將 PV 與 PVC 進行繫結。
掛接由External Attacher完成
AD 控制器(AttachDetachController)觀察到使用 CSI 型別 PV 的 Pod 被排程到某一節點,此時AD 控制器會呼叫內部 in-tree CSI 外掛(csiAttacher)的 Attach 函數;
內部 in-tree CSI 外掛(csiAttacher)會建立一個 VolumeAttachment 物件到叢集中;
External Attacher 觀察到該 VolumeAttachment 物件,並呼叫外部 CSI外掛的ControllerPublish 函數以將卷掛接到對應節點上。當外部 CSI 外掛掛載成功後,External Attacher會更新相關 VolumeAttachment 物件的 .Status.Attached 為 true;
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
掛載由kubelet來完成
Volume Manager(Kubelet 元件)觀察到有新的使用 CSI 型別 PV 的 Pod 排程到本節點上,於是呼叫內部 in-tree CSI 外掛(csiAttacher)的 WaitForAttach 函數;
內部 in-tree CSI 外掛(csiAttacher)等待叢集中 VolumeAttachment 物件狀態 .Status.Attached 變為 true;
in-tree CSI 外掛(csiAttacher)呼叫 MountDevice 函數,該函數內部通過 unix domain socket 呼叫外部 CSI 外掛的NodeStageVolume 函數;之後外掛(csiAttacher)呼叫內部 in-tree CSI 外掛(csiMountMgr)的 SetUp 函數,該函數內部會通過 unix domain socket 呼叫外部 CSI 外掛的NodePublishVolume 函數;
使用者刪除相關 Pod;
Volume Manager(Kubelet 元件)觀察到包含 CSI 儲存卷的 Pod 被刪除,於是呼叫內部 in-tree CSI 外掛(csiMountMgr)的 TearDown 函數,該函數內部會通過 unix domain socket 呼叫外部 CSI 外掛的 NodeUnpublishVolume 函數;
Volume Manager(Kubelet 元件)呼叫內部 in-tree CSI 外掛(csiAttacher)的 UnmountDevice 函數,該函數內部會通過 unix domain socket 呼叫外部 CSI 外掛的 NodeUnpublishVolume 函數。
AD 控制器觀察到包含 CSI 儲存卷的 Pod 被刪除,此時該控制器會呼叫內部 in-tree CSI 外掛(csiAttacher)的 Detach 函數;
csiAttacher會刪除叢集中相關 VolumeAttachment 物件(但由於存在 finalizer,va 物件不會立即刪除);
External Attacher觀察到叢集中 VolumeAttachment 物件的 DeletionTimestamp 非空,於是呼叫外部 CSI 外掛的ControllerUnpublish 函數以將卷從對應節點上摘除。外部 CSI 外掛摘除成功後,External Attacher會移除相關 VolumeAttachment 物件的 finalizer 欄位,此時 VolumeAttachment 物件被徹底刪除;
AD 控制器中內部 in-tree CSI 外掛(csiAttacher)觀察到 VolumeAttachment 物件已刪除,於是更新AD 控制器中的內部狀態;同時AD 控制器更新 Node 資源,此時 Node 資源的 .Status.VolumesAttached 上已沒有相關掛接資訊;
使用者刪除相關 PVC;
External Provisioner 元件觀察到 PVC 刪除事件,根據 PVC 的回收策略(Reclaim)執行不同操作:
Delete:呼叫外部 CSI 外掛的DeleteVolume 函數以刪除卷;一旦捲成功刪除,Provisioner會刪除叢集中對應 PV 物件;
Retain:Provisioner不執行卷刪除操作;
sidecar元件由Kubernetes官方維護
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 在整個叢集範圍內工作,負責自動註冊和更新 CSI 驅動程式的資訊。
Node Driver Registrar 在每個節點上執行,負責啟動和管理該節點上的 CSI 驅動程式,並處理與儲存卷附加和解除安裝相關的操作。
Node-Driver-Registrar 元件會將外部 CSI 外掛註冊到Kubelet,從而使Kubelet通過特定的 Unix Domain Socket 來呼叫外部 CSI 外掛函數
Kubelet 會呼叫外部 CSI 外掛的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函數
Kubelet為本節點 Node 資源打 annotation:Kubelet呼叫外部 CSI 外掛的NodeGetInfo 函數,其返回值 [nodeID]、[driverName] 將作為值用於 「csi.volume.kubernetes.io/nodeid
」 鍵;
Kubelet更新 Node Label:將NodeGetInfo 函數返回的 [AccessibleTopology] 值用於節點的 Label;
Kubelet更新 Node Status:將NodeGetInfo 函數返回的 maxAttachLimit(節點最大可掛載卷數量)更新到 Node 資源的 Status.Allocatable:attachable-volumes-csi-[driverName]=[maxAttachLimit]
Kubelet更新 CSINode 資源(沒有則建立):將 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓撲僅保留 Key 值);
External Provisioner用於建立/刪除實際的儲存卷,以及代表儲存卷的 PV 資源。
External-Provisioner在啟動時需指定引數 — provisioner,該引數指定 Provisioner 名稱,與 StorageClass 中的 provisioner 欄位對應。
External-Provisioner啟動後會 watch 叢集中的 PVC 和 PV 資源:
1)對於PVC資源:
判斷 PVC 是否需要動態建立儲存卷,標準如下:
PVC 的 annotation 中是否包含 「volume.beta.kubernetes.io/storage-provisioner
」 鍵(由卷控制器建立)並且其值是否與 Provisioner 名稱相等
PVC 對應 StorageClass 的 VolumeBindingMode 欄位:
若為 WaitForFirstConsumer,則 PVC 的 annotation 中必須包含 「volume.kubernetes.io/selected-node」 鍵,且其值不為空;
若為 Immediate 則表示需要 Provisioner 立即提供動態儲存卷;
通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 CreateVolume 函數;
建立 PV 資源,PV 名稱為 [Provisioner 指定的 PV 字首] – [PVC uuid]
2)對於PV資源:
判斷 PV 是否需要刪除,標準如下:
判斷其 .Status.Phase 是否為 Release;
判斷其 .Spec.PersistentVolumeReclaimPolicy 是否為 Delete;
判斷其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否為自己;
通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 DeleteVolume 介面;
刪除叢集中的 PV 資源;
External Attacher用於掛接/摘除儲存卷
External-Attacher 內部會時刻 watch 叢集中的 VolumeAttachment 資源和 PersistentVolume 資源
1)對於VolumeAttachment資源:
從 VolumeAttachment 資源中獲得 PV 的所有資訊,如 volume ID、node ID、掛載 Secret 等
判斷 VolumeAttachment 的 DeletionTimestamp 欄位是否為空來判斷其為卷掛接或卷摘除;
若為卷掛接則通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的ControllerPublishVolume 介面;
若為卷摘除則通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的ControllerUnpublishVolume 介面;
2)對於PV資源:
在掛接時為相關 PV 打上 Finalizer:external-attacher/[driver 名稱]
當 PV 處於刪除狀態時(DeletionTimestamp 非空),刪除 Finalizer:external-attacher/[driver 名稱]
External Resizer用於擴容儲存卷
External-Resizer內部會 watch 叢集中的 PersistentVolumeClaim 資源
判斷 PersistentVolumeClaim 資源是否需要擴容:PVC 狀態需要是 Bound 且 .Status.Capacity 與 .Spec.Resources.Requests 不等
更新 PVC 的 .Status.Conditions,表明此時處於 Resizing 狀態
通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 ControllerExpandVolume 介面
更新 PV 的 .Spec.Capacity
若 CSI 支援檔案系統線上擴容,ControllerExpandVolume 介面返回值中 NodeExpansionRequired 欄位為 true,External-Resizer更新 PVC 的 .Status.Conditions 為 FileSystemResizePending 狀態;
若不支援則擴容成功,External-Resizer更新 PVC 的 .Status.Conditions 為空,且更新 PVC 的 .Status.Capacity
Volume Manager(Kubelet 元件)觀察到儲存卷需線上擴容,於是通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的NodeExpandVolume 介面實現檔案系統擴容
livenessprobe用於檢查CSI外掛是否正常
通過對外暴露一個 / healthz HTTP 埠以服務 kubelet 的探針探測器,內部是通過特定的 Unix Domain Socket 呼叫外部 CSI 外掛的 Probe 介面
IdentityServer 主要用於認證 CSI 外掛的身份資訊
ControllerServer 主要負責儲存卷及快照的建立/刪除以及掛接/摘除操作
NodeServer 主要負責儲存卷掛載/解除安裝操作