bash
[root@paas-controller-1:~]$ kubectl get sc -o yaml
apiVersion: v1
items:
- allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
createdBy: storageManager
kubectl.kubernetes.io/last-applied-configuration: |
{"allowVolumeExpansion":true,"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"createdBy":"storageManager","storageClassType":"FileStorage"},"name":"gluster-csi-sc"},"parameters":{"replica":"2"},"provisioner":"fs.csi.gluster"}
storageClassType: FileStorage
creationTimestamp: "2026-04-15T03:59:47Z"
name: gluster-csi-sc
resourceVersion: "177163"
uid: 5e31a968-7f99-4ff4-a5ac-4b544f4ef834
parameters:
replica: "2"
provisioner: fs.csi.gluster
reclaimPolicy: Delete
volumeBindingMode: Immediate
kind: List
metadata:
resourceVersion: ""
您提供的 kubectl get sc -o yaml 输出揭示了您当前 Kubernetes 集群中已有的存储生态,这与我们刚才讨论的 NFS 方案是完全不同的技术路径。
结论先行:
您不需要 再手动配置 NFS 了。您的集群已经预置了 GlusterFS(也就是您之前一直在排查进程的那个服务)作为后端存储,并且配置好了 CSI 插件。
1. 如何在 K8s 中使用现有的 GlusterFS?
您不需要关心底层的 IP 或挂载路径,只需要通过 StorageClass 来申请存储,GlusterFS 的 CSI 插件会自动帮您在后端创建卷并挂载。
- StorageClass 名称 :
gluster-csi-sc - 创建方式:动态供应 (Dynamic Provisioning)
- 数据副本数 :2 (由参数
replica: "2"决定,保证了高可用)
操作步骤:
您只需创建 PVC,K8s 和 GlusterFS 就会自动完成剩下的工作:
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster-pvc
spec:
storageClassName: gluster-csi-sc # 指定使用 GlusterFS 的存储类
accessModes:
- ReadWriteMany # GlusterFS 支持多节点同时读写
resources:
requests:
storage: 10Gi # 申请 10G 空间
---
apiVersion: v1
kind: Pod
metadata:
name: gluster-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: gluster-storage
mountPath: /usr/share/nginx/html
volumes:
- name: gluster-storage
persistentVolumeClaim:
claimName: gluster-pvc
2. 关于您提供的网页解析内容
这两个 URL 是 Kubernetes 内部的 API 组标识,出现在 YAML 的 apiVersion 和注解中,您无需手动访问这些网址。
storage.k8s.io/v1:- 含义 :这是 Kubernetes 存储相关资源(如
StorageClass、VolumeAttachment)的 API 组和版本。 - 出处 :您输出的
apiVersion: storage.k8s.io/v1。
- 含义 :这是 Kubernetes 存储相关资源(如
kubectl.kubernetes.io/last-applied-configuration:- 含义 :这是一个注解(Annotation),Kubectl 用它来记录上一次创建或修改该资源时使用的完整配置(JSON 格式)。用于在执行
kubectl apply时计算差异。 - 出处 :您输出的
metadata.annotations下方的长串 JSON。
- 含义 :这是一个注解(Annotation),Kubectl 用它来记录上一次创建或修改该资源时使用的完整配置(JSON 格式)。用于在执行
3. 总结与建议
- 不要重复造轮子:您已经有了 GlusterFS,它比 NFS 更适合容器云环境(去中心化、高可用、原生集成)。
- 使用方式 :在部署应用(Deployment/StatefulSet)时,将
spec.template.spec.volumes指向一个 PVC,且 PVC 的storageClassName设置为gluster-csi-sc即可。
这个sc(StorageClass)是如何创建的,要先安装provisioner: fs.csi.gluster?
是的,您的理解非常准确。在 Kubernetes 中,必须先安装对应的 Provisioner(存储制备器),然后才能创建使用该 Provisioner 的 StorageClass。
结合您当前集群中 provisioner: fs.csi.gluster 的配置,以下是具体的创建逻辑和步骤:
1. 核心逻辑:先有 Provisioner,后有 StorageClass
StorageClass 本质上是一个"配置模板",它本身不具备创建存储的能力。它必须依赖集群中已经运行的 Provisioner 组件(通常是一个以 Pod 形式运行的控制器)来真正与底层存储(GlusterFS)进行通信并创建卷。
2. 如何安装 fs.csi.gluster Provisioner?
fs.csi.gluster 是一个基于 CSI(Container Storage Interface)标准的 GlusterFS 驱动。安装它通常有以下几种方式:
- 通过 Helm 部署(推荐):GlusterFS 官方或相关社区通常提供 Helm Chart,可以通过一条命令一键部署 Provisioner 及其所需的 DaemonSet、RBAC 权限等组件。
- 通过 YAML 清单部署 :手动
kubectl apply一系列 YAML 文件,包括 Provisioner 的 Deployment、Node 插件、ServiceAccount 和 ClusterRole 等。 - 通过 PaaS 平台自动化部署 :从您之前提供的 YAML 注解中可以看到
"createdBy": "storageManager"和"storageClassType": "FileStorage"。这强烈暗示您当前使用的是一个企业级 PaaS 平台(如 KubeSphere、Rancher 或自研平台)。在这个平台中,您很可能只是在 UI 界面上勾选了"启用 GlusterFS 存储",平台的后台控制器(storageManager)就自动为您完成了 Provisioner 的安装和 StorageClass 的创建。
3. 如何验证 Provisioner 是否已经安装并正常运行?
既然您的集群中已经存在这个 StorageClass,说明 Provisioner 大概率已经部署了。您可以通过以下命令验证:
-
检查 Provisioner Pod 是否运行:
bashkubectl get pods -A | grep gluster您应该能看到类似
glusterfs-csi-provisioner-xxx或glusterfs-csi-node-xxx的 Pod 处于Running状态。 -
检查 CSI Driver 是否注册:
bashkubectl get csidrivers输出列表中应该包含
fs.csi.gluster。
4. 总结
如果您是在一个全新的集群中从零开始配置,您需要先查阅 GlusterFS CSI 的官方文档部署 Provisioner,然后再编写 YAML 创建 StorageClass。但鉴于您当前环境的特殊性(由 storageManager 自动创建),您无需手动安装 Provisioner ,直接使用这个现成的 gluster-csi-sc 来创建 PVC 即可。
fs.csi.gluster 是一个基于 CSI(Container Storage Interface)标准的 GlusterFS 驱动。 ---CSI标准是啥,具体有什么标准接口?
在 Kubernetes 存储生态中,CSI(Container Storage Interface,容器存储接口) 是一个极其重要的行业标准。它旨在将容器编排系统(如 Kubernetes)与底层存储系统解耦。
在 CSI 出现之前,Kubernetes 的存储驱动代码是硬编码在 Kubernetes 核心代码库中的(即"树内"插件)。这导致每次添加新的存储系统或修复存储 Bug,都需要修改 Kubernetes 核心代码并重新发版。CSI 的出现彻底改变了这一现状,它定义了一套标准的 gRPC 接口规范,使得存储提供商可以独立开发、部署和维护存储插件,而无需修改 Kubernetes 的核心代码。
CSI 的核心标准接口
CSI 规范将存储插件的职责划分为三个核心组件,每个组件负责暴露一组特定的 gRPC 服务接口:
1. Controller Service(控制器服务)
该服务通常作为一个独立的 Pod(Controller Plugin)运行,负责处理集群级别的存储卷管理操作。它不关心具体的节点,主要包含以下核心接口:
- CreateVolume / DeleteVolume:在底层存储系统中创建或删除一个存储卷。
- ControllerPublishVolume / ControllerUnpublishVolume:将卷挂载到特定节点,或从节点卸载(例如,将云盘附加到某台虚拟机)。
- ValidateVolumeCapabilities:验证某个卷是否支持请求的访问模式(如 ReadWriteMany)。
- ListVolumes / GetCapacity:列出当前存在的卷或查询底层存储的剩余容量。
2. Node Service(节点服务)
该服务作为一个 DaemonSet 运行在集群的每一个节点上,负责处理与具体节点相关的卷操作。主要接口包括:
- NodeStageVolume / NodeUnstageVolume:在节点级别对卷进行全局格式化或挂载(例如,将云盘挂载到节点的全局路径并格式化文件系统)。
- NodePublishVolume / NodeUnpublishVolume:将全局挂载点绑定(Bind Mount)到具体的容器 Pod 目录中。
- NodeGetInfo:返回当前节点的唯一标识符(NodeID),供 Controller 识别。
3. Identity Service(身份服务)
这是所有 CSI 插件必须实现的基础服务,用于向 Kubernetes 声明自身的能力。主要接口包括:
- GetPluginInfo :返回插件的名称和版本号(例如
fs.csi.gluster)。 - GetPluginCapabilities:声明该插件支持哪些高级功能(例如是否支持动态制备、是否支持卷扩容等)。
- Probe:供 Kubernetes 进行健康检查,确认插件是否处于正常工作状态。
结合您的 GlusterFS 场景
回到您集群中的 fs.csi.gluster,它就是一个严格遵循上述 CSI 标准开发的插件。当您创建一个 PVC 并指定 storageClassName: gluster-csi-sc 时,Kubernetes 会通过 gRPC 调用该插件的 CreateVolume 接口;当 Pod 被调度到某个节点时,Kubernetes 会调用该插件的 NodeStageVolume 和 NodePublishVolume 接口,最终将 GlusterFS 的分布式卷无缝挂载到您的容器内部。
这两个步骤详细解释一下
NodeStageVolume / NodeUnstageVolume:在节点级别对卷进行全局格式化或挂载(例如,将云盘挂载到节点的全局路径并格式化文件系统)。
NodePublishVolume / NodeUnpublishVolume:将全局挂载点绑定(Bind Mount)到具体的容器 Pod 目录中。
在 Kubernetes 的 CSI(容器存储接口)架构中,将存储卷挂载到 Pod 并非一步到位,而是被严谨地拆分为了**"全局准备(Stage)"和"挂载到容器(Publish)"**两个阶段。这种设计主要是为了解决块设备在 Linux 下只能挂载一次,但同一个卷可能被多个 Pod 共享的问题。
以下是这两个步骤的详细原理解析:
1. NodeStageVolume / NodeUnstageVolume(节点级全局准备)
这个阶段发生在存储卷被附加(Attach)到节点之后,但在挂载给具体 Pod 之前。它的主要职责是在宿主机(Node)级别对存储卷进行初始化和全局挂载。
- 核心操作 :
- 格式化与准备:如果这是一个全新的块存储设备,CSI 插件会在此阶段对其进行文件系统格式化(如 ext4、xfs)。
- 全局挂载 :将格式化后的块设备挂载到宿主机上的一个临时全局目录(Global Path / Staging Path)。
- 为什么需要这一步?
因为底层块设备在 Linux 系统中通常只能被挂载一次。通过NodeStageVolume将其挂载到全局目录,相当于在宿主机上建立了一个统一的"数据中转站"。 - 反向操作(NodeUnstageVolume):当节点上所有使用该卷的 Pod 都被销毁,且不再需要该卷时,CSI 会将该卷从这个全局目录中卸载,并清理相关的文件系统状态。
2. NodePublishVolume / NodeUnpublishVolume(容器级挂载)
这个阶段由 Kubelet 在 Pod 启动时触发,它的职责是将上一步准备好的全局数据,安全地映射到具体的容器(Pod)目录中。
- 核心操作 :
- Bind Mount(绑定挂载):CSI 插件利用 Linux 的 Bind Mount 技术,将上一步生成的"全局目录"直接映射到 Pod 内部指定的目标路径(Target Path)。
- 权限与隔离 :在此阶段,还可以设置容器级别的挂载选项(如只读
ro、SELinux 标签等),确保不同 Pod 之间的数据访问权限相互隔离。
- 为什么需要这一步?
由于多个 Pod 可以共享同一个存储卷,通过 Bind Mount,Kubernetes 可以安全地将同一个全局目录映射给同一节点上的多个 Pod,而无需重复挂载底层块设备。 - 反向操作(NodeUnpublishVolume):当 Pod 被删除或迁移时,CSI 会将该卷从 Pod 的目标目录中卸载,断开容器与存储的连接。
💡 总结:两步走的生命周期
您可以将这个过程理解为**"先修路,再开门"**:
- NodeStageVolume:把远端存储挂载到宿主机的全局目录,相当于"修好了一条通往数据的高速公路"。
- NodePublishVolume:把全局目录 Bind Mount 到 Pod 内部,相当于"为每个需要数据的房间(Pod)开了一扇专属的门"。
当删除数据时,流程则完全相反:先关门(NodeUnpublishVolume),等所有房间都不需要时,再拆除高速公路(NodeUnstageVolume)。
这里说的存储卷,全局目录, 指的是pv吗? 有10个pv,就相当于有10个存储卷,10个全局目录?
您的理解非常敏锐,但这里有一个非常关键的概念需要澄清:
"存储卷"和"全局目录"指的是底层的物理/逻辑存储实体(即 PV 背后的真实存储),而不是 PV 这个 K8s 对象本身。
我们可以这样梳理它们之间的关系:
1. PV 是什么?
PV(PersistentVolume)只是 Kubernetes 集群中的一个**"抽象对象"**(一个 API 资源)。它就像一张"提货券"或"说明书",记录了底层存储的容量、访问模式以及连接信息(比如 NFS 的 IP 和路径,或者云盘的 ID)。PV 本身并不存储数据,它只是指向了真正的存储。
2. 存储卷是什么?
"存储卷"指的是 PV 背后对应的真实物理或逻辑存储空间。比如一块真实的云硬盘、一个 NFS 共享目录,或者一个 GlusterFS 的分布式卷。
3. 全局目录是什么?
"全局目录"(Global Path / Staging Path)是宿主机(Node)上的一个本地临时文件夹。当 K8s 将底层的"存储卷"挂载到某台具体的机器上时,就会在这个机器上创建一个全局目录作为"中转站"。
回答您的疑问:有 10 个 PV,就相当于有 10 个存储卷,10 个全局目录吗?
答案是:有 10 个 PV,就一定有 10 个真实的"存储卷",但"全局目录"的数量取决于这些 PV 是否被调度到了同一台机器上。
具体场景如下:
-
关于存储卷(10个 PV = 10 个存储卷) :
是的。如果您创建了 10 个 PV,就意味着 K8s 对接了 10 个真实的底层存储空间(例如 10 个独立的 GlusterFS 卷,或 10 块独立的云盘)。
-
关于全局目录(数量不固定) :
全局目录是**按节点(Node)**生成的,而不是按 PV 生成的。
- 情况 A :如果这 10 个 PV 对应的 Pod 被调度到了同一台 宿主机上,那么这台宿主机上就会生成 10 个全局目录。
- 情况 B :如果这 10 个 Pod 被均匀调度到了 2 台 宿主机上,那么每台宿主机上只会生成 5 个全局目录。
- 情况 C :如果某个 PV 还没有被任何 Pod 使用(即 PVC 还没绑定,或者 Pod 还没调度),那么它就不会产生全局目录。
💡 形象的比喻
- PV:相当于您在京东上买的"10 个快递的物流单号"。
- 存储卷:相当于仓库里真实的"10 个包裹"。
- 全局目录 :相当于您小区楼下的"10 个快递暂存柜"。
- 如果您买的 10 个包裹都被送到了同一个小区,那这个小区就会有 10 个暂存柜。
- 如果快递员把包裹分别送到了您家和您父母家,那这两个地方会各自产生暂存柜。
- Pod 挂载(NodePublishVolume):相当于您从楼下的暂存柜里,把包裹拿回自己家里的具体房间。
Linux 的 Bind Mount 技术 是什么,详细说说
Linux 的 Bind Mount(绑定挂载)是 Linux 虚拟文件系统(VFS)层提供的一种强大且轻量级的功能。它允许将一个已存在的目录或文件"镜像"或"重定向"到另一个路径。
结合我们之前讨论的 Kubernetes 和容器技术,Bind Mount 正是实现 NodePublishVolume(将全局存储挂载到具体容器)的底层核心技术。
以下是关于 Bind Mount 的详细解析:
1. 核心原理:不是复制,而是"视图"
Bind Mount 的本质是在内核级别创建一个新的"视图"。当执行绑定挂载时,Linux 内核并不会复制数据,也不会创建新的文件系统,而是让目标路径和源路径指向同一个底层文件系统对象(inode)。
- 双向实时同步:因为两者指向同一份物理数据,对源路径或目标路径的任何读写、修改、删除操作,都会立即在另一侧生效。
- 对应用完全透明:与符号链接(Symlink)不同,Bind Mount 发生在文件系统级别。应用程序无法区分自己访问的是原始目录还是绑定目录,它看起来就像是真实存在于该路径下。
2. 基本用法
Bind Mount 通常通过 mount 命令来实现,需要 root 权限:
bash
# 1. 确保源目录和目标目录都已存在
mkdir -p /data/source
mkdir -p /mnt/target
# 2. 执行绑定挂载
sudo mount --bind /data/source /mnt/target
执行后,访问 /mnt/target 就等同于访问 /data/source。
3. Bind Mount 与其他机制的区别
- 对比符号链接(Symlink) :Symlink 只是一个包含目标路径的快捷方式文件,应用可以通过
readlink等命令感知到它的存在;而 Bind Mount 是内核级的重定向,对应用完全隐藏了映射关系。 - 对比硬链接(Hardlink):硬链接只能用于单个文件,且不能跨文件系统;Bind Mount 支持整个目录树,并且可以跨越不同的文件系统(只要源文件系统已挂载)。
- 对比普通挂载(Mount) :普通挂载需要将一个设备(如
/dev/sda1)或虚拟文件系统(如tmpfs)关联到目录;Bind Mount 则是纯粹的"目录到目录"或"文件到文件"的映射,不涉及新设备。
4. 高级特性与进阶用法
-
只读绑定(Read-Only Bind) :可以将源目录以只读方式映射到目标路径,防止应用意外修改源数据。
bashsudo mount --bind -o ro /data/source /mnt/target -
递归绑定(Recursive Bind) :如果源目录内部还嵌套了其他的挂载点(如 tmpfs 或其他 bind),普通的
--bind不会映射这些子挂载点。需要使用--rbind才能完整镜像整个目录树。 -
持久化配置 :通过
mount --bind创建的映射在系统重启后会失效。若要永久生效,需将其写入/etc/fstab文件:text/data/source /mnt/target none bind 0 0
5. 在容器与 K8s 中的关键作用
Bind Mount 是现代容器化技术的基石:
- Docker 数据持久化:Docker 允许将宿主机的目录通过 Bind Mount 挂载到容器内部,实现数据的持久化和宿主机与容器的通信。
- Kubernetes 的 NodePublishVolume :正如我们之前探讨的,K8s 的 CSI 插件在执行
NodePublishVolume时,正是利用了 Linux 的 Bind Mount 技术,将节点上的"全局存储目录"无缝映射到了具体的 Pod 容器内部。
⚠️ 注意事项
- 卸载必须使用 umount :绝不能使用
rm -rf去删除目标目录,否则会直接删除源目录下的真实数据。必须先使用sudo umount /mnt/target解除绑定。 - 隐藏原有内容 :如果目标目录
/mnt/target原本就有文件,绑定挂载后这些文件会被"隐藏"(不可见),直到卸载绑定后才会重新显示。