K8s 存储体系:三层抽象
Kubernetes 存储体系通过三层抽象解耦了存储使用方与提供方,这是理解容器持久化存储的核心。根据 K8s 存储文档,三层结构如下:
┌──────────────────────────────────────────────────────┐
│ Pod (使用方) │
│ volumeMounts → volumes │
├──────────────────────────────────────────────────────┤
│ PVC (声明) — 用户申请存储 │
│ "我需要 10Gi RWO 的存储" │
├──────────────────────────────────────────────────────┤
│ StorageClass (动态供给) — 存储模板 │
│ "使用 ceph-rbd 驱动,reclaim: Retain" │
├──────────────────────────────────────────────────────┤
│ PV (物理资源) — 实际存储 │
│ "10.0.0.5:/data/pvc-xxx (NFS)" │
└──────────────────────────────────────────────────────┘
PV(PersistentVolume)
PV 是集群级资源,代表物理存储的抽象。PV 可以由管理员手动创建,也可通过 StorageClass 自动供给:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-data
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteMany # 多节点读写
persistentVolumeReclaimPolicy: Retain
nfs:
server: 10.0.0.5
path: /data/k8s-share
storageClassName: "" # 静态 PV 不绑定 StorageClass
PVC(PersistentVolumeClaim)
PVC 是命名空间级的存储声明,用户通过 PVC 申请所需存储,系统自动匹配满足条件的 PV:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data-pvc
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: ceph-rbd
resources:
requests:
storage: 20Gi
StorageClass(动态供给)
StorageClass 是动态存储供给的核心,绑定 CSI 驱动和参数模板,实现按需创建 PV:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd
provisioner: rbd.csi.ceph.com
reclaimPolicy: Retain
allowVolumeExpansion: true # 支持在线扩容
parameters:
clusterID: ceph-cluster-1
pool: k8s-rbd-pool
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: ceph-secret
csi.storage.k8s.io/provisioner-secret-namespace: ceph-system
AccessMode 详解
| AccessMode | 缩写 | 含义 | 典型场景 |
|---|---|---|---|
| ReadWriteOnce | RWO | 单节点读写 | MySQL、PostgreSQL |
| ReadOnlyMany | ROX | 多节点只读 | 配置文件、静态资源 |
| ReadWriteMany | RWX | 多节点读写 | NFS、CephFS、共享数据 |
| ReadWriteOncePod | RWOP | 单 Pod 读写 | K8s 1.22+,精确到 Pod 级 |
CSI 驱动机制
CSI(Container Storage Interface)是 K8s 与存储后端之间的标准接口,取代了早期的 in-tree 存储插件。根据 CSI 规范,CSI 驱动通过 Sidecar 组件与 K8s 控制面交互。
CSI 架构
┌───────────────┐ gRPC ┌──────────────┐
│ K8s 控制面 │◄──────────►│ CSI Sidecar │
│ (PV/PVC/SC) │ │ (provisioner │
└───────────────┘ │ attacher) │
└──────┬───────┘
│ gRPC
┌──────▼───────┐
│ CSI Driver │
│ (存储后端) │
└──────────────┘
CSI 驱动由三个 Sidecar 组件协同工作:
- external-provisioner:监听 PVC 事件,调用 CSI 驱动创建/删除存储卷
- external-attacher:管理 VolumeAttachment,挂载/卸载存储到 Node
- external-snapshotter:支持存储快照功能
常见 CSI 驱动
NFS CSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 10.0.0.5
share: /data/k8s-share
subDir: ${pvc.metadata.namespace}/${pvc.name}
reclaimPolicy: Delete
volumeBindingMode: Immediate
NFS 适合共享文件场景,优点是原生 RWX 支持,缺点是网络性能依赖 NFS 服务器。
Ceph RBD CSI
Ceph RBD 提供块存储,适合数据库等高 IO 场景:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd-fast
provisioner: rbd.csi.ceph.com
parameters:
clusterID: ceph-cluster-1
pool: ssd-pool # 使用 SSD 池
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
- discard # 启用 TRIM
Local Path Provisioner
Rancher Local Path Provisioner 使用 Node 本地磁盘,适合低延迟、高吞吐场景:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer # 延迟到 Pod 调度时才绑定
reclaimPolicy: Delete
WaitForFirstConsumer 是 Local Volume 的关键设置——它确保 PV 在 Pod 调度到某个 Node 后才创建,避免调度冲突。
存储选型矩阵
不同存储方案在性能、成本和可移植性上各有取舍:
| 存储类型 | 性能 | 成本 | 可移植性 | RWX | 适用场景 |
|---|---|---|---|---|---|
| Local Path | 极高 | 极低 | 差 | 否 | 缓存、临时数据、单副本数据库 |
| NFS | 中 | 低 | 中 | 是 | 共享文件、配置同步 |
| Ceph RBD | 高 | 中 | 中 | 否 | 数据库、块设备 |
| CephFS | 中高 | 中 | 中 | 是 | 共享存储、大数据 |
| 云盘(EBS/PD) | 高 | 高 | 差 | 否 | 云上数据库 |
| Longhorn | 中高 | 低 | 中 | 否 | K8s 原生分布式存储 |
选型决策树
需要 RWX(多 Pod 同时读写)?
├─ 是 → NFS(简单)/ CephFS(高性能)
└─ 否 → 需要高 IOPS?
├─ 是 → Local Path(单节点)/ Ceph RBD SSD(分布式)
└─ 否 → 成本敏感?
├─ 是 → Local Path / Longhorn
└─ 否 → Ceph RBD / 云盘
有状态应用部署注意事项
MySQL 部署
MySQL 是典型的有状态应用,存储配置需格外注意:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless
replicas: 1 # 主从需用 Operator 管理
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
- name: config
mountPath: /etc/mysql/conf.d
volumes:
- name: config
configMap:
name: mysql-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ceph-rbd
resources:
requests:
storage: 50Gi
关键注意事项:
- 数据安全:
reclaimPolicy: Retain,防止误删 PVC 导致数据丢失 - 资源限制:内存限制不低于
innodb_buffer_pool_size+ 1GB - 调度亲和:使用
nodeSelector或podAntiAffinity固定到高性能节点 - 备份策略:使用 Velero 或存储快照定期备份
Redis 部署
Redis 需要区分缓存和持久化两种场景:
# Redis 持久化模式需启用 AOF
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: local-path # Redis IO 敏感,优先 Local
resources:
requests:
storage: 10Gi
Elasticsearch 部署
ES 对存储 IO 要求极高,每个节点需要独立 PV:
# ES Pod 使用 initContainer 优化系统参数
initContainers:
- name: sysctl
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
env:
- name: ES_JAVA_OPTS
value: "-Xms2g -Xmx2g" # 堆内存 = 容器内存的 50%
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
存储故障排查
PVC 一直 Pending
# 查看 PVC 事件
kubectl describe pvc app-data-pvc -n production
# 常见原因排查
# 1. StorageClass 不存在
kubectl get sc
# 2. 没有可用的 PV(静态供给)
kubectl get pv
# 3. StorageClass 的 provisioner 未运行
kubectl get pods -n kube-system | grep csi
# 4. 存储后端连接失败(检查 CSI 日志)
kubectl logs -n ceph-system ceph-csi-rbd-provisioner-xxx
PV 挂载失败
# 查看 Pod 事件中的挂载错误
kubectl describe pod <pod-name>
# 常见错误及处理
# "MountVolume.MountDevice failed" → 存储后端不可达
# "Unable to attach or mount volumes" → Node 上 CSI Node 插件异常
# 登录目标 Node 检查
# 检查块设备
lsblk
# 检查挂载点
mount | grep <pv-name>
# 检查 CSI Node 插件
crictl ps | grep csi
存储扩容失败
# 确认 StorageClass 支持扩容
kubectl get sc ceph-rbd -o jsonpath='{.allowVolumeExpansion}'
# 输出应为 true
# 执行扩容
kubectl patch pvc app-data-pvc -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'
# 查看扩容状态
kubectl get pvc app-data-pvc -o jsonpath='{.status.conditions}'
常见故障速查表
| 故障现象 | 可能原因 | 排查命令 |
|---|---|---|
| PVC Pending | SC 不存在/Provisioner 未运行 | kubectl describe pvc |
| Pod ContainerCreating | PV 挂载失败 | kubectl describe pod |
| Pod 事件 “disk pressure” | Node 磁盘满 | kubectl describe node |
| IO 延迟高 | 存储后端性能差 | iostat -x 1 |
| PVC 扩容无响应 | SC 未开启扩容 | kubectl get sc |
总结
容器持久化存储的核心在于理解三层抽象、合理选型、预防故障。三层抽象(PV/PVC/StorageClass)解耦了使用与供给,CSI 标准化了驱动接口。选型时需要平衡性能、成本和可移植性三要素——Local Path 适合极致性能但牺牲可移植性,Ceph RBD 提供分布式块存储的均衡方案,NFS 则是共享存储的经济选择。
生产环境的关键原则:数据安全第一(Retain 策略)、监控存储健康、做好备份方案。定期验证备份的可用性,比存储选型本身更重要。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- K8s 存储文档 — Kubernetes 官方,参考了K8s 存储文档相关内容
- CSI 规范 — Kubernetes-csi,参考了CSI 规范相关内容
- Rancher Local Path Provisioner — GitHub 开源社区,参考了Rancher Local Path Provisioner相关内容