公司动态

【12-kubenetes的持久化存储】

📅 2026/8/29 17:54:02
【12-kubenetes的持久化存储】
【11-kubenetes的持久化存储】一、核心理念我们把kubenetes集群想象成一个出租公寓楼Pod -- 租客人Node -- 公寓楼物理建筑容器内的数据 -- 租客脑子中的记忆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: # 容器Anginx往共享卷写文件 - name: writer image: nginx:1.25 volumeMounts: - name: shared-data # 引用 volumes 中定义的卷名 mountPath: /opt/data # 容器内的挂载路径 # readOnly: false # 默认就是 false可读写 # 容器Bredis从共享卷读文件 - 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绑定的所有条件满足才能绑定规则如下全部满足才能绑定storageClassName 相同 --- 必须是同一个书架accessModes 兼容 --- 你需要的能力必须满足PV 容量 PVC 请求的 --- 我有这么多才能给你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/Released3.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 30s4.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 创建PVCapiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-app-data spec: storageClassName: nfs-client # 指定 StorageClass accessModes: - ReadWriteMany # 多节点读写 resources: requests: storage: 10Gi4.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目录3apiVersion: 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 只认 PVCPVC 认 PV 所以 PV 叫什么都不重要PVC 的稳定性才是关键六、Volume Snapshot 快照备份---“给数据拍个快照”给磁盘PVC 拍一张照片记录当前的状态。之后随时可以用这张照片把磁盘恢复到拍照片那一个时刻。原始数据(PVC) ──拍快照──▶ 快照(VolumeSnapshot) ──恢复──▶ 新PVC6.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 ---限制单个PVCapiVersion: 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 必须有磁盘写满全跑路