博客 / 詳情

返回

自定義實現Kubernetes CSI

目錄
  • 自定義實現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 動態供應失敗

自定義實現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
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.