- 自定義實現Kubernetes CSI
- 一、CSI架構設計目標與核心組件
- 1.1 設計目標
- 1.2 核心組件
- 1.3 工作原理
- 二、自定義CSI驅動實現步驟
- 2.1 環境準備
- 2.2 接口實現
- 2.3 測試與驗證
- 2.4 鏡像構建
- 三、CSI驅動部署與功能驗證
- 3.1 部署驅動組件
- 3.2 配置CSIDriver對象
- 3.3 配置Sidecar控制器
- 3.4 創建StorageClass
- 3.5 功能驗證
- 四、高級功能實現與優化
- 4.1 多雲存儲支持
- 4.2 存儲性能優化
- 4.3 存儲快照管理
- 五、常見問題與解決方案
- 5.1 驅動註冊失敗
- 5.2 卷掛載失敗
- 5.3 動態供應失敗
- 一、CSI架構設計目標與核心組件
自定義實現Kubernetes CSI
Kubernetes CSI(容器存儲接口)是一種標準化的容器存儲接口規範,旨在解決Kubernetes早期版本中存儲插件與核心代碼耦合的問題。CSI通過將存儲系統與容器編排平台解耦,實現了多供應商存儲系統的靈活集成和動態管理。它已成為Kubernetes存儲擴展的唯一標準,取代了原有的in-tree存儲插件方式。
一、CSI架構設計目標與核心組件
1.1 設計目標
Kubernetes最初採用in-tree方式管理存儲插件,即將存儲驅動的源碼直接集成到Kubernetes代碼庫中。這種方式導致三個主要問題:
首先,耦合性高。每次引入新的存儲系統支持都需要修改Kubernetes核心代碼,增加了維護複雜性和版本升級風險。
其次,發佈週期不同步。存儲插件的發佈週期與Kubernetes的發佈週期綁定,無法獨立開發和發佈,限制了存儲供應商的創新速度。
最後,擴展性受限。Kubernetes核心代碼庫難以容納所有可能的存儲系統實現,導致無法靈活支持多樣化的存儲需求。
為解決這些問題,Kubernetes社區於2017年引入了CSI規範,其核心目標是實現存儲系統與Kubernetes核心代碼的解耦,使存儲供應商能夠獨立開發、發佈和維護存儲插件,同時保持與Kubernetes的兼容性。
1.2 核心組件
CSI架構主要由以下組件構成:
CSI Driver:由存儲供應商提供的核心插件,分為兩個部分:
- Controller Service:運行在Kubernetes控制平面,處理集羣級別的卷管理操作,如創建、刪除、附加、分離、快照和還原等 。
- Node Service:運行在每個Kubernetes節點上,處理節點級別的卷操作,如掛載、卸載和報告磁盤使用情況等 。
Sidecar控制器:由Kubernetes團隊維護的輔助組件,負責監聽Kubernetes資源對象並觸發相應的CSI接口調用 :
- External Provisioner:監聽PersistentVolumeClaim(PVC)對象,觸發CreateVolume和DeleteVolume操作,實現卷的動態供應 。
- External Attacher:監聽VolumeAttachment對象,觸發ControllerPublish和ControllerUnpublish操作,管理卷的附加和分離 。
- External Resizer:監聽PersistentVolumeClaim的大小變更,觸發ResizeVolume操作,實現卷的動態擴容 。
- External Snapshotter:監聽VolumeSnapshot對象,觸發CreateSnapshot和DeleteSnapshot操作,管理卷的快照 。
- Node DriverRegistrar:負責將CSI驅動註冊到kubelet,使節點能夠識別和使用該驅動 。
Kubernetes組件:與CSI驅動交互的核心組件:
- kubelet:負責在節點上執行Pod的創建、管理和刪除,與Node Service通過gRPC通信 。
- 控制器管理器:管理多個控制器,包括負責PVC/PV綁定的控制器等。
- API Server:處理所有Kubernetes API請求,是CSI驅動與Kubernetes交互的入口 。
1.3 工作原理
CSI驅動通過gRPC協議與Kubernetes組件交互,實現存儲卷的全生命週期管理。工作流程可分為四個主要階段:
當用户創建PVC時,External Provisioner監聽到該事件,向CSI Controller Service發起CreateVolume請求,創建底層存儲卷 。完成後,自動創建對應的PV並與PVC綁定。
當Pod被調度到節點上時,kubelet檢查該節點是否具備所需卷的訪問權限。如果卷尚未附加到節點,External Attacher會向CSI Controller Service發起ControllerPublishVolume請求,將卷附加到節點。
卷附加完成後,kubelet通過NodePublishVolume接口將卷掛載到Pod的指定路徑 。這一過程包括NodeStageVolume(在節點上準備卷)和NodePublishVolume(將卷掛載到容器)兩個步驟。
當Pod結束或PVC被刪除時,相關控制器會依次觸發卷的卸載、分離和刪除操作,確保存儲資源的正確回收。
二、自定義CSI驅動實現步驟
2.1 環境準備
實現自定義CSI驅動首先需要準備開發環境。推薦使用Go語言作為開發語言,因為這是CSI規範的主要實現語言,社區資源豐富,且與Kubernetes生態兼容性好。
安裝必要的開發工具:
# 安裝Go語言環境
sudo apt-get install -y golang-go
# 安裝Protobuf編譯器
sudo apt-get install -yprotobuf-compiler
# 安裝Kubernetes CLI工具
sudo apt-get install -ykubectl
# 安裝Docker用於構建鏡像
sudo apt-get install -ydocker.io
配置Kubernetes集羣:
# 檢查Kubernetes版本,確保不低於v1.13
kubectl version --short
# 啓用CSI特性(如果集羣版本較低)
kubectl get nodes -o jsonpath='{.items[*].metadata.name}' | xargs -I {} kubectl label nodes {} feature.csi storage=driver
2.2 接口實現
自定義CSI驅動需要實現三個主要gRPC服務接口:Identity、Controller和Node。核心是實現這些接口與底層存儲系統的交互邏輯 。
首先是Identity服務,用於提供驅動基本信息和能力:
// 實現Identity服務
type identityServer struct {
csi IdentityServiceServer
}
func (s *identityServer) GetPluginInfo(
context.Context, *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
return &csi.GetPluginInfoResponse{
Name: "mystorage.csi.example.com",
VendorName: "Example Storage",
Version: "1.0.0",
}, nil
}
func (s *identityServer) GetPlugin Capabilities(
context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
return &csi.GetPluginCapabilitiesResponse{
Capabilities: &csi.GetPluginCapabilitiesResponse_Capabilities{
Capabilities: []csi PluginCapability{
{
Type: &csi PluginCapabilityattacher{
Attacher: &csi PluginCapabilityAttacher{}
},
},
{
Type: &csi PluginCapabilityvolume Expansion{
VolumeExpansion: &csi PluginCapabilityVolumeExpansion{}
},
},
},
},
}, nil
}
其次是Controller服務,實現集羣級別的卷管理操作 :
// 實現Controller服務
type controllerServer struct {
csi ControllerServiceServer
}
func (s *controllerServer) CreateVolume(
context.Context, *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
// 解析請求參數
capacity := req capacityRange requiredBytes
volumeType := req volumeAttributes["type"]
// 調用底層存儲API創建卷
volumeID, err := createVolumeOnStorageSystem(capacity, volumeType)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// 返回創建結果
return &csi.CreateVolumeResponse{
Volume: &csi Volume{
VolumeId: volumeID,
CapacityBytes: capacity,
},
}, nil
}
func (s *controllerServer) DeleteVolume(
context.Context, *csi DeleteVolumeRequest) (*csi DeleteVolumeResponse, error) {
// 調用底層存儲API刪除卷
err := deleteVolumeFromStorageSystem(req VolumeId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi DeleteVolumeResponse{}, nil
}
func (s *controllerServer) ControllerPublishVolume(
context.Context, *csi ControllerPublishVolumeRequest) (*csi ControllerPublishVolumeResponse, error) {
// 調用底層存儲API將卷附加到節點
err := attachVolumeToNode(req VolumeId, req NodeId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi ControllerPublishVolumeResponse{}, nil
}
最後是Node服務,實現節點級別的卷操作 :
// 實現Node服務
type nodeServer struct {
csi NodeServiceServer
mounter *mount.Mounter
}
func (s *nodeServer) NodeStageVolume(
context.Context, *csi NodeStageVolumeRequest) (*csi NodeStageVolumeResponse, error) {
// 準備卷(如創建掛載點、下載卷數據等)
targetPath := req TargetPath
volumeId := req VolumeId
// 執行掛載準備
err := s.mounter stageVolume(targetPath, volumeId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi NodeStageVolumeResponse{}, nil
}
func (s *nodeServer) NodeUnstageVolume(
context.Context, *csi NodeUnstageVolumeRequest) (*csi NodeUnstageVolumeResponse, error) {
// 取消掛載準備
targetPath := req TargetPath
volumeId := req VolumeId
err := s.mounter unstageVolume(targetPath, volumeId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi NodeUnstageVolumeResponse{}, nil
}
func (s *nodeServer) NodePublishVolume(
context.Context, *csi NodePublishVolumeRequest) (*csi NodePublishVolumeResponse, error) {
// 掛載捲到容器
targetPath := req TargetPath
volumeId := req VolumeId
mountOptions := req VolumeCapability.Mount.MountOptions
// 執行實際掛載
err := s.mounter mountVolume(targetPath, volumeId, mountOptions)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi NodePublishVolumeResponse{}, nil
}
func (s *nodeServer) NodeUnpublishVolume(
context.Context, *csi NodeUnpublishVolumeRequest) (*csi NodeUnpublishVolumeResponse, error) {
// 卸載卷
targetPath := req TargetPath
err := s.mounter unmountVolume(targetPath)
if err != nil {
return nil, status.Error(codes內部, err.Error())
}
return &csi NodeUnpublishVolumeResponse{}, nil
}
2.3 測試與驗證
實現完接口後,需要進行測試以確保驅動符合CSI規範。核心測試工具是CSI sanity test,它會模擬各種存儲操作,驗證驅動的基本功能 。
首先,安裝測試工具:
# 克隆CSI測試項目
git clone https://github.com/container-storage-interface/spec.git
cd spec
# 安裝測試依賴
make build
然後,運行Sanity測試:
# 準備測試環境
export KUBERNETES大佬=集羣API服務器地址
export KUBECONFIG=~/.kube/config
# 運行Sanity測試
./bin/csi-sanity \
-- CSI Socket=/var/lib/kubelet/plugins/mystorage.csi.example.com/csi.sock \
-- driver-name=mystorage.csi.example.com \
-- node-id=節點ID
Sanity測試會驗證驅動的基本功能,如卷的創建、刪除、掛載和卸載等。如果測試通過,説明驅動符合CSI規範的基本要求。
此外,還需要進行功能性測試,驗證驅動的特定能力,如動態供應、卷擴容、快照管理等。可以使用以下YAML文件創建測試PVC和Pod:
# 測試PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-test-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: mystorage-csi
# 測試Pod
apiVersion: v1
kind: Pod
metadata:
name: csi-test-pod
spec:
containers:
- name: test
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: testvol
mountPath: /data
volumes:
- name: testvol
csi:
driver: mystorage.csi.example.com
volumeHandle: 由驅動返回的卷ID
fsType: ext4
創建並驗證測試資源:
kubectl apply -f csi-test-pvc.yaml
kubectl apply -f csi-test-pod.yaml
kubectl get pvc csi-test-pvc
kubectl get pod csi-test-pod
如果PVC狀態變為Bound,Pod狀態變為Running,並且Pod能夠訪問掛載的卷,説明驅動功能正常。
2.4 鏡像構建
實現和測試完成後,需要將驅動構建為Docker鏡像。Dockerfile配置是關鍵,需要確保驅動的正確打包和依賴項的包含 。
創建Dockerfile:
FROM golang:1.20 as builder
WORKDIR /go/src/my-csi-driver
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o csi-driver .
FROM alpine:3.16
WORKDIR /
COPY --from=builder /go/src/my-csi-driver/csi-driver .
COPY --from=builder /go/src/my-csi-driver/protos/protos .
# 添加必要的掛載工具
RUN apk add --no-cache mountutil
EXPOSE 10250
CMD ["/CSI-driver"]
構建並推送鏡像:
# 構建Docker鏡像
docker build -t my-registry/my-csi-driver:v1.0 .
# 推送鏡像到私有倉庫
docker push my-registry/my-csi-driver:v1.0
三、CSI驅動部署與功能驗證
3.1 部署驅動組件
自定義CSI驅動需要部署到Kubernetes集羣中。部署架構通常包括三個部分:驅動組件、Sidecar控制器和CSIDriver對象 。
首先,部署Node組件,使用DaemonSet在所有節點上運行 :
# csi-node-plugin.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: csi-node-plugin
namespace: kube-system
spec:
selector:
matchLabels:
app: csi-node-plugin
template:
metadata:
labels:
app: csi-node-plugin
spec:
containers:
- name: csi-node-plugin
image: my-registry/my-csi-driver:v1.0
args:
- "--node-id=$(KUBERNETES莢節點ID)"
- "--CSI-socket=/var/lib/kubelet/plugins/mystorage.csi.example.com/csi.sock"
volumeMounts:
- name: CSI-socket-direction
mountPath: /var/lib/kubelet/plugins/mystorage.csi.example.com
# 必須設置為Bidirectional
mountPropagation: Bidirectional
- name: plugindata
mountPath: /var/lib/kubelet/plugins登記
- name: node-driver-registrar
image: kubernetes-sigs/csi-node-driver-registrar:v2.3.0
args:
- "--node-id=$(KUBERNETES莢節點ID)"
- "--CSI驅動名稱=mystorage.csi.example.com"
- "--kubeconfig=/etc/kubeconfig/kubeconfig"
volumeMounts:
- name: plugindata
mountPath: /var/lib/kubelet/plugins登記
- name: CSIDriverRegistarSocket
mountPath: /var/lib/kubelet/plugins/mystorage.csi.example.com
volumes:
- name: CSI-socket-direction
hostPath:
path: /var/lib/kubelet/plugins/mystorage.csi.example.com
type: Directory
- name: plugindata
hostPath:
path: /var/lib/kubelet/plugins登記
type: Directory
然後,部署Controller組件,使用StatefulSet在控制平面運行 :
# csi-controller-plugin.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: csi-controller-plugin
namespace: kube-system
spec:
selector:
matchLabels:
app: csi-controller-plugin
template:
metadata:
labels:
app: csi-controller-plugin
spec:
containers:
- name: csi-controller-plugin
image: my-registry/my-csi-driver:v1.0
args:
- "--CSI-socket=/var/lib/kubelet/plugins/mystorage.csi.example.com/csi.sock"
volumeMounts:
- name: CSI-socket-direction
mountPath: /var/lib/kubelet/plugins/mystorage.csi.example.com
# 必須設置為Bidirectional
mountPropagation: Bidirectional
volumes:
- name: CSI-socket-direction
hostPath:
path: /var/lib/kubelet/plugins/mystorage.csi.example.com
type: Directory
3.2 配置CSIDriver對象
部署驅動組件後,需要創建CSIDriver對象,向Kubernetes註冊驅動 :
# csidriver.yaml
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: mystorage.csi.example.com
spec:
attachRequired: false # 根據存儲系統特性設置
volumeLifecycleModes:
- "Persistent"
- "Ephemeral" # 支持的卷生命週期模式
controllerExpand: true # 是否支持卷擴容
nodeExpand: true # 是否支持節點級卷擴容
應用配置:
kubectl apply -f csidriver.yaml
3.3 配置Sidecar控制器
除了驅動組件外,還需要部署相應的Sidecar控制器,以實現特定功能 :
部署Provisioner(動態供應):
# external-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-provisioner
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: external-provisioner
template:
metadata:
labels:
app: external-provisioner
spec:
containers:
- name: external-provisioner
image: kubernetes-sigs/external-provisioner:v1.3.0
args:
- "--CSI-driver=mystorage.csi.example.com"
- "--kubeconfig=/etc/kubeconfig/kubeconfig"
volumeMounts:
- name: kubeconfig
mountPath: /etc/kubeconfig
# 必須設置為Bidirectional
mountPropagation: Bidirectional
volumes:
- name: kubeconfig
hostPath:
path: /etc/kubeconfig
type: Directory
部署Attacher(卷附加):
# external-attacher.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-attacher
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: external-attacher
template:
metadata:
labels:
app: external-attacher
spec:
containers:
- name: external-attacher
image: kubernetes-sigs/external-attacher:v4.2.0
args:
- "--CSI-driver=mystorage.csi.example.com"
- "--kubeconfig=/etc/kubeconfig/kubeconfig"
volumeMounts:
- name: kubeconfig
mountPath: /etc/kubeconfig
# 必須設置為Bidirectional
mountPropagation: Bidirectional
volumes:
- name: kubeconfig
hostPath:
path: /etc/kubeconfig
type: Directory
部署Resizer(卷擴容):
# external-resizer.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-resizer
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: external-resizer
template:
metadata:
labels:
app: external-resizer
spec:
containers:
- name: external-resizer
image: kubernetes-sigs/external-resizer:v1.3.0
args:
- "--CSI-driver=mystorage.csi.example.com"
- "--kubeconfig=/etc/kubeconfig/kubeconfig"
volumeMounts:
- name: kubeconfig
mountPath: provisioner配置
# 必須設置為Bidirectional
mountPropagation: Bidirectional
volumes:
- name: kubeconfig
hostPath:
path: /etc/kubeconfig
type: Directory
應用配置:
kubectl apply -f external-provisioner.yaml
kubectl apply -f external-attacher.yaml
kubectl apply -f external-resizer.yaml
3.4 創建StorageClass
部署完驅動和Sidecar後,需要創建StorageClass,指定使用自定義CSI驅動 :
# my-csi-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: my-csi-storageclass
provisioner: mystorage.csi.example.com # 指定CSI驅動名稱
parameters:
type: ssd # 存儲類型參數,根據驅動需求設置
fsType: ext4 # 文件系統類型
volumeBindingMode: "WaitForFirstConsumer" # 卷綁定模式
allowVolumeExpansion: true # 是否允許卷擴容
應用配置:
kubectl apply -f my-csi-storageclass.yaml
3.5 功能驗證
創建PVC驗證動態供應功能:
# test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: my-csi-storageclass
應用PVC並檢查狀態:
kubectl apply -f test-pvc.yaml
kubectl get pvc test-pvc
如果PVC狀態變為Bound,説明動態供應功能正常。
創建Pod驗證卷掛載功能:
# test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: testvol
mountPath: /data
volumes:
- name: testvol
csi:
driver: mystorage.csi.example.com
volumeHandle: 由驅動返回的卷ID
fsType: ext4
應用Pod並檢查掛載情況:
kubectl apply -f test-pod.yaml
kubectl exec -it test-pod -- ls /data
如果能夠成功列出掛載點中的文件,説明卷掛載功能正常。
驗證卷擴容功能:
# 更新test-pvc.yaml中的storage請求
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: my-csi-storageclass
更新PVC並檢查擴容結果:
kubectl apply -f test-pvc.yaml
kubectl get pv <返回的PV名稱>
kubectl get pvc test-pvc
如果PV的容量變為2Gi,説明卷擴容功能正常。
四、高級功能實現與優化
4.1 多雲存儲支持
實現多雲存儲支持是高級功能之一。可以通過在驅動中集成多個雲服務提供商的SDK,實現跨雲數據遷移和統一管理 。
例如,實現AWS EBS和阿里云云盤的雙支持:
// 創建卷時根據參數選擇雲服務
func createVolumeOnStorageSystem(capacity int64, volumeType string) (string, error) {
if volumeType == "aws-ebs" {
// 使用AWS SDK創建卷
return createAWSVolume(capacity), nil
} else if volumeType == "aliyun-disk" {
// 使用阿里雲SDK創建卷
return createAliyunVolume(capacity), nil
} else {
return "", fmt.Errorf("不支持的卷類型: %s", volumeType)
}
}
4.2 存儲性能優化
存儲性能優化是提升用户體驗的關鍵。可以通過實現緩存機制、預讀策略和智能調度算法,提高存儲訪問效率。
例如,實現卷的緩存機制:
// 在Node服務中實現卷緩存
type nodeServer struct {
csi NodeServiceServer
mounter *mount.Mounter
cache map[string]*volumeCache
}
type volumeCache struct {
volumeId string
mountPath string
accessMode csi VolumeAccessMode
lastAccessed time.Time
}
func (s *nodeServer) NodeStageVolume(
context.Context, *csi NodeStageVolumeRequest) (*csi NodeStageVolumeResponse, error) {
// 檢查緩存中是否已有該卷
if cacheVolume, ok := s.cache[req VolumeId]; ok {
// 如果已有,直接返回
return &CSI NodeStageVolumeResponse{}, nil
}
// 否則,準備卷並添加到緩存
targetPath := req TargetPath
volumeId := req VolumeId
err := s.mounter stageVolume(targetPath, volumeId)
if err != nil {
return nil, status.Error(codes內部, err.Error())
}
s.cache[req VolumeId] = &volumeCache{
volumeId: req VolumeId,
mountPath: targetPath,
accessMode: req VolumeCapability.Mount.MountOptions,
lastAccessed: time.Now(),
}
return &CSI NodeStageVolumeResponse{}, nil
}
4.3 存儲快照管理
存儲快照管理是企業級存儲的重要功能。通過實現CreateSnapshot和DeleteSnapshot接口,可以支持卷的快照和恢復 。
例如,實現卷快照功能:
func (s *controllerServer) CreateSnapshot(
context.Context, *csi CreateSnapshotRequest) (*csi CreateSnapshotResponse, error) {
// 解析請求參數
volumeId := req VolumeId
snapshotName := req Name
// 調用底層存儲API創建快照
snapshotId, err := createSnapshotOnStorageSystem(volumeId, snapshotName)
if err != nil {
return nil, status.Error(codes內部, err.Error())
}
return &CSI CreateSnapshotResponse{
Snapshot: &CSI Snapshot{
SnapshotId: snapshotId,
SourceVolumeId: volumeId,
},
}, nil
}
func (s *controllerServer) DeleteSnapshot(
context.Context, *csi DeleteSnapshotRequest) (*CSI DeleteSnapshotResponse, error) {
// 解析請求參數
snapshotId := req SnapshotId
// 調用底層存儲API刪除快照
err := deleteSnapshotFromStorageSystem(snapshotId)
if err != nil {
return nil, status.Error(codes內部, err.Error())
}
return &CSI DeleteSnapshotResponse{}, nil
}
部署Snapshotter Sidecar:
# external-snapshotter.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-snapshotter
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: external-snapshotter
template:
metadata:
labels:
app: external-snapshotter
spec:
containers:
- name: external-snapshotter
image: kubernetes-sigs/external-snapshotter:v4.2.0
args:
- "--CSI-driver=mystorage.csi.example.com"
- "--kubeconfig=/etc/kubeconfig/kubeconfig"
volumeMounts:
- name: kubeconfig
mountPath: /etc/kubeconfig
# 必須設置為Bidirectional
mountPropagation: Bidirectional
volumes:
- name: kubeconfig
hostPath:
path: /etc/kubeconfig
type: Directory
應用配置:
kubectl apply -f external-snapshotter.yaml
創建VolumeSnapshot驗證快照功能:
# volume-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: my-volume-snapshot
spec:
snapshotClassName: my-snapshot-class
source:
persistentVolumeClaimName: test-pvc
應用快照並檢查狀態:
kubectl apply -f volume-snapshot.yaml
kubectl get volumesnapshot my-volume-snapshot
五、常見問題與解決方案
5.1 驅動註冊失敗
如果CSIDriver對象未被正確註冊,kubelet可能無法識別CSI驅動。解決方案是檢查CSIDriver對象的配置,確保與驅動名稱和能力匹配。
檢查CSIDriver對象:
kubectl get csidriver mystorage.csi.example.com -o yaml
如果發現配置錯誤,更新CSIDriver對象:
# 更新csidriver.yaml中的配置
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: mystorage.csi.example.com
spec:
attachRequired: false
volumeLifecycleModes:
- "Persistent"
- "Ephemeral"
controllerExpand: true
nodeExpand: true
5.2 卷掛載失敗
卷掛載失敗通常是由於權限問題或掛載路徑錯誤。解決方案是檢查kubelet的權限設置和掛載路徑配置 。
檢查kubelet權限:
# 檢查kubelet的卷掛載權限
kubectl describe pod <驅動Pod名稱> | grep "Events"
如果發現權限錯誤,可以嘗試以下解決方案:
# 在驅動Pod的YAML中添加捲掛載權限
spec:
containers:
- name: csi-node-plugin
securityContext:
privileged: true
capabilities:
add:
- "SYS_ADMIN"
- "SYSFS"
5.3 動態供應失敗
動態供應失敗通常是由於StorageClass配置錯誤或驅動未正確實現CreateVolume接口。解決方案是檢查StorageClass參數和驅動代碼中的CreateVolume實現。
檢查StorageClass參數:
kubectl get storageclass my-csi-storageclass -o yaml
如果發現參數錯誤,更新StorageClass配置:
# 更新my-csi-storageclass.yaml中的參數
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: my-csi-storageclass
provisioner: mystorage.csi.example.com
parameters:
type: ssd # 確保參數與驅動要求匹配
fsType: ext4
volumeBindingMode: "WaitForFirstConsumer"
allowVolumeExpansion: true
檢查驅動代碼中的CreateVolume實現:
// 檢查createVolumeOnStorageSystem函數是否正確
func createVolumeOnStorageSystem(capacity int64, volumeType string) (string, error) {
// 確保調用正確的底層存儲API
// 確保處理所有可能的錯誤情況
// 確保返回正確的volumeId
}