一、引言
Kubernetes从版本1.0以后,引入了两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim 实现了对存储供应的独立管理。持久卷(PersistentVolume,PV) 是对存储资源在集群中的抽象;持久卷声明(PersistentVolumeClaim,PVC)则是用户对存储资源的请求。尽管PV和PVC已经实现了对用户存储资源请求的管理,但是管理员仍需手动创建各种类型的PV 实现对用户的存储供应。为了实现存储资源的动态供应,Kubernetes从版本1.4以后就引入了存储类(StorageClass)资源。自1.9版本以后,Kubernetes又引入了容器存储接口(Container Storage Interface CSI),将存储驱动代码从Kubernetes的核心库中剥离了出来,在Kubernetes和外部存储间引入了一套标准的存储管理接口,使得外部存储可以通过实例化该接口动态为容器提供存储资源。本文将从上述资源对象入手,对Kubernetes的存储管理进行简要说明,并简述G行在容器存储管理上的实践。
二、PV和PVC
1、PV
PV(PersistentVolume) 是集群中存储资源,一般由管理员创建或者由StorageClass自动创建。他们不同于普通的volume,其生命周期独立于Pod存在。PV主要包括了存储能力、访问模式、存储类型、回收策略、后端存储类型等关键信息的描述。下文我们来创建一个PV,YAML如下:
针对这个例子,我们来对PV的关键属性进行简要描述:
- Capacity:描述指定存储设备的存储能力,目前仅支持对存储大小(storage)的设置,未来可能支持IOPS、吞入量等;
- VolumeMode:Kubernetes支持两种存储卷模式:Filesystem(文件系统)和Block(块设备),默认值为Filesytem。当VolumeMode指定为Filesystem时,Kubernetes会在首次挂载时实现对文件系统的初始化;当VolumeMode指定为Block时,则会以原始块设备的形式挂载给Pod,Pod和Volume之间不会有任何文件系统层;
- AccessModes:描述用户对存储资源的访问权限,需要注意的是一个PV在同一时刻只能以一种访问模式被挂载,其可选的访问模式包括:a. ReadWriteOnce(RWO):卷可以被一个节点以读写方式挂载,可以被同一节点上的多个Pod访问;b. ReadOnlyMany(ROX):卷可以被多个节点以只读方式挂载;c. ReadWriteMany(RWX):卷可以被多个节点以读写方式挂载;d. ReadWriteOncePod(RWOP):卷可以被单个Pod 以读写方式挂载。此模式必须需要CSI支持以及需要 Kubernetes 1.22 以上版本。
- StorageClassName:指定的存储类别(StorageClass),只有StorageClassName相同的PV和PVC才能进行绑定。若StorageClassName不进行设置,则会被设置为集群默认的 StorageClass。
2、PVC
PVC 是用户对存储资源的申请,主要包括了存储空间请求、访问模式、选择条件和存储类别等信息的描述。集群根据PVC的描述为其选择匹配的PV进行绑定。下面我们来创建一个PVC,YAML如下:
针对这个例子,我们来对PVC的关键属性进行简要描述:
- Resources:描述对存储资源的请求,例如存储大小(requests.storage);
- VolumeMode:存储卷模式,其设置跟PV的设置相同;
- AccessModes:访问模式,其设置跟PV的设置相同;
- StorageClassName:存储类别(StorageClass)名称,当系统设置了StorageClass,则系统会自动为PVC创建相应的PV并进行绑定;
- Selector:选择器,PVC会根据selector的内容对满足条件的PV进行筛选,选择KV匹配的PV进行绑定。
PVC根据上述属性描述选择与之匹配的PV进行绑定,需要注意的是,PVC只会在相同的Namespace内选择PV,同样的,Pod也只能挂载相同的Namespace的PVC。只有当PVC选择到合适的PV时,才可以被Pod进行正常挂载,示例如下:
3、PV和PVC的生命周期
PV生命周期的各个阶段:
- Available(可用):尚未与任何PVC进行绑定;
- Bound(已绑定):已经绑定到某个PVC;
- Released(已释放):所绑定的PVC已被删除,但是资源尚未被集群回收;
- Failed(失败):卷自动回收操作失败。
PV和PVC的相互关系如下图1所示的生命周期:
图1
Kubernetes提供了两种存储资源的供应模式:
静态模式:集群管理员必须联系存储管理员手动来创建新的存储卷, 然后在 Kubernetes 集群创建 PersistentVolume 对象来表示这些卷,最后用户创建PVC进行绑定。
动态模式:集群管理员无需手动创建PV卷,通过配置StorageClass来实现对存储类的管理和资源的动态供应。用户申请PVC时,指定StorageClassName,对应的StorageClass将会自动完成对应存储类的存储卷创建以及Kubernetes集群中PV的创建。若StorageClassName声明为"", 则说明该PVC禁止使用动态模式。
三、StorageClass和CSI
1、StorageClass
动态供应模式主要基于StorageClass对象实现,集群管理员可以针对不同的存储类型创建不同的StorageClass,对用户屏蔽了底层存储的细节。基于StorageClass的动态存储供应逐步成为了云平台的标准存储配置模式。StorageClass主要包括了存储提供者以及相关存储参数的配置。StorageClass一旦创建,则不能被修改,只能删除重建。示例如下:
示例声明一个名为standard,由Kubernetes.io/aws-ebs提供的存储类。
2、CSI
StorageClass提供了存储动态供应的功能,但是各种后端存储插件的代码都必须被放入Kubernetes的主干代码中以供调用,这种紧耦合的开发模式,导致了巨大的维护成本和诸多问题。因此Kubernetes基于上述考虑,推出了容器存储接口标准(CSI)。各存储提供方自行维护自己的存储插件代码,只要满足CSI 标准,即可让Kubernetes进行调用,无需再耦合在Kubernetes的主干代码中。CSI 存储插件的标准实现主要包含两种组件:
Controller Plugin:Controller主要实现存储资源和存储类的管理,一般为单实例部署,可以部署在任意节点上。
Node Plugin:主要实现对Node上存储卷的管理和操作,包括卷的挂载、卸载等,一般部署为Daemonset,每个Node上运行一个Pod。
四、G行的容器存储管理实践
1、背景
G行在云平台3.0之前已经引入了集中式NAS存储,在用户需要申请PV卷时,需要先向存储管理员提出申请,然后由存储管理员在存储端按用户需求创建一块存储卷,再由系统管理员在集群中手动创建PV卷完成资源交付,最后用户在系统中创建PVC完成资源申请,流程如下图2所示:
图2
从上述流程描述可以看出,整个过程中需要大量人为介入,缺乏自动化。Kubernetes本身提供了StorageClass 用于支持资源的动态创建,因此G行考虑在现有基础上引入CSI存储插件,实现了自动化存储交付。并在原有的基础上,引入了高性能本地存储和分布式SAN存储,扩充了存储资源类型。
2、CSI的部署与实践
图3
CSI的部署过程如上图3所示,过程相对比较复杂,每次Kubernetes集群创建后,若都需要手动部署CSI插件,无疑会增加系统运维的成本。G行在此基础上引入APP标准化交付框架,将Kubernetes的集群创建操作,以及CSI插件的部署进行了集成,实现了Kubernetes集群的一键化创建,标准化交付。当集群创建完成后,相应的CSI插件及StorageClass就已经自动完成了部署,用户即可直接使用。
集群中已经部署好用户所需的StorageClass,存储资源申请只需直接创建PVC即可,示例如下:
刚创建完的PVC的状态为Pending。
CSI的Controller会Watch kube-apiserver,检测StorageClass的provisioner为自身的PVC的变化,当感知到PVC的创建后,会自动去存储端进行存储卷的创建,存储卷创建完以后,Controller会自动在集群中创建PV。
图4
整个过程结束后,可以在集群中看到PV已经被成功创建,PVC的状态已经变为了Bound。
从图4上述过程可以看出,用户申请存储资源的整个流程中,无需存储管理员和集群管理员手动干预,即可自动存储资源交付。
3、使用CSI支持动态扩容
在云平台3.0中,G行在云平台3.0中的引入了高性能本地存储和分布式SAN存储,这两类存储在存储层基于KVM动态扩容技术可实现对后端存储卷的动态扩容。在容器平台中,G行对相应的CSI Controller进行了升级优化,实现了EXPAND_VOLUME的功能,响应用户的扩容需求。当CSI Controller在Watch到相应的修改后,会调用存储后端接口对相应的存储卷和PV卷进行扩容,并对文件系统根据相应设置进行调整。
首先在存储插件部署时,需要将StorageClass的allowVolumeExpansion字段设置为True,开启动态扩容,StorageClaas配置示例如下:
对于正在运行中的业务,若用户有扩容需求,无需卸载其使用的PV卷,直接修改PVC的requests大小,CSI Controller则立即尝试调用存储后端接口对相应的存储卷和PV卷进行扩容,后端存储在接收到扩容需求后,调用virsh blockresize命令调整存储卷大小,并将处理结果返回给CSI Controller。CSI Controller确认存储卷已经扩容后,找到当前存储卷所挂载的node节点,请求相应的node节点上的CSI Daemonset对扩容卷进行resize,将扩容的容量扩充进文件系统中。至此,用户的动态扩容需求就完成了。整个扩容流程,用户无需暂停业务,业务连续性和稳定性得到了保障。
4、多种CSI并行
G行在云平台3.0中支持了集中式NAS存储、高性能本地存储和分布式SAN存储,并针对这三类存储类进行了性能测试,针对测试结果梳理了相应的用户使用场景,部分测试数据如下(测试数据仅供参考):
集中式NAS存储:作为文件存储,读写性能弱于块存储,原生支持文件系统的多节点读写,适用于有文件共享需求的用户使用。
分布式SAN存储:分布式块存储,读写性能好。在存储模式为Filesystem时不支持多节点读写,适用于对存储性能要求较高,无共享存储需求的用户使用。
高性能本地存储:本地块存储,读写性能好。同样不支持Filesystem存储模式下的多节点读写,适用于单存储卷容量300G以下,无共享存储需求,对存储性能要求高的用户使用。
五、总结
Kubernetes通过PV、PVC、StorageClass、CSI标准构建了一套完善的存储管理机制,G行充分利用Kubernetes资源对象,并针对行内现状,引入多种存储类型和对应的CSI存储插件,实现了多种CSI并行及自动化容器存储管理。存储管理员和系统管理员无需再手动创建存储卷和PV卷,只需在项目创建之初,针对不同的存储类(StorageClass)给项目分配存储配额。用户需要时,可直接创建PVC,CSI 控制器会根据用户的PVC参数,调用存储端接口,自动创建存储卷以及PV卷,实现了用户的即时申请,即时可用,当用户有扩容需求时,现有CSI控制器也可支持PV卷的动态扩容,无需管理员手动调整。相比静态交付管理的模式,集群管理员无需预先配置存储,既降低了人力成本,又提升了交付效率和业务的连续性。同时,多种存储类的引入也满足了不同使用场景下的用户需求。