kubelet关键代码分析

在上篇博文,我们分析了kubelet进程的启动流程,大致明白了kubelet的核心个哦你工作流程就是不断从Pod Source中获取与本节点相关的Pod,然后开始加工处理,所以我们先来分析Pod source部分代码。前面我们提到,kubelet可是同时支持三类Pod source,为了能够将不同的Pod source汇聚到一起统一处理,谷歌特地设计了Podconfig这个对象,其代码如下:

其中,source属性包括了当前加载的所有Pod source类型,sourceLock是source的排他锁,在新增Pod source的方法里使用它来避免共享冲突。

在Pod发生变动时,例如Pod创建、删除或更新,相关的Pod source就会产生对应的PodUpdate事件并推送到channel,为了能够统一处理来自多个source的channel,谷歌设计了config.mux这个聚合器,它负责监听多路channel,当接收到channel发来的事件后,交给merger对象进行统一处理,merger对象最终把多路channel发来的时间合并写入updates这个汇聚channel等待处理。

下面是config.mux的结构体定义,其属性sources为一个channel Map,key是对应的Pod source的类型:

我们继续深入分析config.Mux的工作过程,前面提到,kubelet在启动过程中在makePodSourceConfig方法里创建了一个PodConfig对象,并且根据启动参数来决定要加载哪些类型的Pod Source,在这个过程中调用了下述方法来创建一个对应的channel:

而channel具体的创建过程则在config.Mux里,channel创建完成后被加入config.mux的sources并启动一个协程开始监听消息,代码如下:

config.Mux的上述listen方法很简单,就是监听新创建的channel,一旦发现channel上有数据就交给merger进行处理:

我们先来看看Pod source是如何发送PodUpdate事件到自己所在的channel上的,在上篇博文,我们所见到的下面这段代码创建了一个config file类型的Pod source:

在NewSourceFile方法里启动了一个协程,每隔指定时间(kc.FileCheckFrequency)就执行一次sourceFile的run方法,在run方法里所调用的主体逻辑就是下面的函数:

看一下上面的代码,我们就大致明白了config file类型的Pod source是如何工作的:它从指定的目录中加载多了Pod定义文件并转换为Pod列表或者加载单个Pod定义文件并转化为单个Pod,然后生成对应的全量类型的PodUpdate事件并写入channel中去。

接下来我们分析merger对象,Podconfig里的merger对象其实是一个config.podStorage实例,它同时是PodConfig的pods属性的一个引用。podStorage的源码位于pkg/kubelet.config/config.go里,其定义如下:

我们看到podstorage的关键属性解释如下。

1.pods:类型是Map,存放每个Pod source上拉过来的pod数据,是podstorage当前保存全量Pod的地方

2.updates:它就是PodConfig里的updates属性的一个引用

3.mode:表明podstorage的pod事件通知模式,有以下几种:

1)PodConfigNotificationSnapshot:全量快照通知模式

2)PodConfigNotificationSnapshotAndUpdates:全量快照+更新Pod通知模式(代码中创建podstorage实例时采用的模式)

3)PodConfigNotificationIncremental:增量通知模式

podStorage实现的merge接口的源码如下:

在上述merge过程中,先调用内部函数merge,将Pod source的channel上发来的PodUpdate事件分解为对应的新增、更新及删除等三类PodUpdate事件,然后判断是否有更新事件,如果有,则直接写入汇总的channel里(podstorage.updates),然后调用mergestate函数复制一份podstorage的当前全量Pod列表,以此产生一个全量的PodUpdate事件并写入汇总的channel中,从而实现了多Pod Source channel的汇聚逻辑。

分析完merger过程以后,我们接下来看看是什么对象,以及如何消费这个汇总的channel。在上篇博文提到,在kubelet进程启动的过程中调用了startKubelet方法,此方法首先启动一个协程,让kubelet处理来自PodSource的Pod update消息,即下面这行代码:

‘’

其中,PodConfig的updates()方法返回了前面我们所说的汇总channel变量的一个引用,下面是kubelet的Run(updates < -chan PodUpdate)方法的代码:

上述代码首先启动了一个HTTP file server来远程获取本节点的系统日志,接下来根据启动参数的设置来决定是否在指定的docker容器中启动kubelet进程,然后分别启动Image manager(负责Image GC)、CAdvisor(Docker性能监控)、Container Manger(Container GC)、OOM Watcher(OOM监测)、Status Manager(负责同步本节点上Pod的状态到API Server上)等组件,最后进入syncLoop方法中,无线循环调用下面的syncLoopIteration方法:

在上述代码中,如果从Channel中拉取到了PodUpdate事件,则先调用podManager的UpdatePods方法来确定此PodUpdate的同步类型,并将结果放入podSyncTypes这个Map中,同时为了提升处理效率,在代码中增加了持续循环拉取PodUpdate数据直到channel为空为止的一段逻辑,在方法的最后,调用syncHandler接口来完成Pod同步的具体逻辑,从而实现了PodUpdate事件的高效批处理模式。

syncHandler在这里就是kubelet实例,它的syncPods方法比较长,其主要逻辑如下:

1.将传入的全量Pod,与Statusmanager中当前保存的Pod集合进行对比,删除Statusmanager中当前已经不存在的Pod(孤儿Pod)。

2.调用kubelet的admitPods方法以过滤掉不适合本节点创建的Pod。此方法首先过滤掉状态为failed或者succeeded的pod,接着过滤掉不适合本节点的Pod,比如Host Port冲突、Node Label的约束不匹配及Node的可用资源不足等情况;最后检查磁盘的使用情况,如果磁盘的可用空间不足,则过滤掉所有Pod。

3.对上述过滤后的Pod集合中的每一个Pod调用podWorkers的UpdatePod方法,而此方法内部创建了一个Pod的workUpdate事件并发布到该Pod对应的一个Work Channel上。

4.对于已经删除或者不存在的Pod,通知podWorkers删除相关联的work channel。

5.对比Node当前运行中的Pod及目标Pod列表,杀掉多余的Pod,并且调用docker runtime API,重新获取当前运行中的Pod列表信息。

6.清理孤儿Pod所遗留的PV和磁盘目录。

要真正理解Pod是怎么在Node上落地的,还要继续深入分析上述第三步的代码,首先我们看看对workUpdatec这个结构体的定义:

其中的属性Pod是当前要操作的Pod对象,mirrorPod则是对应的镜像Pod,下面是对它的解释:

对于每个来自非API Server Pod source上的Pod,kubelet都在API Sever上注册一个几乎一模一样的Pod,这个Pod被称为mirrorPod,这样一来,就将不同的Pod source上的Pod都统一到了kebelet的注册表上,从而统一了Pod生命周期的管理流程。

workUpdate的updateCompleteFn属性是一个回调函数,work完成后会执行此回调函数,在上述第三步中,此函数用来计算该work的调度时延指标。

对于每个要同步的Pod,podWorkers会用一个长度为1的channel来存放其对应workUpdate,而属性lastUndeliveredWorkUpdate则存放最后一个待安排执行的workUpdate,这是因为一个Pod的前一个workUpdate正在执行的时候,可能会有一个新的PodUpdate事件需要处理,理解了这个过程后,再来看podWorkers的定义,就不难了:

下面这个函数就是第3步里产生workUpdate事件并放入podWorkers的对应channel的方法的源码:

上述代码会调用podWorkers的managePodLoop方法来处理podUpdates队列,这里主要是获取必要的参数,最终处理又转手交给syncPodFn方法去处理,下面是managePodLoop的源码:

追踪podWorkers的构造函数调用过程,可以发现syncPodFn函数其实就是kubelet的syncPod方法,这个方法的代码量很多,主要逻辑如下:

1.根据系统配置中的权限控制,检查Pod是否有权在本节点运行,这些权限包括Pod是否有权使用HostNetwork(由Pod source类决定)、Pod中的容器是否被授权以特权模式启动(privileged mode)等,如果没有被授权,则删除当前运行中的旧版本的Pod实例并返回错误信息。

2.创建Pod相关的工作目录、PV存放目录、Plugin插件目录,这些目录都以Pod的UID为上一级目录。

3.如果Pod有PV定义,则针对每个PV执行目录的mount操作。

4.如果是syncPodUpdate类型的Pod,则从Docker runtime的API接口查询获取Pod及相关容器的最新状态信息。

5.如果Pod有imagePullSecrets属性,则在API Server上获取对应的Secret。

6.调度Container Runtime的API接口方法SyncPod,实现Pod真正同步的逻辑。

7.如果Pod source不来自API Server,则继续处理其关联的mirrorPod。

1)如果mirrorPod跟当前Pod的定义不匹配,则它会被删除。

2)如果mirrorPod还不存在(比如新创建的Pod),则会在API Server上新建一个。

kubernetes中container runtime的默认实现是dockers,对应类是dockertools.DockerManager,其源码位于pkg/kublelet/dockertools/manager.go里,在上述kubelet.syncPod方法中所调用的DockerManager的syncPod方法实现了下面的逻辑:

1.判断一个Pod实例的哪些组成部分需要重启:包括Pod的infra容器是否发生变化(如网络模式、Pod里运行的各个容器的端口是否发生变化);Pod里运行的容器是否发生变化;用Probe检测容器的状态以确定容器是否异常等。

2.根据Pod实例重启结果的判断,如果需要重启Pod的infra容器,则先kill Pod然后启动Pod的infra容器,设定好网络,最后启动Pod里的所有container;否则就先kill那些需要重启的container,然后重新启动它们。注意,如果是新创建的Pod,则因为找不到Node上对应的Pod的infra容器,所以会被当做重启Pod的infra容器的逻辑来实现创建过程。

dockermanager创建Pod的infra容器的逻辑在createPodInfracontainer方法里,大致逻辑如下:

1.如果Pod的网络不是HostNetwork模式,则搜集Pod所有容器的Port作为infra容器所要暴露的Port列表。

2.如果infra容器的Image目前不存在,则尝试拉取Image。

3.创建infra的container对象并且启动run ContainerInPod方法。

4.如果容器定义有Lifecycle,并且PostStart回调方法被设置了,就会触发此方法的调用,如果调用失败则kill容器并返回。

5.创建一个软连接文件指向容器的日志问加你,此软连接文件名包括Pod的名称、容器的名称及容器的ID,这样的目的是让ElasticSearch这样的搜索技术容易索引和定位Pod日志。

6.如果此容器是Pod infra容器,则设置其OOM参数低于标准值,使得它比其他容器具备更强的抗灾能力

7.修改docker生成的容器的resolv.conf文件,增加ndots参数并默认设置为5,这是因为kubernetes默认假设的域名分割长度是5.

上述逻辑中所调用的runContainerInPod是Dock人Manager的核心方法之一,不管是创建Pod的infra容器还是Pod里的其他容器,都会通过此方法使得容器被创建和运行,以下是其主要逻辑。

1.生成container必要的环境变量和参数,比如ENV环境变量,Volume Mounts信息、端口映射信息、DNS服务器信息、容器的日志目录、parent cgroup等。

2.调用runContainer方法完成Docker container实例的创建过程,简单地说,就是完成docker create container命令行所需的各种参数的构造过程,并通过程序来调用执行。

3.构造HostConfig对象,主要参数有目录映射、端口映射等、Cgroup设定等,简单地说,就是完成了docker start container命令行所需的必要参数的构造过程,并通过程序来调用执行。

在上述逻辑中,runContainer与startContainer的具体实现都是靠DockerManger中的dockerClient对象完成的,它实现了DockerInterface接口,dockerClient的创建过程在pkg/kubelet/dockertools/docker.go里,下面是这段代码:

这里的dockerEndpoint是本节点上的docker deamon进程的访问地址,默认是unix:///var/run/docker/sock,在上述代码中使用了来自开源项目http://github.com/fsouza/go-dockerclient提供的docker client,它也是Go语言实现的一个用HTTP访问Docker Deamon提供的标准API的客户端框架。

我们来看看dockerclient创建容器的具体代码(createcontainer):

上述代码其实就是通过调用标准的docker Rest API来实现功能的,我们进入docker.client的do方法可以看到更多详情,例如输入参数转化为JSON格式的数据、DockerAPI版本检查及异常处理等逻辑,最有趣的是:在dockerEndpoint是Unix套接字的情况下,会先建立套接字连接,然后在这个连接上创建HTTP连接。

至此,我们分析了kubelet创建和同步Pod实例的整个流程,简单总结如下:

1.汇总,先将多个Pod source上过来的PodUpdate事件汇总到一个总的channel上去。

2.初审:分析并过滤掉不符合本节点的PodUpdate时间,对满足条件的PodUpdate则生成一个workupdate时间,交给podworkers处理。

3.接待:podworkers对每个pod的workUpdate事件排队,并且负责更新cache中的Pod状态,而把具体的任务转给kubelet去处理(syncPod方法)。

4.终审:kubelet对符合条件的Pod进一步审查,例如检查Pod是否有权在本节点运行,对符合审查的Pod开始着手准备工作,包括目录创建、PV创建、image获取、处理Mirror Pod问题等,然后把皮球踢给了dockermanager。

5.落地:任务抵达dockermanager之后,dockermanager尽心尽责分析每个pod的情况,以决定这个Pod究竟是新建、完全重启还是部分更新的,给出分析结果后,剩下的就是dockerclient的工作了。

kubernetes-kubelet进程源码分析(二)相关推荐

  1. Android uevent进程源码分析

    在Android Init进程源码分析中讲到init进程会依次执行被加入到待执行队列action_queue中的Action,在init.rc中我们有这么一段配置: 11 on early-init1 ...

  2. Android7.0 init进程源码分析

    目的 看过一些blog和相关的书籍,大多数文章在介绍init进程时,参考的代码比较久远,同时不同文章行文的重点不太一样,因此决定自己试着来分析一下,并作相应的记录. 背景 当linux内核启动之后,运 ...

  3. linux进程源码分析,Linux内核源代码分析——口述程序猿如何意淫进程(一)

    Jack:hi,淫龙,有空吗?我们来讨论一下Linux的进程吧. 我:没空.不要烦我,最近正在郁闷. Jack:郁闷啥呀? 我:最近大学城通了轻轨,房价涨得厉害,骂了隔壁. Jack:不要郁闷了,来研 ...

  4. nuwa创建新进程源码分析

    nuwa.cpp的作用,fork 对线程进行相关冻结设置复制等.线程级别的设置等.... https://developer.mozilla.org/en-US/docs/Archive/B2G_OS ...

  5. Android应用程序启动Binder线程源码分析

    Android的应用程序包括Java应用及本地应用,Java应用运行在davik虚拟机中,由zygote进程来创建启动,而本地服务应用在Android系统启动时,通过配置init.rc文件来由Init ...

  6. 二次开发:flowable审批流程实践与创建流程源码分析

    二次开发:flowable审批流程实践与创建流程源码分析 上一篇已经描述了基于开源项目https://doc.iocoder.cn/的flowable的快速开发,创建了一个租户,创建了用户和相应的岗位 ...

  7. 【源码分析】storm拓扑运行全流程源码分析

    [源码分析]storm拓扑运行全流程源码分析 @(STORM)[storm] 源码分析storm拓扑运行全流程源码分析 一拓扑提交流程 一stormpy 1storm jar 2def jar 3ex ...

  8. spring bean创建过程源码分析(上)

    大家好,我是@zzyang(小卓),一个热爱技术的90后.这篇文章主要是带大家了解一下spring bean的生命周期,对spring bean的创建过程源码分析.由于篇幅有限,这里说的都是主干流程, ...

  9. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

最新文章

  1. 今日 Paper | 多人线性模型;身体捕捉;会话问答;自然语言解析;神经语义
  2. 机器人学习--Robotics: Estimation and Learning(宾夕法尼亚大学COURSERA课程)
  3. 无废话WCF系列教程 -- 李林峰
  4. XgBoost使用及调参教程
  5. 如果数据库也有一个元宇宙,应该会是什么样子?
  6. Maven实战读书笔记(3)
  7. multer处理post请求的代码演示
  8. PAT 1087 有多少不同的值(20 分)- 乙级
  9. Chrome上网问题解决记录
  10. centos7 网卡命名
  11. 原来 GitHub 网红是这么混出来的 如何以正确的姿势参与开源项目
  12. 备份outlook的时候,请不要忘记同时备份Outlook.NK2文件
  13. python做积分计算器_PyQt5练习:积分计算器
  14. ping测试告警软件,SmartPing:一个服务器Ping值监测工具,带报警功能
  15. matlab利用gui谐波分析,matlab中fft谐波分析
  16. 概率论与数理统计期末考试题及答案
  17. 六大场景,看懂声纹识别技术怎样“抗疫防疫” 小快
  18. kotlin中的val 真的是只读吗?关于val 的一个细节
  19. 系统从win7更新到win10没有声音(扬声器一直显示未插入)
  20. 【信号处理】迫零均衡前与迫零均衡后眼图对比附Matlab代码

热门文章

  1. [转] LTE载波聚合CA的优化 //LTE CA 学习笔记
  2. mysql安装错误集合及 完美解决方案(卸载不干净,360拦截,环境缺失,驱动缺失
  3. 如何提高代码的可维护性?
  4. 人体五脏排毒最简单有效的方法
  5. 代码随想录第25天|216.组合总和III,17.电话号码的字母组合
  6. 部署之用 OneAPM 作为你的监控平台 (一)
  7. C++ STL之 queue和deque用法详解
  8. LOJ 572 「LibreOJ Round #11」Misaka Network 与求和——min_25筛
  9. mysql的ini文件在哪里_mysql的ini文件在哪?
  10. 普利姆算法(prim)求最小生成树(MST)过程详解