Kubernetes-Storage-PV-PVC

Kubernetes的存储机制PV和PVC

#kubernetes #persistent-volume #storage-class #storage

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
  • 步骤 2:创建 PV(pv.yaml)*
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
# STATUS 应为 Available
  • 步骤 3:创建 PVC(pvc.yaml)*
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
# STATUS 应为 Bound,VOLUME 指向 task-pv-volume

Kubernetes 会自动将 PVC 与满足条件(class 相同、容量足够、访问模式兼容)的 PV 绑定。

  • 步骤 4:Pod 使用 PVC*
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
# 输出:Hello from K8s Storage

这个例子中,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
# STATUS 先为 Pending,有 Pod 使用后变为 Bound,PV 自动创建

关键点: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
# mysql-pv-claim 和 wp-pv-claim 均应为 Bound
kubectl get pods
# 两个 Pod 应 Running
  • 关键验证*:删除 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 块设备了。

五、避坑清单与最佳实践

  • 1. 访问模式不是"权限锁"*

ReadWriteOnce 只保证一个节点可以读写挂载,同一节点上的多个 Pod 仍可能同时写入。如果你需要严格单 Pod 独占,使用 ReadWriteOncePod(CSI 驱动需支持,K8s 1.29+ 稳定)。

  • 2. reclaimPolicy 选对,数据不丢*
  • Delete:删 PVC 即删 PV 和底层存储。适合动态生成的临时数据。
  • Retain:删 PVC 后 PV 保留,数据还在,需要手动清理。生产环境重要数据建议用 Retain 或确保有备份。
  • 3. hostPath 只用于测试*

hostPath 的数据留在节点本地,Pod 漂移到其他节点就看不到数据了。生产环境切勿用于有状态服务。

  • 4. 备份比多副本更重要*

分布式存储的多副本防的是磁盘/节点故障,不防误删。用 Velero 做集群级备份,Ceph 用 RBD 快照,Longhorn 配 Backup 到 S3/NFS。

  • 5. PVC 的容量申请宁大勿小*

多数 StorageClass 不支持在线缩容(部分云盘支持扩容但不支持缩容)。初始申请时留出余量。


六、快速排错命令

# PVC 一直 Pending?
kubectl describe pvc <name> # 看 Events,常见原因:没有匹配的 PV、StorageClass 不存在、WaitForFirstConsumer 等待 Pod
# Pod 挂载失败?
kubectl describe pod <name> # 看 Volume 相关 Events
kubectl get pv # 看 PV 状态是否 Bound、是否有 FailedMount
# 验证写入持久化
kubectl exec -it <pod> -- sh -c "echo test > /mounted/path/test.txt"
kubectl delete pod <pod>
# 新 Pod 起来后 cat 同一路径,内容还在即持久化生效

七、NFS 服务器快速搭建(10 分钟,最轻量共享存储)

NFS 是给 K8s 做 ReadWriteMany 存储最简单的方式,也适合内部文件共享。

  • 环境*:Ubuntu/Debian 服务器一台,假设内网网段 192.168.1.0/24。

  • 步骤 1:安装服务端*

sudo apt update
sudo apt install -y nfs-kernel-server
sudo systemctl enable --now nfs-server
  • 步骤 2:创建共享目录并授权*
sudo mkdir -p /data/nfs-share
sudo chown -R nobody:nogroup /data/nfs-share
# 或者按需 chmod 777(仅测试环境)
  • 步骤 3:配置 /etc/exports*
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
  • 步骤 4:验证导出*
sudo showmount -e localhost
# 应显示 /data/nfs-share 192.168.1.0/24
  • 步骤 5:客户端挂载测试*
# 在另一台机器上
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 卷):

# Debian/Ubuntu
sudo apt install -y open-iscsi nfs-common
sudo systemctl enable --now iscsid

# RHEL/CentOS
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
  • 验证与访问 UI*
kubectl get sc
# 应出现 longhorn 这个 StorageClass

kubectl port-forward -n longhorn-system svc/longhorn-frontend 8080:80
# 浏览器打开 http://localhost:8080
  • 关键一步:配置节点磁盘*

Longhorn 默认可能不会自动使用新磁盘。在 UI 中进入 Node → 节点名 → Disk,确认数据盘(如 /dev/sdb 挂载到 /data/longhorn 这类专用目录)的 Allow Scheduling 已开启。如果 PVC 一直 Pending,八成是这里没启用。

  • 测试 PVC*
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 节点上快速拉起一个最小可用集群。

  • 环境要求*:3 台服务器,每台至少 8GB RAM / 4 CPU / 一块未使用的数据盘(如 /dev/sdb),节点间网络互通。

  • 步骤 1:基础准备(所有节点 + 管理机)*

sudo apt update
sudo apt install -y python3 chrony lvm2 podman
sudo systemctl enable --now chrony

# 设置主机名(以 node-a 为例,b/c 同理)
sudo hostnamectl set-hostname ceph-node-a

在每台机器的 /etc/hosts 中加入所有节点 IP 与主机名映射,并确保时间同步。

  • 步骤 2:管理机安装 cephadm 并 bootstrap*
# 安装 cephadm
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

# 在管理机(或第一台节点)执行 bootstrap
sudo cephadm bootstrap --mon-ip <管理机IP> \
--initial-dashboard-user admin \
--initial-dashboard-password '<强密码>'

bootstrap 完成后会输出 Dashboard 地址(默认 https://<ip>:8443)和 ceph 命令的使用方式。

  • 步骤 3:加入其余节点并部署 OSD*
# 将另外两台节点加入集群(在管理机上执行)
sudo cephadm orch host add ceph-node-b
sudo cephadm orch host add ceph-node-c

# 查看主机
sudo ceph orch host ls

# 用所有可用空白磁盘创建 OSD
sudo ceph orch apply osd --all-available-devices

# 验证 OSD
sudo ceph osd tree
  • 步骤 4:验证集群健康*
sudo ceph -s
# 应看到 health: HEALTH_OK(或至少 MON/OSD 正常)

此时 Ceph 已具备块存储能力。如果需要 CephFS(文件共享),还需创建 MDS 和文件系统;如果需要 S3 对象存储,需部署 RGW。在 K8s 场景中,通常用 Rook 来管理 Ceph(下一节)。


十、Rook-Ceph:把 Ceph 变成 K8s 原生存储(生产推荐)

手动管理 Ceph 集群对 K8s 团队来说负担较重。Rook 是 Ceph 的 K8s Operator,用 CRD 声明式地部署、扩容、恢复 Ceph。

  • 前置条件*:kubeadm 集群至少 3 个 worker 节点,每节点一块未格式化磁盘(100GB+),且已安装 containerd 作为运行时。

  • 步骤 1:加载内核模块(所有 worker)*

modprobe rbd
modprobe ceph
echo -e "rbd\nceph" | sudo tee /etc/modules-load.d/rook.conf
  • 步骤 2:清理目标磁盘*
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
# 等待 OSD 就绪
kubectl -n rook-ceph get pods -w
  • 步骤 5:创建 StorageClass*
# RBD 块存储(RWO)
kubectl apply -f https://raw.githubusercontent.com/rook/rook/master/deploy/examples/csi/rbd/storageclass.yaml

# CephFS 文件存储(RWX)
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 是最快的自建选择。

  • Linux 单机快速启动(测试用)*
# 下载二进制
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio

# 准备数据目录
mkdir -p /data/minio

# 启动(默认账号 minioadmin/minioadmin)
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 一两个小时,按需组合即可。