Kubernetes源码分析之kubelet

本节所有的代码基于1.13.4版本。

启动分析

Kubelet的启动参数有两种,kubeletFlagskubeletConfig。其中,kubeletFlags与我们使用的kubelet的--参数命令保持一致;kubeletConfig通过解析特定的配置文件完成参数的配置,它们共同构成kubelet启动参数的配置。如图

基本参数配置完成之后,接下来就是配置启动的 Run方法。Kubelet启动的Run方法代较长,如下

Run: func(cmd *cobra.Command, args []string) {
			// initial flag parse, since we disable cobra's flag parsing
			if err := cleanFlagSet.Parse(args); err != nil {
				cmd.Usage()
				klog.Fatal(err)
			}

			// check if there are non-flag arguments in the command line
			cmds := cleanFlagSet.Args()
			if len(cmds) > 0 {
				cmd.Usage()
				klog.Fatalf("unknown command: %s", cmds[0])
			}

			// short-circuit on help
			help, err := cleanFlagSet.GetBool("help")
			if err != nil {
				klog.Fatal(`"help" flag is non-bool, programmer error, please correct`)
			}
			if help {
				cmd.Help()
				return
			}

			// short-circuit on verflag
			verflag.PrintAndExitIfRequested()
			utilflag.PrintFlags(cleanFlagSet)

			// set feature gates from initial flags-based config
			if err := utilfeature.DefaultFeatureGate.SetFromMap(kubeletConfig.FeatureGates); err != nil {
				klog.Fatal(err)
			}

			// validate the initial KubeletFlags
			if err := options.ValidateKubeletFlags(kubeletFlags); err != nil {
				klog.Fatal(err)
			}

			if kubeletFlags.ContainerRuntime == "remote" && cleanFlagSet.Changed("pod-infra-container-image") {
				klog.Warning("Warning: For remote container runtime, --pod-infra-container-image is ignored in kubelet, which should be set in that remote runtime instead")
			}

			// load kubelet config file, if provided
			if configFile := kubeletFlags.KubeletConfigFile; len(configFile) > 0 {
				kubeletConfig, err = loadConfigFile(configFile)
				if err != nil {
					klog.Fatal(err)
				}
				// We must enforce flag precedence by re-parsing the command line into the new object.
				// This is necessary to preserve backwards-compatibility across binary upgrades.
				// See issue #56171 for more details.
				if err := kubeletConfigFlagPrecedence(kubeletConfig, args); err != nil {
					klog.Fatal(err)
				}
				// update feature gates based on new config
				if err := utilfeature.DefaultFeatureGate.SetFromMap(kubeletConfig.FeatureGates); err != nil {
					klog.Fatal(err)
				}
			}

			// We always validate the local configuration (command line + config file).
			// This is the default "last-known-good" config for dynamic config, and must always remain valid.
			if err := kubeletconfigvalidation.ValidateKubeletConfiguration(kubeletConfig); err != nil {
				klog.Fatal(err)
			}

			// use dynamic kubelet config, if enabled
			var kubeletConfigController *dynamickubeletconfig.Controller
			if dynamicConfigDir := kubeletFlags.DynamicConfigDir.Value(); len(dynamicConfigDir) > 0 {
				var dynamicKubeletConfig *kubeletconfiginternal.KubeletConfiguration
				dynamicKubeletConfig, kubeletConfigController, err = BootstrapKubeletConfigController(dynamicConfigDir,
					func(kc *kubeletconfiginternal.KubeletConfiguration) error {
						// Here, we enforce flag precedence inside the controller, prior to the controller's validation sequence,
						// so that we get a complete validation at the same point where we can decide to reject dynamic config.
						// This fixes the flag-precedence component of issue #63305.
						// See issue #56171 for general details on flag precedence.
						return kubeletConfigFlagPrecedence(kc, args)
					})
				if err != nil {
					klog.Fatal(err)
				}
				// If we should just use our existing, local config, the controller will return a nil config
				if dynamicKubeletConfig != nil {
					kubeletConfig = dynamicKubeletConfig
					// Note: flag precedence was already enforced in the controller, prior to validation,
					// by our above transform function. Now we simply update feature gates from the new config.
					if err := utilfeature.DefaultFeatureGate.SetFromMap(kubeletConfig.FeatureGates); err != nil {
						klog.Fatal(err)
					}
				}
			}

			// construct a KubeletServer from kubeletFlags and kubeletConfig
			kubeletServer := &options.KubeletServer{
				KubeletFlags:         *kubeletFlags,
				KubeletConfiguration: *kubeletConfig,
			}

			// use kubeletServer to construct the default KubeletDeps
			kubeletDeps, err := UnsecuredDependencies(kubeletServer)
			if err != nil {
				klog.Fatal(err)
			}

			// add the kubelet config controller to kubeletDeps
			kubeletDeps.KubeletConfigController = kubeletConfigController

			// start the experimental docker shim, if enabled
			if kubeletServer.KubeletFlags.ExperimentalDockershim {
				if err := RunDockershim(&kubeletServer.KubeletFlags, kubeletConfig, stopCh); err != nil {
					klog.Fatal(err)
				}
				return
			}

			// run the kubelet
			klog.V(5).Infof("KubeletConfiguration: %#v", kubeletServer.KubeletConfiguration)
			if err := Run(kubeletServer, kubeletDeps, stopCh); err != nil {
				klog.Fatal(err)
			}
复制代码

主要包括以下步骤:
1、解析参数,对参数的合法性进行判断;
2、根据kubeletConfig解析一些特殊的特性所需要配置的参数;
3、配置kubeletServer,包括KubeletFlagsKubeletConfiguration两个参数;
4、构造kubeletDeps结构体;
5、启动最终的Run方法。
除了最终的Run方法,其余的步骤还是为kubelet的启动构建初始化的参数,无非就是换一个名称,换一个不同的结构体,并配置相依赖的参数。

启动

启动调用的是Run方法,如图

initForOS通过对操作系统的判断,如果是windows系统需要做一些预先的特殊处理; run方法即通过传入的 kubeDeps参数开始执行启动操作。
进入 run方法,开始主要执行对参数的再一次验证,以及新的结构体的初始化。后续开始构建一些重要的客户端,包括 eventClient主要处理事件的上报,与apiserver打交道; heartbeatClient主要处理心跳操作,与之后的PLEG相关; csiClient主要与CSI接口相关。配置完成之后,最终进入 RunKubelet方法。
RunKubelet方法最重要的方法有两个: CreateAndInitKubeletstartKubelet,可以理解为 CreateAndInitKubelet为参数的配置, startKubelet为最终的启动(说来说去还是把参数封装一遍,重新构造新的结构体)。
CreateAndInitKubelet方法通过调用 NewMainKubelet返回 Kubelet结构体。在 NewMainKubelet中,主要的配置有:
1、PodConfig。通过 makePodSourceConfig可以发现kubelet获取Pod的来源有以下途径: 静态Pod静态Pod的URL地址以及 kube-apiserver
2、容器与镜像的GC参数。
3、驱逐Pod策略。
最终通过参数填充 Kubelet结构体,完成kubelet结构体参数的最终配置。
接下来就是启动了,不过在启动之前会有一个判断
判断是以后台daemon进程一直运行还是只启动一次,即runOnce,基本上都是以后台daemon启动的方式,所以大部分调用的是 startKubelet方法。
startKubelet方法内部调用了最终的 Run方法,如下

func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
	if kl.logServer == nil {
		kl.logServer = http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))
	}
	if kl.kubeClient == nil {
		klog.Warning("No api server defined - no node status update will be sent.")
	}

	// Start the cloud provider sync manager
	if kl.cloudResourceSyncManager != nil {
		go kl.cloudResourceSyncManager.Run(wait.NeverStop)
	}

	if err := kl.initializeModules(); err != nil {
		kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.KubeletSetupFailed, err.Error())
		klog.Fatal(err)
	}

	// Start volume manager
	go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)

	if kl.kubeClient != nil {
		// Start syncing node status immediately, this may set up things the runtime needs to run.
		go wait.Until(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, wait.NeverStop)
		go kl.fastStatusUpdateOnce()

		// start syncing lease
		if utilfeature.DefaultFeatureGate.Enabled(features.NodeLease) {
			go kl.nodeLeaseController.Run(wait.NeverStop)
		}
	}
	go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)

	// Start loop to sync iptables util rules
	if kl.makeIPTablesUtilChains {
		go wait.Until(kl.syncNetworkUtil, 1*time.Minute, wait.NeverStop)
	}

	// Start a goroutine responsible for killing pods (that are not properly
	// handled by pod workers).
	go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop)

	// Start component sync loops.
	kl.statusManager.Start()
	kl.probeManager.Start()

	// Start syncing RuntimeClasses if enabled.
	if kl.runtimeClassManager != nil {
		go kl.runtimeClassManager.Run(wait.NeverStop)
	}

	// Start the pod lifecycle event generator.
	kl.pleg.Start()
	kl.syncLoop(updates, kl)
}
复制代码

可以看到,在该方法内,完成的就是最终的kubelet的任务,通过多个goroutine完成。包括以下系列:
1、volumeManager,volume相关管理;
2、syncNodeStatus,定时同步Node状态;
3、updateRuntimeUp,定时更新Runtime状态;
4、syncNetworkUtil,定时同步网络状态;
5、podKiller,定时清理死亡的pod;
6、statusManager,pod状态管理;
7、probeManager,pod探针管理;
8、启动PLEG;
9、syncLoop,最重要的主进程,不停监听外部数据的变化执行pod的相应操作。
至此,kubelet启动过程完成。启动主要完成的任务就是参数的配置和多个任务的启动,通过构造一个循环进程不停监听外部事件的变化,执行对应的pod处理工作,这也就是kubelet所需要负责的任务。

Pod启动流程

Pod的启动在syncLoop方法下调用的syncLoopIteration方法开始。在syncLoopIteration方法内,有5个重要的参数

1、configCh:获取Pod信息的channel,关于Pod相关的事件都从该channel获取;
2、handler:处理Pod的handler;
3、syncCh:同步所有等待同步的Pod;
4、houseKeepingCh:清理Pod的channel;
5、plegCh:获取PLEG信息,同步Pod。
每个参数都是一个channel,通过select判断某个channel获取到信息,处理相应的操作。Pod的启动显然与configCh相关。
通过获取configCh信息,获取Pod整个生命周期中的多种状态
相对应的,每个状态对应相应的处理方法
其中, ADD操作对应Pod的创建,其对应的处理方法为 HandlePodAdditions
进入 HandlePodAdditions方法,主要以下几个步骤:
1、根据Pod的创建时间对Pod进行排序;
2、podManager添加Pod;(对Pod的管理依赖于podManager)
3、处理mirrorPod,即静态Pod的处理;
4、通过 dispatchWork方法分发任务,处理Pod的创建;
5、probeManager添加Pod。(readiness和liveness探针)
dispatchWork方法内,最核心的是调用了 kl.podWorkers.UpdatePod方法对Pod进行创建。 UpdatePod方法通过 podUpdates的map类型获取相对应的Pod,map的key为Pod的UID,value为 UpdatePodOptions的结构体channel。通过获取到需要创建的Pod之后,单独起一个goroutine调用 managePodLoop方法完成Pod的创建, managePodLoop方法最终调用 syncPodFn完成Pod的创建, syncPodFn对应的就是Kubelet的 syncPod方法,位于 kubernetes/pkg/kubelet/kubelet.go下。经过层层环绕, syncPod就是最终处理Pod创建的方法。
syncPod主要的工作流如注释
1、更新Pod的状态,对应 generateAPIPodStatusstatusManager.SetPodStatus方法;
2、创建Pod存储的目录,对应 makePodDataDirs方法;
3、挂载对应的volume,对应 volumeManager.WaitForAttachAndMount方法;
4、获取ImagePullSecrets,对应 getPullSecretsForPod方法;
5、创建容器,对应 containerRuntime.SyncPod方法,如下
至此,Pod的启动到创建过程完成。通过 kubectl describe pod命令可以查看Pod创建的整个生命周期。

PLEG分析

PLEG,即PodLifecycleEventGenerator,用来记录Pod生命周期中对应的各种事件。在kubelet中,启动主进程的syncLoop之前,先启动pleg,如图

Start方法通过启动一个定时的任务执行 relist方法
relist主要的工作就是通过比对Pod的原始状态和现在的状态,判断Pod当前所处的生命周期,核心代码如下
对每一个Pod,比对Pod内的容器,通过 computeEvents-->generateEvents生成事件。在 generateEvents内,生成以下事件:
1、newState为 plegContainerRunning,对应 ContainerStarted事件;
2、newState为 plegContainerExited,对应 ContainerDied事件;
3、newState为 plegContainerUnknown,对应 ContainerChanged事件;
4、newState为 plegContainerNonExistent,查找oldState,如果对应 plegContainerExited,则生成的事件为 ContainerRemoved,否则事件为 ContainerDiedContainerRemoved

生成完事件之后,将事件一一通知到 eventChannel,该channel对应的就是 syncLoopIteration方法下的plegCh
syncLoopIteration方法下,接收到plegCh channel传输过来的消息之后,执行 HandlePodSyncs同步方法,最终调用到 dispatchWork这个Pod的处理方法,对Pod的生命进行管理。

GC管理

Kubelet会定时去清理多余的container和image,完成ContainerGC和ImageGC。Kubelet在启动的Run方法里,会先去调用imageManager的Start方法,代码位于kubernetes/pkg/kubelet/kubelet.go下,调用了initializeModules方法。imageManager.Start方法主要执行两个步骤:
1、detectImages:主要用来监控images,判断镜像是可被发现的;
2、ListImages:主要用来获取镜像信息,写入到缓存imageCache中。
在启动的CreateAndInitKubelet方法中,开始执行镜像与容器的回收

StartGarbageCollection方法启用两个goroutine,一个用来做ContainerGC,一个用来做ImageGC,代码如下

func (kl *Kubelet) StartGarbageCollection() {
	loggedContainerGCFailure := false
	go wait.Until(func() {
		if err := kl.containerGC.GarbageCollect(); err != nil {
			klog.Errorf("Container garbage collection failed: %v", err)
			kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ContainerGCFailed, err.Error())
			loggedContainerGCFailure = true
		} else {
			var vLevel klog.Level = 4
			if loggedContainerGCFailure {
				vLevel = 1
				loggedContainerGCFailure = false
			}

			klog.V(vLevel).Infof("Container garbage collection succeeded")
		}
	}, ContainerGCPeriod, wait.NeverStop)

	// when the high threshold is set to 100, stub the image GC manager
	if kl.kubeletConfiguration.ImageGCHighThresholdPercent == 100 {
		klog.V(2).Infof("ImageGCHighThresholdPercent is set 100, Disable image GC")
		return
	}

	prevImageGCFailed := false
	go wait.Until(func() {
		if err := kl.imageManager.GarbageCollect(); err != nil {
			if prevImageGCFailed {
				klog.Errorf("Image garbage collection failed multiple times in a row: %v", err)
				// Only create an event for repeated failures
				kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ImageGCFailed, err.Error())
			} else {
				klog.Errorf("Image garbage collection failed once. Stats initialization may not have completed yet: %v", err)
			}
			prevImageGCFailed = true
		} else {
			var vLevel klog.Level = 4
			if prevImageGCFailed {
				vLevel = 1
				prevImageGCFailed = false
			}

			klog.V(vLevel).Infof("Image garbage collection succeeded")
		}
	}, ImageGCPeriod, wait.NeverStop)
}
复制代码

可以看到容器的GC默认是每分钟执行一次,镜像的GC默认是每5分钟执行一次,通过定时执行GC的清理完成容器与镜像的回收。
容器的GC主要完成的任务包括删除被驱除的容器、删除sandboxes以及清理Pod的sandbox的日志目录,代码位于kubernetes/pkg/kubelet/kuberuntime/kuberuntime_gc.go下,调用了GarbageCollect方法;镜像的GC主要完成多余镜像的删除和存储空间的释放,代码位于kubernetes/pkg/kubelet/images/image_gc_manager.go下,调用了GarbageCollect方法。

转载于:https://juejin.im/post/5ccbfdca518825406b5abde9

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值