更多 k8s CSI 的分析,可以查看這篇博客kubernetes ceph-csi分析,以 ceph-csi 為例,做了詳細的源碼分析。
其他關聯博客:kubernetes/k8s CRI分析-容器運行時接口分析
kubernetes/k8s CNI分析-容器網絡接口分析
概述
kubernetes的設計初衷是支持可插拔架構,從而利於擴展kubernetes的功能。在此架構思想下,kubernetes提供了3個特定功能的接口,分別是容器網絡接口CNI、容器運行時接口CRI和容器存儲接口CSI。kubernetes通過調用這幾個接口,來完成相應的功能。
下面我們來對容器存儲接口CSI來做一下介紹與分析。
在本文中,會對CSI是什么、為什么要有CSI、CSI系統架構做一下介紹,然后對CSI所涉及的k8s對象與組件進行了簡單的介紹,以及k8s對CSI存儲進行相關操作的流程分析,存儲相關操作包括了存儲創建、存儲擴容、存儲掛載、解除存儲掛載以及存儲刪除操作。
CSI是什么
CSI是Container Storage Interface(容器存儲接口)的簡寫。
CSI的目的是定義行業標准“容器存儲接口”,使存儲供應商(SP)能夠開發一個符合CSI標准的插件並使其可以在多個容器編排(CO)系統中工作。CO包括Cloud Foundry, Kubernetes, Mesos等。
kubernetes將通過CSI接口來跟第三方存儲廠商進行通信,來操作存儲,從而提供容器存儲服務。
為什么要有CSI
其實在沒有CSI之前kubernetes就已經提供了強大的存儲卷插件系統,但是這些插件系統實現是kubernetes代碼的一部分,需要隨kubernetes組件二進制文件一起發布,這樣就會存在一些問題。
(1)如果第三方存儲廠商發現有問題需要修復或者優化,即使修復后也不能單獨發布,需要與kubernetes一起發布,對於k8s本身而言,不僅要考慮自身的正常迭代發版,還需要考慮到第三方存儲廠商的迭代發版,這里就存在雙方互相依賴、制約的問題,不利於雙方快速迭代;
(2)另外第三方廠商的代碼跟kubernetes代碼耦合在一起,還會引起安全性、可靠性問題,還增加了kubernetes代碼的復雜度以及后期的維護成本等等。
基於以上問題,kubernetes將存儲體系抽象出了外部存儲組件接口即CSI,kubernetes通過grpc接口與第三方存儲廠商的存儲卷插件系統進行通信。
這樣一來,對於第三方存儲廠商來說,既可以單獨發布和部署自己的存儲插件,進行正常迭代,而又無需接觸kubernetes核心代碼,降低了開發的復雜度。同時,對於kubernetes來說,這樣不僅降低了自身的維護成本,還能為用戶提供更多的存儲選項。
CSI系統架構
這是一張k8s csi的系統架構圖,圖中所畫的組件以及k8s對象,接下來會一一進行分析。

CSI相關組件一般采用容器化部署,減少環境依賴。
涉及k8s對象
1. PersistentVolume
持久存儲卷,集群級別資源,代表了存儲卷資源,記錄了該存儲卷資源的相關信息。
回收策略
(1)retain:保留策略,當刪除pvc的時候,保留pv與外部存儲資源。
(2)delete:刪除策略,當與pv綁定的pvc被刪除的時候,會從k8s集群中刪除pv對象,並執行外部存儲資源的刪除操作。
(3)resycle(已廢棄)
pv狀態遷移
available --> bound --> released
2. PersistentVolumeClaim
持久存儲卷聲明,namespace級別資源,代表了用戶對於存儲卷的使用需求聲明。
示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test
namespace: test
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: csi-cephfs-sc
volumeMode: Filesystem
pvc狀態遷移
pending --> bound
3. StorageClass
定義了創建pv的模板信息,集群級別資源,用於動態創建pv。
示例:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-rbd-sc
parameters:
clusterID: ceph01
imageFeatures: layering
imageFormat: "2"
mounter: rbd
pool: kubernetes
provisioner: rbd.csi.ceph.com
reclaimPolicy: Delete
volumeBindingMode: Immediate
4. VolumeAttachment
VolumeAttachment 記錄了pv的相關掛載信息,如掛載到哪個node節點,由哪個volume plugin來掛載等。
AD Controller 創建一個 VolumeAttachment,而 External-attacher 則通過觀察該 VolumeAttachment,根據其狀態屬性來進行存儲的掛載和卸載操作。
示例:
apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
metadata:
name: csi-123456
spec:
attacher: cephfs.csi.ceph.com
nodeName: 192.168.1.10
source:
persistentVolumeName: pvc-123456
status:
attached: true
5. CSINode
CSINode 記錄了csi plugin的相關信息(如nodeId、driverName、拓撲信息等)。
當Node Driver Registrar向kubelet注冊一個csi plugin后,會創建(或更新)一個CSINode對象,記錄csi plugin的相關信息。
示例:
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: 192.168.1.10
spec:
drivers:
- name: cephfs.csi.ceph.com
nodeID: 192.168.1.10
topologyKeys: null
- name: rbd.csi.ceph.com
nodeID: 192.168.1.10
topologyKeys: null
涉及組件與作用

下面來介紹下涉及的組件與作用。
1. volume plugin
擴展各種存儲類型的卷的管理能力,實現第三方存儲的各種操作能力與k8s存儲系統的結合。調用第三方存儲的接口或命令,從而提供數據卷的創建/刪除、attach/detach、mount/umount的具體操作實現,可以認為是第三方存儲的代理人。前面分析組件中的對於數據卷的創建/刪除、attach/detach、mount/umount操作,全是調用volume plugin來完成。
根據源碼所在位置,volume plugin分為in-tree與out-of-tree。
in-tree
在k8s源碼內部實現,和k8s一起發布、管理,更新迭代慢、靈活性差。
out-of-tree
代碼獨立於k8s,由存儲廠商實現,有csi、flexvolume兩種實現。
csi plugin
csi plugin分為ControllerServer與NodeServer,各負責不同的存儲操作。
external plugin
external plugin包括了external-provisioner、external-attacher、external-resizer、external-snapshotter等,external plugin輔助csi plugin組件,共同完成了存儲相關操作。external plugin負責watch pvc、volumeAttachment等對象,然后調用volume plugin來完成存儲的相關操作。如external-provisioner watch pvc對象,然后調用csi plugin來創建存儲,最后創建pv對象;external-attacher watch volumeAttachment對象,然后調用csi plugin來做attach/dettach操作;external-resizer watch pvc對象,然后調用csi plugin來做存儲的擴容操作等。
Node-Driver-Registrar
Node-Driver-Registrar組件負責實現csi plugin(NodeServer)的注冊,讓kubelet感知csi plugin的存在。
組件部署方式
csi plugin controllerServer與external plugin作為容器,使用deployment部署,多副本可實現高可用;而csi plugin NodeServer與Node-Driver-Registrar作為容器,使用daemonset部署,即每個node節點都有。
2. kube-controller-manager
PV controller
負責pv、pvc的綁定與生命周期管理(如創建/刪除底層存儲,創建/刪除pv對象,pv與pvc對象的狀態變更)。
(1)in-tree:創建/刪除底層存儲、創建/刪除pv對象的操作,由PV controller調用volume plugin(in-tree)來完成。
(2)out-tree CSI:創建/刪除底層存儲、創建/刪除pv對象的操作由external-provisioner與csi plugin共同來完成。
AD controller
AD Cotroller全稱Attachment/Detachment 控制器,主要負責創建、刪除VolumeAttachment對象,並調用volume plugin來做存儲設備的Attach/Detach操作(將數據卷掛載到特定node節點上/從特定node節點上解除掛載),以及更新node.Status.VolumesAttached等。
不同的volume plugin的Attach/Detach操作邏輯有所不同,對於csi plugin(out-tree volume plugin)來說,AD controller的Attach/Detach操作只是修改VolumeAttachment對象的狀態,而不會真正的將數據卷掛載到節點/從節點上解除掛載,真正的節點存儲掛載/解除掛載操作由kubelet中volume manager調用csi plugin來完成。
3. kubelet
volume manager
主要是管理卷的Attach/Detach(與AD controller作用相同,通過kubelet啟動參數控制哪個組件來做該操作)、mount/umount等操作。
對於csi來說,volume manager的Attach/Detach操作只創建/刪除VolumeAttachment對象,而不會真正的將數據卷掛載到節點/從節點上解除掛載;csi-attacer組件也不會做掛載/解除掛載操作,只是更新VolumeAttachment對象,真正的節點存儲掛載/解除掛載操作由kubelet中volume manager調用調用csi plugin來完成。
kubernetes創建與掛載volume(in-tree volume plugin)
先來看下kubernetes通過in-tree volume plugin來創建與掛載volume的流程

(1)用戶創建pvc;
(2)PV controller watch到pvc的創建,尋找合適的pv與之綁定。
(3)(4)當找不到合適的pv時,將調用volume plugin來創建volume,並創建pv對象,之后該pv對象與pvc對象綁定。
(5)用戶創建掛載pvc的pod;
(6)kube-scheduler watch到pod的創建,為其尋找合適的node調度。
(7)(8)pod調度完成后,AD controller/volume manager watch到pod聲明的volume沒有進行attach操作,將調用volume plugin來做attach操作。
(9)volume plugin進行attach操作,將volume掛載到pod所在node節點,成為如/dev/vdb的設備。
(10)(11)attach操作完成后,volume manager watch到pod聲明的volume沒有進行mount操作,將調用volume plugin來做mount操作。
(12)volume plugin進行mount操作,將node節點上的第(9)步得到的/dev/vdb設備掛載到指定目錄。
kubernetes創建與掛載volume(out-of-tree volume plugin)
再來看下kubernetes通過out-of-tree volume plugin來創建與掛載volume的流程,以csi-plugin為例。

(1)用戶創建pvc;
(2)PV controller watch到pvc的創建,尋找合適的pv與之綁定。當尋找不到合適的pv時,將更新pvc對象,添加annotation:volume.beta.kubernetes.io/storage-provisioner,讓external-provisioner組件開始開始創建存儲與pv對象的操作。
(3)external-provisioner組件watch到pvc的創建/更新事件,判斷annotation:volume.beta.kubernetes.io/storage-provisioner的值,即判斷是否是自己來負責做創建操作,是則調用csi-plugin ControllerServer來創建存儲,並創建pv對象(這里的pv對象使用了提前綁定特性,將pvc信息填入了pv對象的spec.claimRef屬性)。
(4)PV controller將上一步創建的pv與pvc綁定。
(5)用戶創建掛載pvc的pod;
(6)kube-scheduler watch到pod的創建,為其尋找合適的node調度。
(7)(8)pod調度完成后,AD controller/volume manager watch到pod聲明的volume沒有進行attach操作,將調用csi-attacher來做attach操作(實際上只是創建volumeAttachement對象)。
(9)external-attacher組件watch到volumeAttachment對象的新建,調用csi-plugin進行attach操作(如果volume plugin是ceph-csi,external-attacher組件watch到volumeAttachment對象的新建后,只是修改該對象的狀態屬性,不會做attach操作,真正的attach操作由kubelet中的volume manager調用volume plugin ceph-csi來完成)。
(10)csi-plugin ControllerServer進行attach操作,將volume掛載到pod所在node節點,成為如/dev/vdb的設備。
(11)(12)attach操作完成后,volume manager watch到pod聲明的volume沒有進行mount操作,將調用csi-mounter來做mount操作。
(13)csi-mounter調用csi-plugin NodeServer進行mount操作,將node節點上的第(10)步得到的/dev/vdb設備掛載到指定目錄。
kubernetes存儲相關操作流程具體分析(out-of-tree volume plugin,以csi plugin:ceph-csi為例)
下面來看下kubernetes通過ceph-csi volume plugin來創建/刪除、掛載/解除掛載ceph存儲的流程。
1. 存儲創建
流程圖

流程分析
(1)用戶創建pvc對象;
(2)pv controller監聽pvc對象,尋找現存的合適的pv對象,與pvc對象綁定。當找不到現存合適的pv對象時,將更新pvc對象,添加annotation:volume.beta.kubernetes.io/storage-provisioner,讓external-provisioner組件開始開始創建存儲與pv對象的操作;當找到時,將pvc與pv綁定,結束操作。
(3)external-provisioner組件監聽到pvc的新增事件,判斷pvc的annotation:volume.beta.kubernetes.io/storage-provisioner的值,即判斷是否是自己來負責做創建操作,是則調用ceph-csi組件進行存儲的創建;
(4)ceph-csi組件調用ceph創建底層存儲;
(5)底層存儲創建完成后,external-provisioner根據存儲信息,拼接pv對象,創建pv對象(這里的pv對象使用了提前綁定特性,將pvc信息填入了pv對象的spec.claimRef屬性);
(6)pv controller監聽pvc對象,將第(5)步創建的pv對象,與pvc對象綁定。
2. 存儲擴容
流程圖

流程分析
(1)修改pvc對象,修改申請存儲大小(pvc.spec.resources.requests.storage);
(2)修改成功后,external-resizer監聽到該pvc的update事件,發現pvc.Spec.Resources.Requests.storgage比pvc.Status.Capacity.storgage大,於是調ceph-csi組件進行 controller端擴容;
(3)ceph-csi組件調用ceph存儲,進行底層存儲擴容;
(4)底層存儲擴容完成后,ceph-csi組件更新pv對象的.Spec.Capacity.storgage的值為擴容后的存儲大小;
(5)kubelet的volume manager在reconcile()調諧過程中發現pv.Spec.Capacity.storage大於pvc.Status.Capacity.storage,於是調ceph-csi組件進行 node端擴容;
(6)ceph-csi組件對node上存儲對應的文件系統擴容;
(7)擴容完成后,kubelet更新pvc.Status.Capacity.storage的值為擴容后的存儲大小。
3. 存儲掛載
流程圖
kubelet啟動參數--enable-controller-attach-detach,該啟動參數設置為 true 表示啟用 Attach/Detach controller進行Attach/Detach 操作,同時禁用 kubelet 執行 Attach/Detach 操作(默認值為 true)。實際上Attach/Detach 操作就是創建/刪除VolumeAttachment對象。
(1)kubelet啟動參數--enable-controller-attach-detach=true,Attach/Detach controller進行Attach/Detach 操作。

(2)kubelet啟動參數--enable-controller-attach-detach=false,kubelet端volume manager進行Attach/Detach 操作。

流程分析
(1)用戶創建一個掛載了pvc的pod;
(2)AD controller或volume manager中的reconcile()發現有volume未執行attach操作,於是進行attach操作,即創建VolumeAttachment對象;
(3)external-attacher組件list/watch VolumeAttachement對象,更新VolumeAttachment.status.attached=true;
(4)AD controller更新node對象的.Status.VolumesAttached屬性值,將該volume記為attached;
(5)kubelet中的volume manager獲取node.Status.VolumesAttached屬性值,發現volume已被標記為attached;
(6)於是volume manager中的reconcile()調用ceph-csi組件的NodeStageVolume與NodePublishVolume完成存儲的掛載。
4. 解除存儲掛載
流程圖
(1)kubelet啟動參數--enable-controller-attach-detach=true,Attach/Detach controller進行Attach/Detach 操作。

(2)kubelet啟動參數--enable-controller-attach-detach=false,kubelet端volume manager進行Attach/Detach 操作。

流程分析
(1)用戶刪除聲明了pvc的pod;
(2)AD controller或volume manager中的reconcile()發現有volume未執行dettach操作,於是進行dettach操作,即刪除VolumeAttachment對象;
(3)AD controller或volume manager等待VolumeAttachment對象刪除成功;
(4)AD controller更新node對象的.Status.VolumesAttached屬性值,將標記為attached的該volume從屬性值中去除;
(5)kubelet中的volume manager獲取node.Status.VolumesAttached屬性值,找不到相關的volume信息;
(6)於是volume manager中的reconcile()調用ceph-csi組件的NodeUnpublishVolume與NodeUnstageVolume完成存儲的解除掛載操作。
5. 刪除存儲
流程圖

流程分析
(1)用戶刪除pvc對象;
(2)pv controller發現與pv綁定的pvc對象被刪除,於是更新pv的狀態為released;
(3)external-provisioner watch到pv更新事件,並檢查pv的狀態是否為released,以及回收策略是否為delete;
(4)確認了pv對象的狀態以及回收策略之后,接下來external-provisioner組件會調用ceph-csi的DeleteVolume來刪除存儲;
(5)ceph-csi組件的DeleteVolume方法,調用ceph集群命令,刪除底層存儲;
(6)刪除底層存儲后,external-provisioner組件刪除pv對象。
總結
CSI即Container Storage Interface(容器存儲接口)。
為了解決第三方存儲廠商的存儲卷插件代碼集成到kubernetes代碼中所帶來的各種問題,kubernetes將存儲體系抽象出了外部存儲組件接口即CSI,kubernetes通過grpc接口與第三方存儲廠商的存儲卷插件系統進行通信,來操作存儲,從而提供容器存儲服務。
這樣一來,對於第三方存儲廠商來說,既可以單獨發布和部署自己的存儲插件,進行正常迭代,而又無需接觸kubernetes核心代碼,降低了開發的復雜度。同時,對於kubernetes來說,這樣不僅降低了自身的維護成本,還能為用戶提供更多的存儲選項。
最后,再來回顧一下kubernetes CSI的架構。

更多 k8s CSI 的分析,可以查看這篇博客kubernetes ceph-csi分析,以 ceph-csi 為例,做了詳細的源碼分析。
