kubelet是运行在Minion节点上的重要守护进程,是工作在一线的重要工人,它才是负责实例化和启动一个具体Pod的幕后主导,并且掌管着本节点上的Pod和容器的全生命周期过程,定时向master汇报工作情况。此外kubelet进程也是一个server进程,它默认监听10250端口,接收并执行远程(master)发来的指令。
进程启动过程
kubelet进程的入口类源码位置如下:
cmd/kubelet/kubelet.go
入口main函数的逻辑如下:
我们已经很多次遇见这样的代码风格了。
我们先看看kubelet这个结构体所包含的属性吧,这些属性可分为以下几组。
1.基本配置
kubeconfig:kubelet默认配置文件路径
address、Port、ReadOnlyPort、CadvisorPort、HealthPort、HealthzBindAddress:为kubelet绑定监听的地址,包括自身server的地址、cadvisor绑定的地址,以及自身健康检查服务的绑定地址等。
RootDirectory、CertDirectory:kubelet默认的工作目录(/var/lib/kubelet),用于存放配置及VM卷等数据,CertDirectory用于存放证书目录。
2.管理Pod和容器相关的参数
PodInfraContainerImage:Pod的infra容器的镜像名称,谷歌被屏蔽的时候可以换成自己的私有仓库的镜像名。
CgroupRoot:可选项,创建Pod的时候所使用的顶层的cgroup名字(Root Cgroup)。
ContainerRuntime、DockerDaemonContainer、SystemContainer:这三个参数分别表示选择什么容器技术(docker或RKT)、Docker Daemon容器的模拟购置及可选的系统资源容器名称,用来将所有非kernel的、不在容器中的进程放入此容器中。
3.同步和自动运维相关的参数
SyncFrequency、FileCheckFrequency、HTTPCheckFrequency:Pod容器同步周期、当前运行容器实例分别与kubernetes注册表中的信息,本地的Pod定义文件及HTTP方式提供信息的数据源进行对比同步。
RegistryPullQPS、RegistryBurst:从注册表中拉取待创建的Pod列表时的流控参数。
NodeStatusUpdateFrequency:kubelet多久汇报一次当前Node的状态。
ImageGCHighThresholdPercent、ImageGCLowThresholdPercent、LowDiskSpaceThresholdMB:分别是Image镜像占用磁盘空间的高低水位阈值及本机磁盘最小空闲容量,当可用容量低于这么容量时,所有新Pod的创建请求会被拒绝。
MaxContainerCount、MaxPerPodContainerCount:分别是maximum-dead-containers与max-dead-containers-per-container,表示保留多少个死亡容器的实例在磁盘上,因为每个实例都会占用一定的磁盘,所以需要控制,默认是MaxContainerCount为100,MaxPerPodContainerCount为2,即每个Pod最多保存2个死亡实例,每个Node保存最多100个死亡实例。
通过分析上述kubeletserver结构体的关键属性,我们可以得到这样一个推论:kubelet进程的工作量还是很饱满的。
在继续下面代码分析之前,我们先要理解这里的一个重要概念“Pod Source”,它是kubelet用于获取Pod定义和描述信息的一个数据源,kubelet进程查询并监听Pod source来获取属于自己所在节点的Pod列表,当前支持三种Pod source类型。
1.Config file:本地配置文件作为Pod数据源
2.Http URL:Pod数据源的内容通过一个HTTP URL方式获取
3.kubernetes API server:默认方式,从API server获取Pod数据源。
进程根据启动参数创建了kubeletserver以后,调用kubeletserver的Run方法,进入启动流程,在流程的一开始首先设置了自身进程的oom_adj(默认为-900),这是利用了Linux的OOM机制,当系统发生OOM时,oom-adj的值越小,越不容易被系统kill。
为什么在之前的Master节点进程上都没见到这个调用,而在kubelet进程上却看到这段逻辑?答案很简单,因为Master节点不运行Pod和容器,主机资源通常是稳定和宽裕的,而minion节点由于需要运行大量的Pod和容器,因此容易产生OOM问题,所以这里要确保“守护者”不会因此被系统kill掉。
由于kubelet会跟API Server打交道,所以接下来创建了一个Rest Client对象来访问API Server。随后,启动进程构造了CAdvisor来监控本地的docker容器,CAdvisor具体的创建代码则位于pkg/kubelet/cadvisor/cadvisor_linux.go里。
接着,初始化CloudProvider。这是因为如果kubernetes运行在某个运营商的cloud环境中,则很多环境和资源都需要从CloudProvider中获取,比如在创建Pod过程中可能需要知道某个Node的真实主机名。
虽然容器可以绑定宿主机的网络空间,但若不当使用会导致系统安全漏洞,所以kubeletserver中的hostnetworksources的属性是用来控制哪些Pod允许绑定宿主机的网络空间,默认都是禁止绑定。比如,设置hostnetworksources=api,http,则表明当一个Pod的定义源来自kubernetes API Server或者某个HTTP URL时,则允许此Pod绑定到宿主机的网络空间。下面这行代码即上述处理逻辑的一小部分:
接下来加载数字证书,如果没有提供证书和私钥,则默认创建了一个自签名的X509证书并保存到本地,下一步,创建一个mounter对象,用于实现容器的文件系统挂载功能。
接下来这段代码根据指定了DockerExecHandlerName参数的值,确定dockerExecHandler是采用Docker的exec命令还是nsenter来实现,默认采用了docker的exec这种本地方式,docker从1.3版本开始提供了exec命令,为进入容器内部提供了更好的手段。
运行至此,程序构造了一个kubeletConfig结构体,回座位参数调用RunKubelet()方法,程序运行到这里,才真正进入流程的核心步骤。下面这段代码表面kubelet会把自己的事件通知API Server:
接下来,启动进程进入关键函数createAndInitKubelet中,这里首先创建一个PodConfig对象,并根据启动参数中Pod Source参数是否提供,来创建相应类型的Pod Source对象,这些PodSource在各种协程中运行,拉取Pod信息并汇总输出到同一个Pod Channel中等待kubelet处理。创建PodConfig的具体代码如下:
然后创建一个kubelet并宣告它的诞生:
接着触发kubelet开启垃圾回收协程以清理无用的容器和镜像,释放磁盘空间,下面是代码片段:
createAndInitKubelet方法创建kubelet实例后,返回到RunKubelet方法里,接下来调用startKubelet方法,此方法首先启动一个协程,让kubelet处理来自PodSource的Pod Update消息,然后启动kubelet server,下面是具体代码:
至此,kubelet进程启动完毕。