K8s-CSI卷挂载流程
CSI 存储卷挂载通信流程
目标
这篇文档只回答一个核心问题:kubelet 如何通过 CSI (Container Storage Interface) 完成 Pod 存储卷的挂载?
重点放在 VolumeManager 与 CSI driver 的交互,以及 attach/mount 的完整流程。
一句话摘要
kubelet 的 VolumeManager 通过 CSINode 和 CSIDriver 对象发现 CSI driver,然后通过 gRPC 调用 CSI driver 的 NodeStageVolume/NodePublishVolume 完成卷的挂载。
1. 流程总览
从通信视角看,这条链路可以拆成 3 个阶段:
- 卷准备阶段:CSI controller 完成 attach (对于块存储)。
- 节点挂载阶段:kubelet 通过 CSI driver 完成 stage 和 publish。
- 容器挂载阶段:kubelet 将卷 bind mount 到容器内。
架构总览图
flowchart TB
subgraph 控制面["控制面"]
direction TB
PV[PersistentVolume]
PVC[PersistentVolumeClaim]
ATTACH[CSI Attacher<br/>外部控制器]
end
subgraph Kubelet["Kubelet VolumeManager"]
direction TB
VM[VolumeManager]
DESIRED[DesiredStateOfWorld]
ACTUAL[ActualStateOfWorld]
EXEC[OperationExecutor]
CSI[CSIPlugin]
end
subgraph CSI["CSI Driver"]
direction TB
NODE_REG[CSINode<br/>节点注册信息]
DRIVER[CSIDriver<br/>驱动配置]
PLUGIN[CSI Plugin<br/>gRPC 服务]
end
subgraph 存储["存储后端"]
direction TB
CLOUD[云存储<br/>EBS/CBS/etc]
LOCAL[本地存储]
NAS[网络存储<br/>NFS/Ceph/etc]
end
PVC --> PV
PV --> VM
VM --> DESIRED
VM --> ACTUAL
DESIRED --> EXEC
EXEC --> CSI
CSI --> NODE_REG
CSI --> DRIVER
CSI --> PLUGIN
ATTACH --> CLOUD
PLUGIN --> CLOUD
PLUGIN --> LOCAL
PLUGIN --> NAS
CSI 接口层次图
flowchart TB
subgraph CSI接口["CSI gRPC 接口"]
direction TB
subgraph Controller["Controller Service"]
C1[CreateVolume]
C2[DeleteVolume]
C3[ControllerPublishVolume<br/>Attach]
C4[ControllerUnpublishVolume<br/>Detach]
C5[ControllerExpandVolume]
end
subgraph Node["Node Service"]
N1[NodeStageVolume<br/>临时挂载]
N2[NodeUnstageVolume]
N3[NodePublishVolume<br/>最终挂载]
N4[NodeUnpublishVolume]
N5[NodeExpandVolume]
N6[NodeGetCapabilities]
end
subgraph Identity["Identity Service"]
I1[GetPluginInfo]
I2[GetPluginCapabilities]
I3[Probe]
end
end
subgraph Kubelet调用["Kubelet 调用流程"]
K1[检查 CSIDriver]
K2[发现 CSI Plugin]
K3[调用 NodeStage]
K4[调用 NodePublish]
end
K1 --> I1
K2 --> N6
K3 --> N1
K4 --> N3
这张图想表达什么
- CSI 将存储操作标准化为 gRPC 接口。
- kubelet 只负责节点侧操作 (Stage/Publish)。
- attach 操作由外部控制器完成 (对于需要 attach 的存储)。
2. 分阶段通信流程
阶段一:卷准备 (Attach)
这个阶段的本质是:对于需要 attach 的存储类型,将存储卷附加到节点。
步骤 1.1:PV 绑定到 PVC
用户创建 PVC 后,PV Controller 会找到或创建匹配的 PV 并绑定。
通信方向:PV Controller → API Server
步骤 1.2:AD Controller 调用 Attach
AttachDetach Controller 检测到 Pod 使用了 PV,会调用 CSI Attacher 进行 attach。
通信方向:AD Controller → CSI Attacher
步骤 1.3:CSI Attacher 调用 ControllerPublishVolume
CSI Attacher 通过 gRPC 调用 CSI driver 的 ControllerPublishVolume 方法。
通信方向:CSI Attacher → CSI Controller Plugin (gRPC)
步骤 1.4:存储卷附加到节点
CSI driver 与存储后端通信,将卷附加到目标节点。
通信方向:CSI Driver → 存储后端 (云 API/存储协议)
步骤 1.5:更新 Node.Status.VolumesAttached
Attach 完成后,AD Controller 更新 Node 对象的 status.volumesAttached 字段。
通信方向:AD Controller → API Server
阶段二:节点挂载 (Stage & Publish)
这个阶段的本质是:kubelet 通过 CSI driver 将卷挂载到节点目录。
步骤 2.1:VolumeManager 获取 Pod 的卷信息
kubelet 收到 Pod 后,VolumeManager 解析 Pod 的 volume 配置,包括 PVC。
通信方向:kubelet → Pod spec 解析
步骤 2.2:检查 CSIDriver 和 CSINode
CSIPlugin 检查 CSIDriver 和 CSINode 对象,确定:
- CSI driver 是否支持 NodeStage
- CSI driver 的挂载目录
- CSI driver 的 gRPC 端点
通信方向:kubelet → API Server (读取 CSIDriver/CSINode)
步骤 2.3:调用 NodeStageVolume (如果支持)
如果 CSI driver 支持 STAGE_UNSTAGE_VOLUME 能力,kubelet 会先调用 NodeStageVolume,将卷挂载到一个临时目录(staging path)。
通信方向:kubelet → CSI Node Plugin (gRPC)
步骤 2.4:调用 NodePublishVolume
然后 kubelet 调用 NodePublishVolume,将卷从 staging path bind mount 到最终的 target path。
通信方向:kubelet → CSI Node Plugin (gRPC)
步骤 2.5:更新 ActualStateOfWorld
挂载成功后,VolumeManager 更新 ActualStateOfWorld,记录卷已挂载。
通信方向:VolumeManager 内部状态更新
阶段三:容器挂载
这个阶段的本质是:将节点上的挂载点 bind mount 到容器内。
步骤 3.1:容器创建时传入挂载配置
kubelet 调用 CRI 创建容器时,会将卷的挂载配置传给容器运行时。
通信方向:kubelet → CRI Runtime
步骤 3.2:容器运行时执行 bind mount
容器运行时将节点上的 target path bind mount 到容器内的 mount path。
通信方向:CRI Runtime → Linux 内核 (mount)
步骤 3.3:容器启动
容器启动后,可以通过容器内的路径访问存储卷内容。
通信方向:容器进程 → 文件系统
3. 详细流程图
3.1 卷挂载完整流程图
flowchart TD
subgraph 初始化["初始化阶段"]
A1[Pod 创建] --> A2[VolumeManager 解析卷]
A2 --> A3[添加到 DesiredStateOfWorld]
end
subgraph 调度["调度阶段"]
A3 --> B1[等待 Pod 绑定到节点]
B1 --> B2[等待 PV 绑定到 PVC]
end
subgraph Attach["Attach 阶段 (可选)"]
B2 --> C1{需要 Attach?}
C1 -->|是| C2[AD Controller 调用 Attach]
C2 --> C3[CSI ControllerPublishVolume]
C3 --> C4[存储后端 Attach]
C4 --> C5[更新 Node.VolumesAttached]
C1 -->|否| D1
C5 --> D1
end
subgraph Mount["Mount 阶段"]
D1[D1: 检查 CSIDriver] --> D2[获取 CSI Plugin 地址]
D2 --> D3{支持 Stage?}
D3 -->|是| E1[NodeStageVolume]
E1 --> E2[挂载到 staging path]
E2 --> E3[NodePublishVolume]
E3 --> E4[bind mount 到 target path]
D3 -->|否| E3
E4 --> F1[更新 ActualStateOfWorld]
end
subgraph 容器["容器阶段"]
F1 --> G1[创建容器]
G1 --> G2[bind mount 到容器内]
G2 --> G3[启动容器]
end
3.2 卷卸载流程图
flowchart TD
subgraph 触发["卸载触发"]
A1[Pod 删除] --> A2[VolumeManager 检测]
A2 --> A3[从 DesiredStateOfWorld 移除]
end
subgraph Unmount["Unmount 阶段"]
A3 --> B1[NodeUnpublishVolume]
B1 --> B2[umount target path]
B2 --> C1{支持 Stage?}
C1 -->|是| C2[NodeUnstageVolume]
C2 --> C3[umount staging path]
C1 -->|否| D1
C3 --> D1
end
subgraph Detach["Detach 阶段 (可选)"]
D1 --> D2{需要 Detach?}
D2 -->|是| D3[AD Controller 调用 Detach]
D3 --> D4[CSI ControllerUnpublishVolume]
D4 --> D5[存储后端 Detach]
D5 --> D6[更新 Node.VolumesAttached]
D2 -->|否| E1
D6 --> E1
end
subgraph 清理["清理阶段"]
E1[E1: 清理目录]
E1 --> E2[删除 target path]
E2 --> E3[删除 staging path]
E3 --> E4[从 ActualStateOfWorld 移除]
end
3.3 CSI Plugin 与 kubelet 交互图
sequenceDiagram
autonumber
participant KL as Kubelet
participant VM as VolumeManager
participant OE as OperationExecutor
participant CP as CSIPlugin
participant API as kube-apiserver
participant CSI as CSI Node Plugin
participant FS as 文件系统
KL->>VM: Pod 更新
VM->>VM: 添加到 DesiredStateOfWorld
loop reconciler 循环
VM->>VM: 对比 Desired vs Actual
VM->>OE: MountVolume(volume, pod)
OE->>CP: Mount(device, target)
CP->>API: Get CSIDriver
API-->>CP: CSIDriver 对象
CP->>API: Get CSINode
API-->>CP: CSINode 对象
CP->>CP: 获取 CSI Plugin socket
Note over CP,CSI: NodeStageVolume (可选)
alt 支持 STAGE_UNSTAGE_VOLUME
CP->>CSI: NodeStageVolume(staging_path, volume_context)
CSI->>FS: mount /dev/xxx /var/lib/kubelet/plugins/kubernetes.io/csi/pv/<pv>/globalmount
FS-->>CSI: 成功
CSI-->>CP: 成功
end
Note over CP,CSI: NodePublishVolume
CP->>CSI: NodePublishVolume(target_path, staging_path, volume_context)
CSI->>FS: mount --bind staging_path target_path
FS-->>CSI: 成功
CSI-->>CP: 成功
CP-->>OE: 成功
OE-->>VM: 成功
VM->>VM: 更新 ActualStateOfWorld
end
4. 关键数据结构
4.1 CSIDriver 对象
1 | apiVersion: storage.k8s.io/v1 |
4.2 CSINode 对象
1 | apiVersion: storage.k8s.io/v1 |
4.3 挂载路径结构
1 | /var/lib/kubelet/ |
5. 详细时序图
5.1 动态供应 PV 完整时序图
sequenceDiagram
autonumber
participant User as 用户
participant API as kube-apiserver
participant PVC as PVC Controller
participant PROV as CSI Provisioner
participant CSI as CSI Controller
participant AD as AD Controller
participant KL as Kubelet
participant NODE as CSI Node Plugin
participant STORAGE as 存储后端
User->>API: 创建 PVC
API-->>PVC: watch PVC ADDED
PVC->>PVC: 检查 StorageClass
PVC->>API: 绑定等待
Note over PROV: External Provisioner watch PVC
API-->>PROV: watch PVC (Pending)
PROV->>CSI: CreateVolume
CSI->>STORAGE: 创建存储卷
STORAGE-->>CSI: Volume ID
CSI-->>PROV: Volume ID
PROV->>API: 创建 PV
PVC->>API: 绑定 PV 到 PVC
Note over AD: AD Controller watch Pod
User->>API: 创建 Pod (使用 PVC)
AD->>AD: 检测到 Pod 使用 PV
alt attachRequired: true
AD->>CSI: ControllerPublishVolume
CSI->>STORAGE: Attach 卷到节点
STORAGE-->>CSI: 成功
CSI-->>AD: 成功
AD->>API: 更新 Node.VolumesAttached
end
API-->>KL: watch Pod (已调度)
KL->>KL: VolumeManager 处理
alt 支持 STAGE_UNSTAGE_VOLUME
KL->>NODE: NodeStageVolume
NODE->>STORAGE: 挂载到 staging path
STORAGE-->>NODE: 成功
NODE-->>KL: 成功
end
KL->>NODE: NodePublishVolume
NODE->>STORAGE: bind mount 到 target path
STORAGE-->>NODE: 成功
NODE-->>KL: 成功
KL->>KL: 创建容器
KL->>KL: 启动 Pod
5.2 卷扩容时序图
sequenceDiagram
autonumber
participant User as 用户
participant API as kube-apiserver
participant RESIZE as Resize Controller
participant CSI as CSI Controller
participant KL as Kubelet
participant NODE as CSI Node Plugin
participant STORAGE as 存储后端
User->>API: 更新 PVC.spec.resources.requests.storage
API-->>RESIZE: watch PVC UPDATE
RESIZE->>RESIZE: 检测扩容请求
RESIZE->>CSI: ControllerExpandVolume
CSI->>STORAGE: 扩容存储卷
STORAGE-->>CSI: 成功
CSI-->>RESIZE: 成功
RESIZE->>API: 更新 PV.spec.capacity.storage
Note over KL: 等待 Pod 重启或在线扩容
API-->>KL: watch PV UPDATE
KL->>KL: 检测到卷容量变化
alt 节点扩容
KL->>NODE: NodeExpandVolume
NODE->>STORAGE: resize2fs / xfs_growfs
STORAGE-->>NODE: 成功
NODE-->>KL: 成功
end
KL->>API: 更新 PVC.status.capacity.storage
6. 故障排查指南
6.1 常见问题与诊断方法
问题 1:Pod 卡在 ContainerCreating (卷挂载失败)
症状:
1 | kubectl get pods |
排查流程图:
flowchart TD
A[卷挂载失败] --> B{检查 PV/PVC 状态}
B --> B1[PVC Pending]
B1 --> B1a[检查 StorageClass]
B1a --> B1b[检查 Provisioner]
B --> B2[PV Available]
B2 --> B2a[检查 PV 绑定]
B --> B3[PV Bound]
B3 --> C{检查 Attach 状态}
C --> C1[检查 Node.VolumesAttached]
C1 --> C1a{有 Attach 记录?}
C1a -->|否| D{检查 AD Controller}
D --> D1[检查 AD Controller 日志]
D --> D2[检查 CSI Controller 日志]
C1a -->|是| E{检查 Mount 状态}
E --> E1[检查 kubelet 日志]
E --> E2[检查 CSI Node Plugin 日志]
A --> F{检查 CSI Driver}
F --> F1[检查 CSIDriver 对象]
F --> F2[检查 CSINode 对象]
F --> F3[检查 CSI Pod 状态]
诊断命令:
1 | # 检查 PVC 状态 |
问题 2:卷卸载失败导致 Pod 无法删除
症状:Pod 一直处于 Terminating 状态
排查流程图:
flowchart TD
A[Pod Terminating] --> B{检查 kubelet 日志}
B --> B1[Unmount 失败]
B1 --> B1a[检查挂载点是否存在]
B1a --> B1b[检查是否有进程占用]
B --> B2[NodeUnpublishVolume 失败]
B2 --> B2a[检查 CSI Node Plugin 日志]
B2a --> B2b[检查 CSI Plugin 状态]
B --> B3[Detach 失败]
B3 --> B3a[检查 AD Controller 日志]
B3a --> B3b[检查存储后端状态]
A --> C{手动清理}
C --> C1[umount 挂载点]
C --> C2[删除挂载目录]
C --> C3[强制删除 Pod]
诊断命令:
1 | # 检查 Pod 状态 |
问题 3:CSI Driver 注册失败
症状:CSINode 对象不存在或 driver 未注册
排查流程图:
flowchart TD
A[CSI Driver 未注册] --> B{检查 CSI Driver Pod}
B --> B1[Pod 未运行]
B1 --> B1a[检查 Pod 事件]
B1a --> B1b[检查镜像拉取]
B1a --> B1c[检查资源限制]
B --> B2[Pod 运行中]
B2 --> C{检查 Driver 日志}
C --> C1[注册失败]
C1 --> C1a[检查 KUBELET_DIR 权限]
C1a --> C1b[检查 socket 文件]
C --> C2[gRPC 启动失败]
C2 --> C2a[检查端口占用]
C2 --> C2b[检查 SELinux/AppArmor]
A --> D{检查 CSIDriver 对象}
D --> D1[对象不存在]
D1 --> D1a[创建 CSIDriver CRD]
诊断命令:
1 | # 检查 CSI Driver Pod |
6.2 关键日志关键词
| 组件 | 日志关键词 | 含义 |
|---|---|---|
| kubelet | WaitForAttachAndMount |
等待卷挂载 |
| kubelet | NodeStageVolume |
调用 CSI Stage |
| kubelet | NodePublishVolume |
调用 CSI Publish |
| kubelet | UnmountVolume |
卸载卷 |
| CSI Plugin | GRPC |
gRPC 调用 |
| AD Controller | AttachVolume |
卷附加 |
| AD Controller | DetachVolume |
卷分离 |
6.3 配置参数参考
| 参数 | 默认值 | 说明 |
|---|---|---|
--enable-controller-attach-detach |
true | 启用 AD Controller |
--volume-plugin-dir |
/usr/libexec/kubernetes/kubelet-plugins/volume | 卷插件目录 |
--volume-stats-egress-interval |
10s | 卷统计间隔 |
6.4 一键诊断命令
1 | # CSI 卷问题诊断脚本 |
7. 版本差异说明
1.27 → 1.28 变化
| 变化点 | 说明 |
|---|---|
| SELinux 挂载支持 | CSI driver 可以支持 SELinux 上下文 |
| 存储容量追踪 GA | 存储容量追踪功能正式发布 |
1.28 → 1.29 变化
| 变化点 | 说明 |
|---|---|
| fsGroupPolicy 增强 | 更灵活的 fsGroup 处理策略 |
| 在线卷扩容改进 | 更多存储类型支持在线扩容 |
8. 代码入口(精简版)
如果读者想从流程跳回实现,可从下面几个入口开始:
- VolumeManager 主结构:
pkg/kubelet/volumemanager/volume_manager.go - CSI Plugin 实现:
pkg/volume/csi/csi_plugin.go - CSI Attacher:
pkg/volume/csi/csi_attacher.go - OperationExecutor:
pkg/kubelet/volumemanager/operationexecutor/operation_executor.go - DesiredStateOfWorld:
pkg/kubelet/volumemanager/cache/desired_state_of_world.go
9. 面试题与详细解答
问题 1:CSI(Container Storage Interface)的设计目的是什么?与 in-tree 存储插件相比有什么优势?
回答要点:
CSI 设计目的:
- 解耦存储驱动:将存储驱动从 Kubernetes 核心代码中分离
- 标准化接口:定义统一的 gRPC 接口规范
- 支持多种存储:同一 CSI 驱动可用于不同容器编排系统
- 独立发布:存储厂商可独立发布和更新驱动
与 in-tree 插件对比:
| 特性 | in-tree 插件 | CSI 驱动 |
|---|---|---|
| 代码位置 | Kubernetes 核心代码 | 独立仓库 |
| 发布周期 | 跟随 K8s 版本 | 独立发布 |
| 维护责任 | K8s 社区 | 存储厂商 |
| 功能扩展 | 需要 K8s 代码变更 | 只需修改驱动 |
| 兼容性 | 强绑定 K8s 版本 | 向前兼容 |
CSI 三类服务:
Identity Service:插件身份和能力
GetPluginInfo:获取插件信息GetPluginCapabilities:获取插件能力Probe:健康检查
Controller Service:卷生命周期管理
CreateVolume/DeleteVolumeControllerPublishVolume/ControllerUnpublishVolume(Attach/Detach)ControllerExpandVolume(扩容)
Node Service:节点侧卷操作
NodeStageVolume/NodeUnstageVolume(临时挂载)NodePublishVolume/NodeUnpublishVolume(最终挂载)NodeExpandVolume(文件系统扩容)
CSI 迁移:
- Kubernetes 正逐步将 in-tree 插件迁移到 CSI
- 使用
CSIMigrationfeature gate 启用 - 透明转换:用户无需修改 PV 配置
问题 2:CSI 的 NodeStageVolume 和 NodePublishVolume 有什么区别?为什么需要两个阶段?
回答要点:
两阶段设计目的:
NodeStageVolume:将卷挂载到临时目录(staging path)
- 路径:
/var/lib/kubelet/plugins/kubernetes.io/csi/<pv>/globalmount - 只执行一次(即使多个 Pod 使用同一 PV)
- 用于需要特殊初始化的存储(如块设备格式化)
- 路径:
NodePublishVolume:bind mount 到 Pod 目录(target path)
- 路径:
/var/lib/kubelet/pods/<pod-uid>/volumes/kubernetes.io~csi/<pv>/mount - 每个 Pod 执行一次
- 轻量级操作(bind mount)
- 路径:
为什么需要两阶段:
性能优化:
- 块设备挂载、格式化等耗时操作只在 Stage 阶段执行一次
- 多 Pod 共享同一 PV 时避免重复操作
语义清晰:
- Stage:卷级别的操作
- Publish:Pod 级别的操作
支持共享卷:
- NAS 等存储支持多个 Pod 同时访问
- Stage 一次,Publish 多次
流程图:
1 | NodeStageVolume NodePublishVolume |
CSI Driver 能力声明:
1 | // 如果驱动支持 STAGE_UNSTAGE_VOLUME 能力 |
问题 3:PV 的动态供应(Dynamic Provisioning)完整流程是什么?涉及哪些组件?
回答要点:
动态供应流程:
sequenceDiagram
participant User
participant API as kube-apiserver
participant PVC as PVC Controller
participant PROV as External Provisioner
participant CSI as CSI Controller
participant Storage as 存储后端
User->>API: 创建 PVC (StorageClass)
API-->>PVC: watch PVC ADDED
PVC->>PVC: 检查 StorageClass
PVC->>API: 绑定等待 (Pending)
API-->>PROV: watch PVC (provisioner=xxx)
PROV->>PROV: 检查是否需要供应
PROV->>CSI: CreateVolume
CSI->>Storage: 创建存储卷
Storage-->>CSI: Volume ID
CSI-->>PROV: Volume ID
PROV->>API: 创建 PV
PVC->>API: 绑定 PV 到 PVC (Bound)
Note over User,Storage: 动态供应完成
涉及组件:
PVC Controller(kube-controller-manager):
- 检查 PVC 的 StorageClass
- 处理 PV 绑定逻辑
External Provisioner(Sidecar 容器):
- 监听 PVC 变化
- 调用 CSI CreateVolume
- 创建 PV 对象
CSI Controller(CSI 驱动):
- 实现 CreateVolume gRPC
- 与存储后端通信
关键配置:
1 | # StorageClass |
VolumeBindingMode 说明:
| 模式 | 行为 | 使用场景 |
|---|---|---|
| Immediate | 立即创建 PV | 不关心拓扑 |
| WaitForFirstConsumer | Pod 调度后创建 | 需要拓扑感知 |
问题 4:如何排查 Pod 因为卷挂载失败而卡在 ContainerCreating 的问题?
回答要点:
排查流程图:
1 | Pod ContainerCreating |
详细排查步骤:
步骤 1:检查 Pod 事件
1 | kubectl describe pod <pod-name> -n <namespace> |
步骤 2:检查 PVC/PV 状态
1 | # 检查 PVC |
步骤 3:检查 CSI Driver
1 | # 检查 CSIDriver 对象 |
步骤 4:检查 Attach 状态
1 | # 检查 VolumeAttachment |
步骤 5:在节点上检查
1 | # 检查 kubelet 日志 |
常见问题及解决:
| 问题 | 原因 | 解决方法 |
|---|---|---|
| PVC Pending | StorageClass 不存在 | 检查 StorageClass |
| PV 绑定失败 | 容量不匹配 | 检查 storage 字段 |
| Attach 失败 | 卷已被其他节点使用 | 等待 Detach 完成 |
| Mount 失败 | CSI 驱动未就绪 | 重启 CSI Pod |
| 格式化失败 | 块设备已格式化 | 检查卷内容 |
一键诊断脚本:
1 |
|
问题 5:CSI 卷扩容(Volume Expansion)是如何实现的?在线扩容和离线扩容有什么区别?
回答要点:
卷扩容流程:
1 | 1. 用户更新 PVC.spec.resources.requests.storage |
在线扩容 vs 离线扩容:
| 特性 | 在线扩容 | 离线扩容 |
|---|---|---|
| Pod 状态 | 必须运行 | 必须停止 |
| 文件系统 | 可以在线扩展 | 需要先卸载 |
| 支持的 FS | xfs, ext4(部分) | 所有类型 |
| 风险 | 较低 | 无 |
| 业务影响 | 无 | 有停机时间 |
扩容条件:
StorageClass 必须允许扩容:
1
2
3
4
5
6apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: expandable
provisioner: ebs.csi.aws.com
allowVolumeExpansion: true # 必须设置为 trueCSI 驱动必须支持扩容:
1
2
3
4
5
6
7
8# CSIDriver 对象
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: ebs.csi.aws.com
spec:
# 支持节点扩容
fsGroupPolicy: File文件系统限制:
- xfs:只支持在线扩容,不支持缩小
- ext4:支持在线扩容,缩小需要离线
扩容操作示例:
1 | # 1. 修改 PVC 请求大小 |
扩容失败处理:
1 | # 扩容失败时 PVC 状态 |
恢复方法:
1 | # 1. 查看失败原因 |
问题 6:什么是 CSI 拓扑(Topology)?如何实现跨可用区的存储调度?
回答要点:
CSI 拓扑概念:
拓扑(Topology)用于表达存储资源的可用位置,帮助调度器将 Pod 调度到存储可用的节点。
拓扑键示例:
topology.ebs.csi.aws.com/zone:AWS 可用区topology.gke.io/zone:GKE 可用区topology.kubernetes.io/zone:标准 zone 标签
实现方式:
CSINode 注册拓扑信息:
1
2
3
4
5
6
7
8
9
10
11
12
13apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
name: node-1
spec:
drivers:
- name: ebs.csi.aws.com
nodeID: i-1234567890abcdef0
topologyKeys:
- topology.ebs.csi.aws.com/zone
allocatable:
- name: storage
capacity: 100GiNode 标签:
1
kubectl label node node-1 topology.ebs.csi.aws.com/zone=us-west-2a
StorageClass 配置:
1
2
3
4
5
6
7
8
9
10
11
12apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: zone-aware
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer # 关键:等待调度决策
allowedTopologies:
- matchLabelExpressions:
- key: topology.ebs.csi.aws.com/zone
values:
- us-west-2a
- us-west-2b
调度流程:
1 | 1. Pod 创建,带有 PVC |
WaitForFirstConsumer vs Immediate:
| 模式 | PV 创建时机 | 拓扑感知 |
|---|---|---|
| Immediate | PVC 创建时立即创建 | 无 |
| WaitForFirstConsumer | Pod 调度后创建 | 有 |
跨可用区最佳实践:
- 使用 WaitForFirstConsumer:确保 PV 在正确的可用区创建
- 配置 allowedTopologies:限制存储创建区域
- Pod 反亲和性:分散 Pod 到不同可用区
- 存储复制:使用支持跨区复制的存储(如 Ceph)
问题 7:如何实现 CSI 卷的只读挂载?多 Pod 共享同一个 PV 时有什么注意事项?
回答要点:
只读挂载实现:
1 | apiVersion: v1 |
只读挂载实现原理:
- CSI 层面:
NodePublishVolume请求中设置readonly: true - 挂载选项:
mount -o ro,bind /source /target - 容器层面:容器运行时传入
readonly配置
多 Pod 共享 PV 注意事项:
访问模式:
ReadWriteOnce (RWO):单节点读写ReadOnlyMany (ROX):多节点只读ReadWriteMany (RWX):多节点读写
文件系统限制:
- ext4/xfs:不支持多节点并发写
- NFS:支持 RWX
- 块存储:只支持 RWO
数据一致性:
- 多写需要应用层协调(如分布式锁)
- 建议使用单一写入者模式
共享 PV 配置示例:
1 | # PV 配置 |
共享存储最佳实践:
- 明确读写角色:一个 Pod 写,多个 Pod 读
- 使用文件锁:应用层实现并发控制
- 选择合适的存储:NFS、CephFS 支持 RWX
- 监控 IO 竞争:关注存储性能指标

