查看: 95|回复: 0

自定义实现Kubernetes CSI

[复制链接]

0

主题

0

回帖

0

积分

积极分子

金币
0
阅读权限
220
精华
0
威望
0
贡献
0
在线时间
0 小时
注册时间
2008-4-13
发表于 2025-11-13 18:25:00 | 显示全部楼层 |阅读模式

目录
  • 自定义实现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
    }
    


    来源:https://www.cnblogs.com/styshoo/p/19219172
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    相关侵权、举报、投诉及建议等,请发 E-mail:qiongdian@foxmail.com

    Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

    在本版发帖返回顶部