kubernetes 存储插件CSI开发指导

1.K8s 的持久化存储支持

在支持持久化存储方面,K8s 提供了内嵌原生 Driver 的方式连接外部的常见存储系统例如 NFS、iSCSI、CephFS、RBD 等来满足不同业务的需求。但由于存储生态本身也在不断演进,使用 K8s 内嵌的方式来支持不断变化的存储系统在成本和时效上都会对 K8s 项目自身带来巨大的挑战。

所以和其他服务管理系统一样,K8s 逐渐的将存储系统的具体实现从主项目中分离出来,通过定义接口的方式允许第三方厂商自行接入存储服务。在这个道路上也经历了两个阶段:

  1. Flex Volume, 自 1.2 版本引入。
    第三方存储服务提供商直接在 K8s Server 上部署符合 Flex Volume 规范的 Driver,利用 K8s 暴露出的 mount/unmount/attach/detach 等关键 API 实现存储系统的接入。这个模式主要的问题是,在这个形态下第三方厂商的 Driver 有权限接入物理节点的根文件系统,这会带来安全上的隐患。
  2. Container Storage Interface (CSI), 自 1.9 版本引入,目前已经进入 GA 阶段(1.13)。
    CSI 定义了容器场景所需要的存储控制原语和完整的控制流程,并且在 K8s 的 CSI 实现中,所有的第三方 Driver 和 K8s 的其他服务扩展一样,以服务容器的形态的运行,不会直接影响到 K8s 的核心系统稳定性。

2.存储对象

CSI 定义的存储对象为持久化卷,称之为 Volume。包括两种类型:

  • Mounted File Volume,Node 将会把 Volume 以指定的文件格式 Mount 到 Container 上,从 Container 的角度看到的是一个目录;
  • Raw Block Volume, 直接将 Volume 以 Block Device(磁盘)的形态暴露给 Container,对于某些可以直接操作磁盘的服务,这个形态可以省去文件系统的开销获得更好的性能。

Raw Block Volume 目前还处于 Beta 阶段,所以下文的过程描述和 SMTX 的 CSI Driver 目前的实现方式均针对 Mounted File Volume。

3.基本术语

4.Plugin

RPC接口: CO通过RPC与插件交互, 每个SP必须提供两类插件:

  • Controller Plugin,负责存储对象(Volume)的生命周期管理,在集群中仅需要有一个即可;
  • Node Plugin,在必要时与使用 Volume 的容器所在的节点交互,提供诸如节点上的 Volume 挂载/卸载等动作支持,如有需要则在每个服务节点上均部署。

存储服务商可以根据自身需求实现不同的的 Plugin 组合。例如对于以 NFS 形式提供的存储服务,可以仅实现 Controller Plugin 实现资源的创建和访问权限控制,每个节点均可以通过标准的 NFS 方式获得服务,无需通过 Node Plugin 来实现挂载/卸载等操作。而以 iSCSI 形式提供的存储服务,就需要 Node Plugin 在指定节点上,通过挂载 LUN,格式化,挂载文件系统等一系列动作完成 iSCSI LUN 至容器可见的目录形式的转化。

5.Volume 生命周期

一个典型的 CSI Volume 生命周期如下图(来自 CSI SPEC)所示:

    上面这个图是一个较为复杂的卷供应生命周期图,从这个图我们可以看出一个存储卷的供应分别调用了Controller PluginCreateVolumeControllerPublishVolumeNode PluginNodeStageVolumeNodePublishVolume这4个gRPC接口,存储卷的销毁分别调用了Node PluginNodeUnpublishVolumeNodeUnstageVolumeControllerControllerUnpublishVolumeDeleteVolume这4个gRPC接口。

  1. Volume 被创建后进入 CREATED 状态,此时 Volume 仅在存储系统中存在,对于所有的 Node 或者 Container 都是不可感知的;
  2. 对 CREATED 状态的 Volume 进行 Controlller Publish 动作后在指定的 Node 上进入 NODE_READY 的状态,此时 Node 可以感知到 Volume,但是 Container 依然不可见;
  3. 在 Node 对 Volume 进行 Stage 操作,进入 VOL_READY 的状态,此时 Node 已经与 Volume 建立连接。Volume 在 Node 上以某种形式存在;
  4. 在 Node 上对 Volume 进行 Publish 操作,此时 Volume 将转化为 Node 上 Container 可见的形态被 Container 利用,进入正式服务的状态;
  5. 当 Container 的生命周期结束或其他不再需要 Volume 情况发生时,Node 执行 Unpublish Volume 动作,将 Volume 与 Container 之间的连接关系解除,回退至 VOL_READY 状态;
  6. Node Unstage 操作将会把 Volume 与 Node 的连接断开,回退至 NODE_READY 状态;
  7. Controller Unpublish 操作则会取消 Node 对 Volume 的访问权限;
  8. Delete 则从存储系统中销毁 Volume。

CSI 要求状态转化操作是幂等的,并在原则上保证 Volume 的状态转化是有序进行的。

根据存储使用方式和内部实现的不同,状态机可以略有区别,但对应操作必须是成对出现的。例如在不需要额外建立 Node 与 Volume 之间连接的 Stage/Unstage 阶段时,状态机就可以直接通过 Controller Publish/Unpublish 在 NODE_READY 与 PUBISHED 之间转化,而无需经过 VOL_READY 阶段。Plugin 向 CSI 注册时必须声明自身支持哪些语义。

6.RPC

CSI 要求 Plugin 支持的 RPC 包括:

·  身份服务:Node Plugin和Controller Plugin都必须实现这些RPC集。

·  控制器服务:Controller Plugin必须实现这些RPC集。

·  节点服务:Node Plugin必须实现这些RPC集。

(1)Identity Service:认证服务,Controller 和 Node Plugin 均需要支持

  • GetPluginInfo, 获取 Plugin 基本信息
  • GetPluginCapabilities,获取 Plugin 支持的能力
  • Probe,探测 Plugin 的健康状态

(2)Controller Service:控制服务

  • Volume CRUD,包括了扩容和容量探测等 Volume 状态检查与操作接口
  • Controller Publish/Unpublish Volume ,Node 对 Volume 的访问权限管理
  • Snapshot CRD,快照的创建和删除操作,目前 CSI 定义的 Snapshot 仅用于创建 Volume,未提供回滚的语义

(3)Node Service:节点服务

  • Node Stage/Unstage/Publish/Unpublish/GetStats Volume,节点上 Volume 的连接状态管理
  • Node Expand Volume, 节点上的 Volume 扩容操作,在 volume 逻辑大小扩容之后,可能还需要同步的扩容 Volume 之上的文件系统并让使用 Volume 的 Container 感知到,所以在 Node Plugin 上需要有对应的接口
  • Node Get Capabilities/Info, Plugin 的基础属性与 Node 的属性查询
service Identity {
  rpc GetPluginInfo(GetPluginInfoRequest)
    returns (GetPluginInfoResponse) {}

  rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
    returns (GetPluginCapabilitiesResponse) {}

  rpc Probe (ProbeRequest)
    returns (ProbeResponse) {}
}

service Controller {
  rpc CreateVolume (CreateVolumeRequest)
    returns (CreateVolumeResponse) {}

  rpc DeleteVolume (DeleteVolumeRequest)
    returns (DeleteVolumeResponse) {}

  rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
    returns (ControllerPublishVolumeResponse) {}

  rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
    returns (ControllerUnpublishVolumeResponse) {}

  rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)
    returns (ValidateVolumeCapabilitiesResponse) {}

  rpc ListVolumes (ListVolumesRequest)
    returns (ListVolumesResponse) {}

  ...
}

service Node {
  ...

  rpc NodePublishVolume (NodePublishVolumeRequest)
    returns (NodePublishVolumeResponse) {}

  rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
    returns (NodeUnpublishVolumeResponse) {}

  rpc NodeExpandVolume(NodeExpandVolumeRequest)
    returns (NodeExpandVolumeResponse) {}


  rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
    returns (NodeGetCapabilitiesResponse) {}

  rpc NodeGetInfo (NodeGetInfoRequest)
    returns (NodeGetInfoResponse) {}
  ...
}

7.部署形态

CSI 使用 Sidecar 的方式实现 CSI Plugin 与 K8s 核心逻辑的解耦。Sidecar 代表监听了 CSI 指定 API 的标准容器,它与 CSI Plugin 共同组成一个 Pod 对外提供服务,它们之间通过 Socket 连接。在这个模式下,Sidecar 成为 CSI Plugin 与 K8s 之间连接的中介和隔离带。理想状态下二者可以在不直接交互和影响的情况下共同工作,解决了安全问题。

CSI 定义了如下几种 Sidecar

  • external-provisioner:监听 Volume CRUD API,完成 Volume 的生命周期管理
  • external-attacher:监听 Controller[Publish|Unpublish]Volume API,实现 Node 和 Volume 的可见性控制
  • external-snapshotter:监听 Snapshot CRD API,完成 Snapshot 的生命周期管理
  • node-driver-register:监听 Node 基本信息查询 API,注册 Node Plugin,每个节点 Node Plugin 均需要通过 driver-register 注册自身才可以与 K8s 之间建立连接获取 Node Volume 相关请求
  • cluster-driver-register:用于向 K8s 注册 Plugin 整体支持的模式,包括是否跳过 Attach 阶段/是否在 Node Publish Volume 阶段时需要 K8s 提供 Pod 信息
  • livenessprobe:心跳检测,用于探测 Plugin 的存活状态

8.适用场景


在容器化发展的早期阶段,Container 多用于承担轻量型的无状态服务,对数据存储的需求大多通过本地的临时共享文件,或者用网络访问的方式将数据置于远端的日志收集或者 DB 等外部存储上。这种模式业务和数据之间从程序管理的角度看是松耦合的,互相独立,没有严格的依赖。

但是另一方面,这个模式下数据本身无法成为服务的一部分,并不能通过 K8s 统一管理。并且需要为每个应用打开通往远端存储服务的网络通道,这在安全性上有时并不是一个好的选择。

而基于持久化卷,将数据服务提供方也放入 K8s Pod 中(例如挂载持久化卷作为磁盘,上面部署容器运行 DB)作为完整应用的一部分,数据即可以做到与应用无缝的统一管理,所有应用内部 Pod 间的业务数据请求均可以在 K8s 提供的虚拟网络中进行。而基于 K8s 本身的高可用特性和 CSI Driver 的灵活配置能力也可以获得不逊色于外部存储的可靠性与性能。

9.Kubernetes csi driver开发示例

type driver struct {
    csiDriver   *csicommon.CSIDriver
    endpoint    string

    ids *csicommon.DefaultIdentityServer
    cs  *controllerServer
    ns  *nodeServer
}

首先我们需要定义一个driver结构体,基本包含了plugin启动的所需信息(除了以上信息还可以添加其他参数):

(1)csicommon.CSIDriver

k8s自定义代表插件的结构体, 初始化的时候需要指定插件的RPC功能和支持的读写模式.

func NewCSIDriver(nodeID string) *csicommon.CSIDriver {
    csiDriver := csicommon.NewCSIDriver(driverName, version, nodeID)
    csiDriver.AddControllerServiceCapabilities(
        []csi.ControllerServiceCapability_RPC_Type{
            csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
            csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
            csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
        })
    csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER})
    return csiDriver
}

(2)endpoint:

插件的监听地址,一般的,我们测试的时候可以用tcp方式进行,比如tcp://127.0.0.1:10000,最后在k8s中部署的时候一般使用unix方式:/csi/csi.sock

(3)csicommon.DefaultIdentityServer :

认证服务一般不需要特别实现,使用k8s公共部分的即可.

(4)controllerServer:

实现CSI中的controller服务的RPC功能,继承后可以选择性覆盖部分方法.

type controllerServer struct {
    *csicommon.DefaultControllerServer
}

(5)nodeServer:

实现CSI中的node服务的RPC功能,继承后可以选择性覆盖部分方法.

type nodeServer struct {
    *csicommon.DefaultNodeServer
}

 

(6)driver的Run方法:

该方法中调用csicommon的公共方法启动socket监听,RunControllerandNodePublishServer方法会同时启动controller和node.还可以单独启动controller和node,需要写两个入口main函数.

func (d *driver) Run(nodeID, endpoint string) {
    d.endpoint = endpoint
    d.cloudconfig = cloudConfig

    csiDriver := NewCSIDriver(nodeID)
    d.csiDriver = csiDriver
    // Create GRPC servers
    ns, err := NewNodeServer(d, nodeID, containerized)
    if err != nil {
        glog.Fatalln("failed to create node server, err:", err.Error())
    }

    glog.V(3).Infof("Running endpoint [%s]", d.endpoint)

    csicommon.RunControllerandNodePublishServer(d.endpoint, d.csiDriver, NewControllerServer(d), ns)
}

其他主要函数功能实现如下(以下以我们iscsi产品实现描述):

1.Create Volume:Controller Plugin 收到创建请求之后会创建一个 iSCSI LUN ,如有必要则会自动再创建需要的 Target, iSCSI LUN 以及所属的 Target 均为逻辑对象,不与物理磁盘绑定。

2.Controller Publish Volume:目前 Kubernetes 使用 Open iSCSI 作为节点上的数据接入服务,Open iSCSI 在挂载 Target 时,会将所有 Target 内的 LUN 均挂载到主机上作为一个 Block Device(例如 /dev/sdx 这样的磁盘) 。

3.Node Stage Volume: Node Plugin 会将 LUN 通过 Open iSCSI 命令挂载至主机,呈现为一个磁盘;

4.Node Publish Volume: Node Plugin 对磁盘进行格式化(如果磁盘之前尚未被格式化,如已经格式化则为跳过对应步骤),将磁盘 Mount 到主机上提供给 Container 使用;

5.Node Unpublish Volume: Node Plugin 将磁盘上的文件系统 Unmount;

6.Node Stage Volume: Node Plugin 在主机上将断开磁盘的 iSCSI 链接;

7.Controller Unpublish Volume: Controller Plugin 向后端注销指定 Node 在 LUN 上的访问权限;

8.Delete Volume: Controller Plugin 请求删除对应的 LUN ,LUN 所占用的数据空间将会在存储系统中被回收。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值