1. 概述
这篇文章的目标是把 Kubernetes(简称 K8s)的存储机制讲清楚、讲完整。K8s 作为容器编排平台,存储是生产环境中绕不开的核心话题:Pod 是临时性的,容器重启后文件会丢失,那么有状态应用(数据库、消息队列、文件服务)的数据如何持久化?多个 Pod 之间如何共享数据?存储卷如何动态供给?这些问题都指向 K8s 的存储体系。
文章会从最基础的 Volume 讲起,逐步深入到 PersistentVolume(PV)、PersistentVolumeClaim(PVC)、StorageClass、CSI 插件等核心概念,再扩展到与 K8s 经常一起使用的兄弟组件和工具的存储机制,例如 etcd、Docker、Helm、Prometheus、Harbor、MinIO 等。最后会做横向对比,并专门讨论 K8s 存储机制是否随版本迭代发生过变化。
2. 为什么需要存储抽象
在理解 K8s 存储机制之前,先要理解它要解决什么问题。容器本身是无状态的:容器被删除后,容器内写入的文件随之消失。这在跑无状态服务(如 Web 前端)时问题不大,但数据库、日志系统、文件上传服务等有状态应用必须把数据写到容器之外。
K8s 的存储设计围绕三个核心诉求展开:
- 持久化:Pod 重建后数据不丢失。
- 共享:多个 Pod 或多个容器可以读写同一份数据。
- 解耦:应用开发者不需要关心底层存储的具体实现(是 NFS、云盘还是本地磁盘)。
为了满足这三个诉求,K8s 引入了分层抽象:Volume 解决挂载问题,PV 和 PVC 解决持久化和解耦问题,StorageClass 解决动态供给问题,CSI 解决插件扩展问题。
3. Volume:最基础的存储单元
Volume 是 K8s 中最基础的存储抽象,直接定义在 Pod 上。一个 Pod 可以挂载多个 Volume,Pod 内的容器可以共享这些 Volume。Volume 的生命周期与 Pod 绑定:Pod 存在,Volume 就存在;Pod 被删除,Volume 也随之销毁(除非使用持久化类型的 Volume)。
Volume 的类型非常多,常见的有:
| 类型 | 说明 | 持久性 |
|---|---|---|
| emptyDir | Pod 内临时目录,容器重启不丢,Pod 删除即丢 | 临时 |
| hostPath | 挂载节点宿主机目录 | 节点级持久 |
| configMap | 以文件形式挂载配置数据 | 随 ConfigMap 存在 |
| secret | 以文件形式挂载敏感数据 | 随 Secret 存在 |
| nfs | 挂载 NFS 共享目录 | 持久 |
| persistentVolumeClaim | 通过 PVC 引用 PV | 持久 |
emptyDir 是最常用的临时卷,适合缓存、临时文件等场景。hostPath 适合需要访问节点文件的场景,例如监控 Agent 读取宿主机日志,但生产环境不建议作为数据库存储,因为 Pod 调度到不同节点后数据不跟随。
configMap 和 secret 本质上是把 K8s 对象的内容以文件形式挂载进容器,它们解决的是配置注入问题,而不是数据持久化问题。
4. PV 和 PVC:持久化存储的核心抽象
PV(PersistentVolume)是集群级别的存储资源,由管理员预先创建,或者由 StorageClass 动态创建。PV 独立于 Pod 存在,描述的是底层存储的真实信息,例如容量、访问模式、回收策略、存储类型等。
PVC(PersistentVolumeClaim)是用户对存储的请求,类似于“我要 10Gi 的读写存储”。用户创建 PVC 后,K8s 会尝试将 PVC 与满足条件的 PV 绑定。绑定成功后,Pod 通过 PVC 挂载存储。
这种设计实现了存储供给和存储消费的分离:管理员负责准备 PV,开发者只关心 PVC,不需要知道底层存储是云盘还是 NFS。
4.1 PV 的关键属性
- 容量:PV 声明的存储大小,例如 10Gi。
- 访问模式:ReadWriteOnce(单节点读写)、ReadOnlyMany(多节点只读)、ReadWriteMany(多节点读写)。
- 回收策略:Retain(保留数据)、Recycle(已废弃)、Delete(删除底层存储)。
- 存储类:通过 storageClassName 关联 StorageClass。
4.2 PVC 与 PV 的绑定过程
PVC 创建后,K8s 的 persistentvolume-controller 会扫描集群中的 PV,寻找满足以下条件的 PV:容量足够、访问模式匹配、storageClassName 匹配。找到后,PVC 与 PV 进入 Bound 状态。如果没有现成 PV 匹配,且存在默认 StorageClass,则触发动态供给。
绑定是独占的:一个 PV 同一时间只能绑定一个 PVC。PVC 释放后,PV 根据回收策略决定数据去留。
5. StorageClass:动态供给的入口
StorageClass 解决了 PV 需要管理员手动创建的问题。通过 StorageClass,用户可以声明“我要一块 SSD 云盘”,系统自动调用云厂商或存储系统的 API 创建 PV。
StorageClass 的核心字段包括:
- provisioner:指定存储插件,例如 kubernetes.io/aws-ebs、kubernetes.io/gce-pd、nfs.csi.k8s.io。
- reclaimPolicy:动态创建的 PV 被释放后的回收策略,默认 Delete。
- parameters:传给 provisioner 的参数,例如云盘类型、IOPS、副本数。
- volumeBindingMode:Immediate(立即绑定)或 WaitForFirstConsumer(等待第一个消费者出现再绑定)。
volumeBindingMode 是生产环境中非常重要的参数。Immediate 模式在 PVC 创建时立即创建 PV,但此时 Pod 可能还没调度,导致 PV 创建在错误的可用区。WaitForFirstConsumer 模式会等 Pod 调度完成后,根据 Pod 所在节点创建 PV,避免跨可用区问题。
6. CSI:存储插件标准
CSI(Container Storage Interface)是 K8s 与存储系统之间的标准接口。在 CSI 出现之前,K8s 的存储插件是内置在 kubelet 和 controller-manager 中的,每增加一种存储都要修改 K8s 源码,维护成本极高。
CSI 将存储插件从 K8s 核心代码中剥离出来,以独立组件的形式运行。CSI 插件通常包含三个部分:
- Driver 注册组件:负责向 kubelet 注册驱动。
- Controller 组件:负责创建、删除、挂载、卸载卷。
- Node 组件:负责在节点上执行卷挂载操作。
CSI 的出现让云厂商和存储厂商可以独立开发和发布存储插件,K8s 核心代码不再需要为每种存储单独适配。目前主流的云盘、NFS、Ceph、GlusterFS 等存储都提供了 CSI 驱动。
7. 数据面组件:kubelet 与卷生命周期
存储机制不仅涉及 API 层的 PV、PVC、StorageClass,还涉及数据面组件 kubelet 的实际操作。kubelet 负责在节点上执行卷的挂载、格式化、卸载等操作。
卷的生命周期大致如下:
- 用户创建 PVC,触发 PV 绑定或动态供给。
- Pod 调度到某个节点,kubelet 根据 Pod 定义中的 volume 信息准备卷。
- 对于 CSI 卷,kubelet 调用 CSI Node 组件执行卷挂载。
- 卷挂载到节点后,再绑定到容器内的目标路径。
- Pod 删除时,kubelet 执行卸载操作,并根据回收策略处理数据。
这里有一个容易混淆的概念:卷挂载分为两个层次。第一层是卷挂载到节点(NodeStage),第二层是卷挂载到 Pod 内的容器路径(NodePublish)。CSI 规范中明确区分了这两个阶段,目的是让卷可以在节点上复用,减少重复挂载开销。
8. 与 K8s 经常一起使用的兄弟组件和工具的存储机制
K8s 本身不直接存储业务数据,但围绕 K8s 生态,有一批组件和工具承担着不同的存储职责。理解它们的存储机制,有助于构建完整的知识体系。
8.1 etcd:K8s 的元数据存储
etcd 是 K8s 的基石,所有集群状态(Pod、Service、ConfigMap、Secret、PV、PVC 等对象)都存储在 etcd 中。etcd 是一个分布式键值存储,基于 Raft 协议保证一致性。
etcd 的存储机制有几个关键点:
- 数据持久化:etcd 将数据写入本地磁盘的 WAL(Write-Ahead Log)和快照文件。
- 高可用:生产环境通常部署 3 或 5 节点 etcd 集群,Raft 协议保证多数派写入成功才算提交。
- 备份:etcd 需要定期备份,因为 K8s 的所有状态都在里面,一旦丢失整个集群就瘫痪了。
- 存储介质:etcd 对磁盘 IO 延迟非常敏感,生产环境强烈建议使用 SSD。
etcd 存储的是 K8s 的控制面状态,而不是业务数据。业务数据由 PV/PVC 体系管理,两者职责完全不同。
8.2 Docker:容器运行时的存储机制
Docker 是 K8s 早期最常用的容器运行时。Docker 的存储机制包括:
- 镜像层:镜像由多层只读层组成,基于 UnionFS 技术合并。
- 容器层:容器启动后在镜像层之上增加一个可写层,容器删除后可写层随之消失。
- Volume:Docker 的 Volume 由 Docker 管理,存储在宿主机 /var/lib/docker/volumes 目录下。
- Bind Mount:将宿主机目录直接挂载到容器内。
Docker 的 Volume 和 Bind Mount 是容器级存储,而 K8s 的 Volume 是 Pod 级抽象。K8s 在 Pod 层面管理卷,再通过容器运行时(如 Docker 或 containerd)执行实际的挂载操作。
8.3 Helm:包管理器的存储机制
Helm 是 K8s 的包管理器,用于打包、部署、升级应用。Helm 的存储机制主要体现在 Release 记录上:
- Helm 默认将 Release 信息存储在 K8s 集群内的 Secret 中。
- 每个 Release 对应一个或多个 Secret,记录 Chart 的版本、配置、状态等信息。
- Helm 3 不再使用 Tiller 服务端组件,所有操作都在客户端完成,Release 记录直接写入 K8s API。
Helm 本身不存储业务数据,它存储的是“应用部署状态”这类元数据。
8.4 Prometheus:监控系统的存储机制
Prometheus 是 K8s 生态最常用的监控系统。它的存储机制有几个特点:
- 本地 TSDB:Prometheus 默认将时序数据写入本地磁盘,按 2 小时为一个 block 存储。
- 数据保留策略:通过 --storage.tsdb.retention.time 控制数据保留时长。
- 远程存储:支持通过 Thanos、VictoriaMetrics 等方案将数据持久化到对象存储或远程数据库。
- K8s 集成:Prometheus Operator 通常配合 PVC 使用,将 TSDB 数据挂载到持久化卷上,避免 Pod 重启丢数据。
Prometheus 的存储机制说明了一个常见模式:即使应用本身支持本地存储,在 K8s 中部署时也要通过 PVC 将数据持久化,否则 Pod 重建后监控历史数据全部丢失。
8.5 Harbor:镜像仓库的存储机制
Harbor 是企业级容器镜像仓库,它的存储机制分为两部分:
- 镜像数据:Harbor 后端使用 Registry(Docker Distribution)存储镜像,镜像层数据可以存储在本地文件系统、S3、OSS、Ceph 等对象存储中。
- 元数据:Harbor 使用数据库(PostgreSQL)存储项目、用户、权限、镜像标签等元数据,使用 Redis 做缓存。
在 K8s 中部署 Harbor 时,镜像存储和数据库都需要通过 PVC 持久化,否则重启后镜像和配置全部丢失。
8.6 MinIO:对象存储的存储机制
MinIO 是兼容 S3 协议的对象存储,常与 K8s 配合使用,为应用提供对象存储能力。MinIO 的存储机制:
- 数据分片:MinIO 将对象数据拆分为多个数据块和校验块,分布在不同磁盘上。
- 纠删码:通过 Reed-Solomon 纠删码实现数据冗余,即使部分磁盘损坏也能恢复数据。
- 后端存储:MinIO 直接使用本地磁盘或挂载的持久化卷,在 K8s 中通常通过 PVC 提供存储。
MinIO 适合作为 K8s 集群内的对象存储服务,为应用提供统一的文件上传下载能力。
9. 存储机制横向对比
为了更直观地理解 K8s 存储体系与兄弟组件存储机制的区别,下面做一个横向对比。
| 组件 | 存储内容 | 存储类型 | 持久化方式 | 与 K8s 的关系 |
|---|---|---|---|---|
| K8s PV/PVC | 业务数据 | 块存储、文件存储、对象存储 | PV 独立于 Pod 存在 | K8s 核心存储抽象 |
| etcd | 集群元数据 | 键值存储 | WAL + 快照落盘 | K8s 控制面依赖 |
| Docker Volume | 容器数据 | 宿主机目录 | 宿主机磁盘 | 容器运行时层 |
| Helm Release | 部署记录 | Secret | etcd 持久化 | 应用管理工具 |
| Prometheus | 监控时序数据 | TSDB | 本地磁盘或远程存储 | 监控生态 |
| Harbor | 镜像和元数据 | 对象存储 + 数据库 | PVC 或外部存储 | 镜像仓库 |
| MinIO | 对象数据 | 对象存储 | 本地磁盘或 PVC | 对象存储服务 |
从对比可以看出:K8s 的 PV/PVC 是面向业务数据的通用抽象,etcd 是面向集群状态的专用存储,Docker 是容器运行时的底层存储,而 Helm、Prometheus、Harbor、MinIO 则是各自领域的存储方案,它们要么依赖 K8s 的 PVC 体系,要么独立管理自己的数据。
10. K8s 存储机制是否随版本迭代而改变
答案是肯定的。K8s 的存储机制经历了多次重要演进,下面按版本脉络梳理关键变化。
10.1 早期版本:内置插件时代
在 K8s 1.0 到 1.8 左右,存储插件以内置方式存在于 K8s 核心代码中。每种存储(AWS EBS、GCE PD、NFS、iSCSI 等)都是 kubelet 和 controller-manager 中的一段内置代码。这种方式的缺点是:新增存储类型必须修改 K8s 源码,插件与核心代码耦合严重,升级 K8s 可能破坏存储插件。
10.2 CSI 引入:1.9 到 1.13
CSI 规范在 K8s 1.9 版本以 alpha 特性引入,1.10 进入 beta,1.13 正式 GA。CSI 的引入是 K8s 存储机制最重要的转折点:存储插件从内置走向外置,云厂商和存储厂商可以独立开发驱动,K8s 核心代码不再需要为每种存储单独适配。
CSI 的 GA 意味着存储插件与 K8s 版本解耦,这是存储机制演进中最关键的一步。
10.3 树内插件冻结:1.14 到 1.26
从 K8s 1.14 开始,社区宣布冻结树内存储插件(In-Tree Storage Plugins),不再新增内置存储插件,已有插件逐步迁移到 CSI。例如 AWS EBS、GCE PD、Cinder 等插件陆续从树内移除,改为调用对应的 CSI 驱动。
这一阶段的变化是:用户需要安装 CSI 驱动才能使用云盘存储,树内插件虽然仍然可用,但功能不再演进。
10.4 树内插件移除:1.26 及以后
K8s 1.26 移除了部分树内存储插件的代码,例如 AWS EBS、GCE PD 等。这意味着使用这些存储的用户必须迁移到 CSI 驱动。到 1.29 左右,绝大多数树内存储插件已经完成迁移或移除。
这一变化对用户的影响是:升级 K8s 版本前,需要确认所使用的存储是否已经切换到 CSI 驱动,否则存储功能可能不可用。
10.5 其他重要演进
- Volume Snapshot:K8s 1.20 引入 VolumeSnapshot 和 VolumeSnapshotContent 的 GA,支持对持久化卷做快照备份。
- Volume Expansion:K8s 1.24 将卷扩容能力提升为 GA,用户可以在 PVC 上直接扩容容量。
- Ephemeral Volume:K8s 1.22 引入通用临时卷(Generic Ephemeral Volume),允许使用 PVC 模板创建临时卷。
- CSI Migration:CSI 迁移机制让树内插件自动调用 CSI 驱动,平滑过渡到 CSI 体系。
- Object Storage:K8s 社区正在推进容器对象存储接口(COSI),用于统一管理对象存储的供给和消费。
10.6 版本演进总结
| 版本阶段 | 存储机制状态 | 关键变化 |
|---|---|---|
| 1.0 - 1.8 | 内置插件 | 存储插件耦合在核心代码中 |
| 1.9 - 1.13 | CSI 引入并 GA | 存储插件外置化,标准接口确立 |
| 1.14 - 1.25 | 树内插件冻结 | 不再新增内置插件,逐步迁移 CSI |
| 1.26 及以后 | 树内插件移除 | 必须使用 CSI 驱动 |
总体趋势是:存储机制从“核心内置”走向“标准接口 + 外置驱动”,从“静态供给”走向“动态供给 + 快照 + 扩容”,从“单一存储类型”走向“块、文件、对象统一管理”。
11. 实践建议
基于前面的原理和演进分析,给出几条实践建议:
- 优先使用 CSI 驱动:新项目直接使用 CSI 驱动,避免依赖已冻结或已移除的树内插件。
- 合理设置 StorageClass:为不同性能需求创建多个 StorageClass,例如 SSD 和 HDD 分开。
- 使用 WaitForFirstConsumer:跨可用区部署时,使用 WaitForFirstConsumer 避免 PV 创建在错误区域。
- 定期备份 etcd:etcd 是集群的命脉,必须定期备份并验证恢复流程。
- 为有状态应用配置 PVC:数据库、消息队列、监控系统等有状态应用必须通过 PVC 持久化数据。
- 关注版本升级兼容性:升级 K8s 前,检查存储插件是否已迁移到 CSI,避免升级后存储不可用。
12. 总结
K8s 的存储机制是一个分层、解耦、可扩展的体系:Volume 解决挂载问题,PV/PVC 解决持久化和供给消费分离问题,StorageClass 解决动态供给问题,CSI 解决插件标准化问题。围绕 K8s 生态,etcd、Docker、Helm、Prometheus、Harbor、MinIO 等组件各自承担不同的存储职责,理解它们的存储机制有助于构建完整的知识体系。
K8s 存储机制确实随版本迭代发生了显著变化,核心趋势是插件外置化、供给动态化、能力丰富化。从内置插件到 CSI 标准,从静态 PV 到动态供给和快照扩容,K8s 的存储体系一直在演进。掌握这些演进脉络,不仅有助于理解当前版本的存储机制,也能为未来的技术选型和版本升级提供参考。