【K8S】Kubernetes 持久化存储实战:PV/PVC/StorageClass全链路解析与NFS、Ceph、云盘对接实践
本文系统性地讲解 Kubernetes 存储体系的核心概念、架构设计与生产实践,涵盖从基础 Volume 到 PV/PVC/StorageClass 的完整链路,并深入 NFS、Ceph、云盘三大主流存储后端的对接配置。适合已有 K8s 基础、希望深入理解和落地持久化存储方案的工程师阅读。
一、K8s 存储体系概览
1.1 为什么需要持久化存储
在 Kubernetes 中,Pod 是短暂的(ephemeral)。容器的文件系统本质上是一个临时的可写层(基于 OverlayFS 或其他联合文件系统),当容器重启时,这个可写层会被完全重建,所有写入的数据将丢失。
这带来了几个核心问题:
- Pod 重启数据丢失:应用崩溃、节点故障、滚动更新等场景下,Pod 被重新调度后无法恢复之前的数据
- 容器文件系统的临时性:即使在同一个 Pod 内,容器重启也会导致文件系统回到镜像初始状态
- 多容器数据共享困难:同一 Pod 内的多个容器无法直接共享文件系统中的数据
- 有状态应用部署需求:数据库、消息队列、文件服务等有状态应用必须依赖持久化存储
1.2 Volume → PV → PVC → StorageClass 演进历史
Kubernetes 存储体系经历了清晰的演进路径:
| 阶段 | 机制 | 特点 | 局限 |
|---|---|---|---|
| 早期 | Volume(内联定义) | 直接在 Pod spec 中声明存储 | 存储细节与应用耦合,运维负担重 |
| v1.0+ | PersistentVolume (PV) | 集群级存储资源抽象 | 需要管理员手动预创建 PV |
| v1.0+ | PersistentVolumeClaim (PVC) | 用户通过声明请求存储 | 静态 Provisioning 不够灵活 |
| v1.4+ | StorageClass | 动态 Provisioning | 完全自动化,按需创建 |
| v1.17+ | CSI (Container Storage Interface) | 标准化存储插件接口 | 当前主流方案 |
这一演进的核心思想是关注点分离:
- 集群管理员负责配置存储后端、创建 StorageClass
- 应用开发者只需通过 PVC 声明"我需要多大、什么类型的存储"
- Kubernetes 控制面自动完成 PV 的创建、绑定和生命周期管理
1.3 存储生命周期
一个持久化存储卷的完整生命周期分为四个阶段:
Provisioning → Binding → Using → Reclaiming
(供给) (绑定) (使用) (回收)
-
Provisioning(供给)
- 静态供给:管理员预先手动创建 PV
- 动态供给:用户创建 PVC 时,StorageClass 的 Provisioner 自动创建对应 PV
-
Binding(绑定)
- 控制面将 PVC 与满足条件的 PV 进行一对一绑定
- 绑定关系一旦建立,PVC 独占该 PV
-
Using(使用)
- Pod 通过引用 PVC 使用存储卷
- kubelet 负责将底层存储挂载到 Pod 的容器文件系统中
-
Reclaiming(回收)
- 当 PVC 被删除后,根据 PV 的回收策略决定后续处理
- Retain:保留数据,需手动清理
- Delete:自动删除底层存储资源
二、Volume 基础类型
2.1 emptyDir —— 临时存储
emptyDir 在 Pod 被调度到节点时创建,Pod 删除时销毁。适用于临时性数据存储。
apiVersion: v1
kind: Pod
metadata:
name: sidecar-demo
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "while true; do echo $(date) >> /data/app.log; sleep 5; done"]
volumeMounts:
- name: shared-data
mountPath: /data
- name: log-collector
image: busybox
command: ["sh", "-c", "tail -f /logs/app.log"]
volumeMounts:
- name: shared-data
mountPath: /logs
volumes:
- name: shared-data
emptyDir: {}
使用 Memory 作为存储介质(tmpfs):
volumes:
- name: cache-volume
emptyDir:
medium: Memory # 使用内存作为存储介质
sizeLimit: 256Mi # 限制大小,计入容器内存限额
适用场景:
- Sidecar 容器间的数据共享(如上例日志收集)
- 临时计算缓存(中间文件处理)
- 使用
medium: Memory作为高性能临时缓存
2.2 hostPath —— 节点目录挂载
hostPath 将宿主机节点上的文件或目录挂载到 Pod 中。
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: host-data
mountPath: /usr/share/nginx/html
volumes:
- name: host-data
hostPath:
path: /data/web # 宿主机路径
type: DirectoryOrCreate # 不存在则创建
type 可选值:
"":空字符串,不做任何检查DirectoryOrCreate:目录不存在则创建Directory:目录必须存在FileOrCreate:文件不存在则创建File:文件必须存在
生产环境慎用的原因:
- Pod 调度到不同节点时,数据不一致
- 存在安全隐患(容器可访问宿主机文件系统)
- 破坏了 Pod 的可移植性
- 适用场景仅限:DaemonSet 采集节点日志、访问节点设备文件(如
/dev)、单节点开发调试
2.3 configMap/secret Volume
将 ConfigMap 或 Secret 中的数据以文件形式挂载到容器中:
apiVersion: v1
kind: Pod
metadata:
name: config-demo
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-vol
mountPath: /etc/nginx/conf.d
- name: secret-vol
mountPath: /etc/ssl/certs
readOnly: true
volumes:
- name: config-vol
configMap:
name: nginx-config
items: # 可选:只挂载指定 key
- key: default.conf
path: default.conf # 挂载后的文件名
- name: secret-vol
secret:
secretName: tls-certs
defaultMode: 0400 # 文件权限
关键特性:
- ConfigMap/Secret 更新后,挂载的文件会自动更新(有延迟,约 1-2 分钟)
- 使用
subPath挂载时不会自动更新 - Secret 在 tmpfs 中存储,不落盘
2.4 downwardAPI Volume
将 Pod 的元数据信息以文件形式注入容器:
apiVersion: v1
kind: Pod
metadata:
name: downward-demo
labels:
app: web
version: v2
annotations:
build: "20240101"
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "cat /etc/podinfo/labels && sleep 3600"]
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
- path: "cpu_limit"
resourceFieldRef:
containerName: app
resource: limits.cpu
三、PersistentVolume (PV) 详解
3.1 PV 的本质
PersistentVolume 是集群级别的存储资源抽象(非命名空间资源)。它代表了一块实际的物理存储——可以是 NFS 共享目录、Ceph RBD 块设备、云厂商的云盘,或者本地磁盘。PV 由集群管理员创建和维护,其生命周期独立于任何使用它的 Pod。
3.2 访问模式(Access Modes)
| 访问模式 | 缩写 | 说明 |
|---|---|---|
| ReadWriteOnce | RWO | 只能被单个节点以读写方式挂载 |
| ReadOnlyMany | ROX | 可被多个节点以只读方式挂载 |
| ReadWriteMany | RWX | 可被多个节点以读写方式挂载 |
| ReadWriteOncePod | RWOP | 只能被单个 Pod 以读写方式挂载(v1.27 GA) |
注意:访问模式是存储后端的能力声明,不是强制约束。不同存储后端支持的模式不同:
- 块存储(EBS、Ceph RBD、云盘):通常仅支持 RWO
- 文件存储(NFS、CephFS、GlusterFS):支持 RWX
- 对象存储不直接支持 PV 挂载
3.3 回收策略(Reclaim Policy)
| 策略 | 行为 | 适用场景 |
|---|---|---|
| Retain | PVC 删除后,PV 变为 Released 状态,数据保留,需手动清理 | 生产环境重要数据 |
| Delete | PVC 删除后,PV 及底层存储资源一并删除 | 动态 Provisioning 默认策略 |
| Recycle | 执行 rm -rf /thevolume/* 后重新变为 Available |
已废弃,不建议使用 |
3.4 PV 状态流转
Available ──→ Bound ──→ Released ──→ Available (手动清理后)
↑ │
│ ↓
└──────────────── Failed (回收失败)
- Available:空闲状态,可被 PVC 绑定
- Bound:已与 PVC 绑定
- Released:PVC 已删除,但 PV 尚未被回收
- Failed:自动回收失败
3.5 静态 Provisioning 完整 YAML 示例
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-data01
labels:
type: nfs
environment: production
spec:
capacity:
storage: 50Gi # 存储容量
accessModes:
- ReadWriteMany # 支持多节点读写
persistentVolumeReclaimPolicy: Retain # 回收策略:保留
storageClassName: nfs-slow # 关联的 StorageClass 名称
mountOptions: # NFS 挂载选项
- hard
- nfsvers=4.1
- rsize=1048576
- wsize=1048576
nfs:
server: 192.168.1.100 # NFS 服务器地址
path: /exports/data01 # 导出路径
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local-ssd
spec:
capacity:
storage: 200Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-ssd
local:
path: /mnt/disks/ssd0 # 本地磁盘路径
nodeAffinity: # 本地卷必须设置节点亲和性
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-01
3.6 核心字段说明
| 字段 | 说明 |
|---|---|
capacity.storage |
存储容量声明 |
accessModes |
支持的访问模式列表 |
persistentVolumeReclaimPolicy |
回收策略 |
storageClassName |
关联的 StorageClass,用于与 PVC 匹配 |
mountOptions |
底层存储的挂载选项 |
nodeAffinity |
本地卷必须指定节点亲和性 |
四、PersistentVolumeClaim (PVC) 详解
4.1 PVC 的本质
PersistentVolumeClaim 是用户对存储资源的声明性请求。它是命名空间级别的资源,描述了应用需要多大容量、什么访问模式的存储。PVC 实现了存储消费者(开发者)与存储提供者(管理员)之间的解耦。
4.2 PVC 与 PV 的绑定规则
控制面(PV Controller)按以下规则寻找匹配的 PV:
- storageClassName 一致:PVC 请求的 StorageClass 必须与 PV 声明的一致
- accessModes 匹配:PV 的 accessModes 必须包含 PVC 请求的所有模式
- capacity 满足:PV 的容量必须 ≥ PVC 请求的容量
- selector 匹配(可选):如果 PVC 定义了
selector,则 PV 的 labels 必须满足选择条件 - volumeName 指定(可选):PVC 可通过
volumeName直接指定绑定某个 PV
绑定是一对一的排他性关系。一个 PV 只能绑定一个 PVC,反之亦然。
4.3 PVC 在 Pod 中的使用方式
apiVersion: v1
kind: Pod
metadata:
name: app-with-pvc
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: data-volume
mountPath: /var/www/html # 容器内挂载路径
subPath: website # 可选:使用 PV 中的子目录
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: my-pvc # 引用 PVC 名称
readOnly: false # 是否只读挂载
4.4 完整 YAML 示例(PVC + Pod 引用)
# 1. 创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data-pvc
namespace: production
labels:
app: webapp
spec:
storageClassName: nfs-slow # 指定 StorageClass
accessModes:
- ReadWriteMany # 请求多节点读写
resources:
requests:
storage: 20Gi # 请求 20Gi 容量
selector: # 可选:通过标签筛选 PV
matchLabels:
type: nfs
environment: production
---
# 2. Pod 使用 PVC
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: web-data
mountPath: /usr/share/nginx/html
- name: logs
mountPath: /var/log/nginx
volumes:
- name: web-data
persistentVolumeClaim:
claimName: app-data-pvc
- name: logs
emptyDir: {}
验证绑定状态:
# 查看 PVC 状态
kubectl get pvc -n production
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# app-data-pvc Bound pv-nfs-data01 50Gi RWX nfs-slow 5m
# 查看详细绑定信息
kubectl describe pvc app-data-pvc -n production
五、StorageClass 与动态 Provisioning
5.1 为什么需要 StorageClass
在静态 Provisioning 模式下,管理员需要预先创建足够多的 PV 等待 PVC 绑定。这在大规模集群中带来严重的运维负担:
- 无法预知应用需要多少个多大的 PV
- 人工创建容易出错且效率低
- 存储资源容易碎片化(预创建的 PV 容量与实际需求不匹配)
StorageClass 解决了这个问题:它定义了一类存储的"模板",当 PVC 引用某个 StorageClass 时,对应的 Provisioner 会自动创建符合要求的 PV。
5.2 StorageClass 核心字段
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: ebs.csi.aws.com # CSI 驱动名称
parameters: # 传递给 Provisioner 的参数
type: gp3
iops: "5000"
throughput: "250"
encrypted: "true"
reclaimPolicy: Delete # 动态创建 PV 的回收策略
volumeBindingMode: WaitForFirstConsumer # 延迟绑定
allowVolumeExpansion: true # 允许在线扩容
mountOptions: # 挂载选项
- discard
- noatime
5.3 volumeBindingMode 详解
| 模式 | 行为 | 适用场景 |
|---|---|---|
Immediate |
PVC 创建时立即绑定/创建 PV | 存储不受拓扑限制(如 NFS) |
WaitForFirstConsumer |
等到 Pod 被调度后,根据 Pod 所在节点再创建 PV | 拓扑敏感存储(本地盘、云盘同 AZ) |
WaitForFirstConsumer 的价值在于:
场景:集群有 3 个可用区(AZ-a, AZ-b, AZ-c)
- Immediate 模式:PV 可能创建在 AZ-a,但 Pod 被调度到 AZ-b → 挂载失败
- WaitForFirstConsumer 模式:等 Pod 调度到 AZ-b 后,再在 AZ-b 创建 PV → 确保同 AZ
5.4 allowVolumeExpansion —— 在线扩容
启用后,可通过修改 PVC 的 spec.resources.requests.storage 来触发在线扩容:
# 扩容操作
kubectl patch pvc my-pvc -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'
# 观察扩容进度
kubectl get pvc my-pvc -w
# 等待 CAPACITY 字段更新
# 查看扩容事件
kubectl describe pvc my-pvc | grep -A5 "Conditions"
注意:
- 文件系统扩展(FileSystemResize)需要 Pod 重新挂载(部分 CSI 驱动支持在线 Resize)
- 只能扩容,不能缩容
- 底层存储后端必须支持在线扩容
5.5 设置默认 StorageClass
# 方式一:创建时通过 annotation 指定
# storageclass.kubernetes.io/is-default-class: "true"
# 方式二:事后修改
kubectl patch storageclass fast-ssd -p \
'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# 取消默认
kubectl patch storageclass old-default -p \
'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
当 PVC 没有指定 storageClassName 时,会使用默认 StorageClass(集群中只应有一个默认)。
5.6 完整 StorageClass YAML 示例
# NFS 动态 Provisioning StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-dynamic
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
share: /exports/dynamic
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
mountOptions:
- nfsvers=4.1
- hard
- rsize=1048576
- wsize=1048576
六、NFS 存储对接实战
6.1 NFS Server 搭建
服务端配置(CentOS/RHEL):
# 安装 NFS 服务
yum install -y nfs-utils
# 创建共享目录
mkdir -p /exports/k8s-data
chmod 777 /exports/k8s-data
# 配置导出规则
cat >> /etc/exports << 'EOF'
/exports/k8s-data 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
EOF
# 刷新导出配置
exportfs -rav
# 启动 NFS 服务
systemctl enable --now nfs-server rpcbind
# 验证导出
showmount -e localhost
客户端验证(所有 K8s 节点):
# 安装 NFS 客户端工具
yum install -y nfs-utils # CentOS/RHEL
apt install -y nfs-common # Ubuntu/Debian
# 测试挂载
mount -t nfs 192.168.1.100:/exports/k8s-data /mnt/test
ls /mnt/test
umount /mnt/test
6.2 静态 PV 方式对接 NFS
# PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-static
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: "" # 空字符串表示不关联任何 StorageClass
mountOptions:
- hard
- nfsvers=4.1
nfs:
server: 192.168.1.100
path: /exports/k8s-data
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-static
spec:
storageClassName: "" # 必须与 PV 一致
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
volumeName: nfs-pv-static # 直接指定 PV 名称
6.3 NFS CSI Driver 动态 Provisioning
推荐使用 Kubernetes 官方的 NFS CSI Driver:
# 添加 Helm 仓库
helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm repo update
# 安装 NFS CSI Driver
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs \
--namespace kube-system \
--set controller.replicas=2 \
--set controller.runOnControlPlane=true
# 验证安装
kubectl get pods -n kube-system -l app.kubernetes.io/name=csi-driver-nfs
kubectl get csidrivers
6.4 nfs-subdir-external-provisioner 安装配置
这是另一种广泛使用的 NFS 动态 Provisioner(适合不支持 CSI 的较老集群):
# 添加 Helm 仓库
helm repo add nfs-subdir-external-provisioner \
https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
# 安装
helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--namespace kube-system \
--set nfs.server=192.168.1.100 \
--set nfs.path=/exports/k8s-data \
--set storageClass.name=nfs-dynamic \
--set storageClass.defaultClass=false \
--set storageClass.reclaimPolicy=Delete \
--set storageClass.archiveOnDelete=true \
--set storageClass.accessModes=ReadWriteMany
# 验证
kubectl get storageclass nfs-dynamic
kubectl get pods -n kube-system | grep nfs-provisioner
6.5 完整端到端示例
# Step 1: StorageClass(使用 NFS CSI Driver)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
share: /exports/k8s-data
subDir: ${pvc.metadata.namespace}/${pvc.metadata.name} # 按命名空间/PVC名创建子目录
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
- nfsvers=4.1
- hard
---
# Step 2: PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs-pvc
namespace: default
spec:
storageClassName: nfs-csi
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
---
# Step 3: 验证 Pod
apiVersion: v1
kind: Pod
metadata:
name: nfs-test-pod
namespace: default
spec:
containers:
- name: writer
image: busybox
command: ["sh", "-c", "echo 'Hello NFS Persistent Storage!' > /data/test.txt && cat /data/test.txt && sleep 3600"]
volumeMounts:
- name: nfs-data
mountPath: /data
volumes:
- name: nfs-data
persistentVolumeClaim:
claimName: test-nfs-pvc
验证数据持久化:
# 1. 部署
kubectl apply -f nfs-demo.yaml
# 2. 确认 PVC 绑定
kubectl get pvc test-nfs-pvc
# STATUS 应为 Bound
# 3. 查看 Pod 日志
kubectl logs nfs-test-pod
# 输出: Hello NFS Persistent Storage!
# 4. 删除 Pod 并重建
kubectl delete pod nfs-test-pod
kubectl apply -f nfs-demo.yaml # 只创建 Pod 部分
# 5. 验证数据依然存在
kubectl exec nfs-test-pod -- cat /data/test.txt
# 输出: Hello NFS Persistent Storage!
6.6 NFS 的优缺点与注意事项
| 优点 | 缺点 |
|---|---|
| 支持 RWX 多节点读写 | 性能相对较低(网络 I/O 瓶颈) |
| 部署简单,成本低 | 单点故障(NFS Server) |
| 兼容性好,几乎所有系统支持 | 文件锁机制在容器场景下可能有问题 |
| 支持在线扩容 | 不适合高 IOPS 场景(数据库等) |
注意事项:
- 生产环境务必做 NFS Server 高可用(Keepalived + DRBD 或使用企业级 NAS)
- 关注
no_root_squash的安全隐患 - 大文件场景调大
rsize/wsize(建议 1MB) - 监控 NFS Server 的网络带宽和磁盘 I/O
七、Ceph 存储对接实战
7.1 Ceph RBD(块存储)对接
Ceph RBD(RADOS Block Device)提供高性能块存储,适合数据库等需要 RWO 的场景。
7.1.1 Ceph 集群准备
# 在 Ceph 集群上操作
# 创建专用存储池
ceph osd pool create k8s-rbd-pool 128 128
ceph osd pool application enable k8s-rbd-pool rbd
# 初始化 pool
rbd pool init k8s-rbd-pool
# 创建专用用户(限制权限)
ceph auth get-or-create client.k8s-rbd \
mon 'profile rbd' \
osd 'profile rbd pool=k8s-rbd-pool' \
mgr 'profile rbd pool=k8s-rbd-pool'
# 获取 key(后续配置需要)
ceph auth get-key client.k8s-rbd | base64
# 输出: QVFBdmxxxxxxxxxxxxxxxxxxxx==
# 获取集群 ID
ceph fsid
# 输出: 7f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c
7.1.2 ceph-csi 安装
# 添加 Helm 仓库
helm repo add ceph-csi https://ceph.github.io/csi-charts
helm repo update
# 创建命名空间
kubectl create namespace ceph-csi
# 准备配置
cat > ceph-csi-rbd-values.yaml << 'EOF'
csiConfig:
- clusterID: "7f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"
monitors:
- "192.168.1.10:6789"
- "192.168.1.11:6789"
- "192.168.1.12:6789"
provisioner:
replicaCount: 2
storageClass:
create: true
name: ceph-rbd
clusterID: "7f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"
pool: k8s-rbd-pool
imageFeatures: "layering"
reclaimPolicy: Delete
allowVolumeExpansion: true
secret:
create: true
userID: k8s-rbd
userKey: QVFBdmxxxxxxxxxxxxxxxxxxxx==
EOF
# 安装 ceph-csi-rbd
helm install ceph-csi-rbd ceph-csi/ceph-csi-rbd \
--namespace ceph-csi \
-f ceph-csi-rbd-values.yaml
# 验证
kubectl get pods -n ceph-csi
kubectl get storageclass ceph-rbd
7.1.3 StorageClass 配置(手动方式)
# Secret(存储 Ceph 认证信息)
apiVersion: v1
kind: Secret
metadata:
name: csi-rbd-secret
namespace: ceph-csi
type: kubernetes.io/rbd
stringData:
userID: k8s-rbd
userKey: QVFBdmxxxxxxxxxxxxxxxxxxxx==
---
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd-ssd
provisioner: rbd.csi.ceph.com
parameters:
clusterID: "7f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"
pool: k8s-rbd-pool
imageFormat: "2"
imageFeatures: "layering,exclusive-lock,object-map,fast-diff"
csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi
csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi
csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
7.1.4 RWO 场景实战
# MySQL 使用 Ceph RBD
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
spec:
storageClassName: ceph-rbd-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1 # RBD 是块设备,只能单 Pod 挂载
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
volumes:
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-data
7.2 CephFS(文件存储)对接
7.2.1 RWX 场景(多 Pod 共享)
# Ceph 集群准备
# 确保 MDS 服务运行
ceph fs ls
# 如果没有文件系统,创建一个
ceph osd pool create cephfs-metadata 32
ceph osd pool create cephfs-data 128
ceph fs new k8s-cephfs cephfs-metadata cephfs-data
# 创建 SubVolumeGroup
ceph fs subvolumegroup create k8s-cephfs csi
# 创建认证用户
ceph auth get-or-create client.k8s-cephfs \
mon 'allow r' \
osd 'allow rw pool=cephfs-data' \
mds 'allow rw' \
mgr 'allow rw'
7.2.2 CephFS StorageClass 配置
apiVersion: v1
kind: Secret
metadata:
name: csi-cephfs-secret
namespace: ceph-csi
stringData:
adminID: k8s-cephfs
adminKey: QVFCeHhxxxxxxxxxxxxxxxx==
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cephfs-shared
provisioner: cephfs.csi.ceph.com
parameters:
clusterID: "7f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"
fsName: k8s-cephfs
pool: cephfs-data
csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret
csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi
csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret
csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi
csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret
csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
---
# 多 Pod 共享 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-data
spec:
storageClassName: cephfs-shared
accessModes:
- ReadWriteMany # CephFS 支持 RWX
resources:
requests:
storage: 100Gi
7.3 Rook-Ceph 一体化方案
Rook 是 Kubernetes 上部署 Ceph 的最佳实践方案——将 Ceph 集群完全运行在 K8s 内部:
# 部署 Rook Operator
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 \
--set csi.enableRbdDriver=true \
--set csi.enableCephfsDriver=true
# 等待 Operator 就绪
kubectl -n rook-ceph wait --for=condition=Ready pod -l app=rook-ceph-operator --timeout=300s
# 部署 Ceph 集群(使用集群内节点的裸盘)
cat > ceph-cluster.yaml << 'EOF'
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v18.2
dataDirHostPath: /var/lib/rook
mon:
count: 3
allowMultiplePerNode: false
mgr:
count: 2
dashboard:
enabled: true
storage:
useAllNodes: true
useAllDevices: false
devices:
- name: "sdb" # 指定使用的裸盘
- name: "sdc"
EOF
kubectl apply -f ceph-cluster.yaml
# 创建 Block Pool 和 StorageClass
cat > ceph-block-pool.yaml << 'EOF'
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: replicapool
namespace: rook-ceph
spec:
failureDomain: host
replicated:
size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
EOF
kubectl apply -f ceph-block-pool.yaml
7.4 RBD vs CephFS 对比
| 维度 | Ceph RBD | CephFS |
|---|---|---|
| 存储类型 | 块存储 | 文件存储 |
| 访问模式 | RWO(单节点读写) | RWX(多节点读写) |
| 性能 | 高(直接块 I/O) | 中等(文件系统开销) |
| 适用场景 | 数据库、单实例有状态应用 | 多 Pod 共享数据、CMS、日志存储 |
| 文件系统 | 需要在 RBD 上格式化(ext4/xfs) | 自带 POSIX 兼容文件系统 |
| 快照支持 | 支持 | 支持 |
| 扩展性 | 优秀 | 优秀 |
| 运维复杂度 | 中 | 中高(需要 MDS) |
八、云盘存储对接
8.1 阿里云云盘(ACK + Disk CSI)
# 阿里云 ESSD StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-essd-pl1
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_essd # cloud_efficiency | cloud_ssd | cloud_essd
performanceLevel: PL1 # PL0 | PL1 | PL2 | PL3
encrypted: "true"
fstype: ext4
# 按需付费
# chargeType: PostPaid
# 包年包月(需配合 annotation)
# chargeType: PrePaid
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer # 确保云盘与 Pod 同可用区
---
# 高性能 ESSD PL3(面向数据库)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-essd-pl3
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_essd
performanceLevel: PL3 # 最高 1000000 IOPS
fstype: xfs # XFS 对大文件和并发写入更友好
reclaimPolicy: Retain # 生产数据保留
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
使用 PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
annotations:
# 快照恢复(可选)
# volume.alibabacloud.com/snapshot-id: "s-xxxxxxxxxx"
spec:
storageClassName: alicloud-disk-essd-pl1
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
8.2 AWS EBS(EKS + EBS CSI Driver)
# 安装 EBS CSI Driver(EKS 推荐方式)
eksctl create addon --name aws-ebs-csi-driver --cluster my-cluster \
--service-account-role-arn arn:aws:iam::111122223333:role/AmazonEKS_EBS_CSI_DriverRole \
--force
# gp3 StorageClass(推荐默认)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000" # gp3 基线 3000 IOPS(免费)
throughput: "125" # 基线 125 MiB/s
encrypted: "true"
kmsKeyId: "arn:aws:kms:us-east-1:111122223333:key/xxxx" # 可选 KMS 加密
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
# io2 StorageClass(高性能数据库)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-io2
provisioner: ebs.csi.aws.com
parameters:
type: io2
iops: "10000"
encrypted: "true"
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
8.3 腾讯云 CBS(TKE)
# 腾讯云高性能云盘
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cbs-premium
provisioner: com.tencent.cloud.csi.cbs
parameters:
diskType: CLOUD_PREMIUM # CLOUD_PREMIUM | CLOUD_SSD | CLOUD_HSSD
diskChargeType: POSTPAID_BY_HOUR
encrypt: "ENCRYPT"
# aspId: "asp-xxxxx" # 可选:关联快照策略
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
# 腾讯云增强型 SSD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cbs-hssd
provisioner: com.tencent.cloud.csi.cbs
parameters:
diskType: CLOUD_HSSD
diskChargeType: POSTPAID_BY_HOUR
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
8.4 通用配置模式
各云厂商的 CSI 存储对接遵循统一的架构模式:
CSI Driver (DaemonSet + Deployment)
↓
StorageClass (定义存储模板)
↓
PVC (用户声明)
↓
CSI Provisioner 调用云 API 创建云盘
↓
PV 自动创建并绑定
↓
kubelet 通过 CSI Node Plugin 完成 Attach + Mount
关键 Annotations(通用):
| Annotation | 作用 |
|---|---|
volume.kubernetes.io/selected-node |
记录 PV 所在节点 |
pv.kubernetes.io/provisioned-by |
记录 Provisioner 名称 |
volume.beta.kubernetes.io/storage-provisioner |
兼容旧版标注 |
九、StatefulSet 与存储的配合
9.1 volumeClaimTemplates 详解
StatefulSet 通过 volumeClaimTemplates 为每个 Pod 副本自动创建独立的 PVC:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels:
app: redis-cluster
template:
metadata:
labels:
app: redis-cluster
spec:
containers:
- name: redis
image: redis:7.2
ports:
- containerPort: 6379
name: client
- containerPort: 16379
name: gossip
command: ["redis-server", "/etc/redis/redis.conf"]
volumeMounts:
- name: redis-data
mountPath: /data
- name: redis-config
mountPath: /etc/redis
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "2Gi"
volumes:
- name: redis-config
configMap:
name: redis-cluster-config
volumeClaimTemplates: # 关键:PVC 模板
- metadata:
name: redis-data # PVC 名称前缀
spec:
storageClassName: ceph-rbd-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
9.2 有序创建 PVC
StatefulSet 创建的 PVC 命名遵循固定规则:
{volumeClaimTemplate.name}-{statefulset.name}-{ordinal}
对于上述 Redis 集群示例,会创建:
redis-data-redis-cluster-0
redis-data-redis-cluster-1
redis-data-redis-cluster-2
redis-data-redis-cluster-3
redis-data-redis-cluster-4
redis-data-redis-cluster-5
# 查看 StatefulSet 创建的所有 PVC
kubectl get pvc -l app=redis-cluster
# NAME STATUS VOLUME CAPACITY STORAGECLASS
# redis-data-redis-cluster-0 Bound pvc-a1b2c3 10Gi ceph-rbd-ssd
# redis-data-redis-cluster-1 Bound pvc-d4e5f6 10Gi ceph-rbd-ssd
# redis-data-redis-cluster-2 Bound pvc-g7h8i9 10Gi ceph-rbd-ssd
# ...
9.3 扩缩容对 PVC 的影响
| 操作 | PVC 行为 |
|---|---|
| 扩容(replicas 增加) | 自动创建新的 PVC(如 redis-data-redis-cluster-6) |
| 缩容(replicas 减少) | PVC 不会自动删除(数据保留,需手动清理) |
| 删除 StatefulSet | PVC 不会自动删除(即使设置了级联删除) |
重要提示:缩容后遗留的 PVC 需要手动清理,否则会造成存储资源浪费。但这也是一种安全机制——防止误缩容导致数据丢失。
# 手动清理缩容后遗留的 PVC
kubectl delete pvc redis-data-redis-cluster-5
kubectl delete pvc redis-data-redis-cluster-4
9.4 完整 StatefulSet + 存储示例(MySQL 主从)
# MySQL 主从 StatefulSet
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
primary.cnf: |
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-format=ROW
gtid-mode=ON
enforce-gtid-consistency=ON
replica.cnf: |
[mysqld]
server-id=2
relay-log=relay-bin
read-only=ON
gtid-mode=ON
enforce-gtid-consistency=ON
---
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- port: 3306
name: mysql
clusterIP: None # Headless Service
selector:
app: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:8.0
command:
- bash
- "-c"
- |
set -ex
# 根据 Pod 序号生成 server-id
[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo "[mysqld]" > /mnt/conf.d/server-id.cnf
echo "server-id=$((100 + $ordinal))" >> /mnt/conf.d/server-id.cnf
# 序号 0 为主节点
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2000m"
memory: "4Gi"
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysql", "-uroot", "-p${MYSQL_ROOT_PASSWORD}", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
storageClassName: ceph-rbd-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
十、存储常见问题排查
10.1 PVC 一直 Pending
现象:
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# my-pvc Pending fast-ssd 5m
排查步骤:
# 第一步:查看 PVC 事件
kubectl describe pvc my-pvc
# 常见原因及解决方案:
| 事件信息 | 原因 | 解决方案 |
|---|---|---|
no persistent volumes available |
没有匹配的 PV(静态模式) | 创建满足条件的 PV |
storageclass "xxx" not found |
StorageClass 不存在 | 检查 SC 名称拼写,创建对应 SC |
waiting for first consumer |
volumeBindingMode 为 WaitForFirstConsumer | 正常现象,需要有 Pod 使用该 PVC |
insufficient quota |
配额不足 | 联系管理员扩大 ResourceQuota |
provisioning failed |
Provisioner 创建存储失败 | 查看 Provisioner Pod 日志 |
# 查看 Provisioner Pod 日志(以 NFS CSI 为例)
kubectl logs -n kube-system -l app.kubernetes.io/name=csi-driver-nfs -c nfs
# 查看存储类是否存在
kubectl get sc
# 检查 CSI Driver 状态
kubectl get csidrivers
kubectl get csinodes
10.2 Pod 挂载失败
现象:
kubectl get pod my-pod
# NAME READY STATUS RESTARTS AGE
# my-pod 0/1 ContainerCreating 0 3m
排查步骤:
# 查看 Pod 事件
kubectl describe pod my-pod
# 常见错误信息:
# - "Multi-Attach error": PV 已被其他节点挂载(RWO 限制)
# - "mount failed: exit status 32": 节点无法连接存储后端
# - "rpc error: code = Internal": CSI 插件内部错误
Multi-Attach 问题解决:
# 找出谁在使用该 PV
kubectl get pv <pv-name> -o yaml | grep -A5 claimRef
# 找出使用该 PVC 的 Pod
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.volumes[]?.persistentVolumeClaim.claimName == "my-pvc") | .metadata.name'
# 如果是旧 Pod 未正常终止导致 VolumeAttachment 残留
kubectl get volumeattachments
kubectl delete volumeattachment <name> # 谨慎操作!确认旧 Pod 确实已终止
NFS 挂载失败排查:
# 在问题节点上测试 NFS 连通性
showmount -e 192.168.1.100
mount -t nfs 192.168.1.100:/exports/k8s-data /mnt/test
# 检查节点是否安装了 nfs-utils
rpm -qa | grep nfs-utils # CentOS
dpkg -l | grep nfs-common # Ubuntu
10.3 数据丢失排查
场景: 删除 PVC 后数据丢失
# 检查 StorageClass 的回收策略
kubectl get sc <storageclass-name> -o yaml | grep reclaimPolicy
# 如果是 Delete,PVC 删除时 PV 和底层数据会一并删除!
# 检查已有 PV 的回收策略
kubectl get pv -o custom-columns=NAME:.metadata.name,RECLAIM:.spec.persistentVolumeReclaimPolicy
# 修改 PV 回收策略为 Retain(保护现有数据)
kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
数据恢复(Retain 策略下):
当 PVC 被删除、PV 变为 Released 状态后,需要手动操作才能重新使用:
# 1. 查看 Released 状态的 PV
kubectl get pv | grep Released
# 2. 清除 PV 的 claimRef(使其重新变为 Available)
kubectl patch pv <pv-name> --type json -p '[{"op":"remove","path":"/spec/claimRef"}]'
# 3. 创建新的 PVC 绑定到该 PV
# 使用 volumeName 指定绑定
10.4 扩容失败
# 检查 StorageClass 是否启用扩容
kubectl get sc <sc-name> -o yaml | grep allowVolumeExpansion
# 如果没有或为 false,需要先修改 SC:
kubectl patch sc <sc-name> -p '{"allowVolumeExpansion": true}'
# 检查扩容进度
kubectl describe pvc <pvc-name>
# 关注 Conditions 中的 FileSystemResizePending
# 部分存储需要 Pod 重启才能完成文件系统扩展
kubectl delete pod <pod-name> # Pod 重建后触发 resize
10.5 排查命令汇总
# ========== 全局状态 ==========
# 查看所有 StorageClass
kubectl get sc
# 查看所有 PV 及状态
kubectl get pv -o wide
# 查看指定命名空间 PVC
kubectl get pvc -n <namespace> -o wide
# ========== 详细诊断 ==========
# PV 详情(查看 capacity、accessModes、claimRef)
kubectl describe pv <pv-name>
# PVC 详情(查看 Events、Conditions)
kubectl describe pvc <pvc-name> -n <namespace>
# Pod 挂载详情
kubectl describe pod <pod-name> -n <namespace> | grep -A20 "Volumes:"
# ========== 事件排查 ==========
# 查看存储相关事件
kubectl get events --field-selector reason=ProvisioningFailed -A
kubectl get events --field-selector reason=FailedMount -A
kubectl get events --field-selector involvedObject.kind=PersistentVolumeClaim -A
# ========== CSI 排查 ==========
# CSI Driver 状态
kubectl get csidrivers
kubectl get csinodes
# CSI 插件 Pod 日志
kubectl logs -n kube-system <csi-controller-pod> -c csi-provisioner
kubectl logs -n kube-system <csi-node-pod> -c csi-driver
# VolumeAttachment(卷挂载状态)
kubectl get volumeattachments -o wide
# ========== 节点层面 ==========
# 查看节点上已挂载的卷
kubectl get pods -o json --field-selector spec.nodeName=<node> | \
jq '.items[].spec.volumes[] | select(.persistentVolumeClaim)'
# 进入节点查看实际挂载
# ssh node-01
# mount | grep pvc
# df -h | grep pvc
十一、最佳实践总结
11.1 生产环境存储选型建议
| 应用类型 | 推荐存储 | 理由 |
|---|---|---|
| 数据库(MySQL/PostgreSQL) | Ceph RBD / 云盘 ESSD | 高 IOPS,RWO 满足需求 |
| 消息队列(Kafka/RabbitMQ) | 本地 SSD + Ceph RBD | 极致性能选本地盘,可靠性选 RBD |
| 文件共享(CMS、媒体) | CephFS / NFS | RWX 多 Pod 共享 |
| 日志存储 | CephFS / 对象存储 | 大容量、顺序写入 |
| AI/ML 训练数据 | CephFS / 高性能 NAS | 大文件、高带宽 |
| 临时缓存 | emptyDir (Memory) | 高速、重启可丢 |
| CI/CD 构建 | emptyDir / 本地 SSD | 临时性、高速 |
通用原则:
- 性能优先:数据库、中间件选块存储(RBD/云盘)
- 共享优先:多 Pod 共享选文件存储(CephFS/NFS)
- 成本敏感:评估实际 IOPS 需求,避免过度配置
- 数据安全:生产数据务必使用
Retain回收策略 - 拓扑感知:使用
WaitForFirstConsumer避免跨 AZ 挂载
11.2 备份策略(VolumeSnapshot)
Kubernetes 从 v1.20 起 GA 支持 VolumeSnapshot,可实现存储卷的快照备份:
# VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ceph-rbd-snapshot
driver: rbd.csi.ceph.com
deletionPolicy: Delete
parameters:
clusterID: "7f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"
csi.storage.k8s.io/snapshotter-secret-name: csi-rbd-secret
csi.storage.k8s.io/snapshotter-secret-namespace: ceph-csi
---
# 创建快照
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-data-snapshot-20240101
spec:
volumeSnapshotClassName: ceph-rbd-snapshot
source:
persistentVolumeClaimName: mysql-data-mysql-0 # 要备份的 PVC
---
# 从快照恢复新 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-restored
spec:
storageClassName: ceph-rbd-ssd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
dataSource:
name: mysql-data-snapshot-20240101
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
定期备份 CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: pvc-snapshot-backup
spec:
schedule: "0 2 * * *" # 每天凌晨 2 点
jobTemplate:
spec:
template:
spec:
serviceAccountName: snapshot-creator
containers:
- name: snapshot-creator
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
DATE=$(date +%Y%m%d-%H%M%S)
cat <<SNAP | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-backup-${DATE}
spec:
volumeSnapshotClassName: ceph-rbd-snapshot
source:
persistentVolumeClaimName: mysql-data-mysql-0
SNAP
# 清理 7 天前的快照
kubectl get volumesnapshots -o json | \
jq -r '.items[] | select(.metadata.creationTimestamp < (now - 604800 | strftime("%Y-%m-%dT%H:%M:%SZ"))) | .metadata.name' | \
xargs -r kubectl delete volumesnapshot
restartPolicy: OnFailure
11.3 监控存储容量
使用 Prometheus + Grafana 监控:
关键监控指标:
# kubelet 暴露的 PVC 容量指标
kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="xxx"}
kubelet_volume_stats_available_bytes{persistentvolumeclaim="xxx"}
kubelet_volume_stats_used_bytes{persistentvolumeclaim="xxx"}
kubelet_volume_stats_inodes{persistentvolumeclaim="xxx"}
kubelet_volume_stats_inodes_free{persistentvolumeclaim="xxx"}
kubelet_volume_stats_inodes_used{persistentvolumeclaim="xxx"}
PrometheusRule 告警规则:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: storage-alerts
namespace: monitoring
spec:
groups:
- name: storage.rules
rules:
# PVC 容量使用超过 80% 告警
- alert: PVCUsageHigh
expr: |
(kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} 使用率超过 80%"
description: "命名空间 {{ $labels.namespace }} 的 PVC {{ $labels.persistentvolumeclaim }} 已使用 {{ $value | humanizePercentage }},请及时扩容。"
# PVC 容量使用超过 90% 紧急告警
- alert: PVCUsageCritical
expr: |
(kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.9
for: 2m
labels:
severity: critical
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} 使用率超过 90%(紧急)"
description: "命名空间 {{ $labels.namespace }} 的 PVC {{ $labels.persistentvolumeclaim }} 已使用 {{ $value | humanizePercentage }},即将写满!"
# inode 使用率告警
- alert: PVCInodesHigh
expr: |
(kubelet_volume_stats_inodes_used / kubelet_volume_stats_inodes) > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} inode 使用率超过 85%"
# PV 处于 Failed 状态
- alert: PVFailed
expr: kube_persistentvolume_status_phase{phase="Failed"} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "PV {{ $labels.persistentvolume }} 处于 Failed 状态"
description: "请立即检查该 PV 对应的存储后端状态。"
日常巡检脚本:
#!/bin/bash
# storage-check.sh - K8s 存储日常巡检
echo "========== PV 状态汇总 =========="
kubectl get pv -o custom-columns=\
NAME:.metadata.name,\
CAPACITY:.spec.capacity.storage,\
ACCESS:.spec.accessModes[0],\
RECLAIM:.spec.persistentVolumeReclaimPolicy,\
STATUS:.status.phase,\
STORAGECLASS:.spec.storageClassName
echo ""
echo "========== 异常 PVC(非 Bound 状态) =========="
kubectl get pvc -A --field-selector='status.phase!=Bound'
echo ""
echo "========== Released 状态 PV(需关注) =========="
kubectl get pv --field-selector='status.phase=Released'
echo ""
echo "========== PVC 容量使用 Top10 =========="
kubectl get --raw /api/v1/persistentvolumeclaims | \
jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name) \(.spec.resources.requests.storage)"' | \
sort -k2 -hr | head -10
echo ""
echo "========== 存储相关事件(最近1小时) =========="
kubectl get events -A --field-selector reason=ProvisioningFailed --sort-by=.metadata.creationTimestamp | tail -20
kubectl get events -A --field-selector reason=FailedMount --sort-by=.metadata.creationTimestamp | tail -20
总结
Kubernetes 持久化存储体系的设计遵循了"声明式 API + 关注点分离"的核心理念。从简单的 Volume 到完整的 PV/PVC/StorageClass 动态供给体系,再到 CSI 标准化插件接口,存储架构不断演进以适应云原生场景下的多样化需求。
在实际生产落地中,关键要把握以下几点:
- 选型匹配:根据应用特征(IOPS需求、共享需求、数据重要性)选择合适的存储后端
- 自动化运维:通过 StorageClass + CSI 实现全自动化的存储生命周期管理
- 安全兜底:生产环境必须配置 Retain 回收策略 + 定期快照备份
- 容量监控:建立完善的存储容量告警体系,防止容量耗尽导致业务中断
- 拓扑感知:使用
WaitForFirstConsumer绑定模式确保存储和计算在同一拓扑域
掌握了本文的知识体系,你应该能够自信地设计和实施任何规模的 Kubernetes 持久化存储方案。
参考资料:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)