一句话总结
CSI(容器存储接口)是 Kubernetes 把"对接存储"标准化外包出去的接口--和网络侧的 CNI 是一对孪生兄弟.
它把"给 Pod 准备好一块盘"拆成三段式: provision(造盘) → attach(挂到节点) → mount(挂进 Pod), 并定义了快照, 扩容等能力, 由各家存储插件实现.
前置回顾
第 06 篇我们讲了存储抽象: PVC 指定 StorageClass, K8s 就自动"造盘"绑定. 但一直没说: 那块盘到底是谁, 怎么造出来的? AWS 云盘, Ceph, NFS 各不相同, K8s 怎么统一对接?
答案是 CSI--它和第 03 篇的 CNI 是同一种设计的孪生兄弟. 如果理解了 CNI, 这一篇会有强烈的既视感: 那正是云原生"接口外包"哲学的对称之美.
又一次外包: K8s 也不自己对接存储
问题和网络如出一辙: 存储后端千差万别--AWS EBS, GCP PD, 阿里云盘, Ceph, NFS, 各家厂商的存储... K8s 不可能, 也不应该把对接每一种存储的代码都内置进核心. 于是它做了和 CNI 完全一样的决定:
K8s 定义一个标准接口 CSI(Container Storage Interface), 规定"一个存储插件必须能做到哪些操作(造盘, 挂载, 删除, 快照...)", 至于"怎么对接具体某种存储"由插件实现. 任何存储厂商只要实现 CSI, 就能接入 K8s.
| 网络(第 03 篇) | 存储(本篇) | |
|---|---|---|
| 接口 | CNI | CSI |
| 外包什么 | "怎么给 Pod 配网络" | "怎么给 Pod 备好存储" |
| K8s 只定义 | 四条网络规则(机制) | 存储操作接口(机制) |
| 插件提供 | Flannel/Calico/Cilium | EBS/Ceph/NFS... 的 CSI driver |
CNI, CSI, CRI: K8s 的'三大外包接口'
第 03 篇的补充问答提过这个三件套, 这里正好闭环: CRI 外包运行时, CNI 外包网络, CSI 外包存储. 它们是同一种"机制与策略分离"哲学的三个方向, 让 K8s 核心保持精简, 而运行时/网络/存储生态各自百花齐放.
看懂 CNI 再看 CSI, 会发现连"控制面 + 节点面"的分工方式都高度对称--这种对称不是巧合, 是好设计的自我复制.
CSI 插件的两个组件: 控制面 + 节点面
"给 Pod 准备一块盘"这件事, 其实发生在两个不同层面, 所以 CSI 插件通常拆成两部分(像网络里"控制面下发, 数据面转发"的分工):
| 组件 | 跑在哪 | 管什么 |
|---|---|---|
| Controller 插件(控制面) | 集群里跑一份(Deployment) | "资源管理"层面的事: 创建/删除底层存储卷, 把卷 attach 到某节点, 做快照, 扩容--调用存储后端的 API |
| Node 插件(节点面) | 每个节点一份(DaemonSet) | "本节点挂载"层面的事: 把已 attach 到本节点的卷, 格式化, 挂载到 Pod 能用的目录 |
为什么这么拆? 因为"造一块云盘"是集群级的全局操作(调云 API, 跟具体节点无关), 而"把盘挂进某个容器"必须在Pod 所在的那个节点本地执行. 两件事天然属于不同层面, 故分两个组件.
核心流程: provision → attach → mount 三段式
这是本篇的重点, 也是把第 06 篇"自动造盘"彻底讲透的地方. 当一个 Pod 要用动态供给的存储时, CSI 把整个过程分成三段, 各由不同组件负责:
|
| 阶段 | 做什么 | 谁干的 | 类比 |
|---|---|---|---|
| ① Provision | 创建出一块真实的存储卷 | Controller 插件 | 仓库里造出一个新柜子 |
| ② Attach | 把卷接到 Pod 所在节点(成为节点上的块设备) | Controller 插件 | 把柜子搬到所在的楼层 |
| ③ Mount | 在节点上格式化并挂载到 Pod 的目录 | Node 插件 | 把柜子装进房间, 打开门能用 |
三段式像叫一份外卖到工位:
Provision 是餐厅做好这份餐(创建卷).
Attach 是骑手送到公司楼下(卷到达节点).
Mount 是前台帮送到工位, 拆好包装(挂进 Pod 能直接吃). 三步分属餐厅, 骑手, 前台--对应 Controller, Controller, Node 三个角色的分工.
为什么 attach 和 mount 要分开?
初学者常觉得"挂载"是一件事, 其实是两件: attach 是把存储卷"接到节点这台机器上"(让节点的操作系统能看到这个块设备, 类似插了块硬盘), 这是云平台/存储后端层面的操作; mount 是在节点操作系统里把这个块设备"挂到某个目录"(格式化文件系统, 挂载点), 让容器能通过文件路径读写.
一块盘可能先 attach 到节点, 但还没 mount 给任何 Pod. 分开这两步, K8s 才能精细控制卷在节点间的迁移(如 Pod 漂移时: 先从旧节点 detach, 再 attach 到新节点).
CSI 带来的高级能力: 快照与扩容
CSI 不只管"挂盘". 因为它是个标准接口, K8s 还在上面定义了通用的高级存储能力, 各插件按需实现:
- 卷快照(Snapshot): 给某个卷在某一时刻"拍快照", 可用于备份或克隆出新卷. 通过
VolumeSnapshot资源声明式地创建--这是第 08 篇数据备份的重要手段. - 卷扩容(Expansion): 在线把卷扩大(第 06 篇提过的
allowVolumeExpansion), CSI 驱动负责调底层扩容 + 扩展文件系统. - 拓扑感知(Topology): 让 K8s 知道"这块卷只能在某个可用区用", 调度 Pod 时把它放到能访问该卷的节点上(避免"卷在 A 区, Pod 调度到 B 区却挂不上").
标准接口的红利: 能力一次定义, 所有插件共享
快照, 扩容这些能力, K8s 在 CSI 层面定义一次标准 API(如 VolumeSnapshot 资源), 所有实现了对应 CSI 能力的存储插件就都能用同一套 K8s 操作来驱动--不用为 EBS 和 Ceph 学两套快照命令.
这正是"标准接口"的复利: 定义一次, 生态共享. 和 OpenTelemetry(可观测性系列)用一套标准统一采集, CNI 用一套接口统一网络, 是完全相同的价值.
不是每个 CSI 驱动都支持全部能力
CSI 定义了一堆可选能力, 但具体某个存储插件支不支持快照, 支不支持在线扩容, 支不支持 RWX(第 06 篇), 取决于该驱动的实现和底层存储本身的能力. 比如本地盘的 CSI 驱动通常不支持跨节点 attach, 简单的 NFS 驱动可能不支持快照.
选型时务必查清要用的 CSI 驱动支持哪些能力--这直接决定了第 08 篇能不能做快照备份, 能不能扩容. 接口定义了"可能性", 实现决定了"现实".
快速回顾
- CSI 是存储版的 CNI: K8s 不自己对接各种存储, 定义 CSI 接口外包给插件; 与 CNI, CRI 并称三大外包接口, 同一种机制/策略分离哲学
- 两个组件: Controller 插件(集群一份, 管造盘/attach/快照/扩容, 调存储 API) + Node 插件(每节点一份 DaemonSet, 管本节点的格式化与挂载)
- 三段式流程: Provision(造出真实卷) → Attach(卷接到 Pod 所在节点) → Mount(节点上格式化并挂进 Pod); attach 是"接到机器", mount 是"挂到目录", 分开才能支持卷在节点间迁移
- 高级能力: 快照(备份/克隆, 第 08 篇用), 在线扩容, 拓扑感知; K8s 在 CSI 层定义标准 API, 所有插件共享
- 能力因驱动而异: 快照/扩容/RWX 支不支持取决于具体 CSI 驱动 + 底层存储, 选型必查
CSI 出现之前, K8s 怎么对接存储的?
早期是 in-tree(树内) 插件--把对接 AWS, GCE 等的存储代码直接写进 K8s 主代码库. 问题很明显: 存储厂商想加个新功能或修 bug, 得给 K8s 主项目提 PR, 等 K8s 发版, 节奏完全被绑死; K8s 核心也被一堆厂商代码拖累得越来越臃肿.
CSI 把这些代码移到 K8s 之外(out-of-tree), 厂商自己维护, 自己发版, K8s 核心瘦身. 这和 CNI 把网络实现移出核心是同一个动机--解耦核心与生态, 各自独立演进. 如今 in-tree 插件已基本被迁移/弃用.
三段式里, 如果 Pod 从节点 A 漂移到节点 B, 存储怎么跟过去?
这正是 attach/mount 分离的用武之地. 流程大致是: 旧节点 A 上先 unmount(从 Pod 目录卸载)再 detach(从节点 A 摘下卷), 然后在新节点 B 上重新 attach(把卷接到 B)再 mount(挂进新 Pod). 卷本身(provision 出来的那块)不用重造, 数据自然跟着走.
但注意: 这要求底层存储支持"被不同节点先后挂载"--块存储是 RWO(单节点), 所以同一时刻只能在一个节点, 漂移时串行迁移没问题; 而本地盘(绑死在某台机器)就没法这样漂移, 这会影响第 08 篇的有状态应用调度.
动手练习
- 找到 CSI 组件: 在集群里
kubectl get pods -n kube-system | grep csi, 找到 CSI 的 Controller(Deployment)和 Node(DaemonSet)组件, 对照本篇的两组件分工 - 观察三段式事件: 创建一个 PVC + Pod, 用
kubectl describe pvc和kubectl get events观察 Provisioning → 绑定的事件, 把它和三段式对应起来 - 去云控制台验证: 在云环境里, 创建 PVC 后去云控制台看是不是真的多了一块云盘(Provision), 并查看它 attach 到了哪台节点
- 查驱动能力: 查看 StorageClass / CSI 驱动文档, 确认它是否支持快照和在线扩容; 若支持, 创建一个
VolumeSnapshot给卷拍个快照 - 观察 Pod 漂移时的卷迁移(进阶): 把一个挂了 RWO 卷的 Pod 删除并让它重新调度到另一个节点, 观察卷的 detach/attach 过程(看 events), 体会 Pod 漂移时存储如何跟随