K8s 中的 Pod 在挂载存储卷时需经历三个的阶段:Provision/Delete(创盘/删盘)、Attach/Detach(挂接/摘除)和 Mount/Unmount(挂载/卸载)
Provisioning Volumes 时序流程详解
一、流程图
sequenceDiagram
title Provisioning Volumes 时序流程
participant 集群管理员
participant 用户
participant api-server
participant 卷控制器 as 卷控制器(PersistentVolumeController)
participant ExternalProvisioner
participant CSI插件 as CSI插件(Controller)
集群管理员->>+api-server: 1 创建 StorageClass 资源(type=cloud_ssd)
用户->>+api-server: 2 创建 PersistentVolumeClaim 资源
卷控制器->>+api-server: 3 观察到 PVC 无匹配 PV<br/>且为 out-of-tree 类型
api-server-->>-卷控制器: 返回 PVC 信息
卷控制器->>api-server: 4 为 PVC 打 annotation
ExternalProvisioner->>+api-server: 5 观察到 PVC annotation 匹配自身
api-server-->>-ExternalProvisioner: 返回 PVC 和 StorageClass 参数
ExternalProvisioner->>ExternalProvisioner: 6 解析 StorageClass 参数(type=cloud_ssd)
ExternalProvisioner->>+CSI插件: 7 通过 Unix Domain Socket 调用<br/>CreateVolume(name=xxx, capacity=10Gi, parameters={type=cloud_ssd})
CSI插件->>CSI插件: 8 执行实际存储创建操作
CSI插件-->>-ExternalProvisioner: 9 返回 VolumeID 和 VolumeContext
ExternalProvisioner->>+api-server: 10 创建 PV 资源<br/>(包含 VolumeID 和 CSI 卷信息)
api-server-->>-ExternalProvisioner: PV 创建成功
卷控制器->>+api-server: 11 检测到 PV 和 PVC 可绑定
api-server-->>-卷控制器: 返回 PV/PVC 信息
卷控制器->>api-server: 12 执行 PV-PVC 绑定操作
二、详细流程步骤
创建 StorageClass 资源
集群管理员向 API Server 提交StorageClass
资源定义(例如type=cloud_ssd
),定义存储卷的类型、后端插件、参数等配置,为后续动态创建 PV 提供模板。创建 PersistentVolumeClaim(PVC)
用户通过创建 PVC 声明所需的存储资源(如容量、访问模式等),PVC 会引用步骤1中定义的 StorageClass。卷控制器检测未绑定 PVC
Kubernetes 内置的 卷控制器(PersistentVolumeController) 持续监听 PVC 资源,发现未绑定且关联了 StorageClass 的 PVC(即out-of-tree
类型,依赖外部插件管理的存储)。为 PVC 打标记
卷控制器为该 PVC 添加特定 Annotation(如volume.beta.kubernetes.io/storage-provisioner
),标识需要外部供应器处理。External Provisioner 监听并匹配 PVC
external-provisioner
边车容器持续监听 PVC 变化,通过 Annotation 匹配到需要自身处理的 PVC,并获取对应的 PVC 和 StorageClass 配置。解析存储参数
External Provisioner 解析 StorageClass 中的参数(如type=cloud_ssd
),确定存储卷的具体配置。调用 CSI 插件创建卷
External Provisioner 通过 Unix Domain Socket 调用 CSI 控制器插件的CreateVolume
接口,传递卷名称、容量、参数等信息。CSI 插件执行实际创建
CSI 控制器插件对接底层存储(如 AWS EBS、阿里云 EBS 等),通过云 API 或本地存储接口创建实际存储卷。返回卷信息
CSI 插件创建成功后,向 External Provisioner 返回卷的唯一标识(VolumeID
)和上下文信息(VolumeContext
)。创建 PV 资源
External Provisioner 基于返回的卷信息,向 API Server 创建 PersistentVolume(PV)资源,PV 中包含 CSI 卷的绑定信息。卷控制器检测可绑定关系
卷控制器监听 PV 和 PVC 变化,发现新创建的 PV 与未绑定的 PVC 匹配(容量、访问模式、StorageClass 等一致)。执行 PV-PVC 绑定
卷控制器通过 API Server 将 PV 与 PVC 绑定,此时 PVC 进入Bound
状态,用户 Pod 可通过 PVC 使用该存储卷。
三、不执行该流程的场景
以下情况不会触发上述动态卷供应流程,而是采用静态方式管理存储卷:
静态创建 PV
若集群管理员已手动创建 PV 并指定存储卷信息(如已存在的云盘 ID、本地路径等),且 PV 与 PVC 的参数(容量、访问模式、StorageClass)匹配,则卷控制器会直接绑定 PV 和 PVC,无需调用 External Provisioner 和 CSI 插件创建卷。PVC 未关联 StorageClass
若 PVC 未指定storageClassName
,且集群未配置默认 StorageClass,则卷控制器会等待手动创建匹配的 PV,不会触发动态供应。使用 in-tree 存储插件
对于 Kubernetes 内置的 in-tree 存储插件(如kubernetes.io/aws-ebs
,非 CSI 类型),动态供应由 Kubernetes 内部组件直接处理,无需 External Provisioner 和 CSI 插件参与。
四、组件代码位置与集群部署信息表
组件名称 | 代码位置 | 部署命名空间 | 部署形式 | 部署说明 |
---|---|---|---|---|
卷控制器(PersistentVolumeController) | kubernetes/kubernetes/pkg/controller/volume/persistentvolume/ |
kube-system |
集成于 kube-controller-manager 静态 Pod |
作为 kube-controller-manager 的内置组件运行,随控制平面启动;控制节点上通过静态 Pod 配置文件(/etc/kubernetes/manifests/kube-controller-manager.yaml )定义,由 kubelet 自动管理。 |
API Server | kubernetes/kubernetes/pkg/apiserver/ kubernetes/cmd/kube-apiserver/ |
kube-system |
独立静态 Pod(kube-apiserver ) |
控制平面核心组件,以静态 Pod 形式部署在控制节点;配置文件位于 /etc/kubernetes/manifests/kube-apiserver.yaml ,通过负载均衡器实现多实例高可用,接收所有集群 API 请求。 |
External Provisioner | github.com/kubernetes-csi/external-provisioner | 与 CSI 插件同命名空间(如 csi-system ) |
边车容器(Sidecar) | 与 CSI 控制器插件部署在同一 Pod 中,作为辅助容器运行;通过 Unix Domain Socket 与 CSI 插件通信,需配置 RBAC 权限以监听 PVC、创建 PV 等。 |
CSI 插件(Controller) | 各存储厂商实现 | 自定义命名空间(如 csi-system 、kube-system ) |
Deployment 或 StatefulSet | 以容器化方式部署,包含控制器组件和节点组件;控制器组件需挂载主机的 Unix Domain Socket 目录(供与 External Provisioner 通信),并配置 ClusterRole 权限以访问 API 资源。 |
CSI Volume Attach 时序流程详解
一、流程图
sequenceDiagram
title CSI Volume Attach 时序流程
participant ADController as AD控制器(AttachDetachController)
participant api-server
participant ExternalAttacher
participant CSIDriver
participant CloudProvider
participant Node
%% 1. AD Controller 监听 Pod 调度并触发 Attach
ADController->>api-server: 监听 Pod 调度事件
api-server-->>ADController: 返回调度到节点的 Pod 列表
ADController->>ADController: 计算待 Attach 的 PV 列表(对比节点 status.volumesAttached)
ADController->>ADController: 检查 RWO 卷是否已被其他节点挂载
ADController->>api-server: 调用 in-tree CSI 插件的 Attach 函数
%% 2. 创建并处理 VolumeAttachment(隐式)
ADController->>api-server: 创建 VolumeAttachment 资源
ExternalAttacher->>api-server: 监听 VolumeAttachment 变化
api-server-->>ExternalAttacher: 返回新创建的 VA
%% 3. External Attacher 调用 CSI 插件
ExternalAttacher->>CSIDriver: 调用 ControllerPublishVolume(PV, Node)
CSIDriver->>CloudProvider: 调用云 API(如 AWS EBS Attach)
CloudProvider-->>CSIDriver: 返回设备 ID(如 /dev/xvdf)
CSIDriver-->>ExternalAttacher: 返回挂载成功
%% 4. 更新状态(通过 API Server)
ExternalAttacher->>api-server: 更新 VolumeAttachment.status.attached=true
ADController->>api-server: 监听 VA 状态变化
api-server-->>ADController: 返回 VA.status.attached=true
%% 5. AD Controller 更新节点状态
ADController->>ADController: 更新内部状态 (ActualStateOfWorld)
ADController->>api-server: 更新 Node.status.volumesAttached
api-server->>Node: 添加已挂载卷信息
api-server-->>ADController: 返回更新成功
二、详细流程步骤
AD 控制器监听 Pod 调度事件
Kubelet 内置的 AD 控制器(AttachDetachController) 持续监听集群中 Pod 的调度结果,获取已调度到本节点的 Pod 列表,识别这些 Pod 声明的存储卷需求。计算待挂载卷并检查冲突
AD 控制器对比节点当前已挂载卷(Node.status.volumesAttached
)与 Pod 所需卷,筛选出未挂载的 PV;同时对ReadWriteOnce (RWO)
类型卷进行冲突检查,确保同一卷未被其他节点挂载(避免数据不一致)。触发 Attach 操作并创建 VolumeAttachment
AD 控制器调用内部 in-tree CSI 插件的Attach
函数,通过 API Server 创建VolumeAttachment
资源,记录卷与节点的绑定关系(包含 PV 标识、目标节点等信息)。External Attacher 监听并处理 VolumeAttachment
External Attacher
边车容器持续监听VolumeAttachment
资源变化,发现新创建的资源后,通过 API Server 获取卷和节点信息。调用 CSI 插件执行挂载
External Attacher 调用 CSI 驱动的ControllerPublishVolume
接口,传递 PV 和目标节点信息;CSI 驱动对接底层云厂商存储(如 AWS EBS、阿里云 EBS),调用云 API 执行实际的卷挂载操作(如将云盘挂载到目标节点)。云厂商返回挂载结果
云厂商完成卷挂载后,向 CSI 驱动返回设备标识(如/dev/xvdf
),表示卷已成功挂载到节点。更新 VolumeAttachment 状态
CSI 驱动向 External Attacher 返回挂载成功结果,External Attacher 通过 API Server 将VolumeAttachment.status.attached
更新为true
,标识卷已完成节点级挂载。AD 控制器同步节点状态
AD 控制器监听VolumeAttachment
状态变化,确认挂载成功后更新内部状态(ActualStateOfWorld
),并通过 API Server 将卷信息添加到Node.status.volumesAttached
中,完成节点存储状态的同步。
三、不执行该流程的场景
1. 文件存储协议(NFS/SMB)
NFS(Network File System)和 SMB(Server Message Block)等网络文件系统通过文件级协议实现共享,无需将块设备“Attach”到节点:
- 原理:客户端直接通过网络协议(如NFS的RPC、SMB的CIFS)挂载远程共享目录,本质是建立文件路径到本地目录的逻辑映射,而非块设备与节点的绑定。
- 差异:无需创建
VolumeAttachment
资源或调用ControllerPublishVolume
接口,直接通过mount
命令或文件系统驱动完成网络目录挂载。
2. 对象存储协议(S3/OSS)
S3(Amazon Simple Storage Service)、阿里云 OSS 等对象存储服务基于 HTTP/HTTPS API 提供对象访问,完全不涉及节点级挂载:
- 原理:客户端通过 RESTful API(如
PUT
/GET
)直接操作对象(文件、图片等),无需将存储设备关联到节点。 - 差异:无块设备或文件系统挂载过程,通过 SDK 或工具(如
s3fs
)实现对象与本地目录的映射,但不属于 CSI Attach 流程范畴。
3. 静态挂载的本地存储卷
hostPath
、local
等本地存储类型的 PV 依赖节点本地文件系统或目录,无需网络存储的 Attach 流程:
- 原理:卷对应的路径直接存在于节点本地(如
/data/local-vol
),Pod 通过绑定到该节点直接访问本地路径。 - 差异:无需云厂商 API 调用或
VolumeAttachment
资源,Kubelet 直接验证路径存在性后完成挂载。
4. 已完成挂载的卷复用
若卷已通过 VolumeAttachment
成功挂载到节点(Node.status.volumesAttached
中存在记录),且未被卸载:
- 原理:新调度到该节点的 Pod 复用已有挂载点,AD 控制器检测到卷已在节点挂载,跳过重复的 Attach 流程。
- 场景:同一节点上多个 Pod 使用同一 RWX(ReadWriteMany)卷,或同一 RWO 卷被同一节点的 Pod 重新调度。
5. in-tree 存储插件管理的卷
Kubernetes 内置的 in-tree 存储插件(如 kubernetes.io/aws-ebs
非 CSI 版本)由内部组件直接处理挂载,无需 External Attacher 参与:
- 原理:挂载逻辑集成于 Kubernetes 核心代码,通过内部接口调用云厂商 API,不依赖
VolumeAttachment
资源或外部边车容器。 - 现状:in-tree 插件正逐步被 CSI 插件替代,但存量集群中仍可能存在此类场景。
总结
“Attach 流程”的核心是块存储设备与节点的绑定,依赖 VolumeAttachment
资源和 CSI 插件的协同。而文件存储、对象存储、本地存储等场景因协议设计或实现方式不同,无需通过该流程即可完成存储访问,这体现了 Kubernetes 存储体系对多样化存储类型的适配能力。
四、组件代码位置与集群部署信息表
组件名称 | 代码位置 | 部署命名空间 | 部署形式 | 部署说明 |
---|---|---|---|---|
AD 控制器(AttachDetachController) | kubernetes/kubernetes/pkg/kubelet/volumemanager/attachdetach/ |
无独立命名空间(集成于 Kubelet) | 内置于 Kubelet 进程 | 作为 Kubelet 的核心模块运行在所有节点(控制节点和工作节点),随 Kubelet 启动;负责节点级存储卷的挂载管理和状态同步。 |
API Server | kubernetes/kubernetes/pkg/apiserver/ kubernetes/cmd/kube-apiserver/ |
kube-system |
独立静态 Pod(kube-apiserver ) |
控制平面核心组件,以静态 Pod 形式部署在控制节点;接收所有资源的 CRUD 请求,存储 VolumeAttachment 等资源到 etcd。 |
External Attacher | github.com/kubernetes-csi/external-attacher | 与 CSI 驱动同命名空间(如 csi-system ) |
边车容器(Sidecar) | 与 CSI 驱动部署在同一 Pod 中,通过 Unix Domain Socket 与 CSI 驱动通信;需配置 RBAC 权限以监听 VolumeAttachment 资源和更新状态。 |
CSI 驱动(CSIDriver) | 各存储厂商实现(如 aws-ebs-csi-driver) | 自定义命名空间(如 csi-system ) |
Deployment 或 DaemonSet | 控制器组件以 Deployment 部署(处理挂载调度),节点组件以 DaemonSet 部署(处理本地设备映射);需挂载主机路径(如 /dev 、/var/lib/kubelet )与存储设备交互。 |
云厂商存储服务(CloudProvider) | 云厂商自研实现(如 AWS EBS SDK、阿里云 EBS SDK) | 无(集群外部服务) | 云厂商托管服务 | 独立于 Kubernetes 集群的外部服务,通过 API 接口接收 CSI 驱动的挂载请求,负责底层存储设备的节点级挂载操作。 |
Node 组件 | kubernetes/kubernetes/pkg/kubelet/ |
无独立命名空间(节点级进程) | 内置于 Kubelet 节点状态 | 节点状态(Node.status.volumesAttached )由 Kubelet 维护,通过 API Server 同步集群状态;存储卷挂载点在节点本地路径(如 /var/lib/kubelet/pods/<pod-id>/volumes/ )。 |
CSI 存储卷节点挂载(Node Mount)时序流程详解
一、流程图
sequenceDiagram
title CSI 存储卷节点挂载时序流程
participant VolumeManager as Volume Manager<br/>(Kubelet 组件)
participant InTreeCSIAttacher as in-tree CSI 插件<br/>(csiAttacher)
participant InTreeCSIMountMgr as in-tree CSI 插件<br/>(csiMountMgr)
participant api-server
participant ExternalCSIPlugin as 外部 CSI 插件
participant Host as 主机节点
participant Container as 容器
%% 前置条件:Node 已处于 Ready 状态
Note over Host: Node 已达到 Ready 状态<br/>存储卷在主机内可见
%% 1. Volume Manager 触发节点级存储准备流程
VolumeManager->>InTreeCSIAttacher: 1. 调用 WaitForAttach 函数<br/>(等待卷 Attach 完成)
InTreeCSIAttacher->>api-server: 2. 监听 VolumeAttachment 对象状态
api-server-->>InTreeCSIAttacher: 3. 返回 VolumeAttachment.status.attached=true
%% 2. 执行 NodeStageVolume (分区格式化)
InTreeCSIAttacher->>InTreeCSIAttacher: 4. 确认卷已 Attach 成功
InTreeCSIAttacher->>ExternalCSIPlugin: 5. 通过 Unix Socket 调用<br/>NodeStageVolume 接口<br/>(分区格式化操作)
ExternalCSIPlugin->>Host: 6. 执行存储卷分区、格式化
Host-->>ExternalCSIPlugin: 7. 分区格式化完成
ExternalCSIPlugin-->>InTreeCSIAttacher: 8. 返回 Stage 成功<br/>(Volume Ready 状态)
Note over ExternalCSIPlugin: Volume Ready 状态<br/>(完成分区格式化)
%% 3. 执行 NodePublishVolume (挂载至容器目录)
InTreeCSIAttacher->>InTreeCSIMountMgr: 9. 调用 SetUp 函数<br/>(触发容器挂载流程)
InTreeCSIMountMgr->>ExternalCSIPlugin: 10. 通过 Unix Socket 调用<br/>NodePublishVolume 接口<br/>(挂载至容器目录)
ExternalCSIPlugin->>Host: 11. 将存储卷挂载至容器指定目录
Host-->>ExternalCSIPlugin: 12. 挂载操作完成
ExternalCSIPlugin-->>InTreeCSIMountMgr: 13. 返回 Publish 成功<br/>(Published 状态)
Note over ExternalCSIPlugin: Published 状态<br/>(已挂载至容器目录)
%% 4. 容器访问存储卷
InTreeCSIMountMgr-->>VolumeManager: 14. 通知存储卷准备完成
VolumeManager->>Container: 15. 容器启动并访问挂载目录
Note over Container: 存储卷可用 (容器内可见)
二、详细流程步骤
等待卷 Attach 完成
Kubelet 内置的 Volume Manager 负责节点级存储卷的生命周期管理,当检测到使用 CSI 卷的 Pod 调度到本节点后,首先调用内部 in-tree CSI 插件(csiAttacher
)的WaitForAttach
函数,确保卷已通过 Attach 流程绑定到节点。监听 VolumeAttachment 状态
csiAttacher
通过 API Server 持续监听VolumeAttachment
资源的状态变化,等待其status.attached
变为true
,确认卷已成功挂载到节点(如块设备已通过云 API 关联到主机)。执行 NodeStageVolume(分区格式化)
卷 Attach 完成后,csiAttacher
调用外部 CSI 插件的NodeStageVolume
接口:- 外部 CSI 插件对节点级存储卷执行分区(如 GPT 分区)和格式化(如
mkfs.ext4
)操作,将原始块设备转换为可挂载的文件系统。 - 完成后,卷进入 Volume Ready 状态,表示已具备挂载到容器的基础条件。
- 外部 CSI 插件对节点级存储卷执行分区(如 GPT 分区)和格式化(如
触发容器挂载流程
csiAttacher
调用内部 in-tree CSI 插件(csiMountMgr
)的SetUp
函数,将存储卷从“节点级可用”推进到“容器级可用”,准备将卷挂载到 Pod 的指定目录。执行 NodePublishVolume(容器目录挂载)
csiMountMgr
通过 Unix Domain Socket 调用外部 CSI 插件的NodePublishVolume
接口:- 外部 CSI 插件将已格式化的卷(或网络共享目录)挂载到容器的指定路径(如
/var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~csi/<pv-name>/mount
)。 - 挂载过程可能涉及绑定挂载(
bind mount
)、权限设置(如chmod
)或挂载选项配置(如ro
只读)。 - 完成后,卷进入 Published 状态,表示已成功挂载到容器的目标目录。
- 外部 CSI 插件将已格式化的卷(或网络共享目录)挂载到容器的指定路径(如
通知存储卷准备完成
csiMountMgr
向 Volume Manager 反馈挂载成功,Volume Manager 确认存储卷就绪后,允许容器启动并访问挂载目录。容器访问存储卷
容器启动后,通过 Pod 定义中指定的volumeMounts
路径访问存储卷,此时卷已完全集成到容器的文件系统中,用户数据可通过容器读写存储卷。
三、不执行该流程的场景
以下情况不会触发上述节点挂载流程,存储卷通过其他方式完成容器访问:
网络文件系统(NFS/SMB)
NFS、SMB 等基于文件共享的存储卷无需分区格式化:- 原理:远程共享目录已提前格式化,节点直接通过网络协议(如 NFS v4、CIFS)挂载目录,无需
NodeStageVolume
操作。 - 差异:
NodePublishVolume
仅执行网络目录到容器路径的绑定挂载,跳过块设备相关的分区格式化步骤。
- 原理:远程共享目录已提前格式化,节点直接通过网络协议(如 NFS v4、CIFS)挂载目录,无需
对象存储映射(S3/OSS)
S3、OSS 等对象存储通过工具(如s3fs
)映射为本地目录时:- 原理:通过 FUSE(用户态文件系统)将对象接口转换为目录结构,无需块设备或传统文件系统格式化。
- 差异:
NodeStageVolume
可能被跳过,NodePublishVolume
直接挂载 FUSE 映射的目录。
临时存储卷(emptyDir/ephemeral)
节点本地临时卷(如emptyDir
)依赖主机临时目录:- 原理:直接使用节点本地路径(如
/var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~empty-dir/
),无需 CSI 插件参与。 - 差异:整个流程由 Kubelet 内部处理,不涉及
NodeStageVolume
或NodePublishVolume
调用。
- 原理:直接使用节点本地路径(如
只读卷复用已有挂载点
若卷已通过NodePublishVolume
挂载到节点且为只读模式(ro
):- 原理:新 Pod 复用同一挂载点,通过绑定挂载将只读卷映射到不同容器目录,无需重复执行格式化和挂载流程。
- 场景:多个 Pod 共享同一 RWX 只读卷,或同一 RWO 卷被同一节点的 Pod 重新使用。
四、组件代码位置与集群部署信息表
组件名称 | 代码位置 | 部署命名空间 | 部署形式 | 部署说明 |
---|---|---|---|---|
Volume Manager | kubernetes/kubernetes/pkg/kubelet/volumemanager/ |
无独立命名空间(集成于 Kubelet) | 内置于 Kubelet 进程 | 作为 Kubelet 的核心模块运行在所有节点,负责协调卷的 Attach、挂载和卸载流程,通过控制循环监听 Pod 和卷状态变化。 |
in-tree CSI 插件(csiAttacher/csiMountMgr) | kubernetes/kubernetes/pkg/volume/csi/ |
无独立命名空间(集成于 Kubelet) | 内置于 Kubelet 代码 | Kubelet 内置的 CSI 适配层,负责桥接 Kubelet 与外部 CSI 插件,实现 WaitForAttach 、SetUp 等接口的逻辑转发。 |
外部 CSI 插件(External CSI Plugin) | 各存储厂商实现(如 csi-driver-nfs) | 自定义命名空间(如 csi-system ) |
DaemonSet(节点插件) | 以 DaemonSet 形式部署在所有节点,通过 Unix Domain Socket(如 /var/lib/kubelet/plugins/<driver-name>/csi.sock )提供 NodeStageVolume 和 NodePublishVolume 接口,直接操作节点存储设备。 |
API Server | kubernetes/kubernetes/pkg/apiserver/ kubernetes/cmd/kube-apiserver/ |
kube-system |
独立静态 Pod | 控制平面核心组件,存储 VolumeAttachment 资源并提供状态查询接口,支持 Kubelet 和 CSI 插件的监听操作。 |
Host 节点 | 操作系统内核/工具(如 mount 、mkfs ) |
无(节点本地) | 节点操作系统组件 | 提供存储卷的物理挂载点(如 /dev 设备、/var/lib/kubelet 目录),执行分区、格式化和挂载命令,是存储卷实际运行的硬件载体。 |
Container 容器 | 应用镜像/容器运行时(如 containerd) | 应用所属命名空间 | Pod 中的容器实例 | 通过容器运行时的挂载命名空间隔离,访问绑定挂载的存储卷目录,感知不到底层 CSI 插件的操作细节。 |