本文发布于Cylon的收藏册,转载请著名原文链接~
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 如下所示
// volumeManager implements the VolumeManager interface
type volumeManager struct {
// DesiredStateOfWorldPopulator 用来与 API 服务器通信以获取 PV 和 PVC 对象的 API 客户端
kubeClient clientset.Interface
// VolumePluginMgr 是用于访问 VolumePlugin 插件的 VolumePlugin 管理器。它必须预初始化。
volumePluginMgr *volume.VolumePluginMgr
// desiredStateOfWorld 是一个数据结构,包含根据 VM 所需的状态:即应附加哪些卷以及 "哪些pod” 正在引用这些卷。
// 使用 kubelet pod manager 根据 world populator 的所需状态填充数据结构。
desiredStateOfWorld cache.DesiredStateOfWorld
// 与 desiredStateOfWorld 相似,是实际状态:即哪些卷被 attacted 到该 Node 以及 volume 被 mounted 到哪些 pod。
// 成功完成 reconciler attach,detach, mount, 和 unmount 操作后,将填充数据结构。
actualStateOfWorld cache.ActualStateOfWorld
// operationExecutor 用于启动异步 attach,detach, mount, 和 unmount 操作。
operationExecutor operationexecutor.OperationExecutor
// reconciler reconciler 运行异步周期性循环,通过使用操作执行器触发 attach,detach, mount, 和 unmount操作
// 来协调 desiredStateOfWorld 与 actualStateOfWorld。
reconciler reconciler.Reconciler
// desiredStateOfWorldPopulator 运行异步周期性循环以使用 kubelet Pod Manager 填充desiredStateOfWorld。
desiredStateOfWorldPopulator populator.DesiredStateOfWorldPopulator
// csiMigratedPluginManager keeps track of CSI migration status of plugins
csiMigratedPluginManager csimigration.PluginManager
// intreeToCSITranslator translates in-tree volume specs to CSI
intreeToCSITranslator csimigration.InTreeToCSITranslator
}
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
vm := &volumeManager{
kubeClient: kubeClient,
volumePluginMgr: volumePluginMgr,
desiredStateOfWorld: cache.NewDesiredStateOfWorld(volumePluginMgr),
actualStateOfWorld: cache.NewActualStateOfWorld(nodeName, volumePluginMgr),
operationExecutor: operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
kubeClient,
volumePluginMgr,
recorder,
checkNodeCapabilitiesBeforeMount,
blockVolumePathHandler)),
}
代码2:
vm.intreeToCSITranslator = intreeToCSITranslator
vm.csiMigratedPluginManager = csiMigratedPluginManager
vm.desiredStateOfWorldPopulator = populator.NewDesiredStateOfWorldPopulator(
kubeClient,
desiredStateOfWorldPopulatorLoopSleepPeriod,
desiredStateOfWorldPopulatorGetPodStatusRetryDuration,
podManager,
podStatusProvider,
vm.desiredStateOfWorld,
vm.actualStateOfWorld,
kubeContainerRuntime,
keepTerminatedPodVolumes,
csiMigratedPluginManager,
intreeToCSITranslator)
vm.reconciler = reconciler.NewReconciler(
kubeClient,
controllerAttachDetachEnabled,
reconcilerLoopSleepPeriod,
waitForAttachTimeout,
nodeName,
vm.desiredStateOfWorld,
vm.actualStateOfWorld,
vm.desiredStateOfWorldPopulator.HasAddedPods,
vm.operationExecutor,
mounter,
hostutil,
volumePluginMgr,
kubeletPodsDir)
VM 的 Run
VM 是在 Kubelet 启动时作为异步线程启动,如代码1所示
如下面代码2所示,VM 在运行时会启动 三个 异步线程
- 第一个调用是 第二个是调用 “dswp” 填充其的“ Run ”,这里主要做的操作是从 API 拿到 Pod 列表,根据对应条件来决定 attach,detach, mount, 和 unmount
- 第二个调用的是,reconciler 来协调应该安装的卷是否已安装以及应该卸载的卷是否已卸载。
- 第三个调用的是,volumePluginMgr,启用 CSI informer
// Start volume manager
go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)
func (vm *volumeManager) Run(sourcesReady config.SourcesReady, stopCh <-chan struct{}) {
defer runtime.HandleCrash()
go vm.desiredStateOfWorldPopulator.Run(sourcesReady, stopCh)
klog.V(2).Infof("The desired_state_of_world populator starts")
klog.Infof("Starting Kubelet Volume Manager")
go vm.reconciler.Run(stopCh)
metrics.Register(vm.actualStateOfWorld, vm.desiredStateOfWorld, vm.volumePluginMgr)
if vm.kubeClient != nil {
// start informer for CSIDriver
vm.volumePluginMgr.Run(stopCh)
}
<-stopCh
klog.Infof("Shutting down Kubelet Volume Manager")
}
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
完成的,正如下列 代码 所示
func (rc *reconciler) Run(stopCh <-chan struct{}) {
wait.Until(rc.reconciliationLoopFunc(), rc.loopSleepDuration, stopCh)
}
func (rc *reconciler) reconciliationLoopFunc() func() {
return func() {
rc.reconcile()
// Sync the state with the reality once after all existing pods are added to the desired state from all sources.
// Otherwise, the reconstruct process may clean up pods' volumes that are still in use because
// desired state of world does not contain a complete list of pods.
if rc.populatorHasAddedPods() && !rc.StatesHasBeenSynced() {
klog.Infof("Reconciler: start to sync state")
rc.sync()
}
}
}
func (rc *reconciler) reconcile() {
// Unmounts are triggered before mounts so that a volume that was
// referenced by a pod that was deleted and is now referenced by another
// pod is unmounted from the first pod before being mounted to the new
// pod.
// 卸载会在挂载之前触发,以便已删除的 Pod 引用的卷现在被另一个 Pod 引用,
// 然后再挂载到新 Pod 之前从第一个 Pod 中卸载。
rc.unmountVolumes()
// Next we mount required volumes. This function could also trigger
// attach if kubelet is responsible for attaching volumes.
// If underlying PVC was resized while in-use then this function also handles volume
// resizing.
// 接下来我们安装所需的卷。如果 kubelet 负责附加卷,
// 则此函数还可以触发附加。如果底层 PVC 在使用时调整了大小,则此函数还可以处理卷大小调整。
rc.mountAttachVolumes()
// Ensure devices that should be detached/unmounted are detached/unmounted.
// 确保应 detached/unmounted 的设备已完成 detached/unmounted。
rc.unmountDetachDevices()
}
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 文件系统附加到容器里
Reference
[1] command-line-tools-reference kubelet
[2] What happens when volumeManager in the kubelet starts?
本文发布于Cylon的收藏册,转载请著名原文链接~
链接:https://www.oomkill.com/2023/08/ch29-volumemanager/
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」 许可协议进行许可。