一文讀懂容器存儲(chǔ)接口 CSI
背景
K8s 原生支持一些存儲(chǔ)類型的 PV,如 iSCSI、NFS、CephFS 等等(詳見(jiàn)鏈接),這些 in-tree 類型的存儲(chǔ)代碼放在 Kubernetes 代碼倉(cāng)庫(kù)中。這里帶來(lái)的問(wèn)題是 K8s 代碼與三方存儲(chǔ)廠商的代碼強(qiáng)耦合:
- 更改 in-tree 類型的存儲(chǔ)代碼,用戶必須更新 K8s 組件,成本較高
- in-tree 存儲(chǔ)代碼中的 bug 會(huì)引發(fā) K8s 組件不穩(wěn)定
- K8s 社區(qū)需要負(fù)責(zé)維護(hù)及測(cè)試 in-tree 類型的存儲(chǔ)功能
- in-tree 存儲(chǔ)插件享有與 K8s 核心組件同等的特權(quán),存在安全隱患
三方存儲(chǔ)開(kāi)發(fā)者必須遵循 K8s 社區(qū)的規(guī)則開(kāi)發(fā) in-tree 類型存儲(chǔ)代碼
CSI 容器存儲(chǔ)接口標(biāo)準(zhǔn)的出現(xiàn)解決了上述問(wèn)題,將三方存儲(chǔ)代碼與 K8s 代碼解耦,使得三方存儲(chǔ)廠商研發(fā)人員只需實(shí)現(xiàn) CSI 接口(無(wú)需關(guān)注容器平臺(tái)是 K8s 還是 Swarm 等)。
CSI 核心流程介紹
在詳細(xì)介紹 CSI 組件及其接口之前,我們先對(duì) K8s 中 CSI 存儲(chǔ)流程進(jìn)行一個(gè)介紹。《一文讀懂 K8s 持久化存儲(chǔ)流程》一文介紹了 K8s 中的 Pod 在掛載存儲(chǔ)卷時(shí)需經(jīng)歷三個(gè)的階段:Provision/Delete(創(chuàng)盤(pán)/刪盤(pán))、Attach/Detach(掛接/摘除)和 Mount/Unmount(掛載/卸載),下面以圖文的方式講解 K8s 在這三個(gè)階段使用 CSI 的流程。
1. Provisioning Volumes
1.集群管理員創(chuàng)建 StorageClass 資源,該 StorageClass 中包含 CSI 插件名稱(provisioner:pangu.csi.alibabacloud.com)以及存儲(chǔ)類必須的參數(shù)(parameters: type=cloud_ssd)。sc.yaml 文件如下:
2.用戶創(chuàng)建 PersistentVolumeClaim 資源,PVC 指定存儲(chǔ)大小及 StorageClass(如上)。pvc.yaml 文件如下:
3.卷控制器(PersistentVolumeController)觀察到集群中新創(chuàng)建的 PVC 沒(méi)有與之匹配的 PV,且其使用的存儲(chǔ)類型為 out-of-tree,于是為 PVC 打 annotation:volume.beta.kubernetes.io/storage-provisioner=[out-of-tree CSI 插件名稱](本例中即為 provisioner:pangu.csi.alibabacloud.com)。
4.External Provisioner 組件觀察到 PVC 的 annotation 中包含 "volume.beta.kubernetes.io/storage-provisioner" 且其 value 是自己,于是開(kāi)始創(chuàng)盤(pán)流程。
獲取相關(guān) StorageClass 資源并從中獲取參數(shù)(本例中 parameters 為 type=cloud_ssd),用于后面 CSI 函數(shù)調(diào)用。
通過(guò) unix domain socket 調(diào)用外部 CSI 插件的CreateVolume 函數(shù)。
5.外部 CSI 插件返回成功后表示盤(pán)創(chuàng)建完成,此時(shí)External Provisioner 組件會(huì)在集群創(chuàng)建一個(gè) PersistentVolume 資源。
6.卷控制器會(huì)將 PV 與 PVC 進(jìn)行綁定。
2. Attaching Volumes
1.AD 控制器(AttachDetachController)觀察到使用 CSI 類型 PV 的 Pod 被調(diào)度到某一節(jié)點(diǎn),此時(shí)AD 控制器會(huì)調(diào)用內(nèi)部 in-tree CSI 插件(csiAttacher)的 Attach 函數(shù)。
2.內(nèi)部 in-tree CSI 插件(csiAttacher)會(huì)創(chuàng)建一個(gè) VolumeAttachment 對(duì)象到集群中。
3.External Attacher 觀察到該 VolumeAttachment 對(duì)象,并調(diào)用外部 CSI插件的ControllerPublish 函數(shù)以將卷掛接到對(duì)應(yīng)節(jié)點(diǎn)上。外部 CSI 插件掛載成功后,External Attacher會(huì)更新相關(guān) VolumeAttachment 對(duì)象的 .Status.Attached 為 true。
4.AD 控制器內(nèi)部 in-tree CSI 插件(csiAttacher)觀察到 VolumeAttachment 對(duì)象的 .Status.Attached 設(shè)置為 true,于是更新AD 控制器內(nèi)部狀態(tài)(ActualStateOfWorld),該狀態(tài)會(huì)顯示在 Node 資源的 .Status.VolumesAttached 上。
3. Mounting Volumes
1.Volume Manager(Kubelet 組件)觀察到有新的使用 CSI 類型 PV 的 Pod 調(diào)度到本節(jié)點(diǎn)上,于是調(diào)用內(nèi)部 in-tree CSI 插件(csiAttacher)的 WaitForAttach 函數(shù)。
2.內(nèi)部 in-tree CSI 插件(csiAttacher)等待集群中 VolumeAttachment 對(duì)象狀態(tài) .Status.Attached 變?yōu)?true。
3.in-tree CSI 插件(csiAttacher)調(diào)用 MountDevice 函數(shù),該函數(shù)內(nèi)部通過(guò) unix domain socket 調(diào)用外部 CSI 插件的NodeStageVolume 函數(shù);之后插件(csiAttacher)調(diào)用內(nèi)部 in-tree CSI 插件(csiMountMgr)的 SetUp 函數(shù),該函數(shù)內(nèi)部會(huì)通過(guò) unix domain socket 調(diào)用外部 CSI 插件的NodePublishVolume 函數(shù)。
4. Unmounting Volumes
1.用戶刪除相關(guān) Pod。
2.Volume Manager(Kubelet 組件)觀察到包含 CSI 存儲(chǔ)卷的 Pod 被刪除,于是調(diào)用內(nèi)部 in-tree CSI 插件(csiMountMgr)的 TearDown 函數(shù),該函數(shù)內(nèi)部會(huì)通過(guò) unix domain socket 調(diào)用外部 CSI 插件的 NodeUnpublishVolume 函數(shù)。
3.Volume Manager(Kubelet 組件)調(diào)用內(nèi)部 in-tree CSI 插件(csiAttacher)的 UnmountDevice 函數(shù),該函數(shù)內(nèi)部會(huì)通過(guò) unix domain socket 調(diào)用外部 CSI 插件的 NodeUnpublishVolume 函數(shù)。
5. Detaching Volumes
1.AD 控制器觀察到包含 CSI 存儲(chǔ)卷的 Pod 被刪除,此時(shí)該控制器會(huì)調(diào)用內(nèi)部 in-tree CSI 插件(csiAttacher)的 Detach 函數(shù)。
2.csiAttacher會(huì)刪除集群中相關(guān) VolumeAttachment 對(duì)象(但由于存在 finalizer,va 對(duì)象不會(huì)立即刪除)。
3.External Attacher觀察到集群中 VolumeAttachment 對(duì)象的 DeletionTimestamp 非空,于是調(diào)用外部 CSI 插件的ControllerUnpublish 函數(shù)以將卷從對(duì)應(yīng)節(jié)點(diǎn)上摘除。外部 CSI 插件摘除成功后,External Attacher會(huì)移除相關(guān) VolumeAttachment 對(duì)象的 finalizer 字段,此時(shí) VolumeAttachment 對(duì)象被徹底刪除。
4.AD 控制器中內(nèi)部 in-tree CSI 插件(csiAttacher)觀察到 VolumeAttachment 對(duì)象已刪除,于是更新AD 控制器中的內(nèi)部狀態(tài);同時(shí)AD 控制器更新 Node 資源,此時(shí) Node 資源的 .Status.VolumesAttached 上已沒(méi)有相關(guān)掛接信息。
6. Deleting Volumes
1.用戶刪除相關(guān) PVC。
2.External Provisioner 組件觀察到 PVC 刪除事件,根據(jù) PVC 的回收策略(Reclaim)執(zhí)行不同操作:
Delete:調(diào)用外部 CSI 插件的DeleteVolume 函數(shù)以刪除卷;一旦卷成功刪除,Provisioner會(huì)刪除集群中對(duì)應(yīng) PV 對(duì)象。
Retain:Provisioner不執(zhí)行卷刪除操作。
CSI Sidecar 組件介紹
為使 K8s 適配 CSI 標(biāo)準(zhǔn),社區(qū)將與 K8s 相關(guān)的存儲(chǔ)流程邏輯放在了 CSI Sidecar 組件中。
1. Node Driver Registrar
1)功能
Node-Driver-Registrar 組件會(huì)將外部 CSI 插件注冊(cè)到Kubelet,從而使Kubelet通過(guò)特定的 Unix Domain Socket 來(lái)調(diào)用外部 CSI 插件函數(shù)(Kubelet 會(huì)調(diào)用外部 CSI 插件的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函數(shù))。
2)原理
Node-Driver-Registrar 組件通過(guò)Kubelet 外部插件注冊(cè)機(jī)制實(shí)現(xiàn)注冊(cè),注冊(cè)成功后:
- Kubelet為本節(jié)點(diǎn) Node 資源打 annotation:Kubelet調(diào)用外部 CSI 插件的NodeGetInfo 函數(shù),其返回值 [nodeID]、[driverName] 將作為值用于 "csi.volume.kubernetes.io/nodeid" 鍵。
- Kubelet更新 Node Label:將NodeGetInfo 函數(shù)返回的 [AccessibleTopology] 值用于節(jié)點(diǎn)的 Label。
- Kubelet更新 Node Status:將NodeGetInfo 函數(shù)返回的 maxAttachLimit(節(jié)點(diǎn)最大可掛載卷數(shù)量)更新到 Node 資源的 Status.Allocatable:attachable-volumes-csi-[driverName]=[maxAttachLimit]。
- Kubelet更新 CSINode 資源(沒(méi)有則創(chuàng)建):將 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓?fù)鋬H保留 Key 值)。
2. External Provisioner
1)功能
創(chuàng)建/刪除實(shí)際的存儲(chǔ)卷,以及代表存儲(chǔ)卷的 PV 資源。
2)原理
External-Provisioner在啟動(dòng)時(shí)需指定參數(shù) -- provisioner,該參數(shù)指定 Provisioner 名稱,與 StorageClass 中的 provisioner 字段對(duì)應(yīng)。
External-Provisioner啟動(dòng)后會(huì) watch 集群中的 PVC 和 PV 資源。
對(duì)于集群中的 PVC 資源:
- 判斷 PVC 是否需要?jiǎng)討B(tài)創(chuàng)建存儲(chǔ)卷,標(biāo)準(zhǔn)如下:PVC 的 annotation 中是否包含 "volume.beta.kubernetes.io/storage-provisioner" 鍵(由卷控制器創(chuàng)建),并且其值是否與 Provisioner 名稱相等。PVC 對(duì)應(yīng) StorageClass 的 VolumeBindingMode 字段若為 WaitForFirstConsumer,則 PVC 的 annotation 中必須包含 "volume.kubernetes.io/selected-node" 鍵(詳見(jiàn)調(diào)度器如何處理 WaitForFirstConsumer),且其值不為空;若為 Immediate 則表示需要 Provisioner 立即提供動(dòng)態(tài)存儲(chǔ)卷。
- 通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的 CreateVolume 函數(shù)。
- 創(chuàng)建 PV 資源,PV 名稱為 [Provisioner 指定的 PV 前綴] - [PVC uuid]。
對(duì)于集群中的 PV 資源:
- 判斷 PV 是否需要?jiǎng)h除,標(biāo)準(zhǔn)如下:判斷其 .Status.Phase 是否為 Release。判斷其 .Spec.PersistentVolumeReclaimPolicy 是否為 Delete。判斷其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否為自己。
- 通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的 DeleteVolume 接口。
- 刪除集群中的 PV 資源。
3. External Attacher
1)功能
掛接/摘除存儲(chǔ)卷。
2)原理
External-Attacher 內(nèi)部會(huì)時(shí)刻 watch 集群中的 VolumeAttachment 資源和 PersistentVolume 資源。
對(duì)于 VolumeAttachment 資源:
- 從 VolumeAttachment 資源中獲得 PV 的所有信息,如 volume ID、node ID、掛載 Secret 等。
- 判斷 VolumeAttachment 的 DeletionTimestamp 字段是否為空來(lái)判斷其為卷掛接或卷摘除:若為卷掛接則通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的ControllerPublishVolume 接口;若為卷摘除則通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的ControllerUnpublishVolume 接口。
對(duì)于 PersistentVolume 資源:
- 在掛接時(shí)為相關(guān) PV 打上 Finalizer:external-attacher/[driver 名稱]。
- 當(dāng) PV 處于刪除狀態(tài)時(shí)(DeletionTimestamp 非空),刪除 Finalizer:external-attacher/[driver 名稱]。
4. External Resizer
1)功能
擴(kuò)容存儲(chǔ)卷。
2)原理
External-Resizer內(nèi)部會(huì) watch 集群中的 PersistentVolumeClaim 資源。
對(duì)于 PersistentVolumeClaim 資源:
- 判斷 PersistentVolumeClaim 資源是否需要擴(kuò)容:PVC 狀態(tài)需要是 Bound 且 .Status.Capacity 與 .Spec.Resources.Requests 不等。
- 更新 PVC 的 .Status.Conditions,表明此時(shí)處于 Resizing 狀態(tài)。
- 通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的 ControllerExpandVolume 接口。
- 更新 PV 的 .Spec.Capacity。
- 若 CSI 支持文件系統(tǒng)在線擴(kuò)容,ControllerExpandVolume 接口返回值中 NodeExpansionRequired 字段為 true,External-Resizer更新 PVC 的 .Status.Conditions 為 FileSystemResizePending 狀態(tài);若不支持則擴(kuò)容成功,External-Resizer更新 PVC 的 .Status.Conditions 為空,且更新 PVC 的 .Status.Capacity。
Volume Manager(Kubelet 組件)觀察到存儲(chǔ)卷需在線擴(kuò)容,于是通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的NodeExpandVolume 接口實(shí)現(xiàn)文件系統(tǒng)擴(kuò)容。
5. livenessprobe
1)功能
檢查 CSI 插件是否正常。
2)原理
通過(guò)對(duì)外暴露一個(gè) / healthz HTTP 端口以服務(wù) kubelet 的探針探測(cè)器,內(nèi)部是通過(guò)特定的 Unix Domain Socket 調(diào)用外部 CSI 插件的 Probe 接口。
CSI 接口介紹
三方存儲(chǔ)廠商需實(shí)現(xiàn) CSI 插件的三大接口:IdentityServer、ControllerServer、NodeServer。
1. IdentityServer
IdentityServer 主要用于認(rèn)證 CSI 插件的身份信息。
// IdentityServer is the server API for Identity service.type IdentityServer interface { // 獲取CSI插件的信息,比如名稱、版本號(hào) GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error) // 獲取CSI插件提供的能力,比如是否提供ControllerService能力 GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error) // 獲取CSI插件健康狀況 Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)}
2. ControllerServer
ControllerServer 主要負(fù)責(zé)存儲(chǔ)卷及快照的創(chuàng)建/刪除以及掛接/摘除操作。
// ControllerServer is the server API for Controller service.type ControllerServer interface { // 創(chuàng)建存儲(chǔ)卷 CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error) // 刪除存儲(chǔ)卷 DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error) // 掛接存儲(chǔ)卷到特定節(jié)點(diǎn) ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error) // 從特定節(jié)點(diǎn)摘除存儲(chǔ)卷 ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error) // 驗(yàn)證存儲(chǔ)卷能力是否滿足要求,比如是否支持跨節(jié)點(diǎn)多讀多寫(xiě) ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error) // 列舉全部存儲(chǔ)卷信息 ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error) // 獲取存儲(chǔ)資源池可用空間大小 GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error) // 獲取ControllerServer支持功能點(diǎn),比如是否支持快照能力 ControllerGetCapabilities(context.Context, *ControllerGetCapabilitiesRequest) (*ControllerGetCapabilitiesResponse, error) // 創(chuàng)建快照 CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error) // 刪除快照 DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*DeleteSnapshotResponse, error) // 獲取所有快照信息 ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error) // 擴(kuò)容存儲(chǔ)卷 ControllerExpandVolume(context.Context, *ControllerExpandVolumeRequest) (*ControllerExpandVolumeResponse, error)}
3. NodeServer
NodeServer 主要負(fù)責(zé)存儲(chǔ)卷掛載/卸載操作。
// NodeServer is the server API for Node service.type NodeServer interface { // 將存儲(chǔ)卷格式化并掛載至臨時(shí)全局目錄 NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error) // 將存儲(chǔ)卷從臨時(shí)全局目錄卸載 NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error) // 將存儲(chǔ)卷從臨時(shí)目錄bind-mount到目標(biāo)目錄 NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error) // 將存儲(chǔ)卷從目標(biāo)目錄卸載 NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error) // 獲取存儲(chǔ)卷的容量信息 NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error) // 存儲(chǔ)卷擴(kuò)容 NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error) // 獲取NodeServer支持功能點(diǎn),比如是否支持獲取存儲(chǔ)卷容量信息 NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error) // 獲取CSI節(jié)點(diǎn)信息,比如最大支持卷個(gè)數(shù) NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)}K8s CSI API 對(duì)象
K8s 為支持 CSI 標(biāo)準(zhǔn),包含如下 API 對(duì)象:
CSINode
CSIDriver
VolumeAttachment
1. CSINode
apiVersion: storage.k8s.io/v1beta1kind: CSINodemetadata: name: node-10.212.101.210spec: drivers: - name: yodaplugin.csi.alibabacloud.com nodeID: node-10.212.101.210 topologyKeys: - kubernetes.io/hostname - name: pangu.csi.alibabacloud.com nodeID: a5441fd9013042ee8104a674e4a9666a topologyKeys: - topology.pangu.csi.alibabacloud.com/zone作用:
判斷外部 CSI 插件是否注冊(cè)成功。在 Node Driver Registrar 組件向 Kubelet 注冊(cè)完畢后,Kubelet 會(huì)創(chuàng)建該資源,故不需要顯式創(chuàng)建 CSINode 資源。
將 Kubernetes 中 Node 資源名稱與三方存儲(chǔ)系統(tǒng)中節(jié)點(diǎn)名稱(nodeID)一一對(duì)應(yīng)。此處Kubelet會(huì)調(diào)用外部 CSI 插件NodeServer 的 GetNodeInfo 函數(shù)獲取 nodeID。
顯示卷拓?fù)湫畔?。CSINode 中 topologyKeys 用來(lái)表示存儲(chǔ)節(jié)點(diǎn)的拓?fù)湫畔?,卷拓?fù)湫畔?huì)使得Scheduler在 Pod 調(diào)度時(shí)選擇合適的存儲(chǔ)節(jié)點(diǎn)。
2. CSIDriver
apiVersion: storage.k8s.io/v1beta1kind: CSIDrivermetadata: name: pangu.csi.alibabacloud.comspec: # 插件是否支持卷掛接(VolumeAttach) attachRequired: true # Mount階段是否CSI插件需要Pod信息 podInfoOnMount: true # 指定CSI支持的卷模式 volumeLifecycleModes: - Persistent作用:
簡(jiǎn)化外部 CSI 插件的發(fā)現(xiàn)。由集群管理員創(chuàng)建,通過(guò) kubectl get csidriver 即可得知環(huán)境上有哪些 CSI 插件。
自定 義Kubernetes 行為,如一些外部 CSI 插件不需要執(zhí)行卷掛接(VolumeAttach)操作,則可以設(shè)置 .spec.attachRequired 為 false。
3. VolumeAttachment
apiVersion: storage.k8s.io/v1kind: VolumeAttachmentmetadata: annotations: csi.alpha.kubernetes.io/node-id: 21481ae252a2457f9abcb86a3d02ba05 finalizers: - external-attacher/pangu-csi-alibabacloud-com name: csi-0996e5e9459e1ccc1b3a7aba07df4ef7301c8e283d99eabc1b69626b119ce750spec: attacher: pangu.csi.alibabacloud.com nodeName: node-10.212.101.241 source: persistentVolumeName: pangu-39aa24e7-8877-11eb-b02f-021234350de1status: attached: true作用:VolumeAttachment 記錄了存儲(chǔ)卷的掛接/摘除信息以及節(jié)點(diǎn)信息。
支持特性
1. 拓?fù)渲С?/strong>
在 StorageClass 中有 AllowedTopologies 字段:
- apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: csi-panguprovisioner: pangu.csi.alibabacloud.comparameters: type: cloud_ssdvolumeBindingMode: ImmediateallowedTopologies:- matchLabelExpressions: - key: topology.pangu.csi.alibabacloud.com/zone values: - zone-1 - zone-2外部 CSI 插件部署后會(huì)為每個(gè)節(jié)點(diǎn)打標(biāo),打標(biāo)內(nèi)容NodeGetInfo 函數(shù)返回的 [AccessibleTopology] 值(詳見(jiàn) Node Driver Registrar 部分)。
- External Provisioner在調(diào)用 CSI 插件的 CreateVolume 接口之前,會(huì)在請(qǐng)求參數(shù)設(shè)置 AccessibilityRequirements:
- 對(duì)于 WaitForFirstConsumer當(dāng) PVC 的 anno 中包含 "volume.kubernetes.io/selected-node" 且不為空,則先獲取對(duì)應(yīng)節(jié)點(diǎn) CSINode 的 TopologyKeys,然后根據(jù)該 TopologyKeys 鍵從 Node 資源的 Label 獲取 Values 值,最后拿該 Values 值與 StorageClass 的 AllowedTopologies 比對(duì),判斷其是否包含于其中;若不包含則報(bào)錯(cuò)。
- 對(duì)于 Immediately將 StorageClass 的 AllowedTopologies 的值填進(jìn)來(lái),若 StorageClass 沒(méi)有設(shè)置 AllowedTopologies 則將所有包含 TopologyKeys 鍵的節(jié)點(diǎn) Value 添進(jìn)來(lái)。
Scheduler 如何處理使用存儲(chǔ)卷調(diào)度
基于社區(qū) 1.18 版本調(diào)度器
調(diào)度器的調(diào)度過(guò)程主要有如下三步:
- 預(yù)選(Filter):篩選滿足 Pod 調(diào)度要求的節(jié)點(diǎn)列表。
- 優(yōu)選(Score):通過(guò)內(nèi)部的優(yōu)選算法為節(jié)點(diǎn)打分,獲得最高分?jǐn)?shù)的節(jié)點(diǎn)即為選中的節(jié)點(diǎn)。
- 綁定(Bind):調(diào)度器將調(diào)度結(jié)果通知給 kube-apiserver,更新 Pod 的 .spec.nodeName 字段。
調(diào)度器預(yù)選階段:處理 Pod 的 PVC/PV 綁定關(guān)系以及動(dòng)態(tài)供應(yīng) PV(Dynamic Provisioning),同時(shí)使調(diào)度器調(diào)度時(shí)考慮 Pod 所使用 PV 的節(jié)點(diǎn)親和性。詳細(xì)調(diào)度過(guò)程如下:
Pod 不包含 PVC 直接跳過(guò)。
- FindPodVolumes獲取 Pod 的 boundClaims、claimsToBind 以及 unboundClaimsImmediate。boundClaims:已 Bound 的 PVCclaimsToBind:PVC 對(duì)應(yīng) StorageClass 的 VolumeBindingMode 為 VolumeBindingWaitForFirstConsumerunboundClaimsImmediate:PVC 對(duì)應(yīng) StorageClass 的 VolumeBindingMode 為 VolumeBindingImmediate若 len(unboundClaimsImmediate) 不為空,表示這種 PVC 需要立即綁定 PV(即存 PVC 創(chuàng)建后,立刻動(dòng)態(tài)創(chuàng)建 PV 并將其綁定到 PVC,該過(guò)程不走調(diào)度),若 PVC 處于 unbound 階段則報(bào)錯(cuò)。若 len(boundClaims) 不為空,則檢查 PVC 對(duì)應(yīng) PV 的節(jié)點(diǎn)親和性與當(dāng)前節(jié)點(diǎn)的 Label 是否沖突,若沖突則報(bào)錯(cuò)(可檢查 Immediate 類型的 PV 拓?fù)洌?。?len(claimsToBind) 不為空先檢查環(huán)境中已有的 PV 能否與該 PVC 匹配(findMatchingVolumes),將能夠匹配 PVC 的 PV 記錄在調(diào)度器的 cache 中。未匹配到 PV 的 PVC 走動(dòng)態(tài)調(diào)度流程,動(dòng)態(tài)調(diào)度主要通過(guò) StorageClass 的 AllowedTopologies 字段判斷當(dāng)前調(diào)度節(jié)點(diǎn)是否滿足拓?fù)湟螅ㄡ槍?duì) WaitForFirstConsumer 類型的 PVC)。
- 調(diào)度器優(yōu)選階段不討論。
- 調(diào)度器 Assume 階段
- 調(diào)度器會(huì)先 Assume PV/PVC,再 Assume Pod。
- 將當(dāng)前待調(diào)度的 Pod 進(jìn)行深拷貝。
- AssumePodVolumes(針對(duì) WaitForFirstConsumer 類型的 PVC)更改調(diào)度器 cache 中已經(jīng) Match 的 PV 信息:設(shè)置 annotation:pv.kubernetes.io/bound-by-controller="yes"。更改調(diào)度器 cache 中未匹配到 PV 的 PVC,設(shè)置 annotation:volume.kubernetes.io/selected-node=【所選節(jié)點(diǎn)】。
- Assume Pod 完畢更改調(diào)度器 cache 中 Pod 的 .Spec.NodeName 為【所選節(jié)點(diǎn)】。
調(diào)度器 Bind 階段
BindPodVolumes:
- 調(diào)用 Kubernetes 的 API 更新集群中 PV/PVC 資源,使其與調(diào)度器 Cache 中的 PV/PVC 一致。
- 檢查 PV/PVC 狀態(tài):檢查所有 PVC 是否已處于 Bound 狀態(tài)。檢查所有 PV 的 NodeAffinity 是否與節(jié)點(diǎn) Label 沖突。
- 調(diào)度器執(zhí)行 Bind 操作:調(diào)用 Kubernetes 的 API 更新 Pod 的 .Spec.NodeName 字段。
2. 存儲(chǔ)卷擴(kuò)容
存儲(chǔ)卷擴(kuò)容部分在 External Resizer 部分已提到,故不再贅述。用戶只需要編輯 PVC 的 .Spec.Resources.Requests.Storage 字段即可,注意只可擴(kuò)容不可縮容。
若 PV 擴(kuò)容失敗,此時(shí) PVC 無(wú)法重新編輯 spec 字段的 storage 為原來(lái)的值(只可擴(kuò)容不可縮容)。
3. 單節(jié)點(diǎn)卷數(shù)量限制
卷數(shù)量限制在 Node Driver Registrar 部分已提到,故不再贅述。
4. 存儲(chǔ)卷監(jiān)控
存儲(chǔ)商需實(shí)現(xiàn) CSI 插件的 NodeGetVolumeStats 接口,Kubelet 會(huì)調(diào)用該函數(shù),并反映在其 metrics上:
- kubelet_volume_stats_capacity_bytes:存儲(chǔ)卷容量
- kubelet_volume_stats_used_bytes:存儲(chǔ)卷已使用容量
- kubelet_volume_stats_available_bytes:存儲(chǔ)卷可使用容量
- kubelet_volume_stats_inodes:存儲(chǔ)卷 inode 總量
- kubelet_volume_stats_inodes_used:存儲(chǔ)卷 inode 使用量
- kubelet_volume_stats_inodes_free:存儲(chǔ)卷 inode 剩余量
5. Secret
CSI 存儲(chǔ)卷支持傳入 Secret 來(lái)處理不同流程中所需要的私密數(shù)據(jù),目前 StorageClass 支持如下 Parameter:
- csi.storage.k8s.io/provisioner-secret-name
- csi.storage.k8s.io/provisioner-secret-namespace
- csi.storage.k8s.io/controller-publish-secret-name
- csi.storage.k8s.io/controller-publish-secret-namespace
- csi.storage.k8s.io/node-stage-secret-name
- csi.storage.k8s.io/node-stage-secret-namespace
- csi.storage.k8s.io/node-publish-secret-name
- csi.storage.k8s.io/node-publish-secret-namespace
- csi.storage.k8s.io/controller-expand-secret-name
- csi.storage.k8s.io/controller-expand-secret-namespace
Secret 會(huì)包含在對(duì)應(yīng) CSI 接口的參數(shù)中,如對(duì)于 CreateVolume 接口而言則包含在 CreateVolumeRequest.Secrets 中。
6. 塊設(shè)備
apiVersion: apps/v1kind: StatefulSetmetadata: name: nginx-examplespec: selector: matchLabels: app: nginx serviceName: "nginx" volumeClaimTemplates: - metadata: name: html spec: accessModes: - ReadWriteOnce volumeMode: Block storageClassName: csi-pangu resources: requests: storage: 40Gi template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx volumeDevices: - devicePath: "/dev/vdb" name: html三方存儲(chǔ)廠商需實(shí)現(xiàn) NodePublishVolume 接口。Kubernetes 提供了針對(duì)塊設(shè)備的工具包("k8s.io/kubernetes/pkg/util/mount"),在 NodePublishVolume 階段可調(diào)用該工具的 EnsureBlock 和 MountBlock 函數(shù)。
7. 卷快照/卷克隆能力
鑒于本文篇幅,此處不做過(guò)多原理性介紹。讀者感興趣見(jiàn)官方介紹:卷快照、卷克隆。
總結(jié)
本文首先對(duì) CSI 核心流程進(jìn)行了大體介紹,并結(jié)合 CSI Sidecar 組件、CSI 接口、API 對(duì)象對(duì) CSI 標(biāo)準(zhǔn)進(jìn)行了深度解析。在 K8s 上,使用任何一種 CSI 存儲(chǔ)卷都離不開(kāi)上面的流程,環(huán)境上的容器存儲(chǔ)問(wèn)題也一定是其中某個(gè)環(huán)節(jié)出現(xiàn)了問(wèn)題。本文對(duì)其流程進(jìn)行梳理,以便于廣大程序猿(媛)排查環(huán)境問(wèn)題。