天天看點

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

作者 | 惠志

來源 |

阿裡巴巴雲原生公衆号
導讀:在 《一文讀懂 K8s 持久化存儲流程》 一文我們重點介紹了 K8s 内部的存儲流程,以及 PV、PVC、StorageClass、Kubelet 等之間的調用關系。接下來本文将将重點放在 CSI(Container Storage Interface)容器存儲接口上,探究什麼是 CSI 及其内部工作原理。

背景

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 等)。

CSI 核心流程介紹

在詳細介紹 CSI 元件及其接口之前,我們先對 K8s 中 CSI 存儲流程進行一個介紹。《一文讀懂 K8s 持久化存儲流程》一文介紹了 K8s 中的 Pod 在挂載存儲卷時需經曆三個的階段:Provision/Delete(創盤/删盤)、Attach/Detach(挂接/摘除)和 Mount/Unmount(挂載/解除安裝),下面以圖文的方式講解 K8s 在這三個階段使用 CSI 的流程。

1. Provisioning Volumes

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

1.叢集管理者建立 StorageClass 資源,該 StorageClass 中包含 CSI 插件名稱(provisioner:pangu.csi.alibabacloud.com)以及存儲類必須的參數(parameters: type=cloud_ssd)。sc.yaml 檔案如下:

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

2.使用者建立 PersistentVolumeClaim 資源,PVC 指定存儲大小及 StorageClass(如上)。pvc.yaml 檔案如下:

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

3.卷控制器(PersistentVolumeController)觀察到叢集中新建立的 PVC 沒有與之比對的 PV,且其使用的存儲類型為 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 是自己,于是開始創盤流程。

  • 擷取相關 StorageClass 資源并從中擷取參數(本例中 parameters 為  type=cloud_ssd),用于後面 CSI 函數調用。
  • 通過 unix domain socket 調用外部 CSI 插件的CreateVolume 函數。

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

6.卷控制器會将 PV 與 PVC 進行綁定。

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

2. Attaching Volumes

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

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。

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

4.AD 控制器内部 in-tree CSI 插件(csiAttacher)觀察到 VolumeAttachment 對象的 .Status.Attached 設定為 true,于是更新AD 控制器内部狀态(ActualStateOfWorld),該狀态會顯示在 Node 資源的 .Status.VolumesAttached 上。

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

3. Mounting Volumes

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

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 函數。

4. Unmounting Volumes

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

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 函數。

5. Detaching Volumes

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

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 上已沒有相關挂接資訊。

6. Deleting Volumes

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

1.使用者删除相關 PVC。

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

  • Delete:調用外部 CSI 插件的DeleteVolume 函數以删除卷;一旦卷成功删除,Provisioner會删除叢集中對應 PV 對象。
  • Retain:Provisioner不執行卷删除操作。

CSI Sidecar 元件介紹

為使 K8s 适配 CSI 标準,社群将與 K8s 相關的存儲流程邏輯放在了 CSI Sidecar 元件中。

1. Node Driver Registrar

1)功能

Node-Driver-Registrar 元件會将外部 CSI 插件注冊到Kubelet,進而使Kubelet通過特定的 Unix Domain Socket 來調用外部 CSI 插件函數(Kubelet 會調用外部 CSI 插件的 NodeGetInfo、NodeStageVolume、NodePublishVolume、NodeGetVolumeStats 等函數)。

2)原理

Node-Driver-Registrar 元件通過Kubelet 外部插件注冊機制實作注冊,注冊成功後:

  • 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]。
一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!
  • Kubelet更新 CSINode 資源(沒有則建立):将 [driverName]、[nodeID]、[maxAttachLimit]、[AccessibleTopology] 更新到 Spec 中(拓撲僅保留 Key 值)。

2. External Provisioner

建立/删除實際的存儲卷,以及代表存儲卷的 PV 資源。

External-Provisioner在啟動時需指定參數 -- provisioner,該參數指定 Provisioner 名稱,與 StorageClass 中的 provisioner 字段對應。

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

對于叢集中的 PVC 資源:

  • 判斷 PVC 是否需要動态建立存儲卷,标準如下:
    • PVC 的 annotation 中是否包含 "volume.beta.kubernetes.io/storage-provisioner" 鍵(由卷控制器建立),并且其值是否與 Provisioner 名稱相等。
    • PVC 對應 StorageClass 的 VolumeBindingMode 字段若為 WaitForFirstConsumer,則 PVC 的 annotation 中必須包含 "volume.kubernetes.io/selected-node" 鍵(詳見排程器如何處理 WaitForFirstConsumer),且其值不為空;若為 Immediate 則表示需要 Provisioner 立即提供動态存儲卷。
  • 通過特定的 Unix Domain Socket 調用外部 CSI 插件的 CreateVolume 函數。
  • 建立 PV 資源,PV 名稱為 [Provisioner 指定的 PV 字首] - [PVC uuid]。

對于叢集中的 PV 資源:

  • 判斷 PV 是否需要删除,标準如下:
    • 判斷其 .Status.Phase 是否為 Release。
    • 判斷其 .Spec.PersistentVolumeReclaimPolicy 是否為 Delete。
    • 判斷其是否包含 annotation(pv.kubernetes.io/provisioned-by),且其值是否為自己。
  • 通過特定的 Unix Domain Socket 調用外部 CSI 插件的 DeleteVolume 接口。
  • 删除叢集中的 PV 資源。

3. External Attacher

挂接/摘除存儲卷。

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

對于 VolumeAttachment 資源:

  • 從 VolumeAttachment 資源中獲得 PV 的所有資訊,如 volume ID、node ID、挂載 Secret 等。
  • 判斷 VolumeAttachment 的 DeletionTimestamp 字段是否為空來判斷其為卷挂接或卷摘除:若為卷挂接則通過特定的 Unix Domain Socket 調用外部 CSI 插件的ControllerPublishVolume 接口;若為卷摘除則通過特定的 Unix Domain Socket 調用外部 CSI 插件的ControllerUnpublishVolume 接口。

對于 PersistentVolume 資源:

  • 在挂接時為相關 PV 打上 Finalizer:external-attacher/[driver 名稱]。
  • 當 PV 處于删除狀态時(DeletionTimestamp 非空),删除 Finalizer:external-attacher/[driver 名稱]。

4. External Resizer

擴容存儲卷。

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

對于 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 接口實作檔案系統擴容。

5. livenessprobe

檢查 CSI 插件是否正常。

通過對外暴露一個 / healthz HTTP 端口以服務 kubelet 的探針探測器,内部是通過特定的 Unix Domain Socket 調用外部 CSI 插件的 Probe 接口。

CSI 接口介紹

三方存儲廠商需實作 CSI 插件的三大接口:IdentityServer、ControllerServer、NodeServer。

1. IdentityServer

IdentityServer 主要用于認證 CSI 插件的身份資訊。

// IdentityServer is the server API for Identity service.
type IdentityServer interface {
    // 擷取CSI插件的資訊,比如名稱、版本号
    GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)
    // 擷取CSI插件提供的能力,比如是否提供ControllerService能力
    GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)
    // 擷取CSI插件健康狀況
    Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)
}           

2. ControllerServer

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

// ControllerServer is the server API for Controller service.
type ControllerServer interface {
    // 建立存儲卷
    CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)
    // 删除存儲卷
    DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error)
    // 挂接存儲卷到特定節點
    ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error)
    // 從特定節點摘除存儲卷
    ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error)
    // 驗證存儲卷能力是否滿足要求,比如是否支援跨節點多讀多寫
    ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error)
    // 列舉全部存儲卷資訊
    ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)
    // 擷取存儲資源池可用空間大小
    GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error)
    // 擷取ControllerServer支援功能點,比如是否支援快照能力
    ControllerGetCapabilities(context.Context, *ControllerGetCapabilitiesRequest) (*ControllerGetCapabilitiesResponse, error)
    // 建立快照
    CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)
    // 删除快照
    DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*DeleteSnapshotResponse, error)
    // 擷取所有快照資訊
    ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)
    // 擴容存儲卷
    ControllerExpandVolume(context.Context, *ControllerExpandVolumeRequest) (*ControllerExpandVolumeResponse, error)
}           

3. NodeServer

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

// NodeServer is the server API for Node service.
type NodeServer interface {
    // 将存儲卷格式化并挂載至臨時全局目錄
    NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error)
    // 将存儲卷從臨時全局目錄解除安裝
    NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error)
    // 将存儲卷從臨時目錄bind-mount到目标目錄
    NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)
    // 将存儲卷從目标目錄解除安裝
    NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)
    // 擷取存儲卷的容量資訊
    NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error)
    // 存儲卷擴容
    NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error)
    // 擷取NodeServer支援功能點,比如是否支援擷取存儲卷容量資訊
    NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)
    // 擷取CSI節點資訊,比如最大支援卷個數
    NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)
}           

K8s CSI API 對象

K8s 為支援 CSI 标準,包含如下 API 對象:

  • CSINode
  • CSIDriver
  • VolumeAttachment

1. CSINode

apiVersion: storage.k8s.io/v1beta1
kind: CSINode
metadata:
  name: node-10.212.101.210
spec:
  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           

作用:

  1. 判斷外部 CSI 插件是否注冊成功。在 Node Driver Registrar 元件向 Kubelet 注冊完畢後,Kubelet 會建立該資源,故不需要顯式建立 CSINode 資源。
  2. 将 Kubernetes 中 Node 資源名稱與三方存儲系統中節點名稱(nodeID)一一對應。此處Kubelet會調用外部 CSI 插件NodeServer 的 GetNodeInfo 函數擷取 nodeID。
  3. 顯示卷拓撲資訊。CSINode 中 topologyKeys 用來表示存儲節點的拓撲資訊,卷拓撲資訊會使得Scheduler在 Pod 排程時選擇合适的存儲節點。

2. CSIDriver

apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
  name: pangu.csi.alibabacloud.com
spec:
    # 插件是否支援卷挂接(VolumeAttach)
  attachRequired: true
  # Mount階段是否CSI插件需要Pod資訊
  podInfoOnMount: true
  # 指定CSI支援的卷模式
  volumeLifecycleModes:
  - Persistent           
  1. 簡化外部 CSI 插件的發現。由叢集管理者建立,通過 kubectl get csidriver 即可得知環境上有哪些 CSI 插件。
  2. 自定 義Kubernetes 行為,如一些外部 CSI 插件不需要執行卷挂接(VolumeAttach)操作,則可以設定 .spec.attachRequired 為 false。

3. VolumeAttachment

apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
metadata:
  annotations:
    csi.alpha.kubernetes.io/node-id: 21481ae252a2457f9abcb86a3d02ba05
  finalizers:
  - external-attacher/pangu-csi-alibabacloud-com
  name: csi-0996e5e9459e1ccc1b3a7aba07df4ef7301c8e283d99eabc1b69626b119ce750
spec:
  attacher: pangu.csi.alibabacloud.com
  nodeName: node-10.212.101.241
  source:
    persistentVolumeName: pangu-39aa24e7-8877-11eb-b02f-021234350de1
status:
  attached: true           

作用:VolumeAttachment 記錄了存儲卷的挂接/摘除資訊以及節點資訊。

支援特性

1. 拓撲支援

在 StorageClass 中有 AllowedTopologies 字段:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-pangu
provisioner: pangu.csi.alibabacloud.com
parameters:
  type: cloud_ssd
volumeBindingMode: Immediate
allowedTopologies:
- matchLabelExpressions:
  - key: topology.pangu.csi.alibabacloud.com/zone
    values:
    - zone-1
    - zone-2           

外部 CSI 插件部署後會為每個節點打标,打标内容NodeGetInfo 函數傳回的 [AccessibleTopology] 值(詳見 Node Driver Registrar 部分)。

External Provisioner在調用 CSI 插件的 CreateVolume 接口之前,會在請求參數設定 AccessibilityRequirements:

  • 對于 WaitForFirstConsumer
    • 當 PVC 的 anno 中包含 "volume.kubernetes.io/selected-node" 且不為空,則先擷取對應節點 CSINode 的 TopologyKeys,然後根據該 TopologyKeys 鍵從 Node 資源的 Label 擷取 Values 值,最後拿該 Values 值與 StorageClass 的 AllowedTopologies 比對,判斷其是否包含于其中;若不包含則報錯。
  • 對于 Immediately
    • 将 StorageClass 的 AllowedTopologies 的值填進來,若 StorageClass 沒有設定 AllowedTopologies 則将所有包含 TopologyKeys 鍵的節點 Value 添進來。

Scheduler 如何處理使用存儲卷排程

基于社群 1.18 版本排程器

排程器的排程過程主要有如下三步:

  • 預選(Filter):篩選滿足 Pod 排程要求的節點清單。
  • 優選(Score):通過内部的優選算法為節點打分,獲得最高分數的節點即為選中的節點。
  • 綁定(Bind):排程器将排程結果通知給 kube-apiserver,更新 Pod 的 .spec.nodeName 字段。

排程器預選階段:處理 Pod 的 PVC/PV 綁定關系以及動态供應 PV(Dynamic Provisioning),同時使排程器排程時考慮 Pod 所使用 PV 的節點親和性。詳細排程過程如下:

  1. Pod 不包含 PVC 直接跳過。
  2. FindPodVolumes
    • 擷取 Pod 的 boundClaims、claimsToBind 以及 unboundClaimsImmediate。
      • boundClaims:已 Bound 的 PVC
      • claimsToBind:PVC 對應 StorageClass 的 VolumeBindingMode 為 VolumeBindingWaitForFirstConsumer
      • unboundClaimsImmediate:PVC 對應 StorageClass 的 VolumeBindingMode 為 VolumeBindingImmediate
    • 若 len(unboundClaimsImmediate) 不為空,表示這種 PVC 需要立即綁定 PV(即存 PVC 建立後,立刻動态建立 PV 并将其綁定到 PVC,該過程不走排程),若 PVC 處于 unbound 階段則報錯。
    • 若 len(boundClaims) 不為空,則檢查 PVC 對應 PV 的節點親和性與目前節點的 Label 是否沖突,若沖突則報錯(可檢查 Immediate 類型的 PV 拓撲)。
    • 若 len(claimsToBind) 不為空
      • 先檢查環境中已有的 PV 能否與該 PVC 比對(findMatchingVolumes),将能夠比對 PVC 的 PV 記錄在排程器的 cache 中。
      • 未比對到 PV 的 PVC 走動态排程流程,動态排程主要通過 StorageClass 的 AllowedTopologies 字段判斷目前排程節點是否滿足拓撲要求(針對 WaitForFirstConsumer 類型的 PVC)。

排程器優選階段不讨論。

排程器 Assume 階段

排程器會先 Assume PV/PVC,再 Assume Pod。
  1. 将目前待排程的 Pod 進行深拷貝。
  2. AssumePodVolumes(針對 WaitForFirstConsumer 類型的 PVC)
    • 更改排程器 cache 中已經 Match 的 PV 資訊:設定 annotation:pv.kubernetes.io/bound-by-controller="yes"。
    • 更改排程器 cache 中未比對到 PV 的 PVC,設定 annotation:volume.kubernetes.io/selected-node=【所選節點】。
  3. Assume Pod 完畢
    • 更改排程器 cache 中 Pod 的 .Spec.NodeName 為【所選節點】。

排程器 Bind 階段

BindPodVolumes:

  • 調用 Kubernetes 的 API 更新叢集中 PV/PVC 資源,使其與排程器 Cache 中的 PV/PVC 一緻。
  • 檢查 PV/PVC 狀态:
    • 檢查所有 PVC 是否已處于 Bound 狀态。
    • 檢查所有 PV 的 NodeAffinity 是否與節點 Label 沖突。
  • 排程器執行 Bind 操作:調用 Kubernetes 的 API 更新 Pod 的 .Spec.NodeName 字段。

2. 存儲卷擴容

存儲卷擴容部分在 External Resizer 部分已提到,故不再贅述。使用者隻需要編輯 PVC 的 .Spec.Resources.Requests.Storage 字段即可,注意隻可擴容不可縮容。

若 PV 擴容失敗,此時 PVC 無法重新編輯 spec 字段的 storage 為原來的值(隻可擴容不可縮容)。參考 K8s 官網提供的 PVC 還原方法:

https://kubernetes.io/docs/concepts/storage/persistent-volumes/#recovering-from-failure-when-expanding-volumes

3. 單節點卷數量限制

卷數量限制在 Node Driver Registrar 部分已提到,故不再贅述。

4. 存儲卷監控

存儲商需實作 CSI 插件的 NodeGetVolumeStats 接口,Kubelet 會調用該函數,并反映在其 metrics上:

  • kubelet_volume_stats_capacity_bytes:存儲卷容量
  • kubelet_volume_stats_used_bytes:存儲卷已使用容量
  • kubelet_volume_stats_available_bytes:存儲卷可使用容量
  • kubelet_volume_stats_inodes:存儲卷 inode 總量
  • kubelet_volume_stats_inodes_used:存儲卷 inode 使用量
  • kubelet_volume_stats_inodes_free:存儲卷 inode 剩餘量

5. Secret

CSI 存儲卷支援傳入 Secret 來處理不同流程中所需要的私密資料,目前 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 會包含在對應 CSI 接口的參數中,如對于 CreateVolume 接口而言則包含在 CreateVolumeRequest.Secrets 中。

6. 塊裝置

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-example
spec:
  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           

三方存儲廠商需實作 NodePublishVolume 接口。Kubernetes 提供了針對塊裝置的工具包("k8s.io/kubernetes/pkg/util/mount"),在 NodePublishVolume 階段可調用該工具的 EnsureBlock 和 MountBlock 函數。

7. 卷快照/卷克隆能力

鑒于本文篇幅,此處不做過多原理性介紹。讀者感興趣見官方介紹:

卷快照

卷克隆

總結

本文首先對 CSI 核心流程進行了大體介紹,并結合 CSI Sidecar 元件、CSI 接口、API 對象對 CSI 标準進行了深度解析。在 K8s 上,使用任何一種 CSI 存儲卷都離不開上面的流程,環境上的容器存儲問題也一定是其中某個環節出現了問題。本文對其流程進行梳理,以便于廣大程式猿(媛)排查環境問題。

容器存儲的坑比較多,專有雲環境下尤其如此。不過挑戰越多,機遇也越多!目前國内專有雲市場群雄逐鹿,阿裡雲雲原生部門歡迎大俠的加入,一起共創未來!若有意向可投履歷至郵箱:[email protected]

KubeMeet 杭州站開放報名!

一文讀懂容器存儲接口 CSI背景CSI 核心流程介紹CSI Sidecar 元件介紹CSI 接口介紹K8s CSI API 對象支援特性總結KubeMeet 杭州站開放報名!

4 月 17 日,雲原生基金會 CNCF 和阿裡巴巴聯合主辦的「KubeMeet 開發者沙龍 · 雲原生應用管理專場」來到杭州啦!這裡有 Kubernetes 生态開發者都在關注的開源項目,以及阿裡巴巴、攜程、第四範式的一線雲原生落地實踐。

點選此處即可報名

繼續閱讀