ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

K8s存储从入门到实战:Volume、PV/PVC与NFS共享存储详解

K8s存储从入门到实战:Volume、PV/PVC与NFS共享存储详解 做运维这些年被问到最多的问题之一就是容器删了数据怎么办。K8s 里所有工作负载实际上都暗含一个前提应用可以随时重建日志、配置、缓存状态都可以丢唯独数据不行。从 Volume 到 PersistentVolume再到 NFS 共享存储这一整套链路几乎每个生产集群都躲不开。这一篇我打算把 K8s 存储管理讲透包括最基础的 Volume 类型、PV/PVC 的设计思路以及最常用的 NFS 共享存储完整实战——从服务端搭建、PV/PVC 绑定、业务读写验证到问题排查全部走一遍顺便把文档里不写的坑也一并交底。适合谁看刚把集群装起来还不清楚存储怎么接的同学在 emptyDir、hostPath、PV/PVC 之间犹豫不决的开发者以及想给开发测试环境配一个统一共享存储目录的运维这篇文章都能直接当手册用。不需要你有很深的存储基础只要会敲 kubectl 命令跟着往下走就能跑通。1. 先搞清楚K8s 里为什么需要 Volume1.1 容器文件系统的天然缺陷开始动手之前得先把根本问题讲清楚为什么 K8s 需要一套这么复杂的存储方案答案不在 K8s而在容器的生命周期。Pod 里的容器运行在镜像产生的可写层上这个层跟着容器走容器一删写入的数据基本就没有了。更要命的是K8s 的调度逻辑决定了 Pod 可能在任何节点上重建——今天跑在 node1明天可能就被调度到 node3。如果数据只存在某个节点本地重建之后连文件在哪台机器上都不确定。这里有一个很经典的教训很多人第一次部署有状态应用时随手就把数据写到容器目录里比如 MySQL 的 /var/lib/mysql看起来一切正常。某天发布新版本触发滚动更新Pod 被重建数据库瞬间变成空库。这个坑几乎每个人都踩过而且踩完才真正理解容器不负责持久化这句话的分量。Volume 就是解决这个问题的。它的本质很简单把 Pod 里的某个目录和某种存储后端建立关联这个存储后端可以是一台宿主机上的目录、一块云盘、一个 NFS 共享目录也可以是分布式存储集群。数据写到 Volume 里Pod 删除重建之后卷还在数据就还在。理解这一点后面所有的概念都是在这个基础上做扩展。1.2 常用 Volume 类型与适用场景K8s 的 Volume 类型很多但日常真正用得上的就几类先把它们的定性和边界说清楚免得用错场景。emptyDir 是最简单的卷类型Pod 创建时分配一个空目录Pod 存活期间一直存在。它有两个典型用途一个是同一个 Pod 里多个容器共享文件比如日志采集 Sidecar 容器读主容器写入的日志靠 emptyDir 做中转另一个是放缓存和临时计算数据。注意 emptyDir 的生命周期跟 Pod 绑定Pod 被删除目录就清空所以它只适合放丢了也无所谓的数据。hostPath 直接把宿主机上的目录挂进容器。单机测试或者需要访问宿主机文件的场景比如采集系统日志很顺手但多节点集群里用 hostPath 就要格外小心Pod 被调度到哪台节点访问的就是哪台节点的目录。同一个配置在 node1 上有文件调度到 node2 上可能什么都没有。它适合做节点级的临时存储不适合做跨节点的共享存储更不适合存数据库文件。configMap 和 secret 也可以作为 Volume 挂载。这种方式把配置以文件形式暴露给容器很多应用不用重新打镜像就能改配置靠的就是挂载文件后监听变更并重载。另外像云厂商的云盘卷AWS EBS、阿里云云盘等依赖各自 CSI 插件用法大同小异但涉及云账号权限是另一个话题。1.3 emptyDir 和 hostPath 快速实操空讲不好理解动手验证一遍。先看 emptyDir 的 yaml——下面的例子声明了一个 Pod里面跑两个容器共用同一个 emptyDir 卷。writer 容器往里写文件reader 容器在同一 Pod 里直接读两个容器之间没有任何网络调用apiVersion: v1 kind: Pod metadata: name: emptydir-demo spec: containers: - name: writer image: busybox command: [sh, -c, echo hello storage /data/test.txt sleep 3600] volumeMounts: - name: shared-data mountPath: /data - name: reader image: busybox command: [sh, -c, sleep 3600] volumeMounts: - name: shared-data mountPath: /read volumes: - name: shared-data emptyDir: {}writer 容器把文件写进 /datareader 容器从 /read 里直接能读到这就是 emptyDir 最常见的用法。如果没有卷机制同 Pod 的容器之间想共享文件只能走网络协议复杂度高得多。接下来把这个 Pod 删掉再重新 apply进入 reader 查看 /read/test.txt大概率已经不存在了——emptyDir 的宿命就是这样Pod 不在数据就没了。hostPath 的 yaml 长这样这里我特意加了 nodeName 把 Pod 钉死在 node01 上避免调度漂移带来的混乱apiVersion: v1 kind: Pod metadata: name: hostpath-demo spec: nodeName: node01 containers: - name: main image: nginx volumeMounts: - name: host-log mountPath: /usr/share/nginx/html volumes: - name: host-log hostPath: path: /opt/static type: DirectoryOrCreate挂上之后往宿主机 /opt/static 目录里丢文件nginx 直接就能访问。我在生产环境里见过用 hostPath 做服务目录的全都得加上节点亲和性限制否则 Pod 一漂移目录对不上故障就来了。所以我的建议是hostPath 用在单机验证和特殊场景能不用尽量不用。1.4 用 Volume 时必须避开的坑说完用法补充几个血泪经验。第一个坑是权限。emptyDir 和 hostPath 目录的属主、属组、权限位直接决定了容器内用户能不能读写。容器里跑的是非 root 用户而宿主机目录所有者是 root那基本上就是 Permission denied。解决办法是提前规划 uid/gid或者设置好目录权限位不要指望 K8s 帮你自动 chmod它不会。第二个坑是容量。Volume 级别默认没有配额概念emptyDir 默认不限制大小。一个失控的应用写满宿主机磁盘整个节点都可能被拖挂。K8s 从 1.20 开始 emptyDir 支持 sizeLimit建议声明时顺手加上emptyDir: { sizeLimit: 1Gi }成本几乎为零能挡掉一大半磁盘打满的事故。第三个坑是 hostPath 的 type 字段。如果不写 typeK8s 默认按 DirectoryOrCreate 处理目录不存在会自动创建。这个行为在单机环境很友好但在多节点环境下每台节点都会悄悄自动创建目录很容易掩盖配置错误。明确场景时建议指定 Directory 或 File让 K8s 提前校验路径是否真实存在而不是打太极。2. 从 Volume 到 PV/PVCK8s 存储抽象是怎么一步步设计的2.1 为什么不能直接把存储路径写死在 Pod 里如果存储只是挂个目录这么简单Volume 就够用了。但实际用起来很快会发现应用开发者根本不应该关心存储底层长什么样。开发说我要 10G 存储运维如果直接告诉他这是 NFS 路径 /data/xxx那是 Ceph 的 pool 名两边都痛苦耦合也太重。PVPersistentVolume和 PVCPersistentVolumeClaim就是为了解开这层耦合。一句话概括PV 是集群里的存储资源由管理员创建相当于仓库里备好的货PVC 是应用提出的存储需求相当于采购单。开发者只需要写清楚需要多大容量、什么访问模式K8s 负责把合适的 PV 和 PVC 绑到一起。存储底层的差异被完全隐藏这就是 K8s 存储抽象的核心价值。打个比方PV 是出租房源PVC 是你的租房申请。你只需要告诉中介我要两居室、朝南中介自动帮你匹配房源你不需要直接联系房东也不需要关心房子是砖混结构还是钢结构。这就是声明式思维和命令式操作的本质区别。2.2 PV、PVC 和 StorageClass 的三方关系三者关系里有几个关键点必须说透。StorageClass 是动态供给的入口。没有 StorageClass 时PV 必须事先手工建好PVC 再去匹配这叫静态供给。有了 StorageClassPVC 可以直接声明 storageClassNameK8s 会调用对应的 Provisioner 自动创建 PV这叫动态供给。内部机制是PVC 提交后Provisioner 收到请求去后端云盘、NFS、Ceph 等创建一个真正的存储卷包装成 PV 完成绑定全程不需要人工干预。StorageClass 里有两个字段值得特别留意reclaimPolicy回收策略和 allowVolumeExpansion是否允许扩容。nfs-subdir-external-provisioner 这类社区方案会在集群里生成默认 StorageClass很多新手发现 PVC 只写了两三行 yaml 就能自动绑上 PV其实就是动态供给在背后工作而不是什么魔法。2.3 访问模式与回收策略PV/PVC 绑定之前K8s 需要判断二者是否匹配匹配的核心条件就两个容量足够访问模式一致。访问模式决定一个卷能被多少个节点以什么方式挂载。ReadWriteOnceRWO表示卷只能被一个节点读写同一个节点上的多个 Pod 可以共享跨节点不行云盘基本都是这种。ReadOnlyManyROX表示卷可以被多个节点同时只读挂载适合共享配置和公共数据。ReadWriteManyRWX表示卷可以被多个节点同时读写NFS、CephFS 这类共享文件系统才支持。多个应用实例需要写同一个目录时只能选 RWX。回收策略决定 PV 释放后的命运。Retain 是保留数据等人来处理Delete 是自动删除后端存储Recycle 已经被弃用。生产环境里重要的 PV 我建议一律用 Retain后端数据删了就再也找不回来了宁可留在那里让你手动确认也不能让程序自动销毁。这里有个常见的困惑PVC 删除后PV 并不会自动删除。除非 StorageClass 配置了 Delete 策略且 PV 是动态创建的否则 PV 会继续存在只是状态变成 Released。很多人以为删了 PVC 数据就清空了这是大错特错。2.4 PV 的生命周期从 Available 到 ReleasedPV 的状态流转看起来抽象实际就是四态Available空闲等待 PVC、Bound已绑定、ReleasedPVC 删除后释放但仍保留数据、Failed回收失败。把整个生命周期走一遍就完全懂了。管理员创建 PV状态是 Available。开发者创建 PVC匹配成功后状态变 Bound。Pod 通过 PVC 使用存储。之后如果 PVC 被删除PV 并不会凭空消失而是进入 Released 状态数据还在但不能再被新的 PVC 绑定因为 PV 的 claimRef 还指向旧 PVC 的命名空间和名称。要让这个 PV 重新可用需要人工清理 claimRef或者干脆走动态供给创建全新的 PV。我见过不少人在PV 卡在 Released的问题上耗了大半天本质就是没理解这条状态链路。后面的实战部分我会演示怎么处理这种僵尸 PV先把这个状态模型记住排障时思路就顺了。3. 环境准备手把手搭一个 NFS 共享存储3.1 为什么实战首选 NFS先回答一个问题存储方案这么多为什么实战教程首选 NFS原因其实很务实。第一NFS 部署简单一条命令装包、几行配置就能跑起来不依赖额外的控制组件也没有复杂的运维门槛第二它天然支持 RWX 访问模式多个节点可以同时挂载读写对理解共享存储这个概念非常直观第三学习成本低底层就是 Linux 网络文件系统遇到问题搜nfs 怎么开启nfs 挂载失败参考资料一抓一大把。当然NFS 的定位是够用而不是最强。单点是它最大的问题——服务端挂了所有挂载它的 Pod 都会受影响性能上走网络协议跟本地盘没法比高级能力如快照、克隆也基本没有。所以我的建议很明确开发测试环境、中小规模集群、需要共享读写的场景NFS 完全够用生产核心数据库这类对性能和可靠性要求极高的业务再考虑 Ceph RBD、云盘 CSI 等方案。这篇先把 NFS 玩明白原理通了换其他存储后端技术方案也只是套模板的事。3.2 服务端安装与目录规划下面在一台 NFS 服务端假设 IP 是 192.168.10.10和 K8s 节点之间完成搭建。NFS 服务端也可以是 K8s 节点本身这完全没问题很多实验环境就是这么干的。一个前提条件必须满足K8s 集群本身要健康api-server、kubelet 都正常否则后面 PVC 卡 Pending 时你会分不清是存储问题还是集群问题。服务端如果是 Ubuntu/Debian安装命令是apt update apt install -y nfs-kernel-serverCentOS/Rocky 系则是yum install -y nfs-utils安装完成后创建共享目录。规矩是必须为共享数据单独划分目录不要随手把根目录下某个零散目录共享出去否则后续做权限控制和容量管理都很被动mkdir -p /data/k8s-storage chmod -R 777 /data/k8s-storage这里 chmod 777 是为了测试方便生产环境不建议这样用更合理的做法是设置专用 uid/gid然后让容器通过安全上下文去匹配。权限的问题后面专门说先让环境跑通。3.3 exports 配置与参数说明共享配置的核心文件是 /etc/exports在文件里追加一行/data/k8s-storage 192.168.10.0/24(rw,sync,no_root_squash,no_subtree_check)逐个说明参数的含义。rw 允许读写挂载sync 表示数据同步写入磁盘后再响应客户端防止数据丢失默认建议保留no_root_squash 是关键——不加这个参数客户端以 root 写入的文件会被映射成匿名用户 nobody容器里以 root 写入的数据在服务端一看属主全变了后面会遇到各种诡异的权限问题。测试环境建议加生产环境要慎重因为它意味着客户端 root 能直接以 root 身份操作共享目录。配置完成后需要执行 exportfs -r 让新配置立即生效同时重启服务确保下次开机也能正常加载。然后用 showmount 查看当前导出的共享列表重点确认共享路径和允许网段是否和我们预期一致exportfs -r systemctl restart nfs-kernel-server showmount -e localhost正常情况下服务端会返回类似下面的内容路径和网段都对得上说明这一步完成Export list for localhost: /data/k8s-storage 192.168.10.0/24如果实际输出和预期不符优先检查 /etc/exports 的语法和网段写法。网段写错一位或者漏了括号里的参数都会导致客户端挂载时找不到导出项。确认服务端没问题后再进入下一步。3.4 客户端验证挂载K8s 节点侧需要安装 NFS 客户端工具。Debian 系装 nfs-commonRedHat 系装 nfs-utilsapt install -y nfs-common # debian/ubuntu yum install -y nfs-utils # centos/rocky装完之后先手工挂载一次验证网络和权限都通避免一上来就进 K8s 排查叠加太多变量不好定位mkdir -p /mnt/nfs-test mount -t nfs 192.168.10.10:/data/k8s-storage /mnt/nfs-test touch /mnt/nfs-test/hello.txt umount /mnt/nfs-test echo nfs check ok能看到 nfs check ok 的输出说明协议、端口、权限全部正常。NFS 使用的主要端口是 2049但还会涉及 mountd、rpcbind 等动态端口。如果客户端怎么都挂不上优先检查防火墙是否放行了 nfs、rpc-bind、mountd 这几个服务或者直接把相关端口固定下来再统一放行。这是新手特别容易忽略的点我见过太多装好了但挂不上的案例最后都是防火墙拦截。4. 核心实战创建 NFS 类型 PV、PVC 并跑通业务4.1 编写 PV 声明NFS 服务端就绪后回到 K8s 里干活。第一步是编写 PV。这一步建议单独用文件保存不要图省事直接复制粘贴到命令行里yaml 文件后续要反复查看和审计留档很重要。先看 PV 的完整声明apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv-001 labels: app: k8s-storage type: nfs spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: server: 192.168.10.10 path: /data/k8s-storage创建完成后第一时间查看 PV 状态确认它进入 Available可用而不是其它异常状态kubectl apply -f nfs-pv.yaml kubectl get pv预期输出是 PV 显示 AvailableNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv-001 5Gi RWX Retain Available这里有几个关键点必须说明。capacity 里的 5Gi 对 NFS 来说只是一个逻辑容量NFS 本身不会做配额限制这个值只给调度器做绑定判断真正能写多少取决于服务端磁盘大小。accessModes 写 ReadWriteMany因为 NFS 支持多节点同时读写。reclaimPolicy 选 Retain原因前面说过——NFS 后端没有自动删除的能力策略写 Delete 反而会造成误导仿佛删了 PVC 数据就没了实际上并没有。yaml 里的 labels 不是必须的但配合下面的选择器绑定会很灵活。4.2 创建 PVC 并观察绑定过程PVC 声明非常简单应用开发者只需要关心容量和访问模式完全不涉及存储后端的细节这正是抽象的价值所在apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc-001 spec: accessModes: - ReadWriteMany resources: requests: storage: 5Giapply 之后先看 PVC 状态再看 PV 状态变化两条命令配合起来才能看出完整链路kubectl apply -f nfs-pvc.yaml kubectl get pvc kubectl get pv正常情况下PVC 从 Pending 变为 BoundPV 从 Available 变为 Bound。绑定过程发生在瞬间直接看结果即可。匹配依据前面说过容量够访问模式一致二选一不满足都不可能绑定。这里有一个非常重要的经验如果 PVC 没有显式指定 storageClassName它会尝试使用集群的默认 StorageClass。在新版 K8s 或一些发行版里默认 StorageClass 是存在的比如云厂商的云盘 CSI、Rancher 的 local-path。如果集群里存在默认动态供给PVC 很可能不会绑定我的 NFS PV而是直接动态创建一个新 PV造成抢单现象。排查时先看这两条kubectl get storageclass kubectl get pvc nfs-pvc-001 -o yaml如果只想要静态绑定就在 PVC 里加一行storageClassName: 显式表示不使用默认 StorageClass强制匹配手工创建的 PV。这个细节很多人不知道我在这上面实实在在吃过亏写出来提醒一下。4.3 部署测试 Pod 验证数据读写PV/PVC 绑定只是准备工作真正要验证的是业务能不能读写。先用一个 busybox Pod 做冒烟测试往 PVC 对应的目录里写一个文件apiVersion: v1 kind: Pod metadata: name: nfs-writer spec: containers: - name: writer image: busybox command: [sh, -c, echo hello k8s nfs /mnt/data/test.txt sleep 3600] volumeMounts: - name: share mountPath: /mnt/data volumes: - name: share persistentVolumeClaim: claimName: nfs-pvc-001执行下面的命令创建 Pod然后到 NFS 服务端看文件是否真的出现kubectl apply -f nfs-writer.yaml kubectl get pod -o wide # 等 Pod Running 后到 NFS 服务端执行 ls -l /data/k8s-storage/正常的话服务端目录下能看到 test.txt内容正确。到这里已经能证明一件事Pod 里写的文件最终落在 NFS 服务端的磁盘上。但这还不够真正的核心价值在于应用重建后数据不丢。接下来把 writer Pod 删除换一个全新的 reader Pod 挂同一个 PVC读刚才的文件apiVersion: v1 kind: Pod metadata: name: nfs-reader spec: containers: - name: reader image: busybox command: [sh, -c, cat /mnt/data/test.txt sleep 3600] volumeMounts: - name: share mountPath: /mnt/data volumes: - name: share persistentVolumeClaim: claimName: nfs-pvc-001创建后执行kubectl apply -f nfs-reader.yaml kubectl logs nfs-reader能看到 hello k8s nfs 的输出整套链路就算真正打通了。这个过程模拟了生产环境里最常见的场景应用发布、Pod 重建、数据完好无损。4.4 多 Pod 共享与 StatefulSet 场景验证RWX 的核心价值是多个 Pod 同时读写同一个目录。为了眼见为实可以用 Deployment 部署两个副本让每个副本启动时把自己的主机名追加到共享文件里apiVersion: apps/v1 kind: Deployment metadata: name: nfs-share-demo spec: replicas: 2 selector: matchLabels: app: nfs-share-demo template: metadata: labels: app: nfs-share-demo spec: containers: - name: appender image: busybox command: [sh, -c, echo $(hostname) /mnt/data/pods.txt sleep 3600] volumeMounts: - name: share mountPath: /mnt/data volumes: - name: share persistentVolumeClaim: claimName: nfs-pvc-001两个副本运行后去 NFS 服务端查看 pods.txt里面应该有两行不同的主机名。这证明两个跨节点的 Pod 确实在共享存储上协作而不是各自写各自的本地盘。这个特性对有状态应用非常重要日志收集场景中多个服务实例把日志写到同一共享目录由采集组件统一处理文件上传服务中多个实例共享同一个上传目录用户请求落到任意节点都能看到同一个文件。不过要提醒一句RWX 只解决能不能同时读写不解决并发写同一文件时的冲突问题。两个应用同时写同一个文件的同一个位置仍然会数据错乱这是应用层的并发控制问题K8s 不背这个锅。数据库这类随机读写频繁的应用也不太适合直接跑在 NFS 上NFS 更适合日志、静态文件、备份、上传目录这类顺序读写的场景。5. 踩坑实录存储相关常见问题与排查思路任何一个存储方案跑通只是开始坑都在后面。下面把我在实际环境里遇到频率最高的几个问题整理成清单每个都附上排查思路。5.1 PVC 一直 PendingPVC 卡在 Pending九成是匹配不上 PV或者动态供给没跑通。排查的第一个动作永远是看事件kubectl describe pvc nfs-pvc-001describe 输出里的 Events 字段会直接告诉你原因常见的就几类no persistent volumes available for this claim and no storage class is set集群里没有可匹配的静态 PV同时也没有默认 StorageClass。检查 PV 是否创建成功容量和访问模式是否匹配。waiting for a volume to be created, either by external provisioner or by manually creating a PVPVC 指定了 storageClassName但 Provisioner 没有正常工作。常见原因是外部 Provisioner 的 Pod 没就绪或者 NFS 服务端网络不通。storageclass not foundPVC 引用的 StorageClass 名称不存在检查拼写。经验总结Pending 问题八成因 yaml 细节导致比如访问模式写成了 ReadWriteOnce而 PV 是 RWX永远匹配不上。所以第一个动作永远是 describe而不是凭感觉去猜。5.2 Pod 挂载失败与权限 deniedPod 创建后一直停在 ContainerCreating看事件一般是两类问题。第一类是网络和协议问题报错类似 mount failed: 输入输出错误或者 no route to host。先确认 K8s 节点到 NFS 服务端的网络是否通用 ping 和 showmount -e 验证。再看防火墙。还有一种隐蔽原因NFS 服务端重启后客户端旧的挂载点状态异常此时重启 kubelet 或者重启节点通常能解决。第二类是权限问题报错就是 Permission denied。这基本是 root_squash 和目录属主的问题。测试环境在 exports 里加 no_root_squash 可以规避大部分场景但生产环境如果不加就需要在 Pod 的 securityContext 里指定 fsGroup。K8s 会把挂载目录的属组纠正为对应 gid容器内进程才能正常写入。另外如果报错是 wrong fs type, bad option, bad superblock说明节点上根本没装 nfs-common/nfs-utilskubelet 执行 mount 时找不到客户端工具装上并重启 kubelet 即可。这里有个细节值得记住Pod 失败时不要只盯 describe还要看 kubelet 日志。很多 RPC 错误、字段错误在事件里被截断但 kubelet 日志里有完整输出。日志位置一般在 /var/log/messages 或 journalctl -u kubelet -f。5.3 PV 卡在 Released 的处理方法这是最典型的僵尸 PV问题。PVC 删除后PV 状态变 Released数据还在但无法被新 PVC 绑定。在 Retain 策略下这是正常行为目的是保护数据。如果确认数据确实不需要了想把这个 PV 释放出来重新用需要手动清理 claimRefkubectl patch pv nfs-pv-001 -p {spec:{claimRef:null}}执行后 PV 状态会从 Released 变回 Available可以再次被新 PVC 绑定。如果还不行用 kubectl get pv nfs-pv-001 -o yaml 检查 claimRef 里是不是残留了 uid 字段一并清掉即可。这里想强调一个理念Retain 策略下删 PVC 不等于删数据。有人误以为删掉 PVC 存储就清空了实际上数据盘照旧只是少了一层引用。要彻底清理存储必须去 NFS 后端手动删除对应目录这个动作一定要在确认数据不再需要之后再做。5.4 动态供给nfs-subdir-external-provisioner 一劳永逸手工 PV 适合演示和小规模场景但集群里 PVC 一多每个都要手工建 PV 就太痛苦了。社区里最成熟的 NFS 动态供给方案是 nfs-subdir-external-provisioner。它的原理很简单监听 PVC 创建事件自动在 NFS 后端按命名空间和 PVC 名称创建子目录并生成对应的 PV。这样开发者只要提交 PVC存储立即就绪运维不需要再手工创建任何 PV。部署方式一般用 Helm核心参数是 NFS 服务端地址和共享路径helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ --set nfs.server192.168.10.10 \ --set nfs.path/data/k8s-storage \ --set storageClass.namenfs-client装完集群里会多出一个名为 nfs-client 的 StorageClass。PVC 声明里加 storageClassName: nfs-client就能自动创建子目录并完成绑定。这个方案有两个注意点一是 StorageClass 的 reclaimPolicy 要根据需求设置一般配合 Delete这样 PVC 删除时对应子目录会被清理二是动态创建出来的目录属主经常是 nobody需要在 values 里配置合适的参数或者预先把共享目录 chown 成指定 uid。这个坑我踩过好几次每次都是目录权限不对导致 Pod 里写不进去。5.5 生产环境存储选型建议NFS 很好用但生产环境选型还是要回到业务本身。把踩完这些坑之后的选型思路也分享一下不同规模、不同业务对存储的要求差别很大我自己在项目里习惯用下面这个框架做初步判断场景推荐方案理由测试环境、共享日志、静态文件NFS / nfs-client 动态供给部署简单原生支持 RWX生产数据库、核心有状态服务云盘 CSI / Ceph RBD性能和可靠性更高支持快照大规模文件共享、多读多写CephFS / 对象存储水平扩展能力强容量弹性大临时数据、单节点需求emptyDir sizeLimit零成本生命周期清晰存储选型没有银弹关键是把访问模式、性能、可靠性、运维成本放在一起权衡。NFS 的上限很清晰但它作为入门首选和中小规模集群的主力方案价值无可替代。至少对我来说NFS 是理解 K8s 存储抽象的最佳教材没有之一。最后说点实际体会。K8s 存储这套东西第一次接触时容易觉得概念又多又绕但把 Volume 和 PV/PVC 的本质想明白之后后面接任何存储后端都是同一个套路后端准备好创建 PV或走动态供给声明 PVCPod 挂载。我在实际排查中反复验证过一个原则存储问题最怕层层叠叠的猜测用 describe 看事件、用 kubelet 日志看细节一步一个脚印绝大多数问题都能在十分钟内定位。NFS 这个方案我建议每个玩 K8s 的人都亲手搭一遍踩过权限和挂载的坑比看十篇教程都管用。下一篇如果有机会我打算讲讲 StatefulSet 和有状态应用编排存储和它结合的那些事更有意思咱们到时候接着聊。
返回列表