海小天 發表於 2025-11-13 18:25:00

自定义实现Kubernetes CSI

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>自定义实现Kubernetes CSI<ul><li>一、CSI架构设计目标与核心组件<ul><li>1.1 设计目标</li><li>1.2 核心组件</li><li>1.3 工作原理</li></ul></li><li>二、自定义CSI驱动实现步骤<ul><li>2.1 环境准备</li><li>2.2 接口实现</li><li>2.3 测试与验证</li><li>2.4 镜像构建</li></ul></li><li>三、CSI驱动部署与功能验证<ul><li>3.1 部署驱动组件</li><li>3.2 配置CSIDriver对象</li><li>3.3 配置Sidecar控制器</li><li>3.4 创建StorageClass</li><li>3.5 功能验证</li></ul></li><li>四、高级功能实现与优化<ul><li>4.1 多云存储支持</li><li>4.2 存储性能优化</li><li>4.3 存储快照管理</li></ul></li><li>五、常见问题与解决方案<ul><li>5.1 驱动注册失败</li><li>5.2 卷挂载失败</li><li>5.3 动态供应失败</li></ul></li></ul></li></ul></div><p></p>
<h2 id="自定义实现kubernetes-csi">自定义实现Kubernetes CSI</h2>
<p>Kubernetes CSI(容器存储接口)是一种标准化的容器存储接口规范,旨在解决Kubernetes早期版本中存储插件与核心代码耦合的问题。<strong>CSI通过将存储系统与容器编排平台解耦,实现了多供应商存储系统的灵活集成和动态管理</strong>。它已成为Kubernetes存储扩展的唯一标准,取代了原有的in-tree存储插件方式。</p>
<h3 id="一csi架构设计目标与核心组件">一、CSI架构设计目标与核心组件</h3>
<h4 id="11-设计目标">1.1 设计目标</h4>
<p>Kubernetes最初采用in-tree方式管理存储插件,即将存储驱动的源码直接集成到Kubernetes代码库中。这种方式导致三个主要问题:</p>
<p>首先,<strong>耦合性高</strong>。每次引入新的存储系统支持都需要修改Kubernetes核心代码,增加了维护复杂性和版本升级风险。</p>
<p>其次,<strong>发布周期不同步</strong>。存储插件的发布周期与Kubernetes的发布周期绑定,无法独立开发和发布,限制了存储供应商的创新速度。</p>
<p>最后,<strong>扩展性受限</strong>。Kubernetes核心代码库难以容纳所有可能的存储系统实现,导致无法灵活支持多样化的存储需求。</p>
<p>为解决这些问题,Kubernetes社区于2017年引入了CSI规范,其核心目标是实现存储系统与Kubernetes核心代码的解耦,使存储供应商能够独立开发、发布和维护存储插件,同时保持与Kubernetes的兼容性。</p>
<h4 id="12-核心组件">1.2 核心组件</h4>
<p>CSI架构主要由以下组件构成:</p>
<p><strong>CSI Driver</strong>:由存储供应商提供的核心插件,分为两个部分:</p>
<ul>
<li>Controller Service:运行在Kubernetes控制平面,处理集群级别的卷管理操作,如创建、删除、附加、分离、快照和还原等。</li>
<li>Node Service:运行在每个Kubernetes节点上,处理节点级别的卷操作,如挂载、卸载和报告磁盘使用情况等。</li>
</ul>
<p><strong>Sidecar控制器</strong>:由Kubernetes团队维护的辅助组件,负责监听Kubernetes资源对象并触发相应的CSI接口调用:</p>
<ul>
<li>External Provisioner:监听PersistentVolumeClaim(PVC)对象,触发CreateVolume和DeleteVolume操作,实现卷的动态供应。</li>
<li>External Attacher:监听VolumeAttachment对象,触发ControllerPublish和ControllerUnpublish操作,管理卷的附加和分离。</li>
<li>External Resizer:监听PersistentVolumeClaim的大小变更,触发ResizeVolume操作,实现卷的动态扩容。</li>
<li>External Snapshotter:监听VolumeSnapshot对象,触发CreateSnapshot和DeleteSnapshot操作,管理卷的快照。</li>
<li>Node DriverRegistrar:负责将CSI驱动注册到kubelet,使节点能够识别和使用该驱动。</li>
</ul>
<p><strong>Kubernetes组件</strong>:与CSI驱动交互的核心组件:</p>
<ul>
<li>kubelet:负责在节点上执行Pod的创建、管理和删除,与Node Service通过gRPC通信。</li>
<li>控制器管理器:管理多个控制器,包括负责PVC/PV绑定的控制器等。</li>
<li>API Server:处理所有Kubernetes API请求,是CSI驱动与Kubernetes交互的入口。</li>
</ul>
<h4 id="13-工作原理">1.3 工作原理</h4>
<p>CSI驱动通过gRPC协议与Kubernetes组件交互,实现存储卷的全生命周期管理。<strong>工作流程可分为四个主要阶段</strong>:</p>
<p>当用户创建PVC时,External Provisioner监听到该事件,向CSI Controller Service发起CreateVolume请求,创建底层存储卷。完成后,自动创建对应的PV并与PVC绑定。</p>
<p>当Pod被调度到节点上时,kubelet检查该节点是否具备所需卷的访问权限。如果卷尚未附加到节点,External Attacher会向CSI Controller Service发起ControllerPublishVolume请求,将卷附加到节点。</p>
<p>卷附加完成后,kubelet通过NodePublishVolume接口将卷挂载到Pod的指定路径。这一过程包括NodeStageVolume(在节点上准备卷)和NodePublishVolume(将卷挂载到容器)两个步骤。</p>
<p>当Pod结束或PVC被删除时,相关控制器会依次触发卷的卸载、分离和删除操作,确保存储资源的正确回收。</p>
<h3 id="二自定义csi驱动实现步骤">二、自定义CSI驱动实现步骤</h3>
<h4 id="21-环境准备">2.1 环境准备</h4>
<p>实现自定义CSI驱动首先需要准备开发环境。<strong>推荐使用Go语言作为开发语言</strong>,因为这是CSI规范的主要实现语言,社区资源丰富,且与Kubernetes生态兼容性好。</p>
<p>安装必要的开发工具:</p>
<pre><code class="language-bash"># 安装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
</code></pre>
<p>配置Kubernetes集群:</p>
<pre><code class="language-bash"># 检查Kubernetes版本,确保不低于v1.13
kubectl version --short
# 启用CSI特性(如果集群版本较低)
kubectl get nodes -o jsonpath='{.items[*].metadata.name}' | xargs -I {} kubectl label nodes {} feature.csi storage=driver
</code></pre>
<h4 id="22-接口实现">2.2 接口实现</h4>
<p>自定义CSI驱动需要实现三个主要gRPC服务接口:Identity、Controller和Node。<strong>核心是实现这些接口与底层存储系统的交互逻辑</strong>。</p>
<p>首先是Identity服务,用于提供驱动基本信息和能力:</p>
<pre><code class="language-go">// 实现Identity服务
type identityServer struct {
    csi IdentityServiceServer
}

func (s *identityServer) GetPluginInfo(
    context.Context, *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
    return &amp;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 &amp;csi.GetPluginCapabilitiesResponse{
      Capabilities: &amp;csi.GetPluginCapabilitiesResponse_Capabilities{
            Capabilities: []csi PluginCapability{
                {
                  Type: &amp;csi PluginCapabilityattacher{
                        Attacher: &amp;csi PluginCapabilityAttacher{}
                  },
                },
                {
                  Type: &amp;csi PluginCapabilityvolume Expansion{
                        VolumeExpansion: &amp;csi PluginCapabilityVolumeExpansion{}
                  },
                },
            },
      },
    }, nil
}
</code></pre>
<p>其次是Controller服务,实现集群级别的卷管理操作:</p>
<pre><code class="language-go">// 实现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 &amp;csi.CreateVolumeResponse{
      Volume: &amp;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 &amp;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 &amp;csi ControllerPublishVolumeResponse{}, nil
}
</code></pre>
<p>最后是Node服务,实现节点级别的卷操作:</p>
<pre><code class="language-go">// 实现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 &amp;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 &amp;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 &amp;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 &amp;csi NodeUnpublishVolumeResponse{}, nil
}
</code></pre>
<h4 id="23-测试与验证">2.3 测试与验证</h4>
<p>实现完接口后,需要进行测试以确保驱动符合CSI规范。<strong>核心测试工具是CSI sanity test</strong>,它会模拟各种存储操作,验证驱动的基本功能。</p>
<p>首先,安装测试工具:</p>
<pre><code class="language-bash"># 克隆CSI测试项目
git clone https://github.com/container-storage-interface/spec.git
cd spec
# 安装测试依赖
make build
</code></pre>
<p>然后,运行Sanity测试:</p>
<pre><code class="language-bash"># 准备测试环境
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
</code></pre>
<p>Sanity测试会验证驱动的基本功能,如卷的创建、删除、挂载和卸载等。如果测试通过,说明驱动符合CSI规范的基本要求。</p>
<p>此外,还需要进行功能性测试,验证驱动的特定能力,如动态供应、卷扩容、快照管理等。可以使用以下YAML文件创建测试PVC和Pod:</p>
<pre><code class="language-yaml"># 测试PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-test-pvc
spec:
accessModes:
    - ReadWriteOnce
resources:
    requests:
      storage: 1Gi
storageClassName: mystorage-csi
</code></pre>
<pre><code class="language-yaml"># 测试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
</code></pre>
<p>创建并验证测试资源:</p>
<pre><code class="language-bash">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
</code></pre>
<p>如果PVC状态变为Bound,Pod状态变为Running,并且Pod能够访问挂载的卷,说明驱动功能正常。</p>
<h4 id="24-镜像构建">2.4 镜像构建</h4>
<p>实现和测试完成后,需要将驱动构建为Docker镜像。<strong>Dockerfile配置是关键,需要确保驱动的正确打包和依赖项的包含</strong>。</p>
<p>创建Dockerfile:</p>
<pre><code class="language-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"]
</code></pre>
<p>构建并推送镜像:</p>
<pre><code class="language-bash"># 构建Docker镜像
docker build -t my-registry/my-csi-driver:v1.0 .
# 推送镜像到私有仓库
docker push my-registry/my-csi-driver:v1.0
</code></pre>
<h3 id="三csi驱动部署与功能验证">三、CSI驱动部署与功能验证</h3>
<h4 id="31-部署驱动组件">3.1 部署驱动组件</h4>
<p>自定义CSI驱动需要部署到Kubernetes集群中。<strong>部署架构通常包括三个部分</strong>:驱动组件、Sidecar控制器和CSIDriver对象。</p>
<p>首先,部署Node组件,使用DaemonSet在所有节点上运行:</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>然后,部署Controller组件,使用StatefulSet在控制平面运行:</p>
<pre><code class="language-yaml"># 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
</code></pre>
<h4 id="32-配置csidriver对象">3.2 配置CSIDriver对象</h4>
<p>部署驱动组件后,需要创建CSIDriver对象,向Kubernetes注册驱动:</p>
<pre><code class="language-yaml"># 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# 是否支持节点级卷扩容
</code></pre>
<p>应用配置:</p>
<pre><code class="language-bash">kubectl apply -f csidriver.yaml
</code></pre>
<h4 id="33-配置sidecar控制器">3.3 配置Sidecar控制器</h4>
<p>除了驱动组件外,还需要部署相应的Sidecar控制器,以实现特定功能:</p>
<p>部署Provisioner(动态供应):</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>部署Attacher(卷附加):</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>部署Resizer(卷扩容):</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>应用配置:</p>
<pre><code class="language-bash">kubectl apply -f external-provisioner.yaml
kubectl apply -f external-attacher.yaml
kubectl apply -f external-resizer.yaml
</code></pre>
<h4 id="34-创建storageclass">3.4 创建StorageClass</h4>
<p>部署完驱动和Sidecar后,需要创建StorageClass,指定使用自定义CSI驱动:</p>
<pre><code class="language-yaml"># 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# 是否允许卷扩容
</code></pre>
<p>应用配置:</p>
<pre><code class="language-bash">kubectl apply -f my-csi-storageclass.yaml
</code></pre>
<h4 id="35-功能验证">3.5 功能验证</h4>
<p>创建PVC验证动态供应功能:</p>
<pre><code class="language-yaml"># test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
    - ReadWriteOnce
resources:
    requests:
      storage: 1Gi
storageClassName: my-csi-storageclass
</code></pre>
<p>应用PVC并检查状态:</p>
<pre><code class="language-bash">kubectl apply -f test-pvc.yaml
kubectl get pvc test-pvc
</code></pre>
<p>如果PVC状态变为Bound,说明动态供应功能正常。</p>
<p>创建Pod验证卷挂载功能:</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>应用Pod并检查挂载情况:</p>
<pre><code class="language-bash">kubectl apply -f test-pod.yaml
kubectl exec -it test-pod -- ls /data
</code></pre>
<p>如果能够成功列出挂载点中的文件,说明卷挂载功能正常。</p>
<p>验证卷扩容功能:</p>
<pre><code class="language-yaml"># 更新test-pvc.yaml中的storage请求
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
    - ReadWriteOnce
resources:
    requests:
      storage: 2Gi
storageClassName: my-csi-storageclass
</code></pre>
<p>更新PVC并检查扩容结果:</p>
<pre><code class="language-bash">kubectl apply -f test-pvc.yaml
kubectl get pv &lt;返回的PV名称&gt;
kubectl get pvc test-pvc
</code></pre>
<p>如果PV的容量变为2Gi,说明卷扩容功能正常。</p>
<h3 id="四高级功能实现与优化">四、高级功能实现与优化</h3>
<h4 id="41-多云存储支持">4.1 多云存储支持</h4>
<p>实现多云存储支持是高级功能之一。<strong>可以通过在驱动中集成多个云服务提供商的SDK,实现跨云数据迁移和统一管理</strong>。</p>
<p>例如,实现AWS EBS和阿里云云盘的双支持:</p>
<pre><code class="language-go">// 创建卷时根据参数选择云服务
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)
    }
}
</code></pre>
<h4 id="42-存储性能优化">4.2 存储性能优化</h4>
<p>存储性能优化是提升用户体验的关键。<strong>可以通过实现缓存机制、预读策略和智能调度算法,提高存储访问效率</strong>。</p>
<p>例如,实现卷的缓存机制:</p>
<pre><code class="language-go">// 在Node服务中实现卷缓存
type nodeServer struct {
    csi NodeServiceServer
    mounter *mount.Mounter
    cache map*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; ok {
      // 如果已有,直接返回
      return &amp;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 = &amp;volumeCache{
      volumeId: req VolumeId,
      mountPath: targetPath,
      accessMode: req VolumeCapability.Mount.MountOptions,
      lastAccessed: time.Now(),
    }

    return &amp;CSI NodeStageVolumeResponse{}, nil
}
</code></pre>
<h4 id="43-存储快照管理">4.3 存储快照管理</h4>
<p>存储快照管理是企业级存储的重要功能。<strong>通过实现CreateSnapshot和DeleteSnapshot接口,可以支持卷的快照和恢复</strong>。</p>
<p>例如,实现卷快照功能:</p>
<pre><code class="language-go">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 &amp;CSI CreateSnapshotResponse{
      Snapshot: &amp;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 &amp;CSI DeleteSnapshotResponse{}, nil
}
</code></pre>
<p>部署Snapshotter Sidecar:</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>应用配置:</p>
<pre><code class="language-bash">kubectl apply -f external-snapshotter.yaml
</code></pre>
<p>创建VolumeSnapshot验证快照功能:</p>
<pre><code class="language-yaml"># 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
</code></pre>
<p>应用快照并检查状态:</p>
<pre><code class="language-bash">kubectl apply -f volume-snapshot.yaml
kubectl get volumesnapshot my-volume-snapshot
</code></pre>
<h3 id="五常见问题与解决方案">五、常见问题与解决方案</h3>
<h4 id="51-驱动注册失败">5.1 驱动注册失败</h4>
<p>如果CSIDriver对象未被正确注册,kubelet可能无法识别CSI驱动。<strong>解决方案是检查CSIDriver对象的配置,确保与驱动名称和能力匹配</strong>。</p>
<p>检查CSIDriver对象:</p>
<pre><code class="language-bash">kubectl get csidriver mystorage.csi.example.com -o yaml
</code></pre>
<p>如果发现配置错误,更新CSIDriver对象:</p>
<pre><code class="language-yaml"># 更新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
</code></pre>
<h4 id="52-卷挂载失败">5.2 卷挂载失败</h4>
<p>卷挂载失败通常是由于权限问题或挂载路径错误。<strong>解决方案是检查kubelet的权限设置和挂载路径配置</strong>。</p>
<p>检查kubelet权限:</p>
<pre><code class="language-bash"># 检查kubelet的卷挂载权限
kubectl describe pod &lt;驱动Pod名称&gt; | grep "Events"
</code></pre>
<p>如果发现权限错误,可以尝试以下解决方案:</p>
<pre><code class="language-yaml"># 在驱动Pod的YAML中添加卷挂载权限
spec:
containers:
- name: csi-node-plugin
    securityContext:
      privileged: true
      capabilities:
      add:
          - "SYS_ADMIN"
          - "SYSFS"
</code></pre>
<h4 id="53-动态供应失败">5.3 动态供应失败</h4>
<p>动态供应失败通常是由于StorageClass配置错误或驱动未正确实现CreateVolume接口。<strong>解决方案是检查StorageClass参数和驱动代码中的CreateVolume实现</strong>。</p>
<p>检查StorageClass参数:</p>
<pre><code class="language-bash">kubectl get storageclass my-csi-storageclass -o yaml
</code></pre>
<p>如果发现参数错误,更新StorageClass配置:</p>
<pre><code class="language-yaml"># 更新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
</code></pre>
<p>检查驱动代码中的CreateVolume实现:</p>
<pre><code class="language-go">// 检查createVolumeOnStorageSystem函数是否正确
func createVolumeOnStorageSystem(capacity int64, volumeType string) (string, error) {
    // 确保调用正确的底层存储API
    // 确保处理所有可能的错误情况
    // 确保返回正确的volumeId
}
</code></pre><br><br>
来源:https://www.cnblogs.com/styshoo/p/19219172
頁: [1]
查看完整版本: 自定义实现Kubernetes CSI