本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

使用 CSI 和 Kubernetes 动态扩展卷

Kubernetes 自身拥有一个非常强大的存储子系统,涵盖了相当广泛的使用场景。然而,当计划使用 Kubernetes 构建产品级关系型数据库平台时,我们面临一个巨大的挑战:如何解决存储问题。本文描述了如何扩展最新的容器存储接口 0.2.0 并将其与 Kubernetes 集成,并演示了动态扩展存储卷容量的核心方面。

引言

我们关注客户,尤其是在金融领域,容器编排技术的采用正在大幅增长。

他们期待开源解决方案,以重新设计已经在虚拟化基础设施或裸机上运行多年的现有单体应用程序。

考虑到可扩展性和技术成熟度,Kubernetes 和 Docker 位居榜首。但将单体应用程序迁移到像 Kubernetes 这样的分布式编排环境具有挑战性,关系型数据库对于迁移至关重要。

对于关系型数据库,我们应该关注存储。Kubernetes 自身拥有一个非常强大的存储子系统。它非常有用,涵盖了相当广泛的使用场景。当计划在生产环境中使用 Kubernetes 运行关系型数据库时,我们面临一个巨大的挑战:如何解决存储问题。仍有一些基本功能尚未实现。具体来说,就是动态扩展存储卷。这听起来很无聊,但除了创建、删除、挂载和卸载等操作之外,它是高度必需的。

目前,扩展存储卷仅适用于以下存储供应器:

  • gcePersistentDisk
  • awsElasticBlockStore
  • OpenStack Cinder
  • glusterfs
  • rbd

为了启用此功能,我们应该将功能门 ExpandPersistentVolumes 设置为 true,并打开 PersistentVolumeClaimResize 准入插件。一旦 PersistentVolumeClaimResize 被启用,如果 Storage Class 的 allowVolumeExpansion 字段设置为 true,则允许调整大小。

不幸的是,即使底层存储提供商具有此功能,通过容器存储接口 (CSI) 和 Kubernetes 动态扩展存储卷也仍不可用。

本文将简要介绍 CSI,然后详细说明如何在现有 CSI 和 Kubernetes 上引入新的扩展存储卷功能。最后,本文将演示如何动态扩展存储卷容量。

容器存储接口 (CSI)

为了更好地理解我们将要做什么,首先需要了解什么是容器存储接口。目前,Kubernetes 内部现有的存储子系统仍存在一些问题。存储驱动程序代码维护在 Kubernetes 核心仓库中,这使得测试变得困难。除此之外,Kubernetes 还需要授予存储厂商权限,才能将代码提交到 Kubernetes 核心仓库。理想情况下,这应该在外部实现。

CSI 旨在定义一个行业标准,使支持 CSI 的存储提供商能够在所有支持 CSI 的容器编排系统中使用。

此图描绘了一种与 CSI 集成的高级 Kubernetes 原型。

csi diagram

  • 引入了三个新的外部组件,以解耦 Kubernetes 和存储提供商逻辑。
  • 蓝色箭头表示调用 API Server 的常规方式。
  • 红色箭头表示调用 Volume Driver 的 gRPC。

更多详情,请访问:https://github.com/container-storage-interface/spec/blob/master/spec.md

扩展 CSI 和 Kubernetes

为了在 Kubernetes 上启用扩展存储卷功能,我们应该扩展几个组件,包括 CSI 规范、“in-tree” 存储卷插件、外部供应器和外部附加器。

扩展 CSI 规范

扩展存储卷功能在最新的 CSI 0.2.0 中仍未定义。应引入新的 3 个 RPC,包括 RequiresFSResizeControllerResizeVolumeNodeResizeVolume

service Controller {
 rpc CreateVolume (CreateVolumeRequest)
   returns (CreateVolumeResponse) {}
……
 rpc RequiresFSResize (RequiresFSResizeRequest)
   returns (RequiresFSResizeResponse) {}
 rpc ControllerResizeVolume (ControllerResizeVolumeRequest)
   returns (ControllerResizeVolumeResponse) {}
}

service Node {
 rpc NodeStageVolume (NodeStageVolumeRequest)
   returns (NodeStageVolumeResponse) {}
……
 rpc NodeResizeVolume (NodeResizeVolumeRequest)
   returns (NodeResizeVolumeResponse) {}
}

扩展“In-Tree”存储卷插件

除了扩展 CSI 规范,Kubernetes 中的 csiPlugin 接口还应实现 expandablePlugincsiPlugin 接口将扩展代表 ExpanderControllerPersistentVolumeClaim

type ExpandableVolumePlugin interface {
VolumePlugin
ExpandVolumeDevice(spec Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error)
RequiresFSResize() bool
}

实现存储卷驱动程序

最后,为了抽象实现复杂性,我们应该将单独的存储提供商管理逻辑硬编码到 CSI 规范中明确定义的以下函数中:

  • CreateVolume
  • DeleteVolume
  • ControllerPublishVolume
  • ControllerUnpublishVolume
  • ValidateVolumeCapabilities
  • ListVolumes
  • GetCapacity
  • ControllerGetCapabilities
  • RequiresFSResize
  • ControllerResizeVolume

演示

让我们通过一个具体的用例来演示此功能。

  • 为 CSI 存储供应器创建存储类
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-qcfs
parameters:
  csiProvisionerSecretName: orain-test
  csiProvisionerSecretNamespace: default
provisioner: csi-qcfsplugin
reclaimPolicy: Delete
volumeBindingMode: Immediate
  • 在 Kubernetes 集群中部署 CSI 存储卷驱动程序,包括存储供应器 csi-qcfsplugin

  • 创建 PVC qcfs-pvc,该 PVC 将由存储类 csi-qcfs 动态供应。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: qcfs-pvc
  namespace: default
....
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 300Gi
  storageClassName: csi-qcfs
  • 创建 MySQL 5.7 实例以使用 PVC qcfs-pvc
  • 为了模拟完全相同的生产级场景,实际上有两种不同类型的工作负载,包括:
    • 批量插入以使 MySQL 消耗更多文件系统容量
    • 突发查询请求
  • 通过编辑 pvc qcfs-pvc 配置动态扩展存储卷容量。

Prometheus 和 Grafana 的集成允许我们可视化相应的关键指标。

prometheus grafana

我们注意到,中间读数显示 MySQL 数据文件大小在批量插入期间缓慢增加。同时,底部读数显示文件系统在约 20 分钟内扩展了两次,从 300 GiB 到 400 GiB,然后到 500 GiB。与此同时,顶部读数显示扩展存储卷的整个过程立即完成,并且几乎不影响 MySQL QPS。

结论

无论应用程序运行在何种基础设施上,数据库始终是关键资源。拥有更高级的存储子系统来完全支持数据库需求至关重要。这将有助于推动云原生技术的更广泛采用。