Attach 和 Mount

一、核心概念对比

操作Attach(挂载设备)Mount(挂载文件系统)
定义将存储卷(如 EBS、NFS 等)连接到宿主机将已 Attach 的存储设备映射为宿主机上的文件系统路径
执行者云提供商驱动(AWS EBS CSI Driver)或存储系统插件容器运行时(containerd、Docker)或 kubelet
操作对象存储卷(Volume)文件系统(Filesystem)
Kubernetes 资源VolumeAttachment 对象Pod.spec.volumes 定义
操作结果宿主机可识别存储设备(如 /dev/xvdf容器可访问文件路径(如 /data

二、工作流程与协作关系

1. Attach 流程

请添加图片描述
图源:https://www.lixueduan.com/posts/kubernetes/14-pv-dynamic-provision-process/#1-attach

1. 核心组件与职责

Kubernetes 的存储 Attach 流程由两个核心组件协作完成:

  • AD Controller (AttachDetach Controller)
    位于 kube-controller-manager 中,负责计算节点上需要 Attach/Detach 的卷,并创建 VolumeAttachment 资源。
  • external-attacher
    独立运行的 CSI 插件,监听 VolumeAttachment 资源变化,调用 CSI Driver 的接口执行实际 Attach 操作。
2. Attach 触发条件

AD Controller 通过以下逻辑触发 Attach 操作:

  1. 监听 Pod 调度:当 Pod 被调度到特定节点时,AD Controller 获取该节点上所有 Pod 的 Volume 列表。
  2. 计算待 Attach 卷:对比当前节点的 status.volumesAttached 与 Pod 需要的卷,找出未 Attach 的 PV。
  3. 多节点挂载检查:对 ReadWriteOnce (RWO) 类型的卷,检查是否已被其他节点挂载(若已挂载则报错)。
3. VolumeAttachment 资源

AD Controller 创建的 VolumeAttachment 对象包含三个关键信息:

apiVersion: storage.k8s.io/v1
kind: VolumeAttachment
spec:attacher: nfs.csi.k8s.io  # CSI Driver 名称nodeName: ee              # 目标节点source:persistentVolumeName: pvc-047acd58-...  # 待挂载的 PV
status:attached: false  # 挂载状态(由 external-attacher 更新)
4. 详细执行流程
ADControllerVolumeAttachmentExternalAttacherCSIDriverCloudProviderNode创建 VolumeAttachment 资源监听资源变化调用 ControllerPublishVolume(PV, Node)调用云 API(如 AWS EBS Attach)返回设备 ID(如 /dev/xvdf)返回成功更新 status.attached=true更新 status.volumesAttachedADControllerVolumeAttachmentExternalAttacherCSIDriverCloudProviderNode

2. Mount 流程

2.1 核心组件与数据结构

Kubernetes 的 Mount 流程由 kubeletvolumeManager 组件管理,主要包含以下核心元素:

type volumeManager struct {desiredStateOfWorld cache.DesiredStateOfWorld // 期望状态缓存actualStateOfWorld  cache.ActualStateOfWorld // 实际状态缓存reconciler          reconciler.Reconciler     // 状态协调器desiredStateOfWorldPopulator populator.DesiredStateOfWorldPopulator // 状态填充器// ...其他组件
}
  • desiredStateOfWorld:保存当前节点上所有 Volume 期望的状态
  • actualStateOfWorld:保存当前节点上所有 Volume 实际的状态
  • reconciler:周期性比较两个状态,执行挂载/卸载操作
  • desiredStateOfWorldPopulator:处理节点上的 Pod,更新期望状态
2.2 状态同步机制

reconciler 通过周期性对比状态执行挂载/卸载操作:

func (rc *reconciler) reconcile() {if rc.readyToUnmount() {rc.unmountVolumes() // 卸载不再需要的卷}rc.mountOrAttachVolumes() // 挂载新卷或处理已挂载卷if rc.readyToUnmount() {rc.unmountDetachDevices() // 卸载设备rc.cleanOrphanVolumes()   // 清理孤立卷}// 更新状态同步时间if len(rc.volumesNeedUpdateFromNodeStatus) != 0 {rc.updateReconstructedFromNodeStatus()}if len(rc.volumesNeedUpdateFromNodeStatus) == 0 {rc.updateLastSyncTime()}
}
2.3 卸载流程

遍历 actualStateOfWorld,卸载不再需要的卷:

func (rc *reconciler) unmountVolumes() {for _, mountedVolume := range rc.actualStateOfWorld.GetAllMountedVolumes() {// 检查是否有未完成的操作if rc.operationExecutor.IsOperationPending(mountedVolume.VolumeName, mountedVolume.PodName, nestedpendingoperations.EmptyNodeName) {continue}// 如果卷不在期望状态中,执行卸载if !rc.desiredStateOfWorld.PodExistsInVolume(mountedVolume.PodName, mountedVolume.VolumeName, mountedVolume.SELinuxMountContext) {err := rc.operationExecutor.UnmountVolume(mountedVolume.MountedVolume, rc.actualStateOfWorld, rc.kubeletPodsDir)if err != nil {klog.ErrorS(err, "UnmountVolume failed")}}}
}
2.4 挂载流程

遍历 desiredStateOfWorld,挂载新卷或处理需要更新的卷:

func (rc *reconciler) mountOrAttachVolumes() {for _, volumeToMount := range rc.desiredStateOfWorld.GetVolumesToMount() {// 检查是否有未完成的操作if rc.operationExecutor.IsOperationPending(volumeToMount.VolumeName, nestedpendingoperations.EmptyUniquePodName, nestedpendingoperations.EmptyNodeName) {continue}// 检查卷状态volMounted, devicePath, err := rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName, volumeToMount.DesiredPersistentVolumeSize, volumeToMount.SELinuxLabel)volumeToMount.DevicePath = devicePath// 根据不同错误类型执行不同操作switch {case cache.IsSELinuxMountMismatchError(err):// SELinux 上下文不匹配,标记错误case cache.IsVolumeNotAttachedError(err):// 卷未挂载,等待 Attachrc.waitForVolumeAttach(volumeToMount)case !volMounted || cache.IsRemountRequiredError(err):// 卷未挂载或需要重新挂载rc.mountAttachedVolumes(volumeToMount, err)case cache.IsFSResizeRequiredError(err):// 文件系统需要扩容rc.expandVolume(volumeToMount, err.CurrentSize)}}
}
2.5 实际挂载操作

通过 operationGenerator 执行实际挂载,并更新状态:

func (og *operationGenerator) GenerateMountVolumeFunc(...) volumetypes.GeneratedOperations {mountVolumeFunc := func() {// 获取卷插件volumePlugin, err := og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)if err != nil {return volumetypes.NewOperationContext(err, nil, migrated)}// 创建挂载器volumeMounter, err := volumePlugin.NewMounter(volumeToMount.VolumeSpec, volumeToMount.Pod)if err != nil {return volumetypes.NewOperationContext(err, nil, migrated)}// 等待设备挂载(如果需要)devicePath, err := volumeAttacher.WaitForAttach(volumeToMount.VolumeSpec, devicePath, volumeToMount.Pod, waitForAttachTimeout)if err != nil {return volumetypes.NewOperationContext(err, nil, migrated)}// 执行挂载mountErr := volumeMounter.SetUp(volume.MounterArgs{...})if mountErr != nil {return volumetypes.NewOperationContext(mountErr, nil, migrated)}// 扩容文件系统(如果需要)if resizeNeeded {err = og.expandVolumeDuringMount(volumeToMount, actualStateOfWorld, resizeOptions)if err != nil {return volumetypes.NewOperationContext(err, nil, migrated)}}// 更新实际状态markVolMountedErr := actualStateOfWorld.MarkVolumeAsMounted(markOpts)if markVolMountedErr != nil {return volumetypes.NewOperationContext(markVolMountedErr, nil, migrated)}return volumetypes.NewOperationContext(nil, nil, migrated)}return volumetypes.GeneratedOperations{OperationFunc: mountVolumeFunc,// ...其他回调函数}
}
2.6 实际卸载操作

通过 operationGenerator 执行卸载,并更新状态:

func (og *operationGenerator) GenerateUnmountVolumeFunc(...) {unmountVolumeFunc := func() {// 获取卸载器volumeUnmounter, err := volumePlugin.NewUnmounter(volumeToUnmount.InnerVolumeSpecName, volumeToUnmount.PodUID)if err != nil {return volumetypes.NewOperationContext(err, nil, migrated)}// 清理子路径挂载点if err := subpather.CleanSubPaths(podDir, volumeToUnmount.InnerVolumeSpecName); err != nil {return volumetypes.NewOperationContext(err, nil, migrated)}// 执行卸载unmountErr := volumeUnmounter.TearDown()if unmountErr != nil {// 标记卷状态为不确定actualStateOfWorld.MarkVolumeMountAsUncertain(opts)return volumetypes.NewOperationContext(unmountErr, nil, migrated)}// 更新实际状态actualStateOfWorld.MarkVolumeAsUnmounted(volumeToUnmount.PodName, volumeToUnmount.VolumeName)return volumetypes.NewOperationContext(nil, nil, migrated)}return volumetypes.GeneratedOperations{OperationFunc: unmountVolumeFunc,// ...其他回调函数}
}
2.7 期望状态更新机制

desiredStateOfWorldPopulator 周期性处理 Pod,更新期望状态:

func (dswp *desiredStateOfWorldPopulator) Run(ctx context.Context, sourcesReady config.SourcesReady) {// 周期性执行状态填充wait.UntilWithContext(ctx, dswp.populatorLoop, dswp.loopSleepDuration)
}func (dswp *desiredStateOfWorldPopulator) processPodVolumes(ctx context.Context, pod *v1.Pod) {for _, podVolume := range pod.Spec.Volumes {// 将 Pod 的卷添加到期望状态uniqueVolumeName, err := dswp.desiredStateOfWorld.AddPodToVolume(uniquePodName, pod, volumeSpec, podVolume.Name, volumeGIDValue, seLinuxContainerContexts[podVolume.Name])if err != nil {klog.ErrorS(err, "Failed to add pod to volume")}}
}
2.8 关键数据结构
  • volumesToMount:记录需要挂载的卷
    type volumeToMount struct {volumeName                     v1.UniqueVolumeNamepodsToMount                    map[types.UniquePodName]podToMountpluginIsAttachable             boolpluginIsDeviceMountable        boolvolumeGIDValue                 stringdesiredSizeLimit               *resource.QuantityeffectiveSELinuxMountFileLabel string// ...其他属性
    }
    

Mount 流程总结

  1. 状态初始化desiredStateOfWorldPopulator 从 Pod 中收集卷信息,更新期望状态
  2. 状态对比reconciler 周期性比较期望状态和实际状态
  3. 卸载操作:对不再需要的卷执行卸载
  4. 挂载操作:对新增卷或需要更新的卷执行挂载
  5. 状态更新:挂载/卸载成功后更新实际状态
  6. 错误处理:处理挂载/卸载过程中的各种异常情况

通过这种双缓存、周期性同步的机制,Kubernetes 确保了节点上卷的状态始终与期望状态一致。

3. 协作关系

存储卷生命周期:
创建PV/PVC → Attach(设备挂载到宿主机) → Mount(文件系统挂载到容器) → 
Unmount(从容器卸载) → Detach(从宿主机卸载) → 删除PV/PVC

三、常见存储类型的 Attach/Mount 差异

存储类型Attach 操作Mount 操作
EBS(块存储)将 EBS 卷挂载到 EC2 实例在实例上格式化并挂载文件系统(如 ext4)
NFS(网络存储)建立网络连接(无需显式 Attach)通过 NFS 客户端挂载远程文件系统
HostPath(宿主机路径)无(已在宿主机上)直接将宿主机路径挂载到容器
Ceph RBD将 RBD 设备映射到宿主机在宿主机上挂载 RBD 设备为文件系统

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/914946.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/914946.shtml
英文地址,请注明出处:http://en.pswp.cn/news/914946.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

API Gateway HTTP API 控制客户端访问 IP 源

前言 在 API Gateway REST API 中我们可以配置 Resource policy 来实现对特定客户端 IP 地址的限制. 然而 HTTP API 并不提供这个功能, 不过我们可以用 Lambda 搓一个 Authorizer 实现等效的功能. 创建 Lambda authorizer import json import os import ipaddressdef lambda…

Linux搭建LAMP环境(CentOS 7 与 Ubuntu 双系统教程)

Linux搭建LAMP环境 一、LAMP 环境核心概念 定义:由 Linux、Apache、MySQL、PHP 四大组件组成的开源 Web 应用平台本质:四个独立开源软件的组合体,因长期协同使用形成高度兼容性,成为动态网站和服务器的主流解决方案 二、LAMP 四大…

c# 开机自动启动程序

以下是两种实现C#软件开机自启动的常用方法&#xff0c;根据需求选择适合的方案&#xff1a; 方法1&#xff1a;通过注册表实现&#xff08;需管理员权限&#xff09; using Microsoft.Win32; using System.Diagnostics;public static class AutoStartManager {/// <summa…

C语言---动态内存管理

为什么要有动态内存分配我们在学习动态内存管理之前&#xff0c;一直都是通过开辟变量&#xff0c;或者是开辟数组的方式来在内存的栈区开辟空间的&#xff0c;但是这样的开辟方式有局限性&#xff0c;因为一旦开辟之后&#xff0c;它们的大小就无法改变&#xff0c;就缺少了很…

C++标准库(std)详解

C标准库&#xff08;std&#xff09;详解——目录C标准库&#xff08;std&#xff09;详解一、命名空间&#xff08;namespace&#xff09;二、主要组件1. 输入输出流&#xff08;<iostream>&#xff09;2. 字符串处理&#xff08;<string>&#xff09;3. STL容器&…

ESP32的OTA升级详解:3. 搭建node/python服务器升级(native ota原生API)

一、OTA两种方式&#xff1a;app_update 与 esp_https_ota 区别 ESP32/ESP32-S2/ESP32-C3等可通过Wi-Fi或以太网下载新固件到OTA分区实现运行时升级。ESP-IDF提供两种OTA升级方法&#xff1a; 使用app_update组件的原生API使用esp_https_ota组件的简化API(支持HTTPS升级) 本次…

byte[]作为接口参数传递的方法

在C#中通过WebService或API传递byte参数&#xff08;如文件、图像等二进制数据&#xff09;时&#xff0c;通常有以下几种实现方式&#xff1a; ‌1. 使用Base64编码&#xff08;推荐REST API&#xff09;‌ 将byte数组转换为Base64字符串传输&#xff0c;适用于JSON格式的API&…

元宇宙与Web3的深度融合:构建沉浸式数字体验的愿景与挑战

一、技术特征与融合基础1. 元宇宙的技术架构&#xff08;2025年&#xff09;空间构建技术&#xff1a;3D建模与渲染&#xff1a;实时渲染引擎&#xff08;如Unity HDRP&#xff09;支持路径追踪光追&#xff0c;AI生成模型&#xff08;NVIDIA Get3D&#xff09;3秒生成3D场景。…

什么是脑裂

脑裂定义&#xff1a; 脑裂是分布式系统中由于网络分区&#xff08;Network Partition&#xff09;导致集群节点被分割成多个独立子集&#xff0c;每个子集认为自己是唯一合法的集群&#xff0c;从而导致数据不一致或系统行为异常的现象。详细工作原理&#xff1a;发生原因&…

川翔云电脑:云端算力新标杆,创作自由无边界

一、旗舰机型&#xff1a;4090Ultra 48G 显存颠覆硬件限制 川翔云电脑最新上线的RTX 4090Ultra 48G 显存机型&#xff0c;采用 NVIDIA Ada Lovelace 架构&#xff0c;单卡显存容量达 48GB GDDR6X&#xff0c;较传统 4090 翻倍&#xff0c;可直接加载 1200 万面数的超复杂模型&a…

贪心算法(排序)

码蹄集OJ-活动安排 #include<bits/stdc.h> using namespace std; struct MOOE {int s,e; }; bool compare(const MOOE&a,const MOOE&b) {return a.e<b.e; } int main( ) {int n;cin>>n;vector<MOOE>a(n);for(int i0;i<n;i){cin>>a[i].…

详解序数回归损失函数ordinal_regression_loss:原理与实现

在医疗 AI 领域&#xff0c;很多分类任务具有有序类别的特性&#xff0c;如疾病严重程度&#xff08;轻度→中度→重度&#xff09;、肿瘤分级&#xff08;G1→G2→G3&#xff09;等。这类任务被称为序数回归&#xff08;Ordinal Regression&#xff09;&#xff0c;需要特殊的…

SQL增查

建完库与建完表后后:1.分别查询student表和score表的所有记录student表&#xff1a;score表:2.查询student表的第2条到5条记录SELECT * FROM student LIMIT 1,4;3.从student表中查询计算机系和英语系的学生的信息SELECT * FROM student-> WHERE department IN (计算机系, 英…

二分答案之最大化最小值

参考资料来源灵神在力扣所发的题单&#xff0c;仅供分享学习笔记和记录&#xff0c;无商业用途。 核心思路&#xff1a;本质上是求最大 应用场景&#xff1a;在满足条件的最小值区间内使最大化 检查函数&#xff1a;保证数据都要大于等于答案 补充&#xff1a;为什么需要满…

OCR 赋能档案数字化:让沉睡的档案 “活” 起来

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;企业产品档案包含设计图纸、检测报告、生产记录等&#xff0c;传统数字化仅靠扫描存档&#xff0c;后续检索需人工逐份翻阅&#xff0c;效率极低。​OCR 产品档案解决方案直击痛点&#xff1a;通过智能识别技…

力扣118.杨辉三角

思路1.新建一个vector的vector2.先把空间开出来&#xff0c;然后再把里面的值给一个个修改开空间的手段&#xff1a;new、构造函数、reserve、resize因为我们之后要修改里面的数据&#xff0c;这就意味着我们需要去读取这个数据并修改&#xff0c;如果用reserve的话&#xff0c…

Python 网络爬虫 —— 提交信息到网页

一、模块核心逻辑“提交信息到网页” 是网络交互关键环节&#xff0c;借助 requests 库的 post() 函数&#xff0c;能模拟浏览器向网页发数据&#xff08;如表单、文件 &#xff09;&#xff0c;实现信息上传&#xff0c;让我们能与网页背后的服务器 “沟通”&#xff0c;像改密…

SpringMVC4

一、SpringMVC 注解与项目开发流程1.1注解的生命周期- Target、Retention 等元注解&#xff1a;- Target(ElementType.TYPE) &#xff1a;说明这个注解只能用在类、接口上。- Retention(RetentionPolicy.RUNTIME) &#xff1a;说明注解在运行时保留&#xff0c;能通过反射获取…

数据结构排序算法总结(C语言实现)

以下是常见排序算法的总结及C语言实现&#xff0c;包含时间复杂度、空间复杂度和稳定性分析&#xff1a;1. 冒泡排序 (Bubble Sort)思想&#xff1a;重复比较相邻元素&#xff0c;将较大元素向后移动。 时间复杂度&#xff1a;O(n)&#xff08;最好O(n)&#xff0c;最坏O(n)) 空…

嵌入式学习-PyTorch(2)-day19

很久没有学了&#xff0c;期间打点滴打了一个多星期&#xff0c;太累了&#xff0c;再加上学了一下Python语法基础&#xff0c;再终于开始重新学习pytorchtensorboard 的使用import torch from torch.utils.tensorboard import SummaryWriter writer SummaryWriter("logs…