K8s存储架构及插件使用

开发
容器内部存储的生命周期是短暂的,会随着容器环境的销毁而销毁,具有不稳定性。如果多个容器希望共享同⼀份存储,则仅仅依赖容器本身是很难实现的。

作者 | 中国移动云能力中心PaaS产品部 王琦

容器内部存储的生命周期是短暂的,会随着容器环境的销毁而销毁,具有不稳定性。如果多个容器希望共享同⼀份存储,则仅仅依赖容器本身是很难实现的。

01引言

在Kubernetes系统中,容器应用所需的存储资源被抽象为存储卷(Volume),并以此概念来解决“容器存储所面临”的问题。

Volume是与Pod绑定的(独立于容器)与Pod具有相同生命周期的资源对象。我们可以将Volume的内容理解为⽬录或⽂件,容器如需使⽤某个Volume,则仅需设置volumeMounts将⼀个或多个Volume挂载为容器中的⽬录或⽂件,即可访问Volume中的数据。Volume具体是什么类型,以及由哪个系统提供,对容器应⽤来说是透明的。

Kubernetes⽬前⽀持的Volume类型包括Kubernetes的内部资源对象类型(In-Tree)、开源共享存储等(Out-of-Tree)类型的存储服务。

02Kubernetes存储体系架构

2.1 Kubernetes存储基础知识

PV(Persistent Volume):存储资源的定义,主要涉及存储能力、访问模式、存储类型、回收策略、后端存储类型等关键信息的设置;

图片

图 PV状态流程

PVC(Persistent Volume Claim):用户对存储资源的需求申请,主要涉及存储空间请求、访问模式、PV选择条件和存储类别等信息的设置;

图片

图PVC状态流程

SC(Storage Class):存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,一方面减少了用户对于存储资源细节的关注,另一方面减轻了管理员手工管理PV的压力,转由系统自动完成PV的创建和绑定,实现动态的资源供应。SC资源对象的定义主要包括名称、后端存储的提供者及相关参数配置和回收策略。

2.2 Kubernetes存储总体架构

图片

图 Kubernetes存储架构

  • PV Controller:管理卷的整个生命周期,控制PVC和PV进行绑定,执行卷的Provision或Delete操作;
  • AD Controller:执行卷的Attach或Detach,并将卷挂接到目标节点;
  • Volume Manager:执行卷的Mount或Unmount,以及共享目录挂载、卷格式化等操作;
  • Volume Plugin:扩展卷存储管理的能力,将第三方存储插件能力集成进Kubernetes存储系统,是卷管理能力的具体实现;
  • Scheduler:负责将待调度的Pod按照特定的调度算法和调度策略绑定到集群中某个合适的Node上,并将绑定信息写入etcd。

2.3 Kubernetes存储机制概述

图片

图 Kubernetes持久化存储流程

1)Provision/Delete阶段:

用户创建一个包含PVC的Pod;

PV Controller通过持续监听ApiServer,发现PVC处于待绑定的状态,随即调用Volume Plugin创建存储卷,并在创建成功后将PV与PVC进行绑定;

2)Attach/Detach阶段:

  • Scheduler根据Pod和PV的配置、节点目前的状态等信息,将Pod调度到Worker节点的Node上;
  • AD Controller发现Pod和PVC处于待挂载状态,调用Volume Plugin实现存储设备(卷)挂接到目标节点;

3)Mount/Unmount阶段:

  • Worker节点上的Kubelet(Volume Manager)在卷挂载完成后,通过Volume Plugin将设备挂载到指定目录;
  • Kubelet在被告知挂载目录准备好后,启动Pod中的容器,使用Docker -v的方式将存储设备,也就是已经挂载到本地的卷映射到容器中供Pod使用。

2.4 Kubernetes存储插件

Kubernetes在默认情况下提供了主流的存储卷接入方案,可通过命令“kubectl explain pod.spec.volumes”查看Pod支持的所有存储卷。同时,也提供了一种基于插件的存储管理机制,根据概述可知PV Controller、AD Controller和Volume Manager均通过调用Volume Plugin的功能接口去执行存储卷管理相关Provision、Attach操作。因此,操作的具体实现其实都取决于插件中的接口逻辑。Volume Plugin可以允许除默认类型外的其他存储服务接入到Kubernetes系统中,主要分为In-Tree和Out-of-Tree两类:

  • In-Tree类存储服务实现的源码都在Kubernetes主干代码库中,优势在于和Kubernetes一起发布、管理和迭代,相对稳定。但也因此存在更新迭代速度慢、灵活性较差的问题,属于紧耦合的开发模式;
  • Out-of-Tree类存储服务通过扩展Volume Plugin独立于Kubernetes外,存储提供方只需基于Kubernetes容器对接的存储接口标准进行存储插件的实现,就能使用Kubernetes的原生机制为容器提供存储服务。而存储提供方的代码则可与Kubernetes代码彻底解耦,部署也与Kubernetes核心组件分离。

在Kubernetes中,存储插件的开发方式有Flexvolume和CSI两种,接下来通过原理和案例演示的方式做进一步介绍。

03Felxvolume原理及案例演示

3.1 Flexvolume简介

Flexvolume提供了一种扩展Volume Plugin的方式,用户可以自定义自己的存储插件,实现Attach、Mount等功能接口。比如AD Controller调用插件实现Attach操作时,会首先调用Flexvolume中的Attach接口。Flexvolume是可被Kubelet驱动的可执行二进制文件,该文件需要实现Flexvolume的相关接口,每一次调用相当于执行一次shell脚本,然后返回JSON格式的数据,因此其不是一个常驻内存的守护进程。Flexvolume存储插件默认的存放位置是:/usr/libexec/kubernetes/kubelet-plugins/volume/exec/<vendor~driver>/<driver>,Volume plugin组件会不断watch这个目录来实现插件提供的增删等功能。其中,vendor~driver的名字和Pod中flexVolume.driver的字段名相对应,vendor部分可自定义。

图片

3.2 Flexvolume接口定义

Flexvolume接口可以根据需求自定义的实现,比如使用NFS文件存储,因为不存在存储卷的挂接操作,因此不需要实现Attach、Detach相关接口,只需将接口返回结果定义成{"status":"Not supported","message":"error message"} 即可。以下列举Flexvolume部分接口:

图片

3.3 Flexvolume案例演示

演示前提条件:Kubernetes集群已具备NFS存储服务,并且NFS共享目录已设置,jq工具已安装。

# mkdir -p /root/wq/nfs //设置共享目录
# chmod 755 /root/wq/nfs
# vim /etc/exports //配置NFS,写入/root/wq/nfs *(rw,sync,no_root_squash)设置权限
# yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
# yum install jq -y //安装jq工具
# systemctl restart rpcbind.service
# systemctl enable rpcbind.service
# systemctl restart nfs.service
# systemctl enable nfs.service //重启RPC和NFS,使配置生效

演示案例使用Kubernetes官方给出的一个基于NFS文件存储的Flexvolume插件示例,脚本链接:https://github.com/kubernetes/examples/blob/master/staging/volumes/flexvolume/nfs。根据下图所示,在Flexvolume存储插件默认的存放位置新建插件目录wq-nfs,将脚本放置目录下,并赋予文件可执行权限。

图片

然后部署一个Pod到指定节点,并使用Flexvolume的方式对容器中的数据进行持久化存储,详细yaml文件内容如下。

apiVersion: v1
kind: Pod
metadata:
name: k8s-flexvolume #应用名称
namespace: flexvolume #指定应用所在命名空间
spec:
nodeSelector:
kubernetes.io/hostname: kcs-ks-qq-m-snqnq #指定应用部署节点
volumes:
- name: f-volume
flexVolume:
driver: "wq/nfs" #存储插件定义,用于在默认插件存储位置查询可执行文件
fsType: "nfs" #定义存储系统类型
options: #定义存储服务所需相关参数
server: "192.168.2.49"
share: "/root/wq/nfs" #指定共享存储目录
containers:
- name: nginx-proxy
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: f-volume
subPath: subfv
mountPath: /usr/share/nginx

通过查看Pod本地持久化目录是否已被挂载到了NFS存储系统上,可以得知Flexvolume插件是否正常运行。在NFS共享目录下新建文本文件,并写入一些内容,然后进入Pod查看新建文件和写入内容是否已同步,即可进一步验证存储是否挂载成功,详细操作如下图所示。

图片

04CSI原理及案例演示

4.1 CSI简介

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

图片

图 CSI插件设计架构

4.2 CSI存储体系及接口定义

CSI存储体系主要由两部分组成:External Components和Custom Components。

External Components(Kubernetes外部组件):主要包含Driver Registrar、External Attacher、External Provisioner三个部分,这三个组件源自Kubernetes原本的in-tree存储体系,可以理解为Kubernetes的一个外部Controller,负责Watch Kubernetes的API资源对象,然后根据监听到的状态调用Custom Components实现存储管理和操作。

  • Driver Registrar:CSI Node-Driver-Registrar是一个Sidecar容器,用于从CSI driver获取驱动程序信息(使用NodeGetInfo),并使用Kubelet插件注册机制在该节点上的 Kubelet中对其进行注册。
  • External Attacher:用于监听Kubernetes VolumeAttachment对象,并针对驱动程序端点触发CSI ControllerPublish和ControllerUnpublish操作。
  • External Provisioner:监听Kubernetes中的PVC对象,调用CSI对应的创建、删除等Volume操作。

❖注:External Components目前仍由Kubernetes团队维护,插件开发者无需关心其实现细节。

Custom Components(CSI存储插件):主要包含CSI Identity、CSI Controller、CSI Node三个部分,是需要开发者通过编码来实现的,并以gRPC的方式对外提供服务。

  • CSI Identity:用于对外暴露这个插件本身的信息,确保插件的健康状态。

service Identity {
// 返回插件的名称和版本
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}
// 返回插件具备所有功能
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}
// 检测插件的健康状态,是否正在运行
rpc Probe (ProbeRequest)
returns (ProbeResponse) {}
}

  • CSI Controller:用于实现Volume管理流程中Provision和Attach阶段操作,比如创建和删除Volume、对Volume进行Attach/Detach(Publish/UnPublish)操作等。CSI Controller里定义的所有服务都有一个共同特点,那就是无需在宿主机上进行操作。

service Controller {
// 创建存储卷
rpc CreateVolume (CreateVolumeRequest)
returns (CreateVolumeResponse) {}
// 删除存储卷
rpc DeleteVolume (DeleteVolumeRequest)
returns (DeleteVolumeResponse) {}
// Attach卷,即将存储介质挂接到目标节点
rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
returns (ControllerPublishVolumeResponse) {}
// Unattach卷
rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
returns (ControllerUnpublishVolumeResponse) {}
// 返回存储卷具备的功能,比如是否支持挂载到多个节点上等
rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)
returns (ValidateVolumeCapabilitiesResponse) {}
// 返回所有可用存储卷
rpc ListVolumes (ListVolumesRequest)
returns (ListVolumesResponse) {}
// 返回存储资源池的可用空间大小
rpc GetCapacity (GetCapacityRequest)
returns (GetCapacityResponse) {}
// 返回controller插件具备的功能,比如是否支持Snapshot功能等
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) {}
}

  • CSI Node:用于控制Kubernetes节点上Volume的相关功能操作。Volume在节点的Mount过程被分为NodeStageVolume和NodePublishVolume两个阶段,前者针对块存储类型将存储设备格式化后,先挂载到节点的一个全局的临时目录,之后再调用NodePublishVolume接口将目录挂载进Pod中指定的目录上。

service Node {
// 在Kubernetes节点上格式化存储卷,并将其挂载到一个全局的临时目录
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}
// NodeStageVolume的逆操作,将存储卷从临时目录卸载掉
rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
returns (NodeUnstageVolumeResponse) {}
// 将存储卷从全局的临时目录挂载到指定的目标目录(Pod内的目录)
rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}
// NodePublishVolume的逆操作,将存储卷从Pod目录卸载掉
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
returns (NodeUnpublishVolumeResponse) {}
// 返回可用于该卷的卷容量统计信息
rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
returns (NodeGetVolumeStatsResponse) {}
// Kubernetes节点上执行卷扩容
rpc NodeExpandVolume(NodeExpandVolumeRequest)
returns (NodeExpandVolumeResponse) {}
// 返回节点插件具备的所有功能,比如是否支持Stage/Unstage功能
rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
returns (NodeGetCapabilitiesResponse) {}
// 返回节点的一些信息
rpc NodeGetInfo (NodeGetInfoRequest)
returns (NodeGetInfoResponse) {}
}

4.3 CSI案例演示

演示前提条件:Kubernetes集群中已安装NFS相关的CSI驱动,用于实践通过CSI的方式使用NFS文件存储。Kubernetes官方有一个基于CSI驱动实现NFS存储的项目可以用于初次实践,链接如下:https://github.com/kubernetes-csi/csi-driver-nfs,同时提供以下两种安装部署方式。​

l 远程安装:通过以下命令访问Github一键安装。
# curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v3.1.0/deploy/install-driver.sh | bash -s v3.1.0 -- //本次示例安装csi-driver-nfs版本为v3.1.0,也可安装其他版本
l 本地安装:通过将项目Clone到本地,再执行安装脚本方式进行安装。
# git clone https://github.com/kubernetes-csi/csi-driver-nfs.git
# cd csi-driver-nfs
# ./deploy/install-driver.sh v3.1.0 local

​运行脚本后,可通过命令”kubectl -n kube-system get pod -o wide -l app=csi-nfs-controller”和”kubectl -n kube-system get pod -o wide -l app=csi-nfs-node”查看Pod状态,如下所示则表示已安装完毕。可以注意到,csi-nfs-node会以DaemonSet的方式在每个节点上运行,并执行存储卷的Mount/Unmount操作。

图片

csi-driver-nfs安装完成后,就可以通过”spec.csi.driver”的规范来调用驱动使用NFS存储。下面我们先通过Static Volume Provisioning,也就是静态PV的供应方式调用CSI驱动创建PV。然后通过PVC使用这个PV,完成PVC和PV的绑定操作。

# Static Volume Provisioning创建PV所用yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-csi-nfs
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
mountOptions:
- hard
- nfsvers=4.1
csi:
driver: nfs.csi.k8s.io
readOnly: false
volumeHandle: unique-volumeid
volumeAttributes:
server: 192.168.2.49
share: /root/wq/nfs

# Static Volume Provisioning创建PVC所用yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-csi-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
volumeName: pv-csi-nfs # 指定需要使用的PV
storageClassName: ""

实际操作过程如下图所示。可以看到,通过CSI驱动,PV创建成功并已处于可使用的状态。此时,便可通过PVC使用这个PV,PVC在创建后会与PV进行绑定,绑定成功后PV的状态也会发生改变。

图片

接下来,我们再通过Dynamic Volume Provisioning,动态PV的供应方式做进一步的演示。该方式相比Static Volume Provisioning,集群管理员无需再预先分配PV,而只是创建一个SC模板文件,用户在通过PVC使用PV时,无需再关心PV所需的参数细节,因为这些信息已经预先设置到了SC里。​

# Dynamic Volume Provisioning创建SC所用yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: sc-csi-nfs
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.2.49
share: /root/wq/nfs
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
- hard
- nfsvers=4.1
# Dynamic Volume Provisioning创建PVC所用yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-sc-csi-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: sc-csi-nfs

实际操作过程如下图所示。可以看到,SC、PVC创建成功,PVC通过SC调用CSI驱动自动生成了PV(pvc-4c45d45b-ec52-4276-9cbd-589964da73b3),并最终与PVC完成了绑定。

图片

05总结

本文介绍了Kubernetes存储架构及插件的使用,主要包括以下三部分内容:

  • 第一部分介绍了Kubernetes存储的基本知识、总体架构、存储机制概述以及存储插件的概念;
  • 第二部分对FlexVolume进行了介绍,描述了其用于开发的接口定义,并通过一个实际的例子演示了Flexvolume的使用方法;
  • 第三部分对CSI进行了介绍,解析了其存储体系结构以及开发接口定义,最后通过实际的例子演示了如何通过CSI插件以静态或者动态的方式使用PV进行存储。​
责任编辑:未丽燕 来源: 移动Labs
相关推荐

2022-04-22 13:32:01

K8s容器引擎架构

2023-11-06 07:16:22

WasmK8s模块

2022-02-11 23:11:09

Kubernetes集群容器化

2022-01-02 08:42:50

架构部署容器

2023-09-06 08:12:04

k8s云原生

2021-07-14 14:20:22

root命令Linux

2021-08-05 07:28:26

K8sNFS ProvisiSubdir

2019-09-09 09:53:52

K8s集群架构

2022-11-24 08:55:02

etcdK8sKubeBrain

2020-05-12 10:20:39

K8s kubernetes中间件

2022-09-05 08:26:29

Kubernetes标签

2024-02-22 15:35:05

2023-08-03 08:36:30

Service服务架构

2023-05-25 21:38:30

2023-08-04 08:19:02

2019-09-25 07:17:42

KubernetesIstio测试

2023-09-15 08:00:20

Ingress网关Istio

2022-04-07 10:17:18

云原生服务器优化

2021-04-12 20:42:50

K8S端口内存

2022-12-06 07:30:12

K8s云原生生态系统
点赞
收藏

51CTO技术栈公众号