kubernetes/k8s CSI分析-容器存儲介面分析
- 2021 年 7 月 24 日
- 筆記
- kubernetes CSI, kubernetes源碼解析
更多 k8s CSI 的分析,可以查看這篇部落格kubernetes ceph-csi分析,以 ceph-csi 為例,做了詳細的源碼分析。
概述
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 為例,做了詳細的源碼分析。