Kubernetes从1.9版本开始引入容器存储接口Container Storage Interface(CSI)机制,用于在Kubernetes和外部存储系统之间建立一套标准的存储管理接口,通过该接口为容器提供存储服务。

CSI的设计背景

Kubernetes通过PV、PVC、Storageclass已经提供了一种强大的基于插件的存储管理机制,但是各种存储插件提供的存储服务都是基于一种被称为“in-tree”(树内)的方式提供的,这要求存储插件的代码必须被放进Kubernetes的主干代码库中才能被Kubernetes调用,属于紧耦合的开发模式。这种“in-tree”方式会带来一些问题:

  • 存储插件的代码需要与Kubernetes的代码放在同一代码库中,并与Kubernetes的二进制文件共同发布;
  • 存储插件代码的开发者必须遵循Kubernetes的代码开发规范;
  • 存储插件代码的开发者必须遵循Kubernetes的发布流程,包括添加对Kubernetes存储系统的支持和错误修复;
  • Kubernetes社区需要对存储插件的代码进行维护,包括审核、测试等工作;
  • 存储插件代码中的问题可能会影响Kubernetes组件的运行,并且很难排查问题;
  • 存储插件代码与Kubernetes的核心组件(kubelet和kubecontroller-manager)享有相同的系统特权权限,可能存在可靠性和安全性问题。

Kubernetes已有的FlexVolume插件机制试图通过为外部存储暴露一个基于可执行程序(exec)的API来解决这些问题。尽管它允许第三方存储提供商在Kubernetes核心代码之外开发存储驱动,但仍然有两个问题没有得到很好的解决:

  • 部署第三方驱动的可执行文件仍然需要宿主机的root权限,存在安全隐患;
  • 存储插件在执行mount、attach这些操作时,通常需要在宿主机上安装一些第三方工具包和依赖库,使得部署过程更加复杂,例如部署Ceph时需要安装rbd库,部署GlusterFS时需要安装mount.glusterfs库,等等。

基于以上这些问题和考虑,Kubernetes逐步推出与容器对接的存储接口标准,存储提供方只需要基于标准接口进行存储插件的实现,就能使用Kubernetes的原生存储机制为容器提供存储服务。这套标准被称为CSI(容器存储接口)。

在CSI成为Kubernetes的存储供应标准之后,存储提供方的代码就能和Kubernetes代码彻底解耦,部署也与Kubernetes核心组件分离,显然,存储插件的开发由提供方自行维护,就能为Kubernetes用户提供更多的存储功能,也更加安全可靠。

基于CSI的存储插件机制也被称为“out-of-tree”(树外)的服务提供方式,是未来Kubernetes第三方存储插件的标准方案。

CSI架构

CSI存储组件/部署架构

KubernetesCSI存储插件的关键组件和推荐的容器化部署架构:

clipboard
clipboard

其中主要包括两种组件:CSI Controller和CSI Node。

CSI Controller

CSI Controller的主要功能是提供存储服务视角对存储资源和存储卷进行管理和操作。在Kubernetes中建议将其部署为单实例Pod,可以使用StatefulSet或Deployment控制器进行部署,设置副本数量为1,保证为一种存储插件只运行一个控制器实例。

在这个Pod内部署两个容器:

  • 与Master(kube-controller-manager)通信的辅助sidecar容器。在sidecar容器内又可以包含external-attacher和external-provisioner两个容器,它们的功能分别如下。
    • external-attacher:监控VolumeAttachment资源对象的变更,触发针对CSI端点的ControllerPublish和ControllerUnpublish操作。
    • external-provisioner:监控PersistentVolumeClaim资源对象的变更,触发针对CSI端点的CreateVolume和DeleteVolume操作。
  • CSI Driver存储驱动容器,由第三方存储提供商提供,需要实现上述接口。

这两个容器通过本地Socket(Unix DomainSocket,UDS),并使用gPRC协议进行通信。

sidecar容器通过Socket调用CSI Driver容器的CSI接口,CSI Driver容器负责具体的存储卷操作。

CSI Node

CSI Node的主要功能是对主机(Node)上的Volume进行管理和操作。在Kubernetes中建议将其部署为DaemonSet,在每个Node上都运行一个Pod。

在这个Pod中部署以下两个容器:

  • 与kubelet通信的辅助sidecar容器node-driver-registrar,主要功能是将存储驱动注册到kubelet中;
  • CSI Driver存储驱动容器,由第三方存储提供商提供,主要功能是接收kubelet的调用,需要实现一系列与Node相关的CSI接口,例如NodePublishVolume接口(用于将Volume挂载到容器内的目标路径)、NodeUnpublishVolume接口(用于从容器中卸载Volume),等等。

node-driver-registrar容器与kubelet通过Node主机的一个hostPath目录下的unixsocket进行通信。CSI Driver容器与kubelet通过Node主机的另一个hostPath目录下的unixsocket进行通信,同时需要将kubelet的工作目录(默认为/var/lib/kubelet)挂载给CSIDriver容器,用于为Pod进行Volume的管理操作(包括mount、umount等)。

原理

类似于 CRI,CSI 也是基于 gRPC 实现。详细的 CSI SPEC 可以参考 这里,它要求插件开发者要实现三个 gRPC 服务:

  • Identity Service:用于 Kubernetes 与 CSI 插件协调版本信息
  • Controller Service:用于创建、删除以及管理 Volume 存储卷
  • Node Service:用于将 Volume 存储卷挂载到指定的目录中以便 Kubelet 创建容器时使用(需要监听在 /var/lib/kubelet/plugins/[SanitizedCSIDriverName]/csi.sock

由于 CSI 监听在 unix socket 文件上, kube-controller-manager 并不能直接调用 CSI 插件。为了协调 Volume 生命周期的管理,并方便开发者实现 CSI 插件,Kubernetes 提供了几个 sidecar 容器并推荐使用下述方法来部署 CSI 插件:

Recommended CSI Deployment Diagram
Recommended CSI Deployment Diagram

该部署方法包括:

  • StatefuelSet:副本数为 1 保证只有一个实例运行,它包含三个容器
    • 用户实现的 CSI 插件
    • External Attacher:Kubernetes 提供的 sidecar 容器,它监听 VolumeAttachmentPersistentVolume 对象的变化情况,并调用 CSI 插件的 ControllerPublishVolume 和 ControllerUnpublishVolume 等 API 将 Volume 挂载或卸载到指定的 Node 上
    • External Provisioner:Kubernetes 提供的 sidecar 容器,它监听 PersistentVolumeClaim 对象的变化情况,并调用 CSI 插件的 ControllerPublishControllerUnpublish 等 API 管理 Volume
  • Daemonset:将 CSI 插件运行在每个 Node 上,以便 Kubelet 可以调用。它包含 2 个容器
    • 用户实现的 CSI 插件
    • Driver Registrar:注册 CSI 插件到 kubelet 中,并初始化 NodeId(即给 Node 对象增加一个 Annotation csi.volume.kubernetes.io/nodeid

CSI相关概念

在CSI未出现之前,容器编排系统(Container Orchestration Systems,简称COs,如kubernetes)为了能使用外部存储系统,使这些存储系统为容器工作负载提供存储卷。COs需要在自身的代码中嵌入大量与存储相关的代码,参见kubernetes里的volume包,这个包下面大部分就是所谓的in-tree(意思是在kubernetes的代码树里)存储卷插件。

要支持的存储系统多种多样,而且有些存储系统的支持代码还不便于开源,所以很明显上述设计并不好。

后面又出现了 Flexvolume这种out-tree的存储卷插件机制,允许存储厂商将写好的存储卷插件二进制文件放置到各node节点预设的目录下,kubernetes即可在自动发现它们,并调用它们完成存储卷的供应。详情技术细节可参考这里

上述 Flexvolume方案很类似于kubernetes里用的网络方案CNI,都是将外部插件放置在预设的目录下,以供kubernetes调用。但总的来说还是跟kubernetes这一容器编排系统绑定得太死了。于是人们又发明了CSI。

CSI 代表容器存储接口,CSI 试图建立一个行业标准接口的规范,借助 CSI 容器编排系统(CO)可以将任意存储系统暴露给自己的容器工作负载。有关详细信息,请查看设计方案

csi 卷类型也是一种 out-tree(in-tree是指跟其它存储插件在同一个代码路径下,随 Kubernetes 的代码同时编译,out-tree则刚好相反) 的 CSI 卷插件,用于 Pod 与在同一节点上运行的外部 CSI 卷驱动程序交互。部署 CSI 兼容卷驱动后,用户可以使用 csi 作为卷类型来挂载驱动提供的存储。

CSI 持久化卷支持是在 Kubernetes v1.9 中引入的,作为一个 alpha 特性,必须由集群管理员明确启用。换句话说,集群管理员需要在 apiserver、controller-manager 和 kubelet 组件的 “--feature-gates =” 标志中加上 “CSIPersistentVolume = true”。

CSI 持久化卷具有以下字段可供用户指定:

  • driver:一个字符串值,指定要使用的卷驱动程序的名称。必须少于 63 个字符,并以一个字符开头。驱动程序名称可以包含 “。”、“ - ”、“_” 或数字。
  • volumeHandle:一个字符串值,唯一标识从 CSI 卷插件的 CreateVolume 调用返回的卷名。随后在卷驱动程序的所有后续调用中使用卷句柄来引用该卷。
  • readOnly:一个可选的布尔值,指示卷是否被发布为只读。默认是 false。

CSI插件机制分析

光看上面的概念,还是很难理解到底CSI插件是怎样的。其实说到底一个CSI插件就是实现了CSI规范要求的多个gRPC接口的服务程序。

一个CSI插件一般会以两种形式部署运行着,分别是Controller组件和Node组件。

Controller Plugin

The controller component can be deployed as a Deployment or StatefulSet on any node in the cluster. It consists of the CSI driver that implements the CSI Controller service and one or more sidecar containers. These controller sidecar containers typically interact with Kubernetes objects and make calls to the driver’s CSI Controller service.

It generally does not need direct access to the host and can perform all its operations through the Kubernetes API and external control plane services. Multiple copies of the controller component can be deployed for HA, however it is recommended to use leader election to ensure there is only one active controller at a time.

Controller sidecars include the external-provisioner, external-attacher, external-snapshotter, and external-resizer. Including a sidecar in the deployment may be optional.

Node Plugin

The node component should be deployed on every node in the cluster through a DaemonSet. It consists of the CSI driver that implements the CSI Node service and the node-driver-registrar sidecar container.

The Kubernetes kubelet runs on every node and is responsible for making the CSI Node service calls. These calls mount and unmount the storage volume from the storage system, making it available to the Pod to consume. Kubelet makes calls to the CSI driver through a UNIX domain socket shared on the host via a HostPath volume. There is also a second UNIX domain socket that the node-driver-registrar uses to register the CSI driver to kubelet.

可以看到Controller组件一般是以Deployment或StatefulSet形式部署的,它实现了CSI Controller service,它会与Kubernetes API、外部存储服务的控制面交互,但它并不会实际处理存储卷在宿主机上的挂载等事宜。

而Node组件因为要运行在所有node节点上,因此一般是以DaemonSet形式部署的,它实现了CSI Node service,它会暴露出一个UNIX domain socket文件出来,从而让kubelet在进行存储卷操作时,通过这个UNIX domain socket文件调用它的gRPC接口。

上述两个组件配合,即完成了将存储卷暴露给工作负载的功能。

下面看一下一个CSI插件要实现的三组gRPC接口服务:

  • Identity Service: Both the Node Plugin and the Controller Plugin MUST implement this sets of RPCs.
  • Controller Service: The Controller Plugin MUST implement this sets of RPCs.
  • Node Service: The Node Plugin MUST implement this sets of RPCs.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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) {}

  rpc GetCapacity (GetCapacityRequest)
    returns (GetCapacityResponse) {}

  rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest)
    returns (ControllerGetCapabilitiesResponse) {}

  rpc CreateSnapshot (CreateSnapshotRequest)
    returns (CreateSnapshotResponse) {}

  rpc DeleteSnapshot (DeleteSnapshotRequest)
    returns (DeleteSnapshotResponse) {}

  rpc ListSnapshots (ListSnapshotsRequest)
    returns (ListSnapshotsResponse) {}

  rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
    returns (ControllerExpandVolumeResponse) {}
}

service Node {
  rpc NodeStageVolume (NodeStageVolumeRequest)
    returns (NodeStageVolumeResponse) {}

  rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
    returns (NodeUnstageVolumeResponse) {}

  rpc NodePublishVolume (NodePublishVolumeRequest)
    returns (NodePublishVolumeResponse) {}

  rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
    returns (NodeUnpublishVolumeResponse) {}

  rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
    returns (NodeGetVolumeStatsResponse) {}


  rpc NodeExpandVolume(NodeExpandVolumeRequest)
    returns (NodeExpandVolumeResponse) {}


  rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
    returns (NodeGetCapabilitiesResponse) {}

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

上述这堆接口,看着挺多。其实如果不想实现CSI的某些功能,有些接口也不用实现的。比如如果不想实现存储卷的动态供应,ControllerCreateVolumeDeleteVolume即可不实现。另外有些接口属于元数据接口,仅仅是声明该CSI的Capability,Info的,如 Identity的所有接口,ControllerGetCapabilitiesControllerGetCapabilities接口,NodeNodeGetCapabilitiesNodeGetInfo接口。再看一下storage volume的lifecycle,这些接口之前的调用关系就很清楚了。

   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  |
                +------------+

Figure 6: The lifecycle of a dynamically provisioned volume, from
creation to destruction, when the Node Plugin advertises the
STAGE_UNSTAGE_VOLUME capability.

上面这个图是一个较为复杂的卷供应生命周期图,从这个图我们可以看出一个存储卷的供应分别调用了 Controller PluginCreateVolumeControllerPublishVolumeNode PluginNodeStageVolumeNodePublishVolume这4个gRPC接口,存储卷的销毁分别调用了 Node PluginNodeUnpublishVolumeNodeUnstageVolumeControllerControllerUnpublishVolumeDeleteVolume这4个gRPC接口,这每个接口要完成的工作参见CSI规范

只看规范可能会看得云里雾里的,我是参考一个现成的CSI插件实例来理解CSI规范的。这里推荐下tencentcloud cbs块存储的CSI插件,这个CSI插件实现得相当规范,分别在controller.go里实现了Controller Service里的几个gRPC接口、identity.go里实现了Identify Service里的几个gRPC接口、node.go里实现了Node Service里的几个gRPC接口。

CSI插件的部署

按CSI规范实现了相应的gRPC接口后,一个CSI插件就基本成型了。但这并不是全部,回想下目前整个CSI插件的功能逻辑,我们只是实现了存储卷驱动的核心逻辑,但并没有与Kubernetes产生任何联动啊。这写好的CSI插件如何工作呢?

与Kubernetes的联动逻辑比较统一,基本就是watch Kubrernetes API,根据watch到的资源状态调用相应的CSI接口,根据CSI接口的返回结果更新Kubernetes里的资源状态。官方为了简化开发CSI插件的复杂度,提供了一系列的sidecar来完成这些工作。而CSI的开发人员要做的就是在部署CSI插件时声明将相应的sidecar与CSI插件捆绑部署在一起。

Kubernetes CSI Sidecar Containers are a set of standard containers that aim to simplify the development and deployment of CSI Drivers on Kubernetes.

These containers contain common logic to watch the Kubernetes API, trigger appropriate operations against the “CSI volume driver” container, and update the Kubernetes API as appropriate.

The containers are intended to be bundled with third-party CSI driver containers and deployed together as pods.

The containers are developed and maintained by the Kubernetes Storage community.

Use of the containers is strictly optional, but highly recommended.

使用这些sidecar的好处很多,官方文档中下面这两段话说得很清楚。以后做设计时也可以参考这样优雅地实现。

Benefits of these sidecar containers include:

  • Reduction of “boilerplate” code.
    • CSI Driver developers do not have to worry about complicated, “Kubernetes specific” code.
  • Separation of concerns.
    • Code that interacts with the Kubernetes API is isolated from (and in a different container then) the code that implements the CSI interface.

会使用到的sidecar如下:

  • external-provisioner

    如果CSI插件要实现CREATE_DELETE_VOLUME能力(即动态供应),则CSI插件需要实现Controller Service的 CreateVolumeDeleteVolume接口,并配合上该sidecar就可以了。这样当watch到指定StorageClass的 PersistentVolumeClaim资源状态变更,会自动地调用这两个接口。

  • external-attacher

    如果CSI插件要实现PUBLISH_UNPUBLISH_VOLUME能力,则CSI插件需要实现Controller Service的 ControllerPublishVolumeControllerUnpublishVolume接口,并配合上该sidecar就可以了。这样当watch到VolumeAttachment资源状态变更,会自动地调用这两个接口。

  • external-snapshotter

    如果CSI插件要实现CREATE_DELETE_SNAPSHOT能力,则CSI插件需要实现Controller Service的 CreateSnapshotDeleteSnapshot接口,并配合上该sidecar就可以了。这样当watch到指定SnapshotClass的VolumeSnapshot资源状态变更,会自动地调用这两个接口。

  • external-resizer

    如果CSI插件要实现EXPAND_VOLUME能力,则CSI插件需要实现Controller Service的 ControllerExpandVolume接口,并配合上该sidecar就可以了。这样当watch到PersistentVolumeClaim资源的容量发生变更,会自动地调用这个接口。

  • node-driver-registrar

    CSI插件实现Node Service的 NodeGetInfo接口后,配合上该sidecar。这样当CSI Node Plugin部署到kubernetes的node节点时,该sidecar会自动调用接口获取CSI插件信息,并向kubelet进行注册。

  • livenessprobe

    配合上该sidecar,kubernetes即可检测到CSI插件相关pod的健康状态,当不正常时自动重启相应pod。

怎么将这些sidecar与CSI Driver部署在一起,官方文档其实讲得很清楚的。

同样个人觉得还是结合实际的示例理解文档更快一点。比如tencentcloud cbs块存储的CSI插件的部署清单里:

  1. csi-cbsplugin.yaml以DaemonSet方式部署了 csi-tencentcloud-cbs:v1.0.0这个CSI插件Driver程序,这个插件Driver程序旁边放了一个 csi-node-driver-registrar:v1.0.2的sidecar,这个sidecar会自动调用接口获取CSI插件信息,并向kubelet进行注册。
  2. csi-cbsplugin-provisioner.yaml以StatefulSet方式部署了 csi-provisioner:v1.0.1csi-snapshotter:v1.0.1这两个sidecar,这两个sidecar会watch指定StorageClass的 PersistentVolumeClaim资源状态变更、指定SnapshotClass的VolumeSnapshot资源状态变更,然后通过宿主机上的csi UNIX domain socket与CSI插件驱动通信,调用相应的gRPC接口方法。
  3. csi-cbsplugin-attacher.yaml以StatefulSet方式部署了 csi-attacher:v1.0.1这个sidecar,这个sidecar会watch VolumeAttachment资源状态变更,然后通过宿主机上的csi UNIX domain socket与CSI插件驱动通信,调用相应的gRPC接口方法。

其它的sidecar的使用方法都类似上面示例中的玩法,照着配置就可以了。

CSI的其它技术细节

  1. 在进行存储系统操作时会使用到各种密码、身份凭证,而这些需要按照一定的规约进行配置,详见官方文档
  2. 可以通过 CSIDriver这一自定义资源,定制Kubernetes与CSI存储插件交互的行为,详见官方文档
  3. 可以通过 CSINodeInfo这一自定义资源,将kubernetes的node与CSI Node映射起来。还可以通过 CSINodeInfo指定node节点的的topologyKey,这个能实现基于topology的CSI存储卷供应,详见官方文档1官方文档2

CSI插件的测试支持

官方还为开发CSI插件提供了单元测试与e2e测试的方案,可惜时间关系,并没有演练一把,后面可以考虑把这一点实操一下,毕竟测试驱动开发是趋势。

现成的CSI插件驱动

目前各大知名云厂商都实现了自家存储产品的CSI插件驱动,列表在这里。正常情况下直接使用官方提供的CSI插件即可。当然如果要学习下也是可以,CSI插件的代码一般都不难,5-6个go文件而已。

FlexVolume

FlexVolume 是 Kubernetes v1.8+ 支持的一种存储插件扩展方式。类似于 CNI 插件,它需要外部插件将二进制文件放到预先配置的路径中(如 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/),并需要在系统中安装好所有需要的依赖。

对于新的存储插件,推荐基于 CSI 构建。

FlexVolume 接口

实现一个 FlexVolume 包括两个步骤

  • 实现 FlexVolume 插件接口,包括 init/attach/detach/waitforattach/isattached/mountdevice/unmountdevice/mount/umount 等命令(可参考 lvm 示例NFS 示例
  • 将插件放到 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/<vendor~driver>/<driver> 目录中

FlexVolume 的接口包括

  • init:kubelet/kube-controller-manager 初始化存储插件时调用,插件需要返回是否需要要 attachdetach 操作
  • attach:将存储卷挂载到 Node 上
  • detach:将存储卷从 Node 上卸载
  • waitforattach: 等待 attach 操作成功(超时时间为 10 分钟)
  • isattached:检查存储卷是否已经挂载
  • mountdevice:将设备挂载到指定目录中以便后续 bind mount 使用
  • unmountdevice:将设备取消挂载
  • mount:将存储卷挂载到指定目录中
  • umount:将存储卷取消挂载

而存储驱动在实现这些接口时需要以 JSON 格式返回数据,数据格式为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "status": "<Success/Failure/Not supported>",
  "message": "<Reason for success/failure>",
  "device": "<Path to the device attached. This field is valid only for attach & waitforattach call-outs>",
  "volumeName": "<Cluster wide unique name of the volume. Valid only for getvolumename call-out>",
  "attached": "<True/False (Return true if volume is attached on the node. Valid only for isattached call-out)>",
    "capabilities":
    {
        "attach": "<True/False (Return true if the driver implements attach and detach)>"
    }
}

使用 FlexVolume

在使用 flexVolume 时,需要指定卷的 driver,格式为 <vendor~driver>/<driver>,如下面的例子使用了 kubernetes.io/lvm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: test
      mountPath: /data
    ports:
    - containerPort: 80
  volumes:
  - name: test
    flexVolume:
      driver: "kubernetes.io/lvm"
      fsType: "ext4"
      options:
        volumeID: "vol1"
        size: "1000m"
        volumegroup: "kube_vg"

注意:

  • 在 v1.7 版本,部署新的 FlevVolume 插件后需要重启 kubelet 和 kube-controller-manager;
  • 而从 v1.8 开始不需要重启它们了。

参考资料