Curve CSI Driver 解析

d2e87e52b70e980830b26e717908e1d7.jpeg

1

导言

由于项目需求的驱动,我接触并开发了 Curvefs 的 CSI Driver。结合自身的学习开发经历,这篇文章旨在从一个新接触者的角度介绍 Kubernetes 的 CSI 相关知识内容以及 Curve CSI Driver 的实践。

Curve 是云原生计算基金会 (CNCF) Sandbox 项目,是网易自研和开源的高性能、易运维、云原生的分布式存储系统,由块存储 CurveBS 和文件系统 CurveFS 两部分组成。

2

什么是 CSI ?

CSI 全称为 Container Storage Interface,翻译过来就是容器的存储接口标准规范。这份标准旨在为容器编排系统 Container Orchestration Systems,如 Kubernetes,提供统一的容器的持久化存储接口,包括块(block)与文件系统(filesystem)。 

3

为什么是 CSI ?

接下来我们讨论的内容都基于 Kubernetes,目前使用最广泛的容器编排系统。首先,我们需要了解的是容器编排系统的核心就是资源的调度与管理。与本文相关的主要是动态调度(dynamic provision)的存储资源,包括 SC(Storage Class)、PV( Persistent Volumes)、PVC(Persistent Volumes Claim)。

k8s 里的应用作为存储资源的申请与使用方,必然就需要一个存储资源的提供方(Provisioner)。k8s 原生支持了一些存储后端,如 iSCSI、NFS、Ceph 等等,通过内建(in-tree)驱动的方式与存储后端进行通信,进行存储资源的提供。这些驱动代码都位于 k8s 的核心代码仓库中,以一种强耦合的方式存在。这会带来不少问题:

  • in-tree 驱动的代码更新依赖 k8s 核心组件的更新,难以维护;

  • in-tree 驱动与 k8s 组件存在 bug 相互影响的风险;

  • 如果考虑其他的编排系统,移植工作可能会比较痛苦;

  • 或许某个厂家并不想开源他们的驱动代码;

这种时候 CSI 作为一种解耦的方案就很自然的应运而生。它使得第三方的厂家或者社区可以轻松地基于标准化的接口开发插件化的存储驱动,带来了更好的兼容性、稳定性、可维护性与可移植性。关于 CSI 如何工作的详细文档,可以查看 CSI 的 Proposal① 文档以及目录下的其他相关文档。

3

CSI Sidecar Container

CSI 只是一套接口规范,为了支持这套规范,k8s 官方实现并提供了 CSI Sidecar Containers② 给开发者们。把面向 k8s API 编程转化成了面向 CSI Sidecar Containers 的 gRPC 编程。通用的交互流程如下:

  • CSI Pod 的 Sidecar 监听特定的 k8s 对象;

  • 监听到对象变更后, 通过 Unix Domain Socket 调用 CSI 驱动;

  • CSI 驱动进行响应, 完成所要求的动作;

4

通过 CSI 调度的工作流

整个核心工作流可以主要分为两个部分,一是资源的创建删除,二是资源的调度与挂载。每一个阶段有其对应的 Sidecar 容器与 driver 需要实现的调用接口。

1. 创建与删除

负责存储卷的创建与删除。Sidecar 会调用厂商的驱动,创建和删除指定的资源。可能是块设备,也有可能是分布式文件系统。

  • Sidecar: external-provisioner

  • Driver Interface: CreateVolume

  • Driver Interface: DeleteVolume

2. 调度和挂载

由于相对于创建与删除那一部分更加灵活。许多接口都可以不实现,导致最后每个 driver 呈现的方式会很多样。这里引用官方 spec 文档的 lifecycle 图加以说明。一个完整的实现是如下图一样实现了接口 ControllerPublishVolume、NodeStageVolume 和 NodePublishVolume 以及它的逆操作。

CreateVolume +------------+ DeleteVolume
   +------------->|  CREATED   +--------------+
   |              +---+----^---+              |
   |       Controller |    | Controller       v
  +++         Publish |    | Unpublish       +++
  |X|          Volume |    | Volume          | |
  +-+             +---v----+---+             +-+
                  | NODE_READY |
                  +---+----^---+
                 Node |    | Node
                Stage |    | Unstage
               Volume |    | Volume
                  +---v----+---+
                  |  VOL_READY |
                  +---+----^---+
                 Node |    | Node
              Publish |    | Unpublish
               Volume |    | Volume
                  +---v----+---+
                  | PUBLISHED  |
                  +------------+

我们从 PUBLISHED 完成的状态向上梳理 CSI 接口调用:

  1. 首先是 NodePublishVolume,他意味着将 Volume bind 到申请存储资源的pod 内。

  2. 接下来是 NodeStageVolume,他的作用是先将 Volume 挂载至 node 上一个临时的 Staging 目录,然后再 bind 到申请资源的 pod 内。通常这样做的目的是实现多个 pod 共享同一个挂载点。同时,一般如果 Volume 是块设备的话,需要在这一步进行 format。

  3. 最后是 ControllerPublishVolume,他的作用是把 Volume Attach 到 node 节点上。一般是常见于块设备类型的 Volume,attach 到 node 节点之后通过 NodeStageVolume 来进行 format 等操作。当然,在比较新的 k8s 版本中,已经添加了对块设备的直接支持,如果有需要可以跳过格式化。

需要注意的是,这三个接口中,较新版本的 k8s 已经支持 Skip Attach③,也就是意味着最小集合的接口实现只需要实现 NodePublishVolume 即可,其余两个接口都可以保持未实现的状态。

3. CSI gRPC 接口介绍

厂商的 CSI 驱动需要实现以下接口的一部分来对接 CSI Sidecar Containers,包括 Identity,Controller,Node。

// csi spec v1.6.0


    // Identity用于提供Driver的身份信息, Controller Pod和Node Pod都需要包含
    service Identity {
        rpc GetPluginInfo(GetPluginInfoRequest)
            returns (GetPluginInfoResponse) {}


        rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
            returns (GetPluginCapabilitiesResponse) {}


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


    // Controller用于管理Volume, 需要以deployment或者statefulset的形式存在
    // 厂商设定的Capabilities会标识哪一些接口是Driver需要实现的
    service Controller {
        // Create && Delete (必须实现)
        rpc CreateVolume (CreateVolumeRequest)
            returns (CreateVolumeResponse) {}
        rpc DeleteVolume (DeleteVolumeRequest)
            returns (DeleteVolumeResponse) {}


        // Attach && Detach
        rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
            returns (ControllerPublishVolumeResponse) {}
        rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
            returns (ControllerUnpublishVolumeResponse) {}


        // 检验Volume的Cap是否有效
        rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)
            returns (ValidateVolumeCapabilitiesResponse) {}


        rpc ListVolumes (ListVolumesRequest)
            returns (ListVolumesResponse) {}


        rpc GetCapacity (GetCapacityRequest)
            returns (GetCapacityResponse) {}


        // 获取Controller对于接口的支持情况
        rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest)
            returns (ControllerGetCapabilitiesResponse) {}


        // snapshot相关
        rpc CreateSnapshot (CreateSnapshotRequest)
            returns (CreateSnapshotResponse) {}
        rpc DeleteSnapshot (DeleteSnapshotRequest)
            returns (DeleteSnapshotResponse) {}
        rpc ListSnapshots (ListSnapshotsRequest)
            returns (ListSnapshotsResponse) {}


        // Volume扩容resize
        rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
            returns (ControllerExpandVolumeResponse) {}


        rpc ControllerGetVolume (ControllerGetVolumeRequest)
            returns (ControllerGetVolumeResponse) {
                option (alpha_method) = true;
        }
    }


    // Node提供挂载的能力, 需要以daemonset的形式运行于所有相关节点上
    // 厂商设定的Capabilities会标识哪一些接口是Driver需要实现的
    service Node {
        // mount/umount到staging目录
        rpc NodeStageVolume (NodeStageVolumeRequest)
            returns (NodeStageVolumeResponse) {}
        rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
            returns (NodeUnstageVolumeResponse) {}


        // mount/umount到工作pod内 (必须实现)
        rpc NodePublishVolume (NodePublishVolumeRequest)
            returns (NodePublishVolumeResponse) {}
        rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
            returns (NodeUnpublishVolumeResponse) {}


        rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
            returns (NodeGetVolumeStatsResponse) {}


        rpc NodeExpandVolume(NodeExpandVolumeRequest)
            returns (NodeExpandVolumeResponse) {}


        // 获取Node对于接口的支持情况
        rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
            returns (NodeGetCapabilitiesResponse) {}


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

5

Curve CSI 的实践

Curve 分布式存储目前由两个系统组成,一个是提供块存储的 CurveBS,一个在已有的第三方 S3 兼容的对象存储基础上提供分布式文件存储的 CurveFS。由于一些历史原因,现在是两个分开的仓库。

Curve 块存储

仓库地址

https://github.com/opencurve/curve-csi/

1. 支持的 CSI 特性

  • 创建/删除卷

  • 创建/删除卷快照,从快照进行卷创建

  • 卷的扩容

  • 实现了 NodeStageVolume

  • 实现了 NodePublishVolume

2. 使用场景的优化

  • 将 CurveBS 相关的二进制放置在宿主机上,包括卷的控制命令和挂卸载命令。

  • 通过 nsenter 和 systemd-run 调用宿主机的二进制并且把服务跑在宿主机上,以达到二进制以及服务和 pod 解耦的目的。

CurveFS 分布式共享存储

仓库地址

https://github.com/opencurve/curvefs-csi 

1. 支持的 CSI 特性

  • 创建/删除卷

  • ReadWriteMany 多挂载

  • 实现了 NodePublishVolume

2. 后续计划

  • CurveBS 目前特性较为稳定, CurveFS 后续还会有更多的新特性支持。

  • 优化资源隔离:BS 目标将二进制放到宿主机进行解耦,但是又耦合了宿主机;FS 目前没有做隔离。考虑使用更好的资源隔离与控制方案,以减少 Driver 更新的影响以及 Pod 异常导致的影响范围。

  • 欢迎大家提交 PR,一起不断完善~7d76a0a4c7dfa16eaad6a9dd5e42778d.png

参考

https://github.com/kubernetes/design-proposals-archive/blob/main/storage/container-storage-interface.md

参考

https://kubernetes-csi.github.io/docs/sidecar-containers.html

https://kubernetes-csi.github.io/docs/skip-attach.html

e5db1d3699b490665e0527bef29db556.png

关于 Curve 

Curve 是一款高性能、易运维、云原生的开源分布式存储系统。可应用于主流的云原生基础设施平台:对接 OpenStack 平台为云主机提供高性能块存储服务;对接 Kubernetes 为其提供 RWO、RWX 等类型的持久化存储卷;对接 PolarFS 作为云原生数据库的高性能存储底座,完美支持云原生数据库的存算分离架构。

Curve 亦可作为云存储中间件使用 S3 兼容的对象存储作为数据存储引擎,为公有云用户提供高性价比的共享文件存储。

  • GitHub:https://github.com/opencurve/curve

  • 官网:https://opencurve.io/

  • 用户论坛:https://ask.opencurve.io/

  • 微信群:请扫描添加或搜索群助手微信号 OpenCurve_bot

    5c470298df367335b6893b4ea83f2ea8.jpeg

<原创作者:马杰,Curve Contributor>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值