Kubernetes CSI简介

CSI简介

官方spec文档:CSI Spec

术语简称:

  • CSI:Container Storage Interface
  • CO:Container Orchestrator system
  • SP:Storage Provider

CSI是Container Storage Interface的简称,旨在能为容器编排引擎和存储系统间建立一套标准的存储调用接口,通过该接口能为容器编排引擎提供存储服务。

在CSI之前,K8S里提供存储服务是通过一种称为in-tree的方式来提供,如下图:

kubernetes-without-csi

这种方式需要将存储提供者的代码逻辑放到K8S的代码库中运行,调用引擎与插件间属于强耦合,这种方式会带来一些问题:

  1. 存储插件需要一同随K8S发布。
  2. K8S社区需要对存储插件的测试、维护负责。
  3. 存储插件的问题有可能会影响K8S部件正常运行。
  4. 存储插件享有K8S部件同等的特权存在安全隐患。
  5. 存储插件开发者必须遵循K8S社区的规则开发代码。

当前已有的FlexVolume机制试图通过调用一个可执行的程序包方式去解决这些问题,虽然它已经能够做到让存储提供方进行独立开发,但是有两个问题还没有得到很好解决:

  1. 在部署这些可执行文件时,需要host的root权限,依然存在安全隐患。

  2. 存储插件在执行mount、attach这类操作时,往往需要到host去安装第三方工具或者加载一些依赖库,这样host的OS版本往往需要定制,不再是一个简单的linux发型版本,这样的情况太多,会使部署变得复杂。

    例如:ceph需要安装rbd,gluster mount需要安装mount.glusterfs等等。

基于这些问题和挑战,CO(Container Orchestrator) 厂商提出 Container Storage Interface 用来定义容器存储标准,它独立于 Kubernetes Storage SIG,由 Kubernetes、Mesos、Cloud Foundry 三家一起推动。个人理解它有如下2个核心目标:

  • 提供统一的 CO 和 SP 都遵循的容器存储接口
  • 基于 CSI 实现了自身的 Volume Driver,即可在所有支持 CSI 的 CO 中平滑迁移

CSI 持久化卷支持是在 Kubernetes v1.9 中引入的,作为一个 alpha 特性,必须由集群管理员明确启用。在kubernetes v1.10中,默认CSI是启用的。

Kubernetes CSI架构

Kubernetes的CSI架构如下,包含三个部分:

  1. Kubernetes Core:Kubernetes核心组件
  2. Kubernetes External Component:Kubernetes支持CSI的扩展组件
  3. External Component:第三方存储厂商开发

kubernetes-with-csi

Kubernetes External Component

在CSI的框架里,Kubernetes本身需要提供以下三类插件:

- Driver registrar 

一个Sidecar Container,向Kubernetes注册CSI Driver,添加Drivers的一些信息。
源码:https://github.com/kubernetes-csi/driver-registrar

- External provisioner 

一个Sidecar Container,监控Kubernetes系统里的PVC对象,调用对应CSI的Volume创建、删除等接口。
源码:https://github.com/kubernetes-csi/external-provisioner

- External attacher

一个Sidecar Container,监控Kubernetes系统里的VolumeAttachment对象,调用对应CSI的接口。
源码:https://github.com/kubernetes-csi/external-attacher

K8S CSI扩展组件的镜像下载:
https://quay.io/organization/k8scsi

Storage Vendor external component

在CSI的框架中,存储提供商需要自己实现CSI标准里的存储插件,主要有如下三类:

- CSI Identity

负责认证插件的状态信息,实现如下接口:

1
2
3
4
5
6
7
8
9
10
service Identity {
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}

rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}

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

- CSI Controller 

负责实际创建和管理volumes,实现如下接口:

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
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) {}
}

- CSI Node

负责在Kubernetes Node上volume相关的功能,实现如下接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
service Node {
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}

rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
returns (NodeUnstageVolumeResponse) {}

rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}

rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
returns (NodeUnpublishVolumeResponse) {}

rpc NodeGetId (NodeGetIdRequest)
returns (NodeGetIdResponse) {}

rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
returns (NodeGetCapabilitiesResponse) {}
}

CSI Drivers

当前已经有好多可以在Kubernetes中使用的CSI Drivers,参考:
https://kubernetes-csi.github.io/docs/Drivers.html

开发CSI Driver

SP要开发一个CSI Driver,要遵循以下三条:

  • 实现CSI接口的功能
  • 实现CSI接口的幂等性
  • 符合CSI返回值规范

SP实现CSI Driver

实现的CSI Driver组件有三个:

  • Identity Plugin
  • Controller Plugin
  • Node Plugin

结合这三个组件的功能,开发者有两种选择:

1、两个执行文件:Controller + Identity,Node + Identity

Node and Controller plugins are running as two separate services

2、一个执行文件:Controller + Node + Identity

Node and Controller plugin runs in a single binary

从调研结果看,官方推荐使用第二种:Controller + Node + Identity

优点:一个可执行文件,方便维护和部署

部署CSI组件

如上介绍,Controller负责volumes的创建删除等操作,整个集群只需要部署一个;Node负责volume的attach、detach等操作,需要在每个节点部署一个,则Kubernetes的推荐部署方式为:

  1. Controller plugin:StatefulSet,replicas设为1
  2. Node plugin:DaemonSet

csi plugin deploy

部署Kubernetes扩展组件

单纯的开发完CSI Driver后,还需要CO提供对应的扩展插件来支持CSI,这样CO系统才能正常的使用CSI的方式调用存储。

在Kubernetes系统里,扩容插件和CSI的部署可以按照如下方式:

deploy with kubernetes

Ceph SP Example

Ceph提供了RBD和CephFS两种CSI接口的存储。
源码:https://github.com/ceph/ceph-csi

Docker image:
https://quay.io/organization/cephcsi

Ceph CSI部署

Controller Plugin在哪里?

在各个组件的plugin里!(rbdplugin/cephfsplugin)

疑问:PVName与RBD Image名字是否一致?

问题:https://github.com/kubernetes/kubernetes/issues/69324

在之前的external-storage的代码里,Ceph RBD Image名字与PVName完全无关联,带来了不必要的查询麻烦,那新的CSI框架下,这两个名字是否一致呢?

ceph-csi的实现

文件:pkg/rbd/controllerserver.go

1
2
3
4
5
6
7
8
9
10
11
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
...
// Generating Volume Name and Volume ID, as according to CSI spec they MUST be different
volName := req.GetName()
uniqueID := uuid.NewUUID().String()
if len(volName) == 0 {
volName = rbdVol.Pool + "-dynamic-pvc-" + uniqueID
}
rbdVol.VolName = volName
...
}

从ceph-csi的rbd实现看,volName是否是随机UUID跟CreateVolume传入的参数相关。

Kubernetes扩展组件实现

文件:pkg/controller/controller.go

1
2
3
4
5
func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
...
pvName, err := makeVolumeName(p.volumeNamePrefix, fmt.Sprintf("%s", options.PVC.ObjectMeta.UID), p.volumeNameUUIDLength)
...
}

文件:cmd/csi-provisioner/csi-provisioner.go

1
2
3
4
5
6
var (
...
volumeNamePrefix = flag.String("volume-name-prefix", "pvc", "Prefix to apply to the name of a created volume.")
volumeNameUUIDLength = flag.Int("volume-name-uuid-length", -1, "Truncates generated UUID of a created volume to this length. Defaults behavior is to NOT truncate.")
...
)

从这里可以看出,Kubernetes的Controller组件实现里,支持启动时候指定:

  • volumeNamePrefix:默认为pvc
  • volumeNameUUIDLength:默认为-1,表示不做VolumeName的Truncate

所以在CSI的框架下,默认Kubernetes的Controller组件会传入Volume Name,底层Ceph RBD CSI实现中也会检查这个Name,如果设置的话就会依据它来创建RBD Image。

支持原创