聊 Kubernetes 的存储方案我一直有个感受Pod、Deployment 这些编排对象讲起来都还好数据到底存在哪儿才是真正让人头大的地方。尤其当你试图在 K8s 上跑 MySQL、Redis、Elasticsearch 这类有状态服务时持久存储 PersistentVolumePV和 StatefulSet 这两样东西是根本绕不开的核心。今天这篇就用一套完整的实操记录把 PV 的原理、PV/PVC/StorageClass 三者如何配合以及 StatefulSet 如何绑定存储跑起一个有状态服务一次性讲透。不管你是刚入门 K8s 的小白还是已经被存储折腾过几次的运维这套内容都能直接拿回去用。我先把话放这儿只要你想在生产环境跑任何需要保存数据的应用这一篇的内容你就必须吃透。下面从存储痛点开始逐步拆解原理再进入动手环节。1. 为什么容器环境离不开持久化先搞懂有状态服务的存储痛点1.1 容器天生“无状态”数据为何会被频繁清掉容器的镜像层本身是只读的应用写入的数据都在容器的可写层里。这个可写层和 Pod 的生命周期绑定得死死的——Pod 被删除、被重新调度、节点发生驱逐或者节点宕机可写层里的数据就跟着一起消失。换句话说容器是一个临时工你给它安排的工位是临时的它下班之后桌上所有文件都会被保洁清走。有状态服务恰恰不能接受这种“文件清走”的行为。以数据库为例MySQL 的datadir里存放着所有表结构、数据文件和日志如果这些数据随着 Pod 重建被抹掉那这个数据库在 K8s 上就没有存在的意义了。所以问题的核心在于数据必须独立于 Pod 的生命周期存在。Kubernetes 给出的标准答案就是持久卷PersistentVolume体系。1.2 从临时目录到持久卷几种存储姿势的演进K8s 里能挂载数据的方案不少早期最常用的是emptyDir和hostPath。emptyDir生命周期与 Pod 完全相同Pod 删了目录就没了适合做缓存、临时交换数据。hostPath把宿主机的一个目录挂载进容器数据至少在节点本地留住了但问题同样明显Pod 被调度到另一台节点怎么办多副本需要共享数据怎么办hostPath会把 Pod 和具体节点绑死这在生产环境基本是灾难。持久卷方案和这两种临时方案有本质区别PV 本身是集群级别的存储资源它的生命周期独立于任何 Pod可以跨节点挂载、跨 Pod 重建持续存在。三者的对比见下表存储方案生命周期优点致命缺陷emptyDir与 Pod 相同配置简单、零成本Pod 重建即丢不能用于持久数据hostPath与节点相同数据能留在宿主机节点绑定Pod 漂移后无法访问PersistentVolume独立于 Pod集群管理跨节点、跨 Pod 持久存在需要依赖底层存储配置相对复杂如果你把 K8s 集群想象成一个酒店emptyDir就是一次性纸杯hostPath是放在某个固定房间里的储物柜而 PV 则是酒店的中央仓库——你无论住哪个房间只要凭证在手都能取到自己的东西。1.3 有状态服务的“状态”到底指什么这里说的“状态”不只是数据还包括三件事稳定的身份标识、稳定的存储绑定、稳定的网络标识。身份标识代表每个实例有固定编号;存储绑定代表某个编号的实例永远挂载同一个数据卷;网络标识代表实例通过固定的 DNS 名字可以互相访问。这三件事普通 Deployment 给不了——Deployment 创建的 Pod 名字是随机字符串数据默认不持久网络服务也只是负载均衡式的访问。StatefulSet 就是为了解决“有状态服务上 K8s”这个问题而存在的控制器。2. PersistentVolume 核心原理拆解PV/PVC/StorageClass 三件套2.1 三个角色PV 是资源PVC 是申请StorageClass 是工厂在 K8s 存储体系里最基础的三件套是 PV、PVC 和 StorageClass。它们三个职责不同却配合得相当紧密PersistentVolumePV集群管理员预置或动态创建的存储资源本质上代表一块真实的存储空间可以是 NFS 目录、云厂商磁盘、Ceph RBD、本地盘等。PersistentVolumeClaimPVC用户对存储的“申请单”里面写清楚需要多大容量、什么访问模式、用哪个存储类。PVC 相当于意图声明并不直接和底层存储打交道。StorageClassSC存储类的模板定义了何时使用哪种 provisioner供给器来动态创建 PV也定义了回收策略。用一个生活化的类比会清晰很多PV 是仓库里已经备好的货物PVC 是客户提交的订货单StorageClass 则是让工厂根据订货单自动生产货物的流水线。没有流水线的时候管理员得手工备货静态供给客户下单后手动匹配货物;有了流水线订单来了工厂自动生产动态供给效率高得多。2.2 静态供给手工备货与 PVC 的绑定逻辑在没有 StorageClass 或者管理员希望完全掌控存储资源时运维会先创建好 PV开发再写 PVC 进行申请。静态供给的 PV 示例apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-static-001 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs: server: 192.168.100.10 path: /exports/k8s-data注意这里storageClassName我故意留空字符串表示这个 PV 只绑定到同样storageClassName为空的 PVC。如果集群里存在默认 StorageClass创建 PV 时不指定会默认被注入一个类名导致 PVC 匹配不上这是个很隐蔽的坑。对应的 PVC 声明长这样apiVersion: v1 kind: PersistentVolumeClaim metadata: name: claim-static spec: accessModes: - ReadWriteOnce resources: requests: storage: 5GiPVC 想要匹配 PV需要同时满足几个条件请求的容量不超过 PV 容量、访问模式一致、storageClassName一致都为空或者指向同一个类。如果 PVC 找不到匹配的 PV状态会一直停在Pending。静态供给的好处是可控坏处也很明显——每次申请存储都得靠管理员人工建卷PVC 多了根本忙不过来。2.3 动态供给StorageClass 如何自动创建 PV动态供给是绝大多数现代 K8s 集群的默认方式。用户在 PVC 里指定storageClassName: nfs-sc集群里的 provisioner 控制器会监听 Kubernetes API发现未绑定的 PVC 之后主动向存储后端发起请求创建底层存储目录或块设备然后生成一个 PV 并自动绑定到 PVC。StorageClass 的定义通常长这样apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-sc provisioner: example.com/nfs reclaimPolicy: Delete allowVolumeExpansion: trueprovisioner字段的值要与实际部署的供给组件环境变量里的PROVISIONER_NAME完全一致否则控制器永远监听不到 PVC。动态供给把原来人工建卷的活自动化了对于频繁创建有状态服务的团队来说这是效率最高的一条路。2.4 访问模式、回收策略与 PV 生命周期状态PV 的accessModes决定它能被多少个节点以什么方式挂载访问模式缩写说明ReadWriteOnceRWO仅能被单个节点以读写方式挂载ReadOnlyManyROX多个节点可同时以只读方式挂载ReadWriteManyRWX多个节点可同时以读写方式挂载ReadWriteOncePodRWOP仅能被单个 Pod 以读写方式挂载K8s 1.22回收策略有三档Delete表示删除 PVC 时自动删除 PV 并清理底层数据;Retain表示删除 PVC 后 PV 进入Released状态数据保留但 PV 不能自动复用需要管理员手动处理;Recycle这个策略已经废弃不用管它。PV 的完整生命周期分为四个阶段Available可用→Bound已绑定→Released已释放→Failed失败。Available表示 PV 还没被 PVC 绑定;PVC 申请成功后就变成Bound;PVC 被删除后如果回收策略是RetainPV 会停在Released;某个环节出错则进入Failed。这条链路看着简单实际踩坑时可不止一次要在这上面翻车第 5 节我会专门讲。3. StatefulSet 的存储绑定机制有状态服务如何获得“稳定身份”3.1 有状态服务需要什么身份稳定存储跟着身份走为什么跑数据库不直接上 Deployment因为 Deployment 里每个 Pod 都是“临时工”没有固定编号重启后名字里带一串随机数无法形成稳定的集群成员列表。数据库实例之间要互相通信要认定“我就是原来那个我”必须有一个稳定的身份。StatefulSet 给每个 Pod 编了序号mysql-sts-0、mysql-sts-1、mysql-sts-2这个编号永远不变。Pod 身份稳定之后下一步就是让存储跟着身份走。mysql-sts-0挂载的数据卷在它被删除、重建、迁移之后必须还是同一个卷。StatefulSet 通过volumeClaimTemplates来做到这件事。3.2 StatefulSet 三大核心特性StatefulSet 在 Deployment 之外额外带来了三个能力稳定的网络标识配合 Headless Service副本可以通过podName.serviceName.namespace.svc直接访问。稳定的存储绑定每个序号对应一个 PVCPVC 命名规则固定不会漂移。有序的调度与更新默认按 0、1、2 顺序逐个创建、逐个删除支持OrderedReady策略也可设置Parallel并行操作。这三个能力加在一起让 StatefulSet 成为 K8s 上跑有状态服务的唯一正解。3.3 volumeClaimTemplates 到底是怎么生成 PVC 的volumeClaimTemplates是 StatefulSet spec 里的一个字段它像 Pod 模板一样为每个序号自动生成一个 PVC。命名规则非常固定volumeClaimTemplates中的name-StatefulSet名称-序号举个例子如果 StatefulSet 名字是mysql-stsvolumeClaimTemplates中 metadata.name 是mysql-data那么三个副本生成的 PVC 分别是mysql-data-mysql-sts-0mysql-data-mysql-sts-1mysql-data-mysql-sts-2Pod 重建后StatefulSet 控制器会按照序号重新绑定到同名 PVC数据自然就还在。这个“按序号对号入座”的机制是整个存储绑定方案的核心理解了它你就理解了一半。3.4 与 Deployment 挂载的差异别用 Deployment 跑数据库很多人会把volumes字段从 Deployment 直接复制到 StatefulSet这当然也能挂载但失去了关键能力。Deployment 里所有 Pod 共享一个 PVC或者各自挂载同一份数据Pod 重建后绑定的卷关系是不稳定的。StatefulSet 的volumeClaimTemplates则是为每个序号独立生成 PVC确保“人换、证不换”。实战中我见过不止一次有人用 Deployment 跑一个单实例数据库节点宕机后 Pod 被调度到新节点数据还留在旧节点上最后只能靠快照恢复——这就是典型的存储身份没绑对。4. 实操NFS 存储类 StatefulSet 部署有状态 MySQL 全过程4.1 环境准备搭建 NFS 共享存储动态供给的底层存储我选 NFS原因很简单部署方便、无厂商依赖、支持ReadWriteMany适合大部分测试和中等负载场景。实际生产环境如果用云厂商磁盘原理完全一致只是 provisioner 换成 CSI 驱动。NFS 服务端操作以 Ubuntu 系为例sudo apt install nfs-kernel-server -y sudo mkdir -p /exports/k8s-data sudo chown nobody:nogroup /exports/k8s-data sudo chmod 777 /exports/k8s-data echo /exports/k8s-data *(rw,sync,no_subtree_check,no_root_squash) | sudo tee -a /etc/exports sudo exportfs -ra sudo systemctl enable --now nfs-server这里有个细节no_root_squash用不用取决于你的客户端容器是否以 root 运行。MySQL 镜像内进程以 mysql 用户启动NFS 默认的 root_squash 对普通用户没影响。如果你后面发现 Pod 启动时报权限不足再回来调整导出选项也不迟。K8s 集群的每个节点都要装 NFS 客户端sudo apt install nfs-common -y先在某个节点手动挂载一下测试确保网络和权限都没问题sudo mkdir -p /mnt/nfs-test sudo mount -t nfs 192.168.100.10:/exports/k8s-data /mnt/nfs-test sudo touch /mnt/nfs-test/hello.txt ls -l /mnt/nfs-test测试通过后卸载即可。NFS 本身没有密码认证所以测试环境网络隔离要做好生产建议用专用的存储方案或者在有防火墙规则的安全网段里跑。4.2 部署 NFS 动态供给StorageClass 让 PVC 自动建 PVNFS 动态供给需要先安装一个供给器组件。这里我以社区常用的 NFS subdir external provisioner 为例它做的事很单纯监听 PVC 创建事件在 NFS 共享目录下创建子目录再生成对应的 PV。这套组件的完整 YAML 分四块ServiceAccount、RBAC 权限、Deployment、StorageClass。先创建服务账号和授权apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nfs-provisioner-clusterrole rules: - apiGroups: [] resources: [persistentvolumes] verbs: [get, list, watch, create, delete] - apiGroups: [] resources: [persistentvolumeclaims] verbs: [get, list, watch, update] - apiGroups: [storage.k8s.io] resources: [storageclasses] verbs: [get, list, watch] - apiGroups: [] resources: [events] verbs: [create, update, patch]这里只列了核心权限真实部署时还要补上 endpoints、nodes 等资源的查询权限。这个组件需要监听集群资源变化所以 RBAC 一定要配完整否则会出现 PVC 建了但 PV 一直不出现的情况。Deployment 部分供给器镜像地址以你实际使用环境的镜像仓库为准核心是环境的 PROVISIONER_NAME 要和 StorageClass 里的 provisioner 一致apiVersion: apps/v1 kind: Deployment metadata: name: nfs-provisioner namespace: kube-system spec: replicas: 1 selector: matchLabels: app: nfs-provisioner template: metadata: labels: app: nfs-provisioner spec: serviceAccountName: nfs-provisioner containers: - name: nfs-provisioner image: registry.example.local/k8s/nfs-provisioner:v3.4 env: - name: PROVISIONER_NAME value: example.com/nfs - name: NFS_SERVER value: 192.168.100.10 - name: NFS_PATH value: /exports/k8s-data volumeMounts: - name: nfs-root mountPath: /persistentvolumes volumes: - name: nfs-root nfs: server: 192.168.100.10 path: /exports/k8s-data接着创建 StorageClassprovisioner 必须是上面环境变量的值apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-sc provisioner: example.com/nfs reclaimPolicy: Delete volumeBindingMode: Immediate创建完这些资源后kubectl get sc能看到nfs-sc。从这一刻起只要 PVC 指定storageClassName: nfs-sc供给器就会自动在 NFS 根目录下创建以namespace-pvcname-随机后缀命名的子目录并完成 PV 的创建与绑定。4.3 编写 StatefulSet YAML部署单副本 MySQL 验证绑定存储类准备好了下面动手部署一个 MySQL。先用单副本跑通整个流程再在后面讲扩容。这一步需要的资源有两个一个 Headless Service无头服务和一个 StatefulSet。Headless Service 的clusterIP必须设为None这样才能让每个 Pod 拥有独立的 DNS A 记录apiVersion: v1 kind: Service metadata: name: mysql-sts-svc labels: app: mysql-sts spec: clusterIP: None selector: app: mysql-sts ports: - port: 3306 name: mysqlStatefulSet 的 YAML 如下重点看volumeClaimTemplates这一段apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql-sts spec: serviceName: mysql-sts-svc replicas: 1 selector: matchLabels: app: mysql-sts template: metadata: labels: app: mysql-sts spec: containers: - name: mysql image: mysql:8.0 ports: - containerPort: 3306 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: Root123456 volumeMounts: - name: mysql-data mountPath: /var/lib/mysql volumeClaimTemplates: - metadata: name: mysql-data spec: accessModes: [ReadWriteOnce] storageClassName: nfs-sc resources: requests: storage: 10Gi创建完成后依次查看状态kubectl apply -f mysql-sts.yaml kubectl get pods -l appmysql-sts -o wide kubectl get pvc -l appmysql-sts kubectl get pv正常情况下 Pod 进入RunningPVC 处于BoundPV 名称是一长串随机 ID容量 10Gi。如果 Pod 卡在ContainerCreating先kubectl describe pod mysql-sts-0看事件大概率是 NFS 挂载失败或者权限问题。容器内/var/lib/mysql的数据会通过 NFS 写到服务端共享目录的子目录里。此时去宿主机ls /exports/k8s-data可以看到一个以default-mysql-data-mysql-sts-0前缀开头的目录这就是真实数据落盘的位置。4.4 数据持久性验证删除 Pod 后数据还在不在这是整个实操最关键的一步证明 StatefulSet 的重建不会让数据消失。先进入 MySQL 创建一条测试数据kubectl exec -it mysql-sts-0 -- mysql -uroot -pRoot123456CREATE DATABASE testdb; USE testdb; CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32)); INSERT INTO t1(name) VALUES(hello),(world); SELECT * FROM t1;退出后强制删除 Podkubectl delete pod mysql-sts-0StatefulSet 会自动按照模板原样重建一个mysql-sts-0。等 Pod 重新Running后再次进入容器查询kubectl exec -it mysql-sts-0 -- mysql -uroot -pRoot123456 -e SELECT * FROM testdb.t1;看到hello和world原样躺在表里说明数据持久化链路完全打通。整个过程数据没有经过任何手工迁移Pod 从旧节点跑到新节点也好被删掉重建也好挂载的始终是同一个 PV。这里我再强调一次原理Pod 名字mysql-sts-0没变StatefulSet 控制器去查找同名 PVCmysql-data-mysql-sts-0PVC 已经绑定了底层 PV所以数据无缝接续。如果你用 Deployment 做同样的事Pod 名字变成一堆随机字符串PVC 根本不认识它数据就无从谈起了。4.5 扩容与缩容多副本时存储如何对号入座单副本能跑通多副本的原理也顺理成章。执行扩容kubectl scale sts mysql-sts --replicas3稍等片刻查看 Pod 和 PVCkubectl get pods -l appmysql-sts -o wide kubectl get pvc -l appmysql-sts三个 Pod 会按照mysql-sts-0、mysql-sts-1、mysql-sts-2的顺序依次创建PVC 也对应生成三个全部Bound。每个 Pod 的数据目录是独立的互不共享。如果你只是想验证存储绑定机制到这里就足够了。但我要提醒一句这三个 MySQL 实例之间并不会自动成为集群。它们各自独立运行互不知晓对方的存在。要构建真正的 MySQL 高可用集群你还得在此基础上配置 Group Replication 或主从复制这是另一套更大的话题。StatefulSet 只是解决了“身份存储”的底座问题业务层的高可用逻辑还需要你自己实现。缩容时同样要小心kubectl scale sts mysql-sts --replicas1Podmysql-sts-1和mysql-sts-2会被删除但它们对应的 PVC 和 PV 会保留。这是 StatefulSet 的刻意设计——数据是贵重资产控制器不会在你缩容时擅自销毁。缩容后发现数据卷还在是正常的PVC 需要你手动清理。5. 生产环境避坑指南从访问模式、回收策略到调度耦合的排雷手册5.1 RWO 访问模式的“节点绑定”陷阱NFS 支持 ReadWriteMany所以没有这个问题。但如果你换成云厂商的云盘比如阿里云的云盘、某云厂商的 SSD 盘它们的访问模式通常是 RWO。RWO 的意思是“仅能被单个节点读写”底层实现往往还隐含了存储卷与节点的绑定关系。这带来的坑非常经典Pod 因为节点故障被重新调度到 nodeB但云盘 PV 还在 nodeA 上挂着或者云盘实际只挂载到 nodeA 可用区Pod 到了 nodeB 后启动失败一直ContainerCreating。遇到这种情况先kubectl describe pod看事件如果出现Multi-Attach error或者Volume cannot be attached to the node基本可以断定是节点亲和问题。解决办法是给 StatefulSet 加上 nodeSelector 或 topologySpreadConstraints让副本固定调度在 PV 所在的节点/可用区或者换用支持 RWX 的存储后端。这个坑在生产环境尤其高发值得提前预防。5.2 Retain 策略导致 PV 卡在 Released存储类回收策略设为Retain时PVC 被删除后 PV 不会自动回收而是进入Released状态。这个状态的 PV 是不能再被新 PVC 绑定的管理员必须手动处理先确认数据是否还需要备份然后删除 PV 对象最后在存储后端手工清理数据目录。如果你不清理直接重新创建一个同名 PV底层目录可能还残留老数据新应用挂载上去就会看到脏数据。我在测试环境就干过这事MySQL 启动后莫名其妙加载了上一个测试库的数据排查半天才反应到是 NFS 共享目录没清干净。所以生产用 Retain 策略之前务必把数据清理流程也固化下来。5.3 删除顺序与数据清理STS 删了 PVC 未必能幸免有状态服务下线时正确的删除顺序是先删 StatefulSet再删 PVC最后由回收策略决定 PV 和底层数据是否删除。顺序颠倒会出现问题PVC 先被删StatefulSet 里的 volumeClaimTemplates 还在定义中控制器发现 PVC 丢失可能会立刻重新创建同名 PVC然后去绑定已经 Released 的 PV整个状态就乱了。还有一个常见的误区kubectl delete sts mysql-sts之后很多人以为数据也跟着删了。事实是 PVC 和 PV 都还在数据完好存放在存储后端。如果确定要彻底清理需要执行kubectl delete sts mysql-sts kubectl delete pvc mysql-data-mysql-sts-0 mysql-data-mysql-sts-1 mysql-data-mysql-sts-2然后根据 StorageClass 的reclaimPolicy决定底层数据是否被清除。Delete策略下 PV 会一并删除存储后端的数据目录也会被清理;Retain策略下 PV 变成Released数据遗留等待人工处理。5.4 PVC 一直 Pending、Pod 一直 ContainerCreating 的排查思路PVC Pending 是存储排障中最常见的问题。原因通常有三类StorageClass 名称拼错、动态供给组件未运行、容量资源不足。排查时先看 PVC 的事件kubectl describe pvc mysql-data-mysql-sts-0如果事件里提示storageclass not found检查kubectl get sc里的名字是否与 PVC 里的storageClassName完全一致。如果什么都没提示说明供给器没有响应去kube-system命名空间看供给器 Pod 是否正常运行kubectl get pods -n kube-system | grep nfs-provisioner kubectl logs provisioner-pod-name -n kube-system供给器最常见的启动失败原因包括NFS 路径不存在、NFS 权限不足、镜像拉不下来、RBAC 权限缺失。把这几个点按顺序排查基本五分钟能定位。而 Pod 一直ContainerCreating大概率是挂载阶段出错。同样用kubectl describe pod看事件常见报错是failed to do mount、no such file or directory、permission denied。其中permission denied多与 NFS 导出目录的权限或者root_squash相关;no such file or directory多与 NFS 客户端没装或者路径写错相关;failed to do mount则要检查节点到 NFS 服务端的网络连通性和防火墙规则。下面是我整理的一张速查表可以直接拿来当排障手册问题现象大概率原因排查命令PVC 一直 PendingstorageClassName 不匹配 / 供给器异常kubectl describe pvcPod 一直 ContainerCreatingNFS 挂载失败 / 网络不通 / 权限不足kubectl describe podPod 调度失败RWO 卷绑定节点与调度节点不一致kubectl describe podPV 状态为 ReleasedRetain 策略下 PVC 被删kubectl get pv数据库重启后数据丢失Pod 未绑定原 PVC / 用错存储方式kubectl get pvc 查看 PVC 名称5.5 NFS 的适用边界性能、安全与替代方案NFS 作为入门和中等负载的持久化方案完全可行部署简单、跨平台、支持 RWX是很多人学习 K8s 存储的第一站。但它不是银弹NFS 走网络协议IO 延迟和吞吐都比本地盘差一个量级高并发数据库场景下性能明显不够。另外NFS 共享目录的权限模型是全局的容器内进程如果以 root 跑存在着跨租户访问数据的风险。生产环境建议按场景选型数据库用云厂商 SSD 云盘 CSI 驱动大数据分析和多副本共享场景用分布式文件存储追求极致性能的中间件用本地 NVMe 盘。存储选型本质是在性能、成本、运维复杂度之间做权衡没有绝对最优解只有适合当前业务的选择。6. 写在最后实际心得与进一步扩展这套环境我自己前后搭过几次踩坑最多的反而不是 K8s 配置本身而是底层环境的细节。NFS 的导出权限我试错过三次MySQL 因为datadir权限起不来也遇到过;Retain 策略下 PV 卡在 Released 以为集群坏了后来才想起自己之前动过回收策略。有些问题看着像 K8s 的错到头来都是存储后端或权限模型的问题。所以排障时始终记住一句话先看事件再看日志最后才怀疑配置。这一篇讲清楚了 PV 原理和 StatefulSet 配套存储的完整落地路径。后续如果有机会我打算再写一篇围绕“数据库高可用”的实操在现有 StatefulSet 基础上给 MySQL 配置主从复制或 Group Replication配合多副本调度策略把有状态服务真正推向生产可用。存储这块还有一个大主题是 CSI 扩展和容器快照备份也值得单独开一篇。到时候我们会再见面继续折腾这些让人又爱又恨的存储细节。
阅读完成 · 觉得有帮助?