Kubernetes csi 存储插件

Introduction


CSI is a standard for exposing  storage systems in arbitrary block and file storage to containerized workloads on Container Orchestrations like Kubernetes, Mesos, and Cloud Foundry. It becomes very extensible for third-party storage provider to expose their new storage systems using CSI, without actually touching the Kubernetes code. Single independent implementation of CSI Driver by a storage provider will work on any orchestrator.

CSI 是一种标准,用于将任意块和文件存储中的存储系统暴露给Kubernetes、Mesos 和 Cloud Foundry 等容器编排上的容器化工作负载。第三方存储提供商使用 CSI 公开他们的新存储系统变得非常可扩展,而无需实际接触 Kubernetes 代码。存储提供商对 CSI 驱动程序的单一独立实现将适用于任何编排器。

This new plugin mechanism has been one of the most powerful features of Kubernetes. It enables the storage vendors to:

  1. Automatically create storage when required.
  2. Make storage available to containers wherever they’re scheduled.
  3. Automatically delete the storage when no longer needed.

This decoupling helps the vendors to maintain the independent release and feature cycles and focus on the API implementation without actually worrying about the backward incompatibility and to support their plugin just as easy as deploying a few pods.

这种解耦有助于供应商保持独立的发布和功能周期,专注于 API 实现,而无需担心向后不兼容,并且像部署几个 pod 一样简单地支持他们的插件。

Kubernetes CSI 1.png

Image Source: Weekly Geekly

Why CSI?


Prior to CSI, k8s volume plugins have to be “In-tree”, compiled and shipped with core kubernetes binaries. This means, it will require the storage providers to check-in their into the core k8s codebase if they wish to add the support for a new storage system.

在 CSI 之前,k8s 卷插件必须是“In-tree”,编译并随核心 kubernetes 二进制文件一起提供。这意味着,如果他们希望添加对新存储系统的支持,则需要存储提供商将其签入核心 k8s 代码库。

A plugin-based solution, flex-volume, tried to address this issue by exposing the exec based API for external  plugins. Although it also tried to work on the similar notion of being detached with k8s binary, there were several major problems with that approach. Firstly, it needed the root access to the host and master file system to deploy the driver files. 

基于插件的解决方案 flex-volume 试图通过为外部插件公开基于 exec 的 API 来解决这个问题。尽管它也尝试处理与 k8s 二进制分离的类似概念,但这种方法存在几个主要问题。首先,它需要对主机和主文件系统的 root 访问权限来部署驱动程序文件。 

Secondly, it comes with the huge baggage of prerequisites and OS dependencies which are assumed to be available on the host. CSI implicitly solves all these issues by being containerized and using the k8s storage primitives.

其次,它带来了大量的先决条件和操作系统依赖项,假设它们在主机上可用。CSI 通过容器化和使用 k8s 存储原语隐式地解决了所有这些问题。

CSI has evolved as the one-stop solution addressing all the above issues which enables storage plugins to be out-of-tree and deployed via standard k8s primitives, such as PVC, PV and StorageClasses.

The main aim of introducing CSI is to establish a standard mechanism of exposing any type of storage system under-the-hood for all the container orchestrators.

 其实,通过前面对 FlexVolume 的讲述,你应该可以明白,默认情况下,Kubernetes 里通过存储插件管理容器持久化存储的原理,可以用如下所示的示意图来描述:

可以看到,在上述体系下,无论是 FlexVolume,还是 Kubernetes 内置的其他存储插件,它们实际上担任的角色,仅仅是 Volume 管理中的“Attach 阶段”和“Mount 阶段”的具体执行者。而像 Dynamic Provisioning 这样的功能,就不是存储插件的责任,而是 Kubernetes 本身存储管理功能的一部分。

相比之下,CSI 插件体系的设计思想,就是把这个 Provision 阶段,以及 Kubernetes 里的一部分存储管理功能,从主干代码里剥离出来,做成了几个单独的组件。这些组件会通过 Watch API 监听 Kubernetes 里与存储相关的事件变化,比如 PVC 的创建,来执行具体的存储管理动作。 

而这些管理动作,比如“Attach 阶段”和“Mount 阶段”的具体操作,实际上就是通过调用 CSI 插件来完成的。

这种设计思路,我可以用如下所示的一幅示意图来表示:

可以看到,这套存储插件体系多了三个独立的外部组件(External Components),即:Driver Registrar、External Provisioner 和 External Attacher,对应的正是从 Kubernetes 项目里面剥离出来的那部分存储管理功能。

需要注意的是,External Components 虽然是外部组件,但依然由 Kubernetes 社区来开发和维护。

而图中最右侧的部分,就是需要我们编写代码来实现的 CSI 插件。一个 CSI 插件只有一个二进制文件,但它会以 gRPC 的方式对外提供三个服务(gRPC Service),分别叫作:CSI Identity、CSI Controller 和 CSI Node。

我先来为你讲解一下这三个 External Components。

其中,Driver Registrar 组件,负责将插件注册到 kubelet 里面(这可以类比为,将可执行文件放在插件目录下)。而在具体实现上,Driver Registrar 需要请求 CSI 插件的 Identity 服务来获取插件信息。

而 External Provisioner 组件,负责的正是 Provision 阶段。在具体实现上,External Provisioner 监听(Watch)了 APIServer 里的 PVC 对象。当一个 PVC 被创建时,它就会调用 CSI Controller 的 CreateVolume 方法,为你创建对应 PV。

此外,如果你使用的存储是公有云提供的磁盘(或者块设备)的话,这一步就需要调用公有云(或者块设备服务)的 API 来创建这个 PV 所描述的磁盘(或者块设备)了。

不过,由于 CSI 插件是独立于 Kubernetes 之外的,所以在 CSI 的 API 里不会直接使用 Kubernetes 定义的 PV 类型,而是会自己定义一个单独的 Volume 类型。 

为了方便叙述,在本专栏里,我会把 Kubernetes 里的持久化卷类型叫作 PV,把 CSI 里的持久化卷类型叫作 CSI Volume,请你务必区分清楚。

最后一个 External Attacher 组件,负责的正是“Attach 阶段”。在具体实现上,它监听了 APIServer 里 VolumeAttachment 对象的变化。VolumeAttachment 对象是 Kubernetes 确认一个 Volume 可以进入“Attach 阶段”的重要标志,我会在下一篇文章里为你详细讲解。

一旦出现了 VolumeAttachment 对象,External Attacher 就会调用 CSI Controller 服务的 ControllerPublish 方法,完成它所对应的 Volume 的 Attach 阶段。

而 Volume 的“Mount 阶段”,并不属于 External Components 的职责。当 kubelet 的 VolumeManagerReconciler 控制循环检查到它需要执行 Mount 操作的时候,会通过pkg/volume/csi 包,直接调用 CSI Node 服务完成 Volume 的“Mount 阶段”。 

接下来,我再为你讲解一下 CSI 插件的里三个服务:CSI Identity、CSI Controller 和 CSI Node。

其中,CSI 插件的 CSI Identity 服务,负责对外暴露这个插件本身的信息,如下所示:

service Identity {
  // return the version and name of the plugin
  rpc GetPluginInfo(GetPluginInfoRequest)
    returns (GetPluginInfoResponse) {}
  // reports whether the plugin has the ability of serving the Controller interface
  rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
    returns (GetPluginCapabilitiesResponse) {}
  // called by the CO just to check whether the plugin is running or not
  rpc Probe (ProbeRequest)
    returns (ProbeResponse) {}
}

而 CSI Controller 服务,定义的则是对 CSI Volume(对应 Kubernetes 里的 PV)的管理接口,比如:创建和删除 CSI Volume、对 CSI Volume 进行 Attach/Dettach(在 CSI 里,这个操作被叫作 Publish/Unpublish),以及对 CSI Volume 进行 Snapshot 等,它们的接口定义如下所示:

service Controller {
  // provisions a volume
  rpc CreateVolume (CreateVolumeRequest)
    returns (CreateVolumeResponse) {}
    
  // deletes a previously provisioned volume
  rpc DeleteVolume (DeleteVolumeRequest)
    returns (DeleteVolumeResponse) {}
    
  // make a volume available on some required node
  rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
    returns (ControllerPublishVolumeResponse) {}
    
  // make a volume un-available on some required node
  rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
    returns (ControllerUnpublishVolumeResponse) {}
    
  ...
  
  // make a snapshot
  rpc CreateSnapshot (CreateSnapshotRequest)
    returns (CreateSnapshotResponse) {}
    
  // Delete a given snapshot
  rpc DeleteSnapshot (DeleteSnapshotRequest)
    returns (DeleteSnapshotResponse) {}
    
  ...
}

不难发现,CSI Controller 服务里定义的这些操作有个共同特点,那就是它们都无需在宿主机上进行,而是属于 Kubernetes 里 Volume Controller 的逻辑,也就是属于 Master 节点的一部分。

需要注意的是,正如我在前面提到的那样,CSI Controller 服务的实际调用者,并不是 Kubernetes(即:通过 pkg/volume/csi 发起 CSI 请求),而是 External Provisioner 和 External Attacher。这两个 External Components,分别通过监听 PVC 和 VolumeAttachement 对象,来跟 Kubernetes 进行协作。

而 CSI Volume 需要在宿主机上执行的操作,都定义在了 CSI Node 服务里面,如下所示: 

service Node {
  // temporarily mount the volume to a staging path
  rpc NodeStageVolume (NodeStageVolumeRequest)
    returns (NodeStageVolumeResponse) {}
    
  // unmount the volume from staging path
  rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
    returns (NodeUnstageVolumeResponse) {}
    
  // mount the volume from staging to target path
  rpc NodePublishVolume (NodePublishVolumeRequest)
    returns (NodePublishVolumeResponse) {}
    
  // unmount the volume from staging path
  rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
    returns (NodeUnpublishVolumeResponse) {}
    
  // stats for the volume
  rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
    returns (NodeGetVolumeStatsResponse) {}
    
  ...
  
  // Similar to NodeGetId
  rpc NodeGetInfo (NodeGetInfoRequest)
    returns (NodeGetInfoResponse) {}
}

需要注意的是,“Mount 阶段”在 CSI Node 里的接口,是由 NodeStageVolume 和 NodePublishVolume 两个接口共同实现的。我会在下一篇文章中,为你详细介绍这个设计的目的和具体的实现方式。

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/120147232