本节所有的代码基于1.13.4版本。
前言
在kubernetes中,与存储相关的controller主要由三种:
1、AttachDetachController,简称AD Controller,主要处理真实的与volume相关的操作;
2、PersistentVolumeBinderController,其实就是PV Controller,主要负责pv和pvc的生命周期以及状态的切换;
3、VolumeExpandController,主要负责volume的扩容操作。
PersistentVolumeBinderController
首先追踪PersistentVolumeBinderController方法,直接进入其Run方法。
很直观,依赖于三个goroutine:1、resync;
2、volumeWorker;
3、claimWorker。
resync
进入resync,代码很简单,如下:
resync的主要作用就是不停获取pvc和pv的信息,传入到相应的缓存队列中去。这两个队列就是在volumeWorker和claimWorker用到的数据源的信息。volumeWorker
volumeWorker通过for循环,不停获取resync缓冲的队列信息,对pv,即volume做相应的更新操作。主要实现方法updateVolume
updateVolume
方法,主要调用
syncVolume
方法,这个方法是整个volumeWorker的核心。工作流程如下:
1、如果volume没有被使用,更新PV的状态为
Available
;
2、volume已经被pvc持有:
- 如果volume还没有被绑定到pvc上,更新PV状态为
Available
; - 如果pvc信息为空,pv的状态不为
Released
且不为Failed
,更新PV状态为Released
,按照配置的回收策略执行pv的回收(调用reclaimVolume
方法); - pvc中指定volume的字段和当前volume一致,更新PV状态为
Bound
; - 如果都不满足,根据状态判断是执行回收策略还是解绑(unbindVolume)操作。
claimWorker
claimWorker的工作流程和volumeWorker类似,核心调用方法为updateClaim-->syncClaim
,主要处理的是pvc生命周期中的各种状态:Pending、Bound以及Lost。不做过多赘述。
总结
PersistentVolumeBinderController的执行流程很清晰,依赖三个goroutine的协作,分别处理数据的获取、pv的生命周期的状态更新和pvc的生命周期的状态更新。整个逻辑中,没有对具体的volume做操作,更新的仅仅是kubernetes中定义的pv和pvc资源的信息,说白了就是etcd中的数据。具体干活的主要还是AttachDetachController,即AD Controller。
AttachDetachController
首先由Run
方法进入AD Controller的启动方法,如下:
1、同步各资源的信息,包括Pod、Node、PV、PVC;
2、调用
populateActualStateOfWorld
方法获取Node上Volume的信息;
3、调用
populateDesiredStateOfWorld
方法获取Pod需要对应的Volume信息;
4、
reconciler.Run
负责检查挂载状态,判断是否需要挂载或卸载(真正干活的);
5、
desiredStateOfWorldPopulator.Run
同步Pod与Volume的挂载信息,相应信息输送给
reconciler.Run
使用;
6、
pvcWorker
控制pvc的流控;
7、相应的信息注册到
metrics
中,供Prometheus采集数据使用。
populateActualStateOfWorld
populateActualStateOfWorld
方法主要处理的是Node与Volume之间的关系,主要作用是将Node Volume当前的状态存入到actualStateOfWorld
中。主要方法如下:
1、获取所有的Node信息;
2、一一遍历获取到的所有的Node,针对Node上已经
attached
的Volume,分别置于已经attached状态和
in-user
状态,将Volume信息添加到
actualStateOfWorld
中,并将Node添加到
desiredStateOfWorld
中。
actualStateOfWorld
和
desiredStateOfWorld
的数据会在
reconciler.Run
使用。
populateDesiredStateOfWorld
populateDesiredStateOfWorld
方法主要处理的是Pod与Volume之间的关系,主要作用是将Pod Volume期望的状态添加到desiredStateOfWorld
中去。和populateActualStateOfWorld
类似,主要就是针对Volume做标记操作,并将相应的Pod信息缓存到desiredStateOfWorld
中或者从desiredStateOfWorld
中剔除不匹配的Pod信息。
desiredStateOfWorldPopulator.Run
desiredStateOfWorldPopulator.Run
方法通过不停的循环,调用findAndAddActivePods
方法,通过获取所有的Pod,判断是否需要添加到desiredStateOfWorld
中去。
reconciler.Run
前面几步主要的目的是为了获取Node Volume和Pod Volume的状态。其中,Node上的Volume是已经存在的,故称作为actualStateOfWorld
,而Pod Volume是最终需要生效的资源,故称之为desiredStateOfWorld
。reconciler.Run
的作用就是通过不停获取actualStateOfWorld
和desiredStateOfWorld
状态,将Pod与Volume置于相对应的状态,保证磁盘的最终挂载成功或者卸载成功。主要方法如下:
reconciliationLoopFunc
方法。
首先进入
reconcile
方法,这是真正干活的地方。
reconcile
使用了三个大的for循环,处理三类事件:
1、首先剔除需要解绑的Volume,调用
UnmountVolume
方法最终调用后台存储的解绑接口;
2、将需要Attach或者Mount的volumes调用后台存储接口执行Attach或者Mount操作;
3、将需要Detach或者Unmount的devices调用后台存储接口执行Detach或者Unmount操作。
其中,Attach操作指的是将Volume在Node上生成卷标,如常见的
/dev/xx
等。Mount操作包含了MountDevice和Mount两部分,其中,MountDevice将生成的卷标挂载成Node的路径,一般在
/var/lib/kubelet/xx/kubernetes.io/xx
下,依赖于不同的存储,Mount最终将MountDevice生成的路径和Pod需要使用的路径Mount起来,一般路径为
/var/lib/kubelet/pods/xx/volumes/xx
。
sync
方法主要完成Volume的后续操作。如果Volume未被成功绑定,将Volume进行重建或者解绑操作。
ExpandController
Kubernetes在1.8开始支持卷的扩容操作,1.11功能已经处于Beta阶段。主要代码如下:
pvcPopulator.Run
监听PVC的变化,只要PVC中Request字段的Storage值比Status中的大,即表示PVC容量发生了变化,需要扩容。此时,将相应的PV和PVC的信息缓存到
resizeMap
中去。如下:
syncResize
则是不停获取
resizeMap
中的数据,如果有变化,则调用
ExpandVolume
方法生成扩展动作,完成磁盘的扩容和PV、PVC的状态更新操作。代码如下:
总结
Kubernetes的存储主要针对Node、Pod、PV以及PVC四类资源。通过获取Node上Volume状态,将其与Pod中的Volume进行绑定,完成卷的加载。最终的绑定或者解绑等操作依赖的是后台的存储,包括内置的开源存储插件或者自己实现的插件(FlexVolume或者CSI)。
在kubelet中,同时存在VolumeManager
去管理其节点上的Volume资源信息,基本功能与AD Controller一致。可以通过kube-controller-manager的--disable-attach-detach-reconcile-sync
参数或者kubelet的--enable-controller-attach-detach
参数控制是由kube-controller-manager执行volume的attach/detach操作还是kubelet执行相应的操作。