Overview
阅读完本文,您当了解
- Kubernetes 卷
- CephFS 在 kubernetes 中的挂载
- Kubelet VolumeManager
本文只是个人理解,如果有大佬觉得不是这样的可以留言一起讨论,参考源码版本为 1.18.20,与高版本相差不大
VolumeManager
VolumeManager VM 是在 kubelet 启动时被初始化的一个异步进程,主要是维护 “Pod" 卷的两个状态,”desiredStateOfWorld“ 和 ”actualStateOfWorld“; 这两个状态用于将节点上的卷 “协调” 到所需的状态。
VM 实际上包含三个 “异步进程” (goroutine),其中有一个 reconciler 就是用于协调与挂载的,下面就来阐述 VM 的挂载过程。
VM中的重要组件
- actualStateOfWorld
- mountedPod
- desiredStateOfWorld
- VolumeToMount
- podToMount
VM的组成
VM 的代码位于,由图可以看出,主要包含三个重要部分:
- reconciler:协调器
- populator:填充器
- cache:包含 ”desiredStateOfWorld“ 和 ”actualStateOfWorld“
在代码结构上,volumeManager 如下所示
| |
VM的初始化
- 入口:“volumeManager”(vm) 的 初始化 操作发生在 kubelet Run 时被作为一个异步进程启动。
- VM 初始化:
- 如代码1所示,VM在初始化阶段创建了两个 cache 对象 “desiredStateOfWorld”(dsw)和“actualStateOfWorld”(asw)以及一个 “operationExecutor”,用于启动异步的线程操作 attach,detach, mount, 和 unmount
- 如代码2所示:VM在初始化阶段还创建了 “desiredStateOfWorldPopulator” (dswp) 与 “reconciler”
- “reconciler” 通过使用上面的 “operationExecutor” 触发 attach,detach, mount 和 unmount来协调 dsw 与 asw
代码1
| |
代码2:
| |
VM 的 Run
VM 是在 Kubelet 启动时作为异步线程启动,如代码1所示
如下面代码2所示,VM 在运行时会启动 三个 异步线程
- 第一个调用是 第二个是调用 “dswp” 填充其的“ Run ”,这里主要做的操作是从 API 拿到 Pod 列表,根据对应条件来决定 attach,detach, mount, 和 unmount
- 第二个调用的是,reconciler 来协调应该安装的卷是否已安装以及应该卸载的卷是否已卸载。
- 第三个调用的是,volumePluginMgr,启用 CSI informer
| |
| |
VM 的调用流程
desiredStateOfWorldPopulator
DesiredStateOfWorldPopulator 是一个周期 Loop,会定期循环遍历 Active Pod 列表,并确保每个 Pod 都处于所需状态(如果有卷,World state)。它还会验证 World cache 中处于所需状态的 pod 是否仍然存在,如果不存在,则会将其删除。
desiredStateOfWorldPopulator 结构包含两个方法,ReprocessPod 和 HasAddedPods;ReprocessPod 负责将 processedPods 中指定 pod 的值设置为false,强制重新处理它。这是在 Pod 更新时启用重新挂载卷所必需的。而 HasAddedPods 返回 填充器 是否已循环遍历 Active Pod 列表并将它们添加到 world cache 的所需状态。
在期待填充器 desiredStateOfWorldPopulator 启动时,会运行一个 populatorLoop,这里主要负责运行两个函数,
findAndAddNewPods负责迭代所有 pod,如果它们不存在添加到 desired state of world (desiredStateOfWorld)findAndRemoveDeletedPods负责迭代desiredStateOfWorld下的所有 Pod,如果它们不再存在则将其删除
reconciler
reconciler Run 的过程是通过一个 Loop 函数 reconciliationLoopFunc 完成的,正如下列 代码 所示
| |
Reconciler 是挂载部分最重要的角色,用于协调应该安装的卷是否已安装以及应该卸载的卷是否已卸载;对应的,实际上执行的为三个函数:“unmountVolumes”、“mountAttachVolumes” 和 “unmountDetachDevices”。
mountAttachVolumes
首先,“mountAttachVolumes” 会调用 “dsw” (desiredStateOfWorld) 的函数 “GetVolumesToMount” 来检索所有 “volumesToMount” 并迭代它们,这里主要是为了确保 “volumes” 应完成了 “attached/mounted”
接下来这个循环做的工作是,对于每个 Volume 和 Pod,都会检查该 Volume 或 Pod 是否存在于 “asw” 的 “attachedVolumes” 中。如果 Volume 不存在,则“asw”返回 “newVolumeNotAttachedError ”,否则它检查指定的 pod 是否存在并根据状态返回结果。这里存在 三个状态,返回也是根据这个状态返回。这里主要为了得到挂载路径和是否挂载
VolumeMounted:表示 Volume 已挂载到 pod 的本地路径中
VolumeMountUncertain:表示 Volume 可能会也可能不会安装在 Pod 的本地路径中
VolumeNotMounted:表示 Volume 还未挂载到 pod 的本地路径中
当“ asw” 返回 “ newVolumeNotAttachedError ” 时,“reconciler” 会检查 “controllerAttachDetachEnabled” 是否启用,或 “volumeToMount” 没有实现了对应插件,这里面如果其中任何一个为 true,“reconciler” 将调用 “operationExecutor” 来执行操作“ ,走到这里代表了 Volume 没有被 attach,或者没有实现 attacher,例如 cephfs 没有实现 attacher;或者是 kubelet 禁用了 attach [1] (默认是开启状态),将进入 “ VerifyControllerAttachedVolume ”
在此期间,“operationExecutor” 生成一个名为 “verifyControllerAttachedVolumeFunc” 的函数来实际实现。在此函数中,如果 “volumeToMount” 的 “PluginIsAttachable” 为 false(没有实现),则假设其已经实现并标记 attached,标记出错时进行重试(这是一个函数用于后面的调用,这里只是定义)
如果还没有将 Node attached 到 Volume 节点列表状态中,则返回错误进行重试(这是一个函数用于后面的调用,这里只是定义)
上面两个步骤是为了组装这个操作,返回的是操作的内容,包含执行的函数,完成的hook等,最后运行这个函数并返回
这是步骤3的另外一个分支,即 kubelet 启用了 ADController,并且实现了对应的 attcher,那么将执行附加操作
- 拼接对象
- 执行函数 ”AttachVolume“
- AttachVolume 如上面步骤一样,拼接出最后的执行的动作,进行执行操作(将 node 附加到 volume 之上)
步骤5 表示 3, 4 条件均不满足,也就是 Attached,目前状态为 ”未挂载“ 或者 ”已挂载“,将执行这个步骤,未挂载的进行挂载,已挂载的进行 remount
在该分支中(也就是 步骤5 执行的)执行的是名为 “GenerateMountVolumeFunc“ 的函数,在此函数中,会获取 Plugin ,并通过 Plugin 创建出一个 volumeMounter,在通过 Plugin 获取一个 deviceMouter(能够挂载块设备的);当然我们这里挂载的是 ”cephfs“ 所以没有 ”deviceMouter“ 这里不被执行。
- 如果 ”deviceMounter“ 定义了,那么则执行这个 plugin 的 “MountDevice” 函数
- 如果没有定义,那么执行 volumeMounter 的 SetUp 进行挂载(因为不是块设备)
执行 SetUp 函数,通常 NFS, CephFS, HostPath,都实现了这个函数,那么就会通过这个函数挂载到 Node 对应的目录
最后通过 Overlay2 文件系统附加到容器里
