诺志
软硬件开发技术笔记
k8s spdk-csi对接插件代码阅读
2025-04-23

前置概念
1.spdkcsi : spdk提供的csi对接插件,其本身不在spdk代码中,为spdk为k8s-csi提供的对接插件
2.spdk-server : 真正提供存储服务的spdk服务器节点,接收业务端connect/login/io请求
3.spdk-server-api : spdk-serve本身提供的是基于socket文件的rpc统信,为对外暴露更通用的配置接口,spdk提供了rpc_http_proxy.py代理,该代理的作用是将apt-restful请求转换为spdk-server-rpc请求
4.调用顺序: k8s-> spdkcsi -> spdk-server-api -> spdk-server-rpc -> spdk-server

1.配置文件解析

配置文件用于告知spdk插件,spdk-server地址和支持的传输协议,用于spdkcsi插件向spdk-server(以rpc_http_proxy.py方式运行的http代理转发给spdk-server)发送配置请求

配置spdk节点地址和传输协议支持通过deploy时的配置项ConfigMap

# cat ./deploy/kubernetes/config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: spdkcsi-cm
data:
  config.json: |-
    {
      "nodes": [
        {
          "name": "k8s-1_24-oe2203-214-spdk",
          "rpcURL": "http://10.24.8.214:9009",      # spdk-server的rpc_http_proxy地址,该路径仅用来走配置请求
          "targetType": "nvme-tcp",                 # 当前spdk-server支持的传输协议
          "targetAddr": "10.24.8.214"               # 当前spdk-server对外暴露的IO target地址,该路径仅用来走IO请求,用于后面connect/login使用存储资源
        }
      ]
    }

该配置项通过controller部署文件中spdkcsi-config配置进入controller容器的/etc/spdkcsi-config/config.json文件

# cat ./deploy/kubernetes/controller.yaml
...
      - name: spdkcsi-controller
        image: spdkcsi/spdkcsi:canary
        imagePullPolicy: "IfNotPresent"
        args:
        - "--v=5"
        - "--endpoint=unix:///csi/csi-provisioner.sock"
        - "--nodeid=$(NODE_ID)"
        - "--controller"
        env:
        - name: NODE_ID
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        volumeMounts:
        - name: socket-dir
          mountPath: /csi
        - name: spdkcsi-config              # 管理下面的卷列表定义中的spdkcsi-config
          mountPath: /etc/spdkcsi-config/   # 卷挂载位置
          readOnly: true
      volumes:                              # 此处定义卷列表
      - name: socket-dir
        emptyDir:
          medium: "Memory"
      - name: spdkcsi-config                # 定义卷名称
        configMap:                          # 实际对应为configMap类型
          name: spdkcsi-cm                  # 在configMap中的名称
...

controller在启动时读取该配置文件
其中SPDKCSI_CONFIG是环境变量如果指定具体的配置文件值
/etc/spdkcsi-config/config.json是如果没有环境变量,则默认使用的值
当前实际代码运行时并没有设置SPDKCSI_CONFIG环境变量的地方,仅在test程序中,通过该环境变量传递参数
实际使用controllerserver_test.go时,可以临时创建/etc/spdkcsi-config/config.json

# cat pkg/spdk/controllerserver.go**
func newControllerServer(d *csicommon.CSIDriver) (*controllerServer, error) {
...

    # 此处通过配置文件,注册所有的spdk-server配置节点
    config, err := util.NewCSIControllerConfig("SPDKCSI_CONFIG", "/etc/spdkcsi-config/config.json")
    if err != nil {
        return nil, err
    }
    for i := range config.Nodes {
        server.spdkNodeConfigs[config.Nodes[i].Name] = &config.Nodes[i]
    }

...
}

2.请求认证

认证用于spdkcsi向spdk-servcer-api执行配置请求时的用户名和密码

以下是认证配置文件,如果有多个spdk-server-api可以写多个

# cat ./deploy/kubernetes/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: spdkcsi-secret
stringData:
  secret.json: |-
    {
      "rpcTokens": [
        {
          "name": "k8s-1_24-oe2203-214-spdk",   # 此名称和config-map.yaml配置中的name对应
          "username": "spdkcsiuser",            # 启动rpc_http_proxy.py时指定,如/root/spdk/scripts/rpc_http_proxy.py 10.24.8.214 9009 spdkcsiuser spdkcsipass
          "password": "spdkcsipass"
        }
      ]
    }

该配置项通过定义sc的时候传入

# cat deploy/kubernetes/storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: spdkcsi-sc
provisioner: csi.spdk.io
parameters:
  fsType: ext4
  csi.storage.k8s.io/provisioner-secret-name: spdkcsi-secret  # 此处定义使用secret
  csi.storage.k8s.io/provisioner-secret-namespace: default
reclaimPolicy: Delete
volumeBindingMode: Immediate

后续在逻辑请求时,请求函数的参数将传递该参数给csi的实现,如

# 创建卷函数入口
func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {

# csi.CreateVolumeRequest定义中会将secrete传递
type CreateVolumeRequest struct {
...
    // Secrets required by plugin to complete volume creation request.
    // This field is OPTIONAL. Refer to the `Secrets Requirements`
    // section on how to use this field.
    Secrets map[string]string `protobuf:"bytes,5,rep,name=secrets,proto3" json:"secrets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
...

在spds-csi中,通过节点和认证名字的配对,确定哪个节点使用哪个认证

# cat pkg/spdk/controllerserver.go
func (cs *controllerServer) getSpdkNode(nodeName string, secrets map[string]string) (util.SpdkNode, error) {
...
    for i := range spdkSecrets.Tokens {
        token := spdkSecrets.Tokens[i]
        if token.Name == nodeName {             # 在此处进行config-map.yaml和secret.yaml名字对比,找到对应的用户信息
            spdkNode, err := util.NewSpdkNode(node.URL, token.UserName, token.Password, node.TargetType, node.TargetAddr)
            if err != nil {
                klog.Errorf("failed to create spdk node %s: %s", node.Name, err.Error())
            }
            return spdkNode, nil
        }
    }
...
}
分类
2篇
c
1篇
8篇
18篇
8篇
2篇
k8s
2篇
搜索