zoukankan      html  css  js  c++  java
  • kubelet源码分析——kubelet简介与启动

    kubelet是k8s集群中一个组件,其作为一个agent的角色分布在各个节点上,无论是master还是worker,功能繁多,逻辑复杂。主要功能有

    1. 节点状态同步:kublet给api-server同步当前节点的状态,会同步当前节点的CPU,内存及磁盘空间等资源到api-server,为scheduler调度pod时提供基础数据支撑
    2. Pod的启停及状态管理:kubelet会启动经scheduler调度到本节点的pod,同步它的状态保障它运行,当Pod关闭时负责资源回收

    主要模块

    kublet有以下几个监听端口

    • 10250 kubelet API :用于与api-server通讯,访问可获得node的资源和状态
    • 4194 cAdvisor :用于获取节点的环境信息和pod的状态的指标信息,
    • 10255 readonly API :以只读形式获取Pod和node的信息
    • 10248 /healthz :用于健康检查

    kubelet包含的模块有以下

    PLEG,cAdvisor,Container Manager,Volume Manager,Eviction Manager,OOMWatcher,ProbeManager,StatusManager,ImageGC,ContainerGC,ImageManager,CertificateManager,runtimeClassManager……如下图所示
    avatar

    挑几个介绍一下

    • PLEG(Pod Lifecycle Event Generator):kubelet的核心模块,一直调用 container runtime 获取本节点 containers/sandboxes 的信息,并与自身维护的 pods cache 信息进行对比,生成对应的 PodLifecycleEvent,通过 eventChannel 发送到 kubelet syncLoop 进行消费,然后由 kubelet syncPod 来触发 pod 同步处理过程,最终达到用户的期望状态。
    • cAdvisor :起到收集本节点和容器的监控信息,对外提供了 interface 接口,该接口也被 imageManager,OOMWatcher,containerManager 等所使用。
    • OOMWatcher:系统 OOM 的监听器,会与 cadvisor 模块之间建立 SystemOOM,通过 Watch方式从 cadvisor 那里收到的 OOM 信号,并产生相关事件。
    • statusManager : 负责维护状态信息,并把 pod 状态更新到 apiserver,但是它并不负责监控 pod 状态的变化,而是提供对应的接口供其他组件调用,比如 probeManager。
    • volumeManager : 负责 node 节点上 pod 所使用 volume 的管理,volume 与 pod 的生命周期关联,负责 pod 创建删除过程中 volume 的 mount/umount/attach/detach 流程
    • ProbeManager:依赖于 statusManager,livenessManager,containerRefManager,会定时去监控 pod 中容器的健康状况,当前支持两种类型的探针:livenessProbe 和readinessProbe。

    启动命令

    kubelet是以二进制运行在各个节点上,通过ps -ef可以看到其启动命令,一般安装集群的时候会让其托管到system service中,让节点启动的时候将kubelet也自动启动。

    进入/usr/lib/systemd/system/kubelet.service.d目录打开里面的文件查看

    cd /usr/lib/systemd/system/kubelet.service.d
    ls
    10-kubeadm.conf
    cat 10-kubeadm.conf
    Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
    Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
    # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
    EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
    # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
    # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
    EnvironmentFile=-/etc/sysconfig/kubelet
    ExecStart=
    ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
    

    kubelet启动有四个环境变量拼凑成参数,前两个环境变量已在1,2行提供。按照注释KUBELET_CONFIG_ARGS是“kubeadm init”和“kubeadm join”在运行时生成的文件,文件的路径是/var/lib/kubelet/kubeadm-flags.env;最后一个参数KUBELET_EXTRA_ARGS是用户用于覆盖kubelet的最后手段,文件的路径在/etc/sysconfig/kubelet。按照实验机器内容如下

    cat /var/lib/kubelet/kubeadm-flags.env
    KUBELET_KUBEADM_ARGS="--cgroup-driver=systemd --cluster-dns=10.96.0.10 --docker-endpoint=unix:///var/run/docker.sock --hostname-override=master1 --network-plugin=cni --pod-infra-container-image=deploy.deepexi.com/kubeadm/pause:3.2 --root-dir=/var/lib/kubelet --v=4"
    cat /etc/sysconfig/kubelet
    -bash: cd: /etc/sysconfig/kubelet: No such file or directory
    

    最终组合而成的kubelet的启动命令是

    /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=systemd --cluster-dns=10.96.0.10 --docker-endpoint=unix:///var/run/docker.sock --hostname-override=master1 --network-plugin=cni --pod-infra-container-image=deploy.deepexi.com/kubeadm/pause:3.2 --root-dir=/var/lib/kubelet --v=4
    

    与ps -ef|grep kubelet得到的结果是一致的

    ps -ef|grep kubelet
    root 1302 1 3 9月08 ? 16:17:27 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=systemd --cluster-dns=10.96.0.10 --docker-endpoint=unix:///var/run/docker.sock --hostname-override=master1 --network-plugin=cni --pod-infra-container-image=deploy.deepexi.com/kubeadm/pause:3.2 --root-dir=/var/lib/kubelet --v=4
    

    上述参数中还有config参数传递的是一个yaml文件,打开它发现还有相当一部分的参数,由于篇幅太长则不在这展示

    kubelet启动流程

    源码版本:1.19

    kubelet通过cobra框架来处理启动命令和启动参数,从main函数进来直接跳到cobra的Run函数注册则可,大致做下面几件事情

    1. 初始化启动命令和参数
    2. 初始化FeatureGate
    3. 校验命令行参数
    4. 加载KubeletConfigFile并验证之,即“启动命令”一节中提到的config参数传入的文件
    5. 加载使用动态配置,如果有启用
    6. 构造kubeletServer及kubeletDeps
    7. 调用Run函数运行kubelet
      代码位于/cmd/kubelet/app/server.go
    func(cmd *cobra.Command, args []string) {
    	//1. 初始化启动命令和参数
    	if err := cleanFlagSet.Parse(args); err != nil {
    	}
    	//2. 初始化FeatureGate
    	if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(kubeletConfig.FeatureGates); err != nil {
    	}
    	//3. 校验命令行参数
    	if err := options.ValidateKubeletFlags(kubeletFlags); err != nil {
    	}
    	//4. 加载KubeletConfigFile并验证之
    	if configFile := kubeletFlags.KubeletConfigFile; len(configFile) > 0 {
    		kubeletConfig, err = loadConfigFile(configFile)
    	}
    	if err := kubeletconfigvalidation.ValidateKubeletConfiguration(kubeletConfig); err != nil {
    		klog.Fatal(err)
    	}
    
    	////5. 加载使用动态配置的部分略
    
    	//6. 构造kubeletServer及kubeletDeps
    	// 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, utilfeature.DefaultFeatureGate)
    	if err != nil {
    		klog.Fatal(err)
    	}
    
    	//7. 调用Run函数运行kubelet
    	if err := Run(ctx, kubeletServer, kubeletDeps, utilfeature.DefaultFeatureGate); err != nil {
    		klog.Fatal(err)
    	}
    
    } 
    
    

    run函数

    run函数主要为kubelet启动做一些环境检查,准备及校验操作

    1. 将当前的配置文件注册到 http server /configz URL 中
    2. 初始化各种客户端
    3. 初始化 auth,cgroupRoot,cadvisor,ContainerManager
    4. 为 kubelet 进程设置 oom 分数
    5. 初始化Runtime Server,设置CRI
    6. 调用 RunKubelet 方法执行后续的启动操作
    7. 启动 Healthz http server
      代码位于 /cmd/kubelet/server/server.go
    func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Dependencies, featureGate featuregate.FeatureGate) (err error) {
    	//1 将当前的配置文件注册到 http server /configz URL 中
    	err = initConfigz(&s.KubeletConfiguration)
    	//2 初始化各种客户端,主要是非standalone模式下会进入这个,否则会将所有客户端都置为空
    	switch {
    	case kubeDeps.KubeClient == nil, kubeDeps.EventClient == nil, kubeDeps.HeartbeatClient == nil:
    		kubeDeps.KubeClient, err = clientset.NewForConfig(clientConfig)
    		kubeDeps.EventClient, err = v1core.NewForConfig(&eventClientConfig)
    		kubeDeps.HeartbeatClient, err = clientset.NewForConfig(&heartbeatClientConfig)
    	}
    	//3 初始化 auth,cgroupRoot,cadvisor,ContainerManager
    	if kubeDeps.Auth == nil {
    		auth, runAuthenticatorCAReload, err := BuildAuth(nodeName, kubeDeps.KubeClient, s.KubeletConfiguration)
    	}
    	nodeAllocatableRoot := cm.NodeAllocatableRoot(s.CgroupRoot, s.CgroupsPerQOS, s.CgroupDriver)
    	if kubeDeps.CAdvisorInterface == nil {
    		imageFsInfoProvider := cadvisor.NewImageFsInfoProvider(s.ContainerRuntime, s.RemoteRuntimeEndpoint)
    	}
    	if kubeDeps.ContainerManager == nil {
    		kubeDeps.ContainerManager, err = cm.NewContainerManager(...)
    	}
    	//4. 为 kubelet 进程设置 oom 分数
    	if err := oomAdjuster.ApplyOOMScoreAdj(0, int(s.OOMScoreAdj)); err != nil {
    	}
    	//5. 初始化Runtime Server,设置CRI
    	err = kubelet.PreInitRuntimeService(...)
    	//6. 调用 RunKubelet 方法执行后续的启动操作
    	if err := RunKubelet(s, kubeDeps, s.RunOnce); err != nil {
    		return err
    	}
    	//7. 启动 Healthz http server
    	if s.HealthzPort > 0 {
    		go wait.Until(func() {
    			err := http.ListenAndServe(net.JoinHostPort(s.HealthzBindAddress, strconv.Itoa(int(s.HealthzPort))), mux)
    		}, 5*time.Second, wait.NeverStop)	
    	}
    }
    

    RunKubelet

    RunKubelet函数核心就两个

    1. 初始化kubelet对象
    2. 将kubelet及相关kubelet的api跑起来
    func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencies, runOnce bool) error {
    	k, err := createAndInitKubelet(...)
    	if runOnce {
    	}else{
    		startKubelet(k, podCfg, &kubeServer.KubeletConfiguration, kubeDeps, kubeServer.EnableCAdvisorJSONEndpoints, kubeServer.EnableServer)
    	}
    }
    
    createAndInitKubelet

    createAndInitKubelet先构造出kubelet对象,NewMainKubelet的函数传入的参数也很多,函数里面包含了前文中“主要模块”的初始化操作。构造完毕后调用BirthCry方法往k8s发一个Starting kubelet.的event。然后就马上启动containerGC

        func createAndInitKubelet(......) {
            k, err = kubelet.NewMainKubelet(...)
    
            k.BirthCry()
            k.StartGarbageCollection()
            return k, nil
        }
    
    startKubelet

    startKubelet函数是通过调用kubelet的Run方法将kubelet跑起来,kubelet.Run包含了一部分“主要模块”中提及的manager的start方法调用,意味着kubelet的各个模块从此开始运行起来,此外还包括了kubelet的核心循环syncLoop在这里开始调用

    运行了kubelet后,kubelet api、readonly API等server也在这里开始运行

    func startKubelet(...) {
    	// start the kubelet
    	go k.Run(podCfg.Updates())
    
    	// start the kubelet server
    	if enableServer {
    		go k.ListenAndServe(net.ParseIP(kubeCfg.Address), uint(kubeCfg.Port), kubeDeps.TLSOptions, kubeDeps.Auth,
    			enableCAdvisorJSONEndpoints, kubeCfg.EnableDebuggingHandlers, kubeCfg.EnableContentionProfiling, kubeCfg.EnableSystemLogHandler)
    
    	}
    	if kubeCfg.ReadOnlyPort > 0 {
    		go k.ListenAndServeReadOnly(net.ParseIP(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort), enableCAdvisorJSONEndpoints)
    	}
    	if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPodResources) {
    		go k.ListenAndServePodResources()
    	}
    }
    

    至此,kubelet运行起来了,开始执行它节点资源上报,Pod的启停及状态管理等工作。

    启动流程的调用链

    通过下面调用链大致回顾整个启动流程

    main                                                                             // cmd/kubelet/kubelet.go
     |--NewKubeletCommand                                                            // cmd/kubelet/app/server.go
       |--Run                                                                        // cmd/kubelet/app/server.go
          |--initForOS                                                               // cmd/kubelet/app/server.go
          |--run                                                                     // cmd/kubelet/app/server.go
            |--initConfigz                                                           // cmd/kubelet/app/server.go
            |--BuildAuth
            |--cm.NodeAllocatableRoot
            |--cadvisor.NewImageFsInfoProvider
            |--NewContainerManager
            |--ApplyOOMScoreAdj
            |--PreInitRuntimeService
            |--RunKubelet                                                            // cmd/kubelet/app/server.go
            | |--k = createAndInitKubelet                                            // cmd/kubelet/app/server.go
            | |  |--NewMainKubelet
            | |  |  |--watch k8s Service
            | |  |  |--watch k8s Node
            | |  |  |--klet := &Kubelet{}
            | |  |  |--init klet fields
            | |  |
            | |  |--k.BirthCry()
            | |  |--k.StartGarbageCollection()
            | |
            | |--startKubelet(k)                                                     // cmd/kubelet/app/server.go
            |    |--go k.Run()                                                       // -> pkg/kubelet/kubelet.go
            |    |  |--go cloudResourceSyncManager.Run()
            |    |  |--initializeModules
            |    |  |--go volumeManager.Run()
            |    |  |--go nodeLeaseController.Run()
            |    |  |--initNetworkUtil() // setup iptables
            |    |  |--go Until(PerformPodKillingWork, 1*time.Second, neverStop)
            |    |  |--statusManager.Start()
            |    |  |--runtimeClassManager.Start
            |    |  |--pleg.Start()
            |    |  |--syncLoop(updates, kl)                                         // pkg/kubelet/kubelet.go
            |    |
            |    |--k.ListenAndServe
            |
            |--go http.ListenAndServe(healthz)
    

    小结

    本篇是kubelet源码之旅的开篇,先稍微介绍了kublet在k8s集群中的地位,它的功能,包含的模块,启动命令。然后通过追代码调用链的方式一层层地探索kubelet的启动过程,初始化了哪几个manager,启动kubelet时启动哪几个manager,监听了哪些端口。

    如有兴趣,可阅读鄙人“k8s源码之旅”系列的其他文章
    kubelet源码分析——kubelet简介与启动
    kubelet源码分析——启动Pod
    kubelet源码分析——关闭Pod
    kubelet源码分析——监控Pod变更
    scheduler源码分析——调度流程
    apiserver源码分析——启动流程
    apiserver源码分析——处理请求

    参考文章

    kubelet 架构浅析
    kubelet 启动流程分析
    万字长文:K8s 创建 pod 时,背后到底发生了什么?

  • 相关阅读:
    三种实现AJAX的方法以及Vue和axios结合使用的坑
    一个简陋的个人小项目,也是个人第一个真正意义上的独立项目——Graph
    使用docsify并定制以使它更强大
    使用particles.js实现网页背景粒子特效
    使用nginx和tomcat配置反向代理和动静分离
    php (zip)文件下载设置
    php 获取当前完整url地址
    php 实现重定向的三种方式
    php 查看使用多少内存
    linux 查看系统信息
  • 原文地址:https://www.cnblogs.com/HopeGi/p/15351158.html
Copyright © 2011-2022 走看看