Un breve análisis del código fuente de actualización en caliente de configmap de kubernetes

1. Información general:

1.1 Medio Ambiente

La información de la versión es la siguiente:
a. Sistema operativo: centos 7.6
c, versión de kubernetes: v1.15.0


1.2 Descripción general del principio de actualización en caliente de configmap

configmap (la actualización en caliente de secret también es un principio) es un tipo de volumen compatible con kubernetes. El principio subyacente es que kubelet escribe varios archivos en el host de acuerdo con el contenido en configmap (el directorio predeterminado es / var / lib / kubelet / pods / <pod UUI> /volumes/kubernetes.io~configmap/ <nombre de volumen>) y, finalmente, se asignan al interior del contenedor mediante el montaje de enlaces. Estos archivos se pueden modificar directamente en el host y el contenedor también véalo en tiempo real, porque es el mismo archivo A. Un componente de kubelet se denomina administrador de volumen La rutina del reconciliador de este administrador de volumen se especializa en conectar / desconectar dispositivos de bloque y montar / volver a montar / desmontar directorios. La operación de montaje de la rutina del reconciliador incluye la actualización del contenido de los archivos en el directorio / var / lib / kubelet / pods / <UUI del pod> /volumes/kubernetes.io~configmap/ <nombre del volumen> /. La frecuencia con la que se realiza la actualización en caliente también se puede establecer mediante el parámetro de inicio.


2 Parámetros que afectan la actualización en caliente de configmap:

1) - parámetro de frecuencia de sincronización, este es el intervalo de tiempo para que kubelet sincronice el estado del pod, y también es el intervalo de tiempo para volver a montar el volumen (por ejemplo, actualización en caliente de configmap).

fs.DurationVar(&c.SyncFrequency.Duration, "sync-frequency", c.SyncFrequency.Duration, "Max period between synchronizing running containers and config")

3 Breve análisis del código fuente:

3.1 Inicialización del administrador de volumen (un atributo de kubelet)

func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,)

{

	/*
	其他代码
	*/

	// 为kubelet的对象设置volumeManager
	klet.volumeManager = volumemanager.NewVolumeManager(
		kubeCfg.EnableControllerAttachDetach,
		nodeName,
		klet.podManager,
		klet.statusManager,
		klet.kubeClient,
		klet.volumePluginMgr,
		klet.containerRuntime,
		kubeDeps.Mounter,
		klet.getPodsDir(),
		kubeDeps.Recorder,
		experimentalCheckNodeCapabilitiesBeforeMount,
		keepTerminatedPodVolumes)
		
	return klet, nil	

}


3.2 Iniciar el administrador de volumen

El kubelet iniciará muchos componentes durante el proceso de inicio, y uno de los componentes importantes es el administrador de volumen.

func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
	/*
		其他代码
	*/

	// 启动volume manager
	go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)
	
	/*
		其他代码
	*/
	
	// 启动kubelet的主循环
	kl.syncLoop(updates, kl)
}

El inicio del administrador de volumen iniciará 3 corrutinas más. Este artículo se ocupa principalmente de la segunda corrutina: la corrutina iniciada por el reconciliador.


func (vm *volumeManager) Run(sourcesReady config.SourcesReady, stopCh <-chan struct{}) {
	defer runtime.HandleCrash()

	// 1)启动volume manager的desiredStateOfWorldPopulator
	go vm.desiredStateOfWorldPopulator.Run(sourcesReady, stopCh)

	// 2)启动volume manager的reconciler
	go vm.reconciler.Run(stopCh)

	if vm.kubeClient != nil {
		// 3)启动volume manager的volumePluginMgr
		vm.volumePluginMgr.Run(stopCh)
	}

	<-stopCh
	klog.Infof("Shutting down Kubelet Volume Manager")
}

3.3 Iniciar el reconciliador del administrador de volumen

La co-rutina del reconciliador (bucle de 100 milisegundos, porque rc.loopSleepDuration = 100 milisegundos) es principalmente responsable de adjuntar / separar y montar (volver a montar) / desmontar el volumen.
Esta corrutina también realiza la actualización en caliente de configmap.


func (rc *reconciler) Run(stopCh <-chan struct{}) {
	wait.Until(rc.reconciliationLoopFunc(), rc.loopSleepDuration, stopCh)
}

func (rc *reconciler) reconciliationLoopFunc() func() {
	return func() {
		rc.reconcile()
		
		if rc.populatorHasAddedPods() && !rc.StatesHasBeenSynced() {
			klog.Infof("Reconciler: start to sync state")
			rc.sync()
		}
	}
}

3.4 método reconciliador ()

La lógica central del negocio es el método reconciliar (), que atravesará de forma continua deseadaStateOfWorld y actualStateOfWorld, y luego realizará una serie de operaciones de conectar / desconectar y montar (volver a montar) / desmontar.

func (rc *reconciler) reconcile() {
	
	/*
	    第一步:
		先把不需要挂载的卷执行卸载操作,代码省略
	*/

	/*
	    第二步:
		执行attach操作和mount(remount)操作。
	*/
	for _, volumeToMount := range rc.desiredStateOfWorld.GetVolumesToMount() {
	
		volMounted, devicePath, err := rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName)
		volumeToMount.DevicePath = devicePath

		/* 
		这个err对象很重要,不同的err进入不同的if分支,而configmap热更新就是进入其中一个分支。
		很多时候,这个err对象是nil,因此 reconcile()方法其实是空跑。
		*/
				
		if cache.IsVolumeNotAttachedError(err) {
			if rc.controllerAttachDetachEnabled || !volumeToMount.PluginIsAttachable {		
				/*
					kubelet等待其他程序(例如一些控制器)将卷attach到当前节点。
				*/

			} else {
				/*
					卷没有attach到当前节点,因此kubelet将卷attach到当前节点。
					err := rc.operationExecutor.AttachVolume(volumeToAttach, rc.actualStateOfWorld)
				*/
			}
		} else if !volMounted || cache.IsRemountRequiredError(err) {
		
			/*
				进入此语句,说明卷未挂载,或者已经挂载的卷需要重新挂载
				confingmap、secret等热更新的情景就是在此处实现,isRemount = true。
			*/
			isRemount := cache.IsRemountRequiredError(err)				
			// 执行挂载或重新挂载的操作
			err := rc.operationExecutor.MountVolume(
				rc.waitForAttachTimeout,
				volumeToMount.VolumeToMount,
				rc.actualStateOfWorld,
				isRemount)
				
			/*
				打印一些日志
			*/
				
		} else if cache.IsFSResizeRequiredError(err) &&
			utilfeature.DefaultFeatureGate.Enabled(features.ExpandInUsePersistentVolumes) {
			
			/*
				kubelet对正在使用的卷的文件系统进行扩容
			*/
		}
	}

	// Ensure devices that should be detached/unmounted are detached/unmounted.
	
	/*
	   第三步:
		umount和detach块设备,代码省略
	*/
	
}
func (oe *operationExecutor) MountVolume(
	waitForAttachTimeout time.Duration,
	volumeToMount VolumeToMount,
	actualStateOfWorld ActualStateOfWorldMounterUpdater,
	isRemount bool) error {
	
	fsVolume, err := util.CheckVolumeModeFilesystem(volumeToMount.VolumeSpec)
	if err != nil {
		return err
	}

	// 根据volume的类型来构建generatedOperations对象
	// 挂载卷的方法就保存在generatedOperations对象。
	var generatedOperations volumetypes.GeneratedOperations
	if fsVolume {
		/*
		    这是文件系统卷的情景,configmap等卷会进入这里
		*/
		generatedOperations = oe.operationGenerator.GenerateMountVolumeFunc(
			waitForAttachTimeout, volumeToMount, actualStateOfWorld, isRemount)
	} else {
		/*
		    这是块设备的情景,代码省略
		*/
	}
	if err != nil {
		return err
	}
	
	/*
		其他代码
	*/

	// 底层会启动一个协程来执行generatedOperations对象的Run()方法
	return oe.pendingOperations.Run(
		volumeToMount.VolumeName, podName, generatedOperations)
}

3.5 mountVolumeFunc del objeto generateOperations

El método de montaje del volumen (mountVolumeFunc) se almacena en el objeto generateOperations. mountVolumeFunc toma principalmente un complemento de la administración de complementos de volumen volumePluginMgr (mantiene la lista de complementos), y luego obtiene un mouter del complemento, y el montador realiza la operación de montaje real.

type GeneratedOperations struct {
	OperationName     string
	OperationFunc     func() (eventErr error, detailedErr error)
	EventRecorderFunc func(*error)
	CompleteFunc      func(*error)
}
func (og *operationGenerator) GenerateMountVolumeFunc(
	waitForAttachTimeout time.Duration,
	volumeToMount VolumeToMount,
	actualStateOfWorld ActualStateOfWorldMounterUpdater,
	isRemount bool) volumetypes.GeneratedOperations {
		
	/*
		其他代码
	*/

	// 这就是挂载卷的方法,最终作为generatedOperations对象的一个属性
	mountVolumeFunc := func() (error, error) {

		/*
			其他代码
		*/
			
		// 从volume plgin管理器中获取一个volume plgin
		volumePlugin, err := og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)
		if err != nil || volumePlugin == nil {
			return volumeToMount.GenerateError("MountVolume.FindPluginBySpec failed", err)
		}

		/*
			其他代码
		*/

		// volume plgin对象中得到一个mounter
		// 对于configmap这种卷,插件的实现是结构体configMapVolumeMounter
		volumeMounter, newMounterErr := volumePlugin.NewMounter(
			volumeToMount.VolumeSpec,
			volumeToMount.Pod,
			volume.VolumeOptions{})
		
		/*
			其他代码
		*/
		
		// mounter的SetUp(...)方法就是执行挂载操作
		mountErr := volumeMounter.SetUp(volume.MounterArgs{
			FsGroup:     fsGroup,
			DesiredSize: volumeToMount.DesiredSizeLimit,
			PodUID:      string(volumeToMount.Pod.UID),
		})
		
		
		// 更新actualStateOfWorld,标记当前卷已经被挂载
		actualStateOfWorld.MarkVolumeAsMounted(...)
		
		return nil, nil
	}

	/*
		其他代码
	*/
	return volumetypes.GeneratedOperations{
		OperationName:     "volume_mount",
		OperationFunc:     mountVolumeFunc,
		EventRecorderFunc: eventRecorderFunc,
		CompleteFunc:      util.OperationCompleteHook(util.GetFullQualifiedPluginNameForVolume(volumePluginName, volumeToMount.VolumeSpec), "volume_mount"),
	}
}

Los complementos comunes compatibles con volumePluginMgr son los siguientes:
Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí

3.6 configMapPlugin 和 configMapVolumeMounter

Obtener un objeto montador del objeto configMapPlugin es en realidad asignar el método getConfigMap del complemento a la propiedad getConfigMap del objeto montador.

func (plugin *configMapPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
	return &configMapVolumeMounter{
		configMapVolume: &configMapVolume{
			spec.Name(),
			pod.UID,
			plugin,
			plugin.host.GetMounter(plugin.GetPluginName()),
			volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))),
		},
		source:       *spec.Volume.ConfigMap,
		pod:          *pod,
		opts:         &opts,
		getConfigMap: plugin.getConfigMap,
	}, nil
}

El método SetUp () de configMapVolumeMounter es el método para realizar realmente la operación de montaje (escribir el contenido del configmap en un archivo).


func (b *configMapVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
	return b.SetUpAt(b.GetPath(), mounterArgs)
}

// 入参dir就是/var/lib/kubelet/pods/<pod的UUI>/volumes/kubernetes.io~configmap/<卷名>
func (b *configMapVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
	klog.V(3).Infof("Setting up volume %v for pod %v at %v", b.volName, b.pod.UID, dir)

	/*
		其他代码
	*/
	
	// configmap对象可以来自kube-apiserver,也可以来自kubelet中的缓存,取决于configMapManager的实现
	configMap, err := b.getConfigMap(b.pod.Namespace, b.source.Name)
	
	/*
		其他代码
	*/

	payload, err := MakePayload(b.source.Items, configMap, b.source.DefaultMode, optional)
	if err != nil {
		return err
	}
	/*
		其他代码
	*/
	setupSuccess := false

	writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
	writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
	
	// 把configmap的键值对写到入参dir下
	err = writer.Write(payload)
	/*
		其他代码
	*/
	setupSuccess = true
	return nil
}

4 Colaboración entre el bucle principal de kubelet y el administrador de volumen

La rutina de reconciliación del administrador de volumen se ejecuta vacía la mayor parte del tiempo y la operación de volver a montar se realiza solo cuando se considera que el pod necesita volver a montar el volumen. ¿Cuándo se considerará que la cápsula necesita volver a montar el volumen montado? Este intervalo de tiempo está relacionado con el bucle principal de kubelet, y el intervalo de tiempo del bucle principal se establece mediante el parámetro -sync-frequency. Cada vez que se sincroniza el estado de un pod, en realidad le da al pod la oportunidad de volver a montar el volumen montado. Cuando se considera que el pod necesita volver a montar el volumen, el método reconciliar () del conciliador del volumeManager llama a rc.actualStateOfWorld.PodExistsInVolume (volumeToMount.PodName, volumeToMount.VolumeName) para devolver un objeto err, y el código ingresará la instrucción if para volver a montar el volumen en.


func (kl *Kubelet) syncPod(o syncPodOptions) error {

	/*
		其他代码
	*/

	
	if !kl.podIsTerminated(pod) {
		// WaitForAttachAndMount(...)等待卷的挂载,同时也给了该pod对已挂载的卷进行重新挂载的机会	
		if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil {
			kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to mount volumes for pod %q: %v", format.Pod(pod), err)
			klog.Errorf("Unable to mount volumes for pod %q: %v; skipping pod", format.Pod(pod), err)
			return err
		}
	}

	/*
		其他代码
	*/
	
}
func (vm *volumeManager) WaitForAttachAndMount(pod *v1.Pod) error {
	/*
		其他代码
	*/

	
	// 标记这个pod的卷要被重新挂载一次(其实是从一个map中删除数据,这样volume manager会以为pod未被处理)
	// volumeManager的reconciler的reconcile()方法中调用rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName)会返回一个err对象,从而触发重新挂载的操作
	vm.desiredStateOfWorldPopulator.ReprocessPod(uniquePodName)

	/*
		其他代码
	*/

	klog.V(3).Infof("All volumes are attached and mounted for pod %q", format.Pod(pod))
	return nil
}

5 Resumen

El principio de actualización en caliente de configmap es muy simple. Kubelet tiene una corrutina especial para modificar / var / lib / kubelet / pods / <pod UUI> /volumes/kubernetes.io~configmap/ <nombre de volumen> / de acuerdo con el contenido en el objeto configmap Archivos en este tipo de directorio. En cuanto a la frecuencia con la que la corrutina debe actualizarse en caliente (volumen de montaje en el administrador de volumen), este intervalo de tiempo está determinado por el parámetro -sync-frequency. Este parámetro determina directamente el intervalo de tiempo para sincronizar pods e indirectamente determina la actualización en caliente de Intervalo de tiempo, porque el bucle principal de kubelet marcará que el administrador de volumen debe volver a procesar el pod durante el proceso de sincronización del pod.

Supongo que te gusta

Origin blog.csdn.net/nangonghen/article/details/111147525
Recomendado
Clasificación