news 2026/8/29 17:53:52

【12-kubenetes的持久化存储】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【12-kubenetes的持久化存储】

【11-kubenetes的持久化存储】

一、核心理念

我们把kubenetes集群想象成一个出租公寓楼:

  1. Pod --> 租客(人)

  2. Node --> 公寓楼(物理建筑)

  3. 容器内的数据 --> 租客脑子中的记忆

  4. Volume (存储卷) --> 租客能用的"写字的地方"

核心问题:Pod 是临时工,随时可能被杀死重建(就像租客随时可能搬走)。数据只存在Pod内,Pod一删,数据就没了。所以我们需要各种"外部笔记本"来保存数据。


二、存储类型对比

2.1 EmptyDir ---一次性的便签纸

本质:Pod里的临时共享空间

想想同一个房间内住着两人(nginx,redis),他们之间传递纸条:

Pod(一个房间) ├── nginx 容器(人A)→ 往 /opt 写纸条 ├── redis 容器(人B)→ 从 /mnt 看纸条 └── emptyDir(桌上的一叠纸)← 两个人共享
apiVersion: v1 kind: Pod metadata: name: disk-emptydir-demo spec: # ──────────── 两个容器共享一个 emptyDir containers: # 容器A:nginx,往共享卷写文件 - name: writer image: nginx:1.25 volumeMounts: - name: shared-data # 引用 volumes 中定义的卷名 mountPath: /opt/data # 容器内的挂载路径 # readOnly: false # 默认就是 false,可读写 # 容器B:redis,从共享卷读文件 - name: reader image: redis:7 volumeMounts: - name: shared-data # 同一个卷名 → 同一份存储 mountPath: /mnt/data # 容器B 用自己的路径挂载 # ──────────── 卷定义 ──────────── volumes: - name: shared-data emptyDir: medium: "" # 空字符串 = 使用磁盘(默认行为) sizeLimit: "100Mi" # 限制最大 100MiB

为什么 mountPath 不同,读到的数据却一样?

name 决定"接的是哪间仓库",mountPath 决定"在你家叫什么门牌号"。门牌号不同,进的是同一间仓库。 关键是 name 匹配: 容器A 说:我要挂载 name: note-paper 的卷 → 找到 emptyDir 容器B 说:我要挂载 name: note-paper 的卷 → 找到同一个 emptyDir name 相同 = 同一份存储 mountPath 不同 = 从各自内部看到的路径不同 在 Kubernetes 中,emptyDir 卷是由 **kubelet(节点级别的组件)** 来维护的,而非控制平面(API Server、Controller Manager 等)

卷定义关键参数:

volumes: - name: <string> # 必填:卷的名称,供容器引用 emptyDir: medium: <string> # 可选:"Memory" 或 ""(默认空=磁盘) sizeLimit: <quantity> # 可选:最大容量限制 #mainC使用 volumeMounts: - name: shared mountPath: /data #挂载点 readOnly: true #默认可读性,trune 只读
  • medium:“” --> 数据存磁盘,便宜,慢,不限内存额度。

  • medium:“Memory” -->数据存内存,贵,快,占内存额度。

  • sizeLimit: “X” --> 磁盘型是软限制(驱逐该 Pod 自身;如果导致节点磁盘压力,才可能触发节点级驱逐(按 QoS 排序驱逐,非全部),内存型是硬限制(报错);不设置定时炸弹,一个进程能瘫痪整个节点。

内存额度计算:

容器Memory limit = 256Mi emptyDir sizeLimit =64MI (从Memory limit里扣) 实际MainC 可用 约等于 256 - 64 = 192Mi **写的量会"占用"容器的内存额度!**

2.2 hostPath --- “借用房东的桌子”

本质:直接用节点(服务器)上的某个目录,数据跟着节点走,Pod被调度到别的节点就看不到原来的数据。

Node01(一栋楼) ├── /data 目录(房东的桌子) │ └── file1.txt │ ├── Pod A(租客)→ 挂载 /data → 看到 file1.txt ✓ └── Pod B(租客)→ 挂载 /data → 看到 file1.txt ✓ Node02(另一栋楼) └── /data 目录(另一张桌子) └── (空的) Pod C(租客)→ 挂载 /data → 什么都没有 ✗
volumes: - name: data hostPath: path: /data # 用节点上的 /data 目录 type: DirectoryOrCreate # 如果 /data 不存在,自动创建 #type: # DirectoryOrCreate ,目录没有就创建一个。 # Dirrectory ,没有就报错 # FileOrCreate,没有创建空文件 # File ,没有就报错

生产环境中不建议使用hostPath,因为Pod被调度到其他的节点就找不到数据了,除非是Daemonset日志采集这类场景。

2.3 NFS --- “公共图书馆的书架”

本质:所有人共享的网络存储。

NFS 服务器(图书馆,IP: 192.168.62.15) └── /data/nfs(公共书架) Node01 上的 Pod A → 写入 hello.txt Node02 上的 Pod B → 读到 hello.txt ✓✓✓ 这就是"跨节点共享"!

NFS搭建:

# 1. 装工具 yum install nfs-utils rpcbind -y # 2. 建共享目录 + 配置谁能访问 mkdir -p /data/nfs echo "/data/nfs 192.168.62.0/24(rw,sync,no_root_squash)" >> /etc/exports #exports 参数: 192.168.62.0/24 --> 谁可以范围,可网段/ip/域名 rw --> 可读可写 sync --> 写完立刻同步 no_subtree_check -->不检查子目录(性能优化) no_root_squash --> 客户端的root到服务端还是root(权限问题) insecure --> 允许1024以上端口连接 # 3. 生效 + 启动 exportfs -rav systemctl enable --now nfs-server rpcbind # 4. 节点客户端 yum install nfs-utils -y showmount -e 192.168.62.15 # 5. 放行防火墙,selinux.

Pod 挂载使用

apiVersion: v1 kind: Pod metadata: name: nfs-basic spec: nodeName: node01 # 指定跑在哪个节点(方便测试) containers: - name: app image: busybox command: - sh - -c - | echo "=== 我在 /data 里看到的文件 ===" ls -la /data/ echo "" echo "=== 写入一个新文件 ===" echo "hello from pod on $(hostname) at $(date)" > /data/from-pod.txt echo "写入完成" volumeMounts: - name: nfs-vol mountPath: /data # Pod 里的挂载点 volumes: - name: nfs-vol nfs: server: 192.168.62.15 # NFS 服务器 IP path: /data/nfs # NFS 服务器上的共享目录(必须已存在!) readOnly: false # 可读可写,true只能读

三、PV 和 PVC

核心矛盾点:Pod 被删除重建后,它之前写的数据就没了。需要一种机制,把存储从Pod中解耦出来,变成独立管理的资源。

本质:类图书借阅系统,职责分离。

管理员(运维) → 买书,放在书架上,登记入库 = 创建 PV 读者(开发) → 填借书单,说"我要一本数据结构" = 创建 PVC 图书管理系统 → 自动把借书单和书匹配起来 = K8s 的绑定机制 书架 → 实际的存储(NFS、云盘等)

3.1 PV (PersistentVolume) ---书架上的一本书

PV 是管理员提前准备好的"存储资源",入库操作:

apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs # 这本书叫 pv-nfs spec: capacity: storage: 5Gi # 这本书有 5GB accessModes: - ReadWriteMany # 多人可以同时翻阅(RWX) nfs: server: 192.168.62.15 path: /data/nfs/pvnfs storageClassName: nfs-class # 它属于"nfs-class"这个书架 persistentVolumeReclaimPolicy: Retain # 还书后不销毁(Retain)

关键理解:PV是集群级别资源,不属于任何namespace.

3.2 PVC(PersistentVolume) ---借书单

PVC 是用户说"我需要多大的存储":

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs # 借书单叫 pvc-nfs spec: storageClassName: nfs-class # 我要从"nfs-class"书架找 accessModes: - ReadWriteMany # 我需要多人同时读写 resources: requests: storage: 3Gi # 我要借3GB

Ⓜ️ accessModes :

不同存储支持不同模式

本地磁盘 hostPath ├── 磁盘插在一台机器上 ├── 其他机器物理上就访问不到 └── 所以只支持 RWO ✅ AWS EBS / 阿里云云盘 ├── 一块云盘同时只能挂载到一台机器 ├── (就像移动硬盘,一次只能插一台电脑) └── 支持 RWO ✅ 和 RWOP ✅,不支持多节点 NFS ├── 网络文件系统,天然支持多台机器同时挂载 ├── 可以设置读写或只读权限 └── 支持 RWO ✅ ROX ✅ RWX ✅ CephFS ├── 分布式文件系统,功能最全 ├── 多节点读写没问题,还支持单 Pod 独占 └── 全部支持 ✅✅✅✅

不同的阅读权限,适配不同的场景。

全称缩写含义场景
ReadWriteOnceRWO节点级,只能被一个节点以读写模式挂载单节点数据库
ReadOnlyManyROX多节点级,可以被多个节点以只读模式挂载共享配置文件、证书、静态资源
ReadWriteManyRWX多节点级,可以被多个节点以读写模式挂载共享日志目录、多人文件上传、NFS共享存储
ReadWriteOncePodRWOPPod 级,只能被一个Pod以读写模式挂载严格的隔离存储需求,防止同节点多个Pod 意外共享。

PV 的能力必须 >= PVC 的要求

3.3 Pod 使用PVC ---读者拿到书开始使用

apiVersion: v1 kind: Pod metadata: name: my-app spec: containers: - name: app image: nginx volumeMounts: - mountPath: /data # 在容器里挂到 /data name: my-storage volumes: - name: my-storage persistentVolumeClaim: claimName: pvc-nfs # 引用借书单(PVC)

完整流程:管理员备书--> 读者写借书单---> 读者用书

3.4 绑定规则--- “图书管理系统的匹配逻辑”

PV和PVC绑定的所有条件满足才能绑定,规则如下(全部满足才能绑定):

  1. storageClassName 相同 ---> 必须是同一个书架

  2. accessModes 兼容 ---> 你需要的能力必须满足

  3. PV 容量 >= PVC 请求的 ---> 我有这么多才能给你

  4. PV 状态是 Available --> 没被借走

注意:PV 和 PVC 是 1:1 独占绑定。 一个 PV 绑了这个 PVC,就不会再分给别人。所以不存在"切割"的场景。3Gi 是下限要求,不是上限限制。绑上之后,PV 有多少你就用多少。

筛选条件(全部满足) ├── 1. storage ClassName 相同 ├── 2. accessModes 兼容 ├── 3. PV.capacity >= PVC.request └── 4. PV.status == Available 优选规则(从通过筛选的里挑) └── 候选中选容量最小的 绑定时机 ├── Immediate:立刻绑 └── WaitForFirstConsumer:等 Pod 出现再绑

3.5 回收策略---“还书后怎么办”

生产环境中必须使用Retain!,默认是Delete,PVC一删,底层数据就没了,不可恢复。

persistentVolumeReclaimPolicy: Retain Retain (保留) -> 还书,管理员处理 Delete (删除) -> 还书,之间销毁

3.6 PV的生命周期

Released ---> Available 这一步不是自动的,需要管理员:

# 1. 查看 PV 状态 kubectl get pv pv-nfs# 2. 编辑 PV,清除绑定引用 kubectl edit pv pv-nfs # 删除 spec.claimRef 字段段,绑定后自动生成字段。 # PV 就会回到 Available 状态 status.phase: Available/Bound/Released

3.7 PVC 一直Pending的原因

就像借书单一直没人处理: 1. 书架上没有合适的书(没有匹配的 PV) 2. 你说要"科幻类"(storageClassName),但书架上只有"历史类" 3. 你要能多人翻阅(RWX),但所有书都只允许一人借(RWO) 4. 书都被借走了(没有 Available 的 PV)

四、动态存储---“自动买书机器人”

核心矛盾点:手动创建PV太麻烦了(每本书都要管理员登记)。

本质:动态存储就是你说你要什么书,机器人自己去采购。

流程对比:

手动存储(Static): 管理员创建 PV → 开发创建 PVC → K8s 绑定 → Pod 使用 (就像:图书馆先买好书 → 你来借) 动态存储(Dynamic): 管理员配置 StorageClass → 开发创建 PVC → 机器人自动创建 PV 并绑定 → Pod 使用 (就像:图书馆配了自动购书机 → 你填借书单 → 机器人自动去买书并给你)

4.1 nfs-provisioner---“买书机器人”

当你创建一个PVC要求(要16G的NFS存储)---> nfs-provisioner监听到这个请求 ---> 自动在NFS 服务器上创建子目录并且自动创建对应的PV ---> 自动绑定PVC 和 PV ---> Pod 就可以使用了。

部署nfs-provisioner :

# nfs-provisioner 需要一个真正的 NFS 服务器作为后端。 # 确认 NFS 服务器可达 showmount -e 192.168.62.15 # 创建命名空间(可选) kubectl create namespace storage # 部署 RBAC ,Provisioner需要权限去创建/删除 PV、监听 PVC 事件。 kubectl apply -f rbac.yaml # 部署 provisioner kubectl apply -f deployment.yaml # 确认 provisioner 运行 kubectl get pods -n kube-system | grep nfs # nfs-provisioner-xxxxx 1/1 Running 0 30s

4.2 StorageClass 即购书规则

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client annotations: # 设为默认 StorageClass storageclass.kubernetes.io/is-default-class: "true" # 谁来干活,名字必须和 provisioner 里 PROVISIONER_NAME 一致 provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 删 PVC 时自动删除 PV(数据也会删!生产环境建议改成 Retain) reclaimPolicy: Delete # 立即绑定(生产环境推荐 WaitForFirstConsumer) volumeBindingMode: Immediate # 允许 PVC 扩容 allowVolumeExpansion: true parameters: # 删 PVC 时不真删,改名为 archived-xxx archiveOnDelete: "true"

关键字段翻译:

provisioner → 谁来干活(指定用哪个机器人) reclaimPolicy → 删 PVC 时 PV 怎么处理 volumeBindingMode: - Immediate → PVC 创建立刻绑定 PV(不管 Pod 在哪) - WaitForFirstConsumer → 等 Pod 确定调度到哪个节点后再绑定(推荐生产用)

4.3 创建PVC

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-app-data spec: storageClassName: nfs-client # 指定 StorageClass accessModes: - ReadWriteMany # 多节点读写 resources: requests: storage: 10Gi

4.4 Pod使用

apiVersion: v1 kind: Pod metadata: name: test-app spec: containers: - name: app image: nginx volumeMounts: - mountPath: /usr/share/nginx/html name: web-data volumes: - name: web-data persistentVolumeClaim: claimName: my-app-data

五、StatefulSet + volume Claim Templates --- “每人一个独立保险箱”

问题场景: deployment部署3个Mysqal 副本,如果共用一个PVC:

MySQL-0 ─┐ MySQL-1 ─┼──→ 共用同一个 PVC → 数据互相覆盖!灾难! MySQL-2 ─┘

使用statefulset 控制器部署有状态应用

MySQL-0 ──→ PVC: mysql-data-mysql-stateful-0 ──→ NFS目录1 MySQL-1 ──→ PVC: mysql-data-mysql-stateful-1 ──→ NFS目录2 MySQL-2 ──→ PVC: mysql-data-mysql-stateful-2 ──→ NFS目录3
apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: serviceName: mysql # 关联的 Headless Service 名字 replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD value: "my-secret-pw" volumeMounts: - mountPath: /var/lib/mysql # 容器内挂载路径 name: data # 对应下面的 volume 名字 volumeClaimTemplates: # PVC 模板定义 - metadata: name: data # PVC 名字的前缀 spec: accessModes: - ReadWriteOnce # 数据库用 RWO storageClassName: nfs-client # 使用的 StorageClass resources: requests: storage: 10Gi # 每个副本请求 10Gi

这样每个Pod都有自己稳定的PVC ,互不影响,别忘了Headless Service服务来给我们的Pod 提供一个稳定的DNS 名字。

StatefulSet 缩容或者删除时,PVC 和里面的数据保留---这是故意设计的,防止误删数据。要清理数据得手动删除PVC。

kubectl delete pvc mysql-data-mysql-stateful-2 PVC 名 = 模板名-StatefulSet名-序号 → 稳定可预测 PV 名 = pvc-随机UID → 系统生成,不用关心 Pod 只认 PVC,PVC 认 PV 所以 PV 叫什么都不重要,PVC 的稳定性才是关键

六、Volume Snapshot 快照备份---“给数据拍个快照”

给磁盘PVC 拍一张"照片",记录当前的状态。之后随时可以用这张照片,把磁盘恢复到拍照片那一个时刻。

原始数据(PVC) ──拍快照──▶ 快照(VolumeSnapshot) ──恢复──▶ 新PVC

6.1 创建快照

apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: db-snapshot-2025-04-12 spec: volumeSnapshotClassName: csi-snapclass #指定快照的"规格" source: persistentVolumeClaimName:>6.2从快照恢复
apiVersion: v1 kind: PersistentVolumeClaim # 注意:恢复出来的是 PVC,不是新的快照 metadata: name:>七、fsGroup 与文件权限---“门禁卡”

核心矛盾点:容器以非 root 运行(readOnlyRootFilesystem: true)时,挂载的 PVC 可能因为卷的权限和容器用户不匹配而写不进去

securityContext: runAsUser: 10000 runAsGroup: 10000 fsGroup: 10000 # 给卷加一个"门禁卡",让 Pod 能写进去 seccompProfile: type: RuntimeDefault

八、多租户存储配额---“每人限额”

核心矛盾点:集群里不能随便使用存储,得设置上限。

团队 A:一口气申请了 500 个 PVC,把存储全占了 团队 B:一个 PVC 申请了 10TB,完全用不上 团队 C:啥也申请不到,PVC 一直 Pending

所以得设置两种限制策略:**单个PVC的限制 **和整个团队的总配额

8.1 LimitRange ---限制单个PVC

apiVersion: v1 kind: LimitRange metadata: name: tenant-limits namespace: team-alpha spec: limits: - type: PersistentVolumeClaim max: storage: 50Gi # 最大不能超过 50Gi min: storage: 1Gi # 最小不能低于 1Gi

作用:单笔订单限额,kubenetes 自动校验,超出范围直接拒绝创建。

8.2 ResourceQuota --- 限制整个命名空间的总量

apiVersion: v1 kind: ResourceQuota metadata: name: team-quota namespace: team-alpha spec: hard: requests.storage: 200Gi # 所有 PVC 加起来最多 200Gi persistentvolumeclaims: "10" # 最多创建 10 个 PVC

作用:限制团队的总额和PVC总数。


两者缺一不可:

  • 只有 ResourceQuota 没有 LimitRange → 用户可以申请一个 190Gi 的巨型 PVC,一个人占掉几乎全部配额.

  • 只有 LimitRange 没有 ResourceQuota → 每个 PVC 不超过 50Gi,但用户可以申请 100 个小的,总量还是爆炸.

九、速记

EmptyDir 便签纸,Pod 一删就消失 hostPath 房东桌,换节点就找不到 NFS 共享图书馆,跨节点都能读 PV 是书架上书,PVC 是借书单 StorageClass 购书机,动态创建不用愁 StatefulSet 保险箱,每人一个 PVC Retain 保留数据,Delete 销毁不可逆 fsGroup 门禁卡,非 root 也能写 sizeLimit 必须有,磁盘写满全跑路
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 17:50:37

知识蒸馏原理与PyTorch实战:避开过度蒸馏的陷阱

最近两三年&#xff0c;“蒸馏”这个词被反复提到&#xff0c;甚至有几分被妖魔化的味道。模型体积大了要蒸馏&#xff0c;边缘设备部署要蒸馏&#xff0c;训练数据不够要蒸馏&#xff1b;更夸张的是&#xff0c;在一些社区里还能看到“把一本书蒸馏成知识库”“把某个 skill 蒸…

作者头像 李华
网站建设 2026/8/29 17:49:25

CVTE秋招面试全攻略:从技术原理到实战策略的深度复盘

1. 项目概述&#xff1a;一次秋招的深度复盘与策略拆解 又到了一年秋招季&#xff0c;后台和社群里关于“面经”的讨论又热了起来。最近看到不少同学在整理“CVTE面经”&#xff0c;这让我想起了几年前自己亲身经历的那场“战役”。CVTE&#xff0c;也就是广州视源股份&#xf…

作者头像 李华
网站建设 2026/8/29 17:44:27

免费查ai率去哪里才可靠?AIGC检测、AI降重和论文查重入口区别

免费查ai率去哪里才可靠&#xff1f;AIGC检测、AI降重和论文查重入口区别 你现在要做什么应进入的入口能得到什么不能代替什么确认学校最终结果学校论文系统或学校指定检测入口学校采用平台的AIGC报告不能默认免费&#xff0c;也不负责改论文修改前免费自查检测平台公开自查页…

作者头像 李华
网站建设 2026/8/29 17:41:48

迅雷AI工程师笔试复盘:核心考点与答题策略

2018年秋招季&#xff0c;我投了迅雷的AI工程师岗位。当时在线笔试用的是第三方评测平台&#xff0c;限时90分钟&#xff0c;分选择题、简答题和两道编程题。说实话&#xff0c;那年头AI岗的笔试还没有现在这么“卷”&#xff0c;但迅雷的卷子考察面挺综合的&#xff0c;既考机…

作者头像 李华
网站建设 2026/8/29 17:41:35

基于SpringBoot的救援物资管理系统(毕设源码+文档)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/8/29 17:41:07

本地开源大模型实战:社交文本情感识别与意图拆解全流程

“今晚见吗❓这是个坏主意对吗&#x1faea;”——这种社交文本几乎每天都出现在聊天窗口里&#xff1a;表面是一个邀请&#xff0c;背后却混杂着试探、犹豫、退缩和期待。过去这类语义只能靠人脑体会&#xff0c;现在本地开源大模型完全可以做这件事。本文不讨论约会话术&…

作者头像 李华

关于博客

这是一个专注于编程技术分享的极简博客,旨在为开发者提供高质量的技术文章和教程。

订阅更新

输入您的邮箱,获取最新文章更新。

© 2025 极简编程博客. 保留所有权利.