如何接入 K8s 持久化儲存?K8s CSI 實現機制淺析
王成,騰訊雲研發工程師,Kubernetes contributor,從事資料庫產品容器化、資源管控等工作,關注 Kubernetes、Go、雲原生領域。
概述
相關術語
Term | Definition |
---|---|
CSI | Container Storage Interface. |
CNI | Container Network Interface. |
CRI | Container Runtime Interface. |
PV | Persistent Volume. |
PVC | Persistent Volume Claim. |
StorageClass | Defined by provisioner(i.e. Storage Provider), to assemble Volume parameters as a resource object. |
Volume | A unit of storage that will be made available inside of a CO-managed container, via the CSI. |
Block Volume | A volume that will appear as a block device inside the container. |
Mounted Volume | A volume that will be mounted using the specified file system and appear as a directory inside the container. |
CO | Container Orchestration system, communicates with Plugins using CSI service RPCs. |
SP | Storage Provider, the vendor of a CSI plugin implementation. |
RPC | Remote Procedure Call [1]. |
Node | A host where the user workload will be running, uniquely identifiable from the perspective of a Plugin by a node ID. |
Plugin | Aka “plugin implementation”, a gRPC endpoint that implements the CSI Services. |
Plugin Supervisor | Process that governs the lifecycle of a Plugin, MAY be the CO. |
Workload | The atomic unit of "work" scheduled by a CO. This MAY be a container or a collection of containers. |
本文及後續相關文章都基於 K8s v1.22
流程概覽
-
apiserver
建立 Pod,根據PodSpec.Volumes
建立 Volume; -
PVController
監聽到 PV informer,新增相關 Annotation(如 pv.kubernetes.io/provisioned-by),調諧實現 PVC/PV 的繫結(Bound); -
判斷
StorageClass.volumeBindingMode
:WaitForFirstConsumer
則等待 Pod 排程到 Node 成功後再進行 PV 建立,Immediate
則立即呼叫 PV 建立邏輯,無需等待 Pod 排程; -
external-provisioner
監聽到 PV informer, 呼叫 RPC-CreateVolume 建立 Volume; -
AttachDetachController
將已經繫結(Bound) 成功的 PVC/PV,經過 InTreeToCSITranslator 轉換器,由 CSIPlugin 內部邏輯實現VolumeAttachment
資源型別的建立; -
external-attacher
監聽到 VolumeAttachment informer,呼叫 RPC-ControllerPublishVolume 實現 AttachVolume; -
kubelet
reconcile 持續調諧:透過判斷controllerAttachDetachEnabled || PluginIsAttachable
及當前 Volume 狀態進行 AttachVolume/MountVolume,最終實現將 Volume 掛載到 Pod 指定目錄中,供 Container 使用;
從 CSI 說起
CreateVolume +------------+ DeleteVolume
+------------->| CREATED +--------------+
| +---+----^---+ |
| Controller | | Controller v
+++ Publish | | Unpublish +++
|X| Volume | | Volume | |
+-+ +---v----+---+ +-+
| NODE_READY |
+---+----^---+
Node | | Node
Stage | | Unstage
Volume | | Volume
+---v----+---+
| VOL_READY |
+---+----^---+
Node | | Node
Publish | | Unpublish
Volume | | Volume
+---v----+---+
| PUBLISHED |
+------------+
The lifecycle of a dynamically provisioned volume, from
creation to destruction, when the Node Plugin advertises the
STAGE_UNSTAGE_VOLUME capability.
CreateVolume -> ControllerPublishVolume -> NodeStageVolume -> NodePublishVolume
NodeUnpublishVolume -> NodeUnstageVolume -> ControllerUnpublishVolume -> DeleteVolume
多元件協同
元件介紹
-
kube-controller-manager :K8s 資源控制器,主要透過 PVController, AttachDetach 實現持久卷的繫結(Bound)/解綁(Unbound)、附著(Attach)/分離(Detach);
-
CSI-plugin :K8s 獨立拆分出來,實現 CSI 標準規範介面的邏輯控制與呼叫,是整個 CSI 控制邏輯的核心樞紐;
-
node-driver-registrar :是一個由 官方 K8s sig 小組維護的輔助容器(sidecar),它使用 kubelet 外掛序號產生器制向 kubelet 註冊外掛,需要請求 CSI 外掛的 Identity 服務來獲取外掛資訊;
-
external-provisioner :是一個由 官方 K8s sig 小組維護的輔助容器(sidecar),主要功能是實現持久卷的建立(Create)、刪除(Delete);
-
external-attacher :是一個由 官方 K8s sig 小組維護的輔助容器(sidecar),主要功能是實現持久卷的附著(Attach)、分離(Detach);
-
external-snapshotter :是一個由 官方 K8s sig 小組維護的輔助容器(sidecar),主要功能是實現持久卷的快照(VolumeSnapshot)、備份恢復等能力;
-
external-resizer:是一個由 官方 K8s sig 小組維護的輔助容器(sidecar),主要功能是實現持久卷的彈性擴縮容,需要雲廠商外掛提供相應的能力;
-
kubelet :K8s 中執行在每個 Node 上的控制樞紐,主要功能是調諧節點上 Pod 與 Volume 的附著、掛載、監控探測上報等;
-
cloud-storage-provider :由各大雲端儲存廠商基於 CSI 標準介面實現的外掛,包括 Identity 身份服務、Controller 控制器服務、Node 節點服務;
元件通訊
RPC 呼叫
-
Identity 身份服務 :Node Plugin 和 Controller Plugin 都必須實現這些 RPC 集,協調 K8s 與 CSI 的版本資訊,負責對外暴露這個外掛的資訊。
-
Controller 控制器服務 :Controller Plugin 必須實現這些 RPC 集,建立以及管理 Volume,對應 K8s 中 attach/detach volume 操作。
-
Node 節點服務 :Node Plugin 必須實現這些 RPC 集,將 Volume 儲存卷掛載到指定目錄中,對應 K8s 中的 mount/unmount volume 操作。
建立/刪除 PV
// external-provisioner/cmd/csi-provisioner/csi-provisioner.go
main() {
...
// 初始化控制器,實現 Volume 建立/刪除介面
csiProvisioner := ctrl.NewCSIProvisioner(
clientset,
*operationTimeout,
identity,
*volumeNamePrefix,
*volumeNameUUIDLength,
grpcClient,
snapClient,
provisionerName,
pluginCapabilities,
controllerCapabilities,
...
)
...
// 真正的 ProvisionController,包裝了上面的 CSIProvisioner
provisionController = controller.NewProvisionController(
clientset,
provisionerName,
csiProvisioner,
provisionerOptions...,
)
...
run :=
func
(ctx context.Context) {
...
// Run 執行起來
provisionController.Run(ctx)
}
}
PV 建立:runClaimWorker -> syncClaimHandler -> syncClaim -> provisionClaimOperation -> Provision -> CreateVolume PV 刪除:runVolumeWorker -> syncVolumeHandler -> syncVolume -> deleteVolumeOperation -> Delete -> DeleteVolume
// 透過 vendor 方式引入 sigs.k8s.io/sig-storage-lib-external-provisioner
// external-provisioner/vendor/sigs.k8s.io/sig-storage-lib-external-provisioner/v7/controller/volume.go
type Provisioner
interface {
// 呼叫 PRC CreateVolume 介面實現 PV 建立
Provision(context.Context, ProvisionOptions) (*v1.PersistentVolume, ProvisioningState, error)
// 呼叫 PRC DeleteVolume 介面實現 PV 刪除
Delete(context.Context, *v1.PersistentVolume) error
}
Controller 調諧
K8s 中與 PV 相關的控制器有 PVController、AttachDetachController。
PVController
// kubernetes/pkg/controller/volume/persistentvolume/pv_controller_base.go
func
NewController
(p ControllerParameters)
(*PersistentVolumeController, error) {
...
// 初始化 PVController
controller := &PersistentVolumeController{
volumes: newPersistentVolumeOrderedIndex(),
claims: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
kubeClient: p.KubeClient,
eventRecorder: eventRecorder,
runningOperations: goroutinemap.NewGoRoutineMap(
true
/* exponentialBackOffOnError */),
cloud: p.Cloud,
enableDynamicProvisioning: p.EnableDynamicProvisioning,
clusterName: p.ClusterName,
createProvisionedPVRetryCount: createProvisionedPVRetryCount,
createProvisionedPVInterval: createProvisionedPVInterval,
claimQueue: workqueue.NewNamed(
"claims"),
volumeQueue: workqueue.NewNamed(
"volumes"),
resyncPeriod: p.SyncPeriod,
operationTimestamps: metrics.NewOperationStartTimeCache(),
}
...
// PV 增刪改事件監聽
p.VolumeInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc:
func
(obj
interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
UpdateFunc:
func
(oldObj, newObj
interface{}) { controller.enqueueWork(controller.volumeQueue, newObj) },
DeleteFunc:
func
(obj
interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
},
)
...
// PVC 增刪改事件監聽
p.ClaimInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc:
func
(obj
interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
UpdateFunc:
func
(oldObj, newObj
interface{}) { controller.enqueueWork(controller.claimQueue, newObj) },
DeleteFunc:
func
(obj
interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
},
)
...
return controller,
nil
}
接著,呼叫 PVC/PV 繫結/解綁邏輯:
PVC/PV 繫結:claimWorker -> updateClaim -> syncClaim -> syncBoundClaim -> bind PVC/PV 解綁:volumeWorker -> updateVolume -> syncVolume -> unbindVolume
AttachDetachController
VolumeAttachment
資源型別的建立/刪除,調諧(reconcile) 任務完成。然後交給 external-attacher 元件進行下一步邏輯處理。
// kubernetes/pkg/controller/volume/attachdetach/reconciler/reconciler.go
func
(rc *reconciler)
reconcile
() {
// 先進行 DetachVolume,確保因 Pod 重新排程到其他節點的 Volume 提前分離(Detach)
for _, attachedVolume :=
range rc.actualStateOfWorld.GetAttachedVolumes() {
// 如果不在期望狀態的 Volume,則呼叫 DetachVolume 刪除 VolumeAttachment 資源物件
if !rc.desiredStateOfWorld.VolumeExists(
attachedVolume.VolumeName, attachedVolume.NodeName) {
...
err = rc.attacherDetacher.DetachVolume(attachedVolume.AttachedVolume, verifySafeToDetach, rc.actualStateOfWorld)
...
}
}
// 呼叫 AttachVolume 建立 VolumeAttachment 資源物件
rc.attachDesiredVolumes()
...
}
附著/分離 Volume
// external-attacher/cmd/csi-attacher/main.go
func
main
() {
...
ctrl := controller.NewCSIAttachController(
clientset,
csiAttacher,
handler,
factory.Storage().V1().VolumeAttachments(),
factory.Core().V1().PersistentVolumes(),
workqueue.NewItemExponentialFailureRateLimiter(*retryIntervalStart, *retryIntervalMax),
workqueue.NewItemExponentialFailureRateLimiter(*retryIntervalStart, *retryIntervalMax),
supportsListVolumesPublishedNodes,
*reconcileSync,
)
run :=
func
(ctx context.Context) {
stopCh := ctx.Done()
factory.Start(stopCh)
ctrl.Run(
int(*workerThreads), stopCh)
}
...
}
接著,呼叫 Volume 附著/分離邏輯:
Volume 附著(Attach):syncVA -> SyncNewOrUpdatedVolumeAttachment -> syncAttach -> csiAttach -> Attach -> ControllerPublishVolume Volume 分離(Detach):syncVA -> SyncNewOrUpdatedVolumeAttachment -> syncDetach -> csiDetach -> Detach -> ControllerUnpublishVolume
kubelet 掛載/解除安裝 Volume
K8s 中持久卷 PV 的掛載(Mount)與解除安裝(Unmount),由 kubelet 元件實現。
// kubernetes/pkg/kubelet/volumemanager/reconciler/reconciler.go
func
(rc *reconciler)
reconcile
() {
// 先進行 UnmountVolume,確保因 Pod 刪除被重新 Attach 到其他 Pod 的 Volume 提前解除安裝(Unmount)
rc.unmountVolumes()
// 接著透過判斷 controllerAttachDetachEnabled || PluginIsAttachable 及當前 Volume 狀態
// 進行 AttachVolume / MountVolume / ExpandInUseVolume
rc.mountAttachVolumes()
// 解除安裝(Unmount) 或分離(Detach) 不再需要(Pod 刪除)的 Volume
rc.unmountDetachDevices()
}
相關呼叫邏輯如下:
Volume 掛載(Mount):reconcile -> mountAttachVolumes -> MountVolume -> SetUp -> SetUpAt -> NodePublishVolume Volume 解除安裝(Unmount):reconcile -> unmountVolumes -> UnmountVolume -> TearDown -> TearDownAt -> NodeUnpublishVolume
小結
相關資料
-
CSI 規範 [4] -
Kubernetes 原始碼 [5] -
kubernetes-csi 原始碼 [6] -
kubernetes-sig-storage 原始碼 [7] -
K8s CSI 概念 [8] -
K8s CSI 介紹 [9]
參考資料
Remote Procedure Call: 【 】
[2]: 【 】
[3]k8s-club: 【 】
[4]CSI 規範: 【 】
[5]Kubernetes 原始碼: 【 】
[6]kubernetes-csi 原始碼: 【 】
[7]kubernetes-sig-storage 原始碼: 【 】
[8]K8s CSI 概念: 【 https://blog.csdn.net/zhonglinzhang/article/details/89532389 】
[9]K8s CSI 介紹: 【 https://www.cnblogs.com/yangyuliufeng/p/14360558.html 】
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984638/viewspace-2839432/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- k8s使用glusterfs實現動態持久化儲存K8S持久化
- k8s使用ceph實現動態持久化儲存K8S持久化
- K8S中如何使用Glusterfs做持久化儲存?K8S持久化
- [k8s] k8s基於csi使用rbd儲存K8S
- K8S儲存外掛-FlexVolume && CSIK8SFlex
- K8s 如何提供更高效穩定的編排能力?K8s Watch 實現機制淺析K8S
- 一文讀懂 K8s 持久化儲存流程K8S持久化
- kubernetes/k8s CSI分析-容器儲存介面分析K8S
- NX實現機制淺析
- K8S系列第九篇(持久化儲存,emptyDir、hostPath、PV/PVC)K8S持久化
- 淺談 K8s Service 網路機制K8S
- 如何配置K8S儲存叢集?K8S
- k8s 1.28.2 叢集部署 docker registry 接入 MinIO 儲存K8SDocker
- Kubernetes 使用 ceph-csi 消費 RBD 作為持久化儲存持久化
- 在K8S中,是怎麼實現資料持久化的?K8S持久化
- ios持久化儲存iOS持久化
- Redis持久化儲存Redis持久化
- 利用Kubernetes實現容器的持久化儲存持久化
- k8s之資料儲存-配置儲存K8S
- 淺析雜湊儲存
- 使用容器化塊儲存OpenEBS在K3s中實現持久化儲存持久化
- k8s StorageClass 儲存類K8S
- k8s資料儲存K8S
- 淺談:Redis持久化機制(二)AOF篇Redis持久化
- 淺談:Redis持久化機制(一)RDB篇Redis持久化
- 容器儲存介面--CSI
- MVVM機制淺析MVVM
- k8s之資料儲存-高階儲存K8S
- VMware 與 SmartX 分散式儲存快取機制淺析與效能對比分散式快取
- k8s接入ldapK8SLDA
- 在K8S中,CSI模型有哪些?K8S模型
- k8s CSI 外掛註冊原理K8S
- K8S之Volume儲存K8S
- Flutter持久化儲存之檔案儲存Flutter持久化
- 雲原生儲存詳解:容器儲存與 K8s 儲存卷K8S
- 容器持久化儲存訓練營”啟動倒數計時!3天攻破K8s難點持久化K8S
- redis持久化機制Redis持久化
- k8s實踐——名稱空間隔離+request-key機制解決CSI核心態域名解析K8S