合作机构:阿里云 / 腾讯云 / 亚马逊云 / DreamHost / NameSilo / INWX / GODADDY / 百度统计
现在,我将继续和大家聊一聊关于 K8s 存储的一个重要组成部分:Container Storage Interface (CSI)。在接下来的内容中,我们将会了解到 CSI 的工作原理、核心概念以及如何将其集成到你的容器化环境中。
在学习 CSI 之前,了解其产生的背景以及它能够解决的问题我觉得是很有必要的。
虽然 Kubernetes 平台它本身支持了非常多的存储插件,但是毕竟也是有限的,永远无法满足用户日益增长的需求,比方说有客户要求我们的 Paas 平台必须接国产的存储怎么办?
Kubernetes 本身提供了一个强大的 Volume 插件系统,最直接的方式就是向这个 Volume Plugin 增加新的插件。
但是,想必大家也知道 Kubernetes 太复杂了,它有一定的学习曲线,这样做一来成本比较高,再者直接集成第三方代码,可能会对 Kubernetes 平台系统的可靠性和安全性产生隐患。
另外,这种方法它也不方便测试和维护,比方说第三方存储服务如果有变更,我们就需要提交变更代码到 Kubernetes,等待 Code Review。换句话说,我们必须要等到 Kubernetes 发布才能将存储服务的变动上线,这就意味着存储的集成与 Kubernetes 的发布周期捆绑在一起了。
所以直接和 Kubernetes 做集成是非常不方便的。
让我们重新回过来看下,上面反复提到过的 In-Tree 和 Out-Of-Tree 这两个概念,我相信从字面意思上大家都已经理解了,再结合下面这张表格,大家心里是否都已经有了答案?
图片
CSI 将三方存储代码与 K8s 代码解耦,不同的存储插件只要实现这些统一的接口,就能对接 K8s,用户无需接触核心的 K8s 代码。
最重要的是,CSI 规范现在是业界容器编排统一的存储接入标准。
图片
Container Orchestrators(CO)
以下是 ChatGPT 给出的回答:
CSI(Container Storage Interface) 是一个规范化的接口,用于容器编排引擎与存储供应商之间的通信。它允许存储供应商编写标准的插件,以便容器编排引擎可以与其集成,从而实现更加灵活和可扩展的存储解决方案。
CSI 驱动器由三个主要组件组成,每个组件都扮演着特定的角色:
Node Service: 运行在每个 Kubernetes 节点上,负责在节点上挂载和卸载存储卷,并处理节点级别的存储操作。
Controller Service: 运行在 Kubernetes 控制平面中,负责管理存储卷的生命周期,包括创建、删除和扩容等操作。
Identity Service: 它是 CSI 驱动器的第三个组件,它在 CSI 驱动器注册时提供标识信息,并向 Kubernetes 集群公开驱动器的支持能力。它负责告知 Kubernetes 驱动器的存在,提供驱动器的基本信息和功能支持。
CSI 的设计思想是将存储管理和容器编排系统解耦,使得新的存储系统可以通过实现一组标准化的接口来与 Kubernetes 进行集成,而无需修改 Kubernetes 的核心代码。
CSI 驱动器的出现为 Kubernetes 用户带来了更多的存储选择,同时也为存储供应商和开发者提供了更方便的接入点,使得集群的存储管理更加灵活和可扩展。
图片
Storage in Cloud Native Environment
CSI 适配工作是由容器编排系统(CO)和存储提供商(SP)共同完成的,CO 通过 gRPC 与 CSI 插件进行通信。相信大家也都观察到了,CSI 在这里充当了连接的纽带,上层连接容器编排系统,下层操作三方存储服务。
下面是 CSI 的一个典型架构,虽然 CSI 对于存储提供商来说只需实现三个模块即可,但是整个编排流程可以说是相当复杂的。
图片
Kubernetes cluster with CSI
CSI 的整个运转流程会涉及到两方面的组件:
?? 由 Kubernetes 官方维护的一组标准 external 组件,他们主要负责监听 K8s 里的资源对象,从而向 CSI Driver 发起 gRPC 调用,详见:Kubernetes CSI Sidecar Containers[1]。它们是与 CSI 驱动器一起部署在同一个 Pod 中,用于辅助 CSI Driver 完成一些额外的任务和功能。?? 各存储厂商开发的组件(需要实现 Identity Service,Controller Service,Node Service)
我们来看下左边的 CSI Driver Controller 部分,它是通过多个 Sidecar 组件配合第三方实现的插件(Controller Service)来完成的。
正如上面提到的,它负责管理存储卷的生命周期,包括创建、删除和扩容等操作。换句话说,我们的存储厂商能够提供什么样的能力,部署 Controller 的时候,就需要提供与之对应的 Sidecar 容器一起部署。
好比说我的 CSI Driver 只提供了 Dynamic provisioning 的能力,其他能力的接口我都没实现,在控制器部署的时候,我只需要将 external-provisioner 和我的 Controller Service 部署在一个 Pod 即可,组件的选择完全取决于三方的实现。
external-provisioner 是一个 Sidecar 容器,用于在 Kubernetes 中动态地创建和删除外部存储卷。
当一个新的 PVC (PersistentVolumeClaim) 被创建时,external-provisioner 会向外部存储系统发起请求,以创建相应的存储卷,并将其与 PVC 关联,从而满足 Pod 对持久化存储的需求。
external-provisioner 实际上会执行检查,以验证 PVC 中是否存在注解 volume.kubernetes.io/storage-provisioner,并且该注解的值是否与 CSI 驱动程序的名称相匹配。整个流程贯穿了 PV Controller 这个组件。
图片
provision
这里涉及到的两个操作分别对应着 Controller Service 中的 CreateVolume 和 DeleteVolume 两个接口的实现,它们的调用者正是 External Provisoner。
这一流程的核心是,external-provisioner 充当了中间人,通过 Kubernetes 的 PVC 和 StorageClass 机制,将 Pod 的持久存储需求传递给外部存储系统。这使得存储卷的创建和管理能够无缝集成到 Kubernetes 集群中,为应用提供了持久性的存储解决方案。
假设每个 PVC 背后对应的 Volume 都需要独立加密,并且加密密钥也各不相同,PVC 的 Spec 中已经没有额外的参数来提供这些信息了,那么我们如何将这些加密密钥传递给 CSI 接口呢?
这里有必要提一下 CSI Operation Secrets 这个概念,它允许针对每种不同的 CSI 操作定制不同的 Secret,并且通过 StorageClass 与之配合使用。
让我们来看下面这个 StorageClass 的定义作为例子:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: xfs2-sc-4-per-volume
provisioner: xfs2.csi.basebit.ai
parameters:
csi.storage.k8s.io/provisioner-secret-name: ${pvc.name}
csi.storage.k8s.io/provisioner-secret-namespace: ${pvc.namespace}
TOP