Kubernetes 存储实战指南:从 PV/PVC 到分布式存储服务器搭建
容器天生是"短命"的,Pod 重启或重新调度后,写在容器内部的数据会消失。Kubernetes 存储要解决的核心问题就是:让数据活得比 Pod 久。这篇指南从零开始,带你理解 PV、PVC、StorageClass 的关系,并给出可直接执行的代码实例,最后扩展到常见的分布式存储选型与存储服务器的快速搭建。
目录
一、核心概念:三分钟搞懂 PV、PVC、StorageClass
你可以把存储想象成"租房":
- PV(PersistentVolume):集群里的一块实际存储资源,由管理员预先创建或由系统动态生成。它是集群级别的,不属于任何命名空间。
- PVC(PersistentVolumeClaim):用户(开发者)发出的存储申请,比如"我要 5Gi 空间,读写权限,给我一块"。PVC 是命名空间级别的。
- StorageClass:存储的"模板"或"类型"。它定义了用哪种后端(如 AWS EBS、Ceph、NFS)来创建 PV,以及创建时的参数。有了它,PVC 一创建,PV 就自动生成,无需管理员手动干预。
- 最简关系链*:Pod 挂载 PVC → PVC 根据 StorageClass 匹配或动态生成 PV → PV 背后是真实的磁盘/NFS/Ceph。
二、动手实战:从静态 PV 到动态供给
2.1 静态供给(手动创建 PV + PVC)
这是理解原理的最佳起点。适用于本地测试或已有存储无法自动创建 PV 的场景。
- 步骤 1:在节点上准备目录(以 Minikube 或单节点为例)*
minikube ssh sudo mkdir -p /mnt/data sudo sh -c "echo 'Hello from K8s Storage' > /mnt/data/index.html" exit
|
apiVersion: v1 kind: PersistentVolume metadata: name: task-pv-volume spec: storageClassName: manual capacity: storage: 10Gi accessModes: - ReadWriteOnce hostPath: path: "/mnt/data"
|
kubectl apply -f pv.yaml kubectl get pv task-pv-volume
|
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: task-pv-claim spec: storageClassName: manual accessModes: - ReadWriteOnce resources: requests: storage: 3Gi
|
kubectl apply -f pvc.yaml kubectl get pvc task-pv-claim
|
Kubernetes 会自动将 PVC 与满足条件(class 相同、容量足够、访问模式兼容)的 PV 绑定。
apiVersion: v1 kind: Pod metadata: name: task-pv-pod spec: volumes: - name: task-pv-storage persistentVolumeClaim: claimName: task-pv-claim containers: - name: task-pv-container image: nginx volumeMounts: - mountPath: "/usr/share/nginx/html" name: task-pv-storage
|
kubectl apply -f pod.yaml kubectl exec -it task-pv-pod -- cat /usr/share/nginx/html/index.html
|
这个例子中,Pod 完全不关心底层存储是什么,它只声明"我要用 task-pv-claim 这个申请"。
2.2 动态供给(StorageClass + PVC,无需手动建 PV)
生产环境推荐方式。管理员预先定义 StorageClass,用户只写 PVC,PV 自动诞生。
- 创建 StorageClass(以 AWS EBS 为例)*
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: ebs.csi.aws.com parameters: type: gp3 reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer
|
kubectl apply -f storageclass.yaml
|
- 用户侧只需 PVC(pvc-dynamic.yaml)*
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-app-data spec: storageClassName: fast-ssd accessModes: - ReadWriteOnce resources: requests: storage: 20Gi
|
kubectl apply -f pvc-dynamic.yaml kubectl get pvc my-app-data
|
关键点:volumeBindingMode: WaitForFirstConsumer 表示等到有 Pod 调度后再创建 PV,这样 PV 可以创建在 Pod 所在的可用区,避免跨区挂载失败。
三、生产级实战:WordPress + MySQL 持久化部署
这是 Kubernetes 官方教程中的经典案例,展示有状态应用如何用 PVC 保住数据。
- 创建 kustomization.yaml(含密码 Secret 和资源引用)*
secretGenerator: - name: mysql-pass literals: - password=YourStrongPassword
resources: - mysql-deployment.yaml - wordpress-deployment.yaml
|
- mysql-deployment.yaml(核心片段)*
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 20Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: wordpress-mysql spec: replicas: 1 selector: matchLabels: app: wordpress tier: mysql template: metadata: labels: app: wordpress tier: mysql spec: containers: - image: mysql:8.0 name: mysql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: mysql-pv-claim
|
kubectl apply -k ./ kubectl get pvc
kubectl get pods
|
- 关键验证*:删除 MySQL Pod,新 Pod 启动后数据仍在。因为 PVC 和 PV 的生命周期独立于 Pod。
四、常见分布式存储选型对比
当集群有多节点、需要高可用或共享存储时,HostPath 和本地盘就不够用了。以下是主流方案:
| 方案 |
访问模式支持 |
部署复杂度 |
适用场景 |
核心特点 |
| NFS |
RWO / RWX |
低 |
共享配置、日志归档、简单文件共享 |
部署简单,需额外维护 NFS 服务器高可用 |
| Ceph (Rook) |
RWO / RWX(CephFS)/ ROX |
中高 |
数据库、消息队列、生产级有状态服务 |
多副本/纠删码,块/文件/对象三合一,功能最全 |
| Longhorn |
RWO(部分版本支持 RWX) |
中 |
轻量有状态、边缘/小型集群 |
基于云原生架构,界面化管理,备份到 S3/NFS 方便 |
| 云厂商 CSI(EBS/云盘等) |
通常 RWO |
低 |
云上生产环境 |
与云平台深度集成,快照/加密等能力开箱即用 |
- 如果你只需要跨 Pod 共享文件(如配置文件、上传目录),NFS 最快上手,但记得做 NFS 服务器的高可用。
- 如果你要跑数据库、需要块存储的性能和数据保护(快照/克隆),Ceph RBD 或云盘 CSI 是首选。
- 如果你在边缘或资源有限的环境,想要比 NFS 更现代、比 Ceph 更轻量的方案,Longhorn 值得考虑。
- Ceph Rook 快速感知*:Rook 是 Ceph 的 Kubernetes Operator,它把 Ceph 集群的部署、扩容、故障恢复都变成 Kubernetes 原生操作。你只需要定义
CephCluster CRD,Rook 帮你拉起 MON、OSD、MGR,然后创建 StorageClass 引用 rook-ceph-block,PVC 就能动态申请 RBD 块设备了。
五、避坑清单与最佳实践
ReadWriteOnce 只保证一个节点可以读写挂载,同一节点上的多个 Pod 仍可能同时写入。如果你需要严格单 Pod 独占,使用 ReadWriteOncePod(CSI 驱动需支持,K8s 1.29+ 稳定)。
- 2. reclaimPolicy 选对,数据不丢*
Delete:删 PVC 即删 PV 和底层存储。适合动态生成的临时数据。
Retain:删 PVC 后 PV 保留,数据还在,需要手动清理。生产环境重要数据建议用 Retain 或确保有备份。
hostPath 的数据留在节点本地,Pod 漂移到其他节点就看不到数据了。生产环境切勿用于有状态服务。
分布式存储的多副本防的是磁盘/节点故障,不防误删。用 Velero 做集群级备份,Ceph 用 RBD 快照,Longhorn 配 Backup 到 S3/NFS。
多数 StorageClass 不支持在线缩容(部分云盘支持扩容但不支持缩容)。初始申请时留出余量。
六、快速排错命令
kubectl describe pvc <name>
kubectl describe pod <name> kubectl get pv
kubectl exec -it <pod> -- sh -c "echo test > /mounted/path/test.txt" kubectl delete pod <pod>
|
七、NFS 服务器快速搭建(10 分钟,最轻量共享存储)
NFS 是给 K8s 做 ReadWriteMany 存储最简单的方式,也适合内部文件共享。
sudo apt update sudo apt install -y nfs-kernel-server sudo systemctl enable --now nfs-server
|
sudo mkdir -p /data/nfs-share sudo chown -R nobody:nogroup /data/nfs-share
|
echo "/data/nfs-share 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports sudo exportfs -rav
|
sudo showmount -e localhost
|
sudo mount -t nfs <nfs-server-ip>:/data/nfs-share /mnt echo "hello nfs" | sudo tee /mnt/test.txt
|
- 接入 K8s*:创建一个指向该 NFS 服务器的 PV 或 StorageClass(使用
nfs-subdir-external-provisioner 可实现动态供给),PVC 即可申请 ReadWriteMany 卷。
注意:no_root_squash 意味着客户端 root 用户拥有服务端 root 权限,生产环境应改用 root_squash 并配合用户 ID 映射。同时务必只在内网暴露 NFS,不要开放到公网。
八、Longhorn 快速搭建(K8s 原生分布式块存储)
Longhorn 是 CNCF 项目,专为 K8s 设计,安装极简,带 UI,适合自建集群或边缘场景。
Longhorn 依赖 open-iscsi 和 nfs-common(用于 RWX 卷):
sudo apt install -y open-iscsi nfs-common sudo systemctl enable --now iscsid
sudo yum install -y iscsi-initiator-utils nfs-utils sudo systemctl enable --now iscsid
|
同时确认节点文件系统支持 file extents(ext4/XFS 均可),以及满足最低配置:3 节点,每节点 4 vCPU / 4 GiB RAM。
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.7.3/deploy/longhorn.yaml
|
等待所有 Pod 就绪:
kubectl get pods -n longhorn-system -w
|
kubectl get sc
kubectl port-forward -n longhorn-system svc/longhorn-frontend 8080:80
|
Longhorn 默认可能不会自动使用新磁盘。在 UI 中进入 Node → 节点名 → Disk,确认数据盘(如 /dev/sdb 挂载到 /data/longhorn 这类专用目录)的 Allow Scheduling 已开启。如果 PVC 一直 Pending,八成是这里没启用。
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: longhorn-test spec: accessModes: ["ReadWriteOnce"] storageClassName: longhorn resources: requests: storage: 1Gi
|
九、Ceph 快速搭建(全能型:块 / 文件 / 对象)
Ceph 是生产级分布式存储的"重武器",提供 RBD 块存储、CephFS 文件系统、RGW 对象存储。下面用 cephadm 在三台 Ubuntu 24.04 节点上快速拉起一个最小可用集群。
sudo apt update sudo apt install -y python3 chrony lvm2 podman sudo systemctl enable --now chrony
sudo hostnamectl set-hostname ceph-node-a
|
在每台机器的 /etc/hosts 中加入所有节点 IP 与主机名映射,并确保时间同步。
- 步骤 2:管理机安装 cephadm 并 bootstrap*
wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo tee /etc/apt/trusted.gpg.d/ceph.asc sudo apt-add-repository 'deb https://download.ceph.com/debian-squid/ jammy main' sudo apt update && sudo apt install -y cephadm
sudo cephadm bootstrap --mon-ip <管理机IP> \ --initial-dashboard-user admin \ --initial-dashboard-password '<强密码>'
|
bootstrap 完成后会输出 Dashboard 地址(默认 https://<ip>:8443)和 ceph 命令的使用方式。
sudo cephadm orch host add ceph-node-b sudo cephadm orch host add ceph-node-c
sudo ceph orch host ls
sudo ceph orch apply osd --all-available-devices
sudo ceph osd tree
|
此时 Ceph 已具备块存储能力。如果需要 CephFS(文件共享),还需创建 MDS 和文件系统;如果需要 S3 对象存储,需部署 RGW。在 K8s 场景中,通常用 Rook 来管理 Ceph(下一节)。
十、Rook-Ceph:把 Ceph 变成 K8s 原生存储(生产推荐)
手动管理 Ceph 集群对 K8s 团队来说负担较重。Rook 是 Ceph 的 K8s Operator,用 CRD 声明式地部署、扩容、恢复 Ceph。
modprobe rbd modprobe ceph echo -e "rbd\nceph" | sudo tee /etc/modules-load.d/rook.conf
|
sudo wipefs -a /dev/sdb sudo dd if=/dev/zero of=/dev/sdb bs=1M count=100
|
- 步骤 3:安装 Rook Operator(Helm)*
helm repo add rook-release https://charts.rook.io/release helm repo update helm install rook-ceph rook-release/rook-ceph \ --namespace rook-ceph --create-namespace \ --version 1.13.0 \ --set csi.enableRbdDriver=true \ --set csi.enableCephfsDriver=true
|
- 步骤 4:创建 CephCluster(核心 CRD)*
apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: cephVersion: image: quay.io/ceph/ceph:v18.2.0 dataDirHostPath: /var/lib/rook mon: count: 3 allowMultiplePerNode: false storage: useAllNodes: false useAllDevices: false nodes: - name: "worker1" devices: - name: "sdb" - name: "worker2" devices: - name: "sdb" - name: "worker3" devices: - name: "sdb"
|
kubectl apply -f cluster.yaml
kubectl -n rook-ceph get pods -w
|
kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/csi/rbd/storageclass.yaml
kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/filesystem.yaml kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/csi/cephfs/storageclass.yaml
|
之后 PVC 指定 storageClassName: rook-ceph-block 或 rook-cephfs 即可动态供给。CephFS 的 PVC 可使用 ReadWriteMany 实现多 Pod 同时挂载。
十一、对象存储快速补充:MinIO
如果需求是 S3 兼容的对象存储(备份、日志归档、AI 数据集),MinIO 是最快的自建选择。
wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio
mkdir -p /data/minio
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD='强密码' ./minio server /data/minio --console-address ":9001"
|
浏览器访问 http://<ip>:9001 进入控制台。生产环境建议使用 RPM/DEB 包配合 systemd 管理,并按照官方推荐使用独立数据盘、匹配硬件配置。
在 K8s 中,可用 MinIO Operator 部署分布式 MinIO 集群,并通过 mc 客户端或 S3 SDK 访问。
十二、存储服务器选型速查
| 场景 |
推荐方案 |
搭建耗时 |
核心优势 |
| 只要共享文件,K8s 内外都要用 |
NFS |
10 分钟 |
最简单,ReadWriteMany 直接支持 |
| K8s 自建集群,要块存储 + UI 管理 |
Longhorn |
15 分钟 |
云原生,一条命令,快照/备份方便 |
| 生产级,块/文件/对象全都要 |
Ceph + Rook |
1–2 小时 |
功能最全,多副本/纠删码,自愈 |
| 对象存储(S3 兼容) |
MinIO |
5 分钟(单机) |
轻量,S3 兼容,AI/备份场景首选 |
- 一句话建议*:先把 NFS 跑通,理解共享存储的挂载逻辑;再用 Longhorn 体验 K8s 原生的动态供给和 UI 管理;生产环境需要块存储性能和数据保护时,上 Rook-Ceph。
- 一句话总结*:Pod 是临时工,PV/PVC 是它的"外挂硬盘"。先用 hostPath 静态 PV 理解绑定机制,再用 StorageClass 动态供给跑通生产流程,最后根据场景选 NFS(共享)/ Longhorn(轻量)/ Ceph(全能)来落地。存储服务器这边,NFS 十分钟、Longhorn 一刻钟、Ceph 一两个小时,按需组合即可。