【架构实战】Kubernetes存储实战:从EmptyDir到Ceph CSI的持久化存储选型指南
上篇聊了K8s故障排查,这篇接上存储------这是K8s里最容易踩坑、也最容易被低估的部分。很多人以为"挂个PV就行了",结果生产环境遇到:数据库被调度到新节点数据丢失、读写分离应用连不到同一个PVC、存储性能瓶颈拖垮整个集群......存储选型不对,轻则发布抖动,重则数据永失。
这篇把K8s存储从原理到选型讲透,帮你避开那些"文档里没写但踩了就炸"的坑。
一、先理解PV/PVC的抽象价值
很多人觉得PV/PVC是"多此一举",直接挂路径不就行了?不对。PV/PVC解决的核心问题是存储供给与消费解耦:
- PV(Persistent Volume):存储管理员视角,描述"我有块存储,100GB,性能XX,类型是NFS/Ceph/iSCSI",不关心谁用;
- PVC(Persistent Volume Claim):应用开发者视角,声明"我要100GB,读写模式RWO,存储类SSD",不关心底层是谁提供的;
- StorageClass:存储类,定义"这类存储怎么动态创建PV",把管理员从手动创建PV的苦海中解放出来。
三者的关系:PVC绑定PV,PV由StorageClass动态创建(或管理员手动创建),Pod通过PVC使用存储。一层抽象一层自由------应用开发者不用关心底层是NFS还是Ceph,存储管理员不用关心哪个应用用哪块盘。
二、临时存储:EmptyDir与HostPath
2.1 EmptyDir:Pod内的临时共享空间
EmptyDir在Pod创建时分配,Pod删除时数据随之消失。典型场景:
- 缓存目录(不需要持久化);
- 临时计算结果(MapReduce中间文件);
- InitContainer与主容器共享数据(配置生成、代码拉取)。
yaml
volumes:
- name: cache-volume
emptyDir: {} # 默认用节点磁盘
# 也可以用内存(更快但大小受Pod内存限制)
volumes:
- name: tmp-volume
emptyDir:
medium: Memory
sizeLimit: 512Mi
注意 :EmptyDir的生命周期绑死Pod,不是容器。Pod里所有容器共享同一个EmptyDir,容器重启数据不丢,Pod删除数据才消失。
2.2 HostPath:挂节点路径(慎用)
HostPath把节点上的路径挂到Pod里,常见于:
- 节点监控Agent(访问/proc、/sys);
- 日志采集(访问/var/log);
- 单机测试环境(绕过网络存储)。
yaml
volumes:
- name: node-logs
hostPath:
path: /var/log
type: Directory
致命风险 :HostPath绕过了K8s的存储抽象,数据与节点绑定,Pod被调度到新节点就找不到原数据。生产环境慎用甚至禁用(通过PodSecurityPolicy限制)。我见过多次事故:数据库用HostPath,节点故障迁移后数据"消失",实际还在旧节点上。
三、网络存储:NFS、Ceph、GlusterFS
3.1 NFS:最简单但也最脆弱
NFS是K8s里最常见的共享存储,配置简单,一个PV多个Pod同时读写。
yaml
volumes:
- name: nfs-volume
nfs:
server: 192.168.1.100
path: /data/shared
readOnly: false
优点:
- 部署简单,一台NFS服务器即可;
- 支持ReadWriteMany(多Pod同时读写);
- 数据不在节点上,Pod迁移不影响数据。
缺点:
- 单点故障:NFS服务器挂了,所有依赖它的Pod全废;
- 性能瓶颈:NFS协议本身开销大,高并发写容易卡顿;
- 文件锁不可靠:某些应用(如SQLite)依赖文件锁,NFS的锁机制在Pod跨节点时有坑。
适用场景 :配置中心、静态资源、日志归档、对性能要求不高的共享数据。数据库、消息队列这种高IO应用别用NFS。
3.2 Ceph RBD:高性能块存储
Ceph是分布式存储系统,RBD(RADOS Block Device)是其块存储接口,在K8s里用StorageClass动态创建PV。
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd
provisioner: rbd.csi.ceph.com
parameters:
pool: rbd
clusterID: ceph-cluster
csi.storage.k8s.io/provisioner-secret-name: ceph-secret
csi.storage.k8s.io/node-stage-secret-name: ceph-secret
reclaimPolicy: Delete
allowVolumeExpansion: true
优点:
- 高性能:块设备直接映射到Pod,接近本地盘性能;
- 高可用:Ceph多副本,节点故障数据不丢;
- 动态扩容:PVC扩容后Ceph自动扩展RBD镜像。
缺点:
- ReadWriteOnce:一个PVC只能被一个Pod挂载(块存储特性),多Pod共享需要用CephFS;
- 部署复杂:Ceph集群本身要搭、要运维,门槛不低;
- 脑裂风险:网络抖动时可能触发OSD切换,IO短暂中断。
适用场景:数据库(MySQL、PostgreSQL)、消息队列(Kafka、RocketMQ)、单实例有状态应用。
3.3 CephFS:共享文件系统
CephFS是Ceph的文件系统接口,支持ReadWriteMany,多Pod共享数据。
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cephfs
provisioner: cephfs.csi.ceph.com
parameters:
clusterID: ceph-cluster
fsName: myfs
pool: cephfs-data
优点 :兼具NFS的共享能力和Ceph的高可用。缺点:性能略逊于RBD,MDS(元数据服务器)是瓶颈。
四、云厂商存储:AWS EBS、阿里云盘、腾讯CBS
4.1 云盘的本质
云厂商的块存储(AWS EBS、阿里云盘、腾讯CBS)本质是远程块设备,通过StorageClass自动创建。
yaml
# 阿里云盘示例
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: alicloud-disk-ssd
provisioner: diskplugin.csi.alibabacloud.com
parameters:
type: cloud_ssd
regionId: cn-hangzhou
reclaimPolicy: Delete
allowVolumeExpansion: true
优点:
- 免运维:云厂商负责底层高可用;
- 性能分层:SSD、高效云盘、普通云盘可选;
- 快照备份:云盘快照一键备份恢复。
缺点:
- ReadWriteOnce:一个PVC只能挂一个节点,节点间迁移有短暂IO中断;
- 跨可用区受限:云盘绑可用区,Pod跨AZ迁移要额外配置。
适用场景:云上生产环境,数据库、中间件等有状态应用,对性能有要求。
4.2 云盘的多挂特性
部分云厂商支持"多挂盘"(Multi-Attach),一个云盘同时挂到多个节点,但只读。适合只读数据共享场景,如机器学习模型文件。
五、本地存储:Local PV与TopoLVM
5.1 Local PV:极高性能但绑定节点
Local PV是节点本地的磁盘或SSD,性能最高(无网络开销),但Pod被调度到其他节点就找不到数据。
yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-ssd
spec:
capacity:
storage: 500Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/ssd
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
关键点 :Local PV必须配nodeAffinity,绑死节点。调度器看到PVC绑定这个PV后,会自动把Pod调度到对应节点。
适用场景 :高性能数据库(TiDB、Cassandra)、对IO延迟极度敏感的应用。前提:应用自己处理跨节点数据同步(Replica)。
5.2 TopoLVM:动态本地存储
Local PV的痛点是"手动创建PV、手动绑定节点"。TopoLVM是开源方案,自动在节点上创建LVM卷、动态分配PV,支持扩容。
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: topolvm-provisioner
provisioner: topolvm.io
parameters:
"csi.storage.k8s.io/fstype": "xfs"
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values:
- zone-1
volumeBindingMode: WaitForFirstConsumer是关键:等Pod调度到节点后,再在那个节点上创建PV,避免"提前创建结果Pod调度不到"的尴尬。
六、CSI插件:K8s存储的统一接口
CSI(Container Storage Interface)是K8s存储的标准接口,所有存储厂商实现CSI插件,K8s通过统一API调用。
6.1 CSI核心组件
- Node Plugin:运行在每个节点上,负责挂载/卸载卷;
- Controller Plugin:负责创建/删除卷、快照、扩容;
- External Provisioner:监听PVC创建事件,调用Controller Plugin创建卷;
- External Attacher:监听卷附加事件,调用Controller Plugin挂载卷到节点。
6.2 CSI插件的选型依据
选择存储方案时,评估CSI插件成熟度:
| 维度 | 检查点 |
|---|---|
| 功能完整性 | 是否支持快照、扩容、克隆? |
| 高可用 | Controller是否支持多副本?故障切换时间? |
| 性能 | 延迟、吞吐、IOPS是否满足应用需求? |
| 运维复杂度 | 部署、升级、监控、故障排查难度? |
| 社区活跃度 | Issue响应速度、Release频率、文档质量? |
七、数据安全:快照、备份、容灾
7.1 VolumeSnapshot
CSI标准支持快照,一键备份PVC状态。
yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-snapshot-20260814
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: mysql-pvc
应用场景:
- 发布前快照,出问题秒回滚;
- 数据库定期快照,替代脚本导出;
- 开发测试环境克隆生产数据。
7.2 备份方案:Velero与Stash
Velero是K8s备份开源方案,支持PV备份到对象存储(S3、OSS、COS)。
bash
# 备份整个命名空间
velero backup create myapp-backup --include-namespaces myapp
# 恢复
velero restore create --from-backup myapp-backup
Stash是另一款工具,支持应用感知备份(MySQL逻辑备份、MongoDB导出等)。
7.3 容灾:跨集群/跨云
关键应用要做跨集群容灾,方案有:
- 存储层复制:Ceph的RBD Mirror、GlusterFS的Geo-Replication;
- 应用层复制:MySQL主从、MongoDB ReplicaSet、TiDB多AZ部署;
- K8s层迁移:Velero跨集群恢复,但PV要重新挂载。
八、常见故障排查
8.1 PVC一直Pending
bash
kubectl describe pvc <pvc-name>
常见原因:
- StorageClass不存在或provisioner错误;
- 存储后端资源不足(Ceph OSD满、云盘配额用光);
- VolumeBindingMode是WaitForFirstConsumer,但没有Pod使用这个PVC。
8.2 Pod挂载失败
bash
kubectl describe pod <pod-name>
常见错误:
FailedMount:节点上CSI插件异常、存储后端不可达;Volume is already exclusively attached:块存储被其他节点占用,ReadWriteOnce限制。
8.3 性能抖动
- NFS:网络延迟、NFS服务器负载高;
- Ceph:OSD抖动、PG分布不均;
- 云盘:底层共享资源竞争(邻居效应)。
排查工具:
bash
# 在Pod里测IO性能
kubectl exec -it <pod> -- fio --name=randwrite --ioengine=libaio --direct=1 --bs=4k --size=1G --rw=randwrite
# 在节点上测磁盘
dd if=/dev/zero of=/mnt/test bs=1M count=1000 oflag=direct
九、选型决策树
最后给一个简化版选型决策树:
需要持久化吗?
├─ 否 → EmptyDir
└─ 是
└─ 需要跨Pod共享吗?
├─ 是
│ └─ 性能要求高吗?
│ ├─ 高 → CephFS
│ └─ 低 → NFS
└─ 否(单Pod独占)
└─ 性能要求极高吗?
├─ 极高 → Local PV / TopoLVM(应用自己处理副本)
└─ 高 → Ceph RBD / 云盘SSD
记住三句话:共享数据用文件存储(NFS/CephFS),独占数据用块存储(RBD/云盘),极致性能用本地盘但自己管副本。
十、小结
K8s存储的复杂性在于:抽象层虽好,但底层物理世界的限制无法回避。ReadWriteOnce是块存储的物理约束,网络延迟是NFS的天花板,Local PV的高性能换来的是节点绑定。选型时,先想清楚应用的需求(共享还是独占、性能还是成本、单AZ还是跨AZ),再选对应方案,别被"一个PV走天下"的幻觉坑了。
存储选对了,数据库迁移不丢数据、发布抖动不抖、性能监控不爆表。选错了,轻则天天排查IO慢、重则关键时刻数据永失。把存储当核心架构决策,而不是"顺带挂个盘"。
下篇预告:Kubernetes安全实战------从RBAC到PodSecurityPolicy的权限控制体系。