调度器和kublet

一、调度器scheduler

1.调度器概览

主要负责pod的绑定和调度,predicate(预选),priority(优选)

调度器的计算逻辑:将机器根据你预选的一些算法排序排除,然后优选的机器丢进数组里,给每个机器的资源可排序能力进行打分,根据打分的情况去进行排序,从上往下取节点,取那个优先级较高的节点。

调度器行为逻辑filter+score(预选优选)把机器丢进数组,然后排序,取index为0的元素实际上实现还是control loop,和控制器差不多

同一个优先级的资源调度完全公平调度平衡,不同作业资源需求不同,资源是多维度的  资源上限,超发超售实例之间和节点/应用相关数据本地化 

公平调度:针对于节点,同一个node怎么调度,不同node怎么调度

高效利用:成本上、Qos这两方面看

Qos: 每个调度的pod之间是否有调度的优先级

affinity和anti-affinity:亲和性(两个pod的关联性比较强,两个pod运行在一个节点之上,那么他们运行效率可能会自动化,亲和性可以让这两个pod尽量调度在一个节点上)和反亲和性(两个pod最好不要调度到一个节点上)

2.predicates策略

调度器中的每一个功能对于调度器来说都是一个插件,调度过程中就是一个个遍历插件

每一个因素来说,对于k8s的调度器来说都是一个插件调度的过程其实就是遍历一个个的这样的插件凡是selector出现的时候,就是和label产生关联关系的时候

调度完成之后,把nodename改成你要调度的节点,hostname就是在你调度之前,检查一下哪个域名是hostname所指定的那一个,然后把nodename改成你的节点

一旦发现不可调度的污点,所有的pod在预选阶段将其排除掉,

CheckNodeMemoryPressure:当前节点有一个MemoryPressure的标签,kublet在检测中,当你内存发生积压的时候,就会打一个一个MemoryPressure标签到你的etcd中,当Node有这个标签,pod是不能调度到这个节点上的,这个插件控制你的node允不允许调度到你的pod上面

CheckNodeDiskPressure:节点硬盘存在积压时,会打一个nodeDiskPressure的标签

各个插件匹配的顺序: 

3.priorities策略

预选的插件和策略

Tainttolerationpriority:可以对常规性服务打污点(就是你不想让其访问服务的服务),一般情况下在做预选的情况下,就会将这些打了污点的标签就行排除掉,但是如果检测到pod对这个节点可以容忍,这类pod可以正常调度污点,匹配到tainttolerationpriority。

4.资源需求

cpu和内存数从下面取,limits和request是从pod中取 

limit和request的不同形成了一个超售的效果
【注】:如果request很大,那他可能就调度不上来了 

requests是给调度器用的,limits是给cgroup使用的,根据request信息做调度,会先丢件数组中去一一匹配,然后根据cpu、内存进行匹配所有node的信息,哪个符合哪个就通过,limits>request是超授,资源节点不足时,limit和request就要进行配置,最多它们限制在一起去,不能在做超授了,超授是在资源还有剩余才可以做,通过超授可以将一些实际使用和实际限制不符的资源,进行资源差的匹配,从而让节点调度更多的资源

cgroup比调度器高一点的情况下,可以形成超授,超授情况下可以多调用一些资源,超授比例尽量不超过10%可能会发生源节点om

init container资源需求
和主容器一样,可以定义资源需求
他是顺序执行的,所以遍历的时候,会按照initcontainer和maincontainer的资源需求的最大值来决定的 

5.指定node的调度

将pod调度到指定的node上,pod之间的调度

预选和优选的结果实际上是绑定到nodename上,kublet发现这个和自己域名相关,就会发生一个调度的行为,nodeselector把pod定向调度到node上,可以设置一个标签,让你的pod调度到某一列含有标签的node上

always:每次遇到镜像拉取都是从远端拉取

ifNotPresent:如果找不到镜像,才进行拉取

Nerver:只用本地的镜像,从不向远端拉取

 节点亲和性(亲和性的一个扩展)
required:强亲和,满足亲和条件才能进行调度
preferred:弱亲和,优先满足条件的进行调度,不满足也可以选取其他的节点

redis要分散调度, 分散调度到三个节点上,三个redis节点让他跑到固定一组集群上,如何做?

给一组节点打上一定的标签,redis做选取的时候,按照响应标签做选取,选取完了再预选阶段打上svc的标签,让其尽量分散开部署,如果不想打svc预选标签,就打上pod亲和性。

6.pod亲和与反亲和

这个找的是pod的label

亲和:优先往这个节点调度, 按满足性要求往你节点上进行调度。

非亲和:按照预选阶段满足性让其剥离

弱亲和:在没满足条件的情况下,可以容忍

另外运行在这个集群内pod的label
topologyKey:k8s里面针对节点打的一些annotation,包括zone,region,hostname等可用区
region:代表的是一个城市或者说是一个数据中心
zone:代表的是一个数据中心里面的多个可用区
hostname:代表的是节点自身
podAntiAffinity:代表的是反亲和 

7.污点和容忍

本身也是标签,但比label更强一点的隔离手段

当你节点进行维护时,你如何将节点上的pod进行驱除?

打Noexecute污点,然后在做驱除。然后再做维护行为

隔离:可见性隔离,权限隔离 

③不愿意做强隔离的话,就要做监控,进行告警 

8.调度优先级

还能定义是否抢占,见扩展页 

globalDefault:是否全局占用

每个pod在进行调度,都会给其设置优先级 ,高等级pod会侵占低等级的pod的资源,如果高等级pod和低等级pod发生竞争关系,优先级不一样,高等级就会把低等级踢走,当高等级跑完业务,低等级才可以回来。

定义优先级,给pod设置 

 

超发:一个节点上,发的资源和它实际需求的资源不符合,发的资源比实际的资源大,这叫做超发

超售:注:超售(超卖)允许给容器分配的资源量中未被使用的部分,多容器共用,从逻辑上增大了资源池的供给能力,但是有多容器争抢资源的风险,慎用!
场景:生产环境,当资源池紧张(未被pod占用的剩余资源少),并突然有一个大的资源需求需要满足,资源池无法满足,则可开启超售临时解决,当需求下降时再关闭超售超售额度:10%-20%,属于高风险操作,走审批流程

limitRange资源对象是给你namespace使用的,在namespace预分配时,如果pod没有指定limits和request,那么通过limitrange会给你pod分配一个limitrange,而默认分配的limitrange是从下图看的,1.2倍资源是在pod和namespace争抢使用,最多给1.2指定倍数的超售模式,危险行为

还有一种超售行为是由kublet为你的节点开一些超售,这个行为更为危险:

-system-reserved: 设置系统预留资源,例如--system-reserved=cpu=200m,memory=1Gi,表示为系统预留 200 毫核 CPU 和 1GB 内存。
-.eviction-hard: 设置节点资源不足时的驱逐门槛,例如 -eviction-hard=memory.available<10%,表示当节点可用内存低于 10% 时开始驱逐 Pod。

二、控制器controllerManager

1.工作流程

k8s中实际控制行为就是controllerManager,它是一个集群的大脑

为什么要放key而不是整个对象(应对频繁变更)

 当持续监控时会弹出一条数据,这条数据在controller manager这是怎么实现的?

controller manager可以通过代码生成器去生成一些东西,由他生成的代码可以帮助你去创建一个controller manager出来,逻辑如下:

生成代码的模版,下来有两种方法,一种方法是你去做一个get请求,一种方法是做一个watch请求。首先当你的时件过来时有一个informer,接下来会看你到底做一个addfunc(新创建一个事务)、deletefunc(对一个事务进行删除)、updatefunc(对同一个对象进行变更),建立不同的对象会生成一个行为,当有这样行为时就会生成一个事件,事件就会往生产者消费模型里面推,推得时候只丢了一个key(也就是namespace对象名称),由下面的worker去到你的生产者消费者里面,去将这个key拿到(取出来只是一个key),如果说要对这个对象进行一些行为,它就可以根据这个key到这个lister里面取出一些完整的模版。

如果对象频繁变更就推一个事件出来,对象可能变动是相同的时候,work一直工作,会出问题。如果现在只存对象一个关键信息,work只需在工作的时候查看对象是个什么状态就好,如果不是这样,worker每次就会去取对象。这样减少work的算力,结果是和原来想法一样的。所以在这里只存一些关键信息,当work进行实际工作时,就可以去list中观察实际对象到底是谁。

控制器生成和etcd建立连接,资源内部的控制器和etcd建立连接是一个长连接的方式,当etcd中有消息,etcd会进行推出,这时controller就会消费这条消息,消费下来就走两条通道,以上步骤

2.informer的内部机制

share Informer,绝大多数配置管理的组件都是基于这个框架搭建起来的

informer:窃窃私语、告密者,主动将消息推出的组件

在informer对外连接api server时,本身是一个长连接,在不同组件之间消息传递机制是http协议,所以网络之间传递是http协议,当数据拿进来之后,有golang做处理的时候,要将这些数据做反序列化,做成golang可以识别的样子,接着将http传过来的对象的数据存到一个管道中,存进之后如果事件发生变动会弹出来。然后一边将对象添加到indexer,一边将这个事件变动信息触发一个event到handler,去提供一个可触发的事件;添加对象这边会存一个索引,实际对象会存到thread safe store(线程安全存储)中,存储完之后会弹出一个event,然后会注册一个handler,接着将这条消息的key值放到一个生产者消费者模型管道中,而真正处理的process会从中取出键值,取出键值的worker会根据这些键值到indexer中,根据indexer从线程安全中取出它要处理的对象,取出之后,这个work就可以根据对象当前实际行为的状况去做一下它的行为逻辑。

3.控制器的协同工作原理

图解:

replicaset的控制器会监控你deployment的控制器,一旦你的deployment发生创建并且指定rs之后,那么rs就会根据你deployment的控制行为去创建一个rs出来,并且在其里面模版化一堆数据,相应的pod控制器也和rs监听deployment相同,pod也在监听rs,会监听rs创建的模版的lables是否和自己相关,如果相关pod就会被kublet监听并且创建,在创建之前,需要有调度器去做一个轮循的监听,去监听这个pod要把其绑定到哪个关系之上。当replicaset确定去创建pod时,pod的控制器会和kublet去做一个协商,首先由调度器根据你的limitrange和传回来的node~去计算一个最佳的可顾调度的节点 出来,计算出来的节点会直接绑定你pod的nodename上。当调度器调度完成之后,kublet监听到要绑定至本节点的pod之后,会通过CRI去启动一个容器,通过CNI去配置你容器的网络,通过CSI去配置你容器的存储,至此一个服务就出来了

高内聚,松耦合,同一个职责放到一个组件里面,不同的职责分开放
deployment是一个部署策略,描述的是部署行为 
更新细节
replica中的pod-template-hash是pod的前缀(pod对象的hash值)
生成pod名字的时候generate填好后,会在生成一个随机的字符串
确保pod名字是唯一的

在你的控制器中如何确定你的pod名称就是唯一的?

就是rs在你的名称后面加一个随机哈希值,哈希值就是关于你rs的status的一个模版,关于你pod的status部分去算出一个模版,然后它会将你这个模版计算出来的值取几位放到后面,保证你pod名称的唯一性,由rs创建出来的pod也是有owerreferences这个属性,它就是给你的pod创建一个级联关系,代表谁是你的上级,谁是你的副级,可以看到副级是一个rs,name是nginx-deployment-rs的哈希值;resourceversion是这个对象在你的relision中存的最新那个值

为什么要指一个owerreferences?

 假如要删除一个ns,ns不能被直接删除,所以在删除ns的时候,ns会变成一个逻辑删除的状态,先把资源进行锁住,然后去找下面有哪些资源,先删除deployment,然后再删namespace

4.通用controller

job:
perl -Mbignum=bpi -w -e 'print bpi(2000)'
replicaset
statefulset
ownerReferences
garbageCollector  内含graph builder,会给所有的对象构建父子关系图,collector组件会在
删除行为发生的时候扫graphBuilder,删父会发生极端删除(级联删除)

假如定义一次启动两个pod,然后处理一次任务就将其停掉,然后一共处理5次,控制这种处理job的行为控制器就叫做job,实际是做了一个圆周率的计算

Garbage collector:垃圾回收器,是在你做级联删除的时候,做一种东西, 级联删除时需要去识别你服务上下游的关系,要做一个个从下往上的删除

5.cloud controller manager

早期controller Manager是和谷歌云一起的

controller 除了本身自带的 控制器,还有一类需要定制的控制器,可以通过两种方法

ingress:处理svc对外访问的所有情况,为了避免svc暴露端口不安全,k8s只提供了ingress这个对象,并没有提供其向内处理的队列,所以早期使用ingress,都是ingress+nginx,ingress+一些七层负载代理的功能,才可以实现一个流量全部接入由外部处理 

6.生产上的经验

假如在node上进行访问集群,只需把这个config文件进行复制 ,这个文件一定要保护好,不要泄漏

故障:随着时间的积累,所有的worker都被卡住,这样所有svc都匹配不到后端的pod,

高可用: 上面与api server一样,都做的是冗余部署,通过一种拿锁的手段进行的。假如资源endpoint,它在去你的api server进行流量走向的时候,如果走到了controller Manager这,它们会对资源对象进行抢锁,如果抢到锁,它就会成为一个主,就有对这个资源的修改权限,当抢到锁金九更新一个holderldentity(身份验证),每拿到这个资源有15s的操作,基于租期续约机制(注册一个key,这个key有效失效是30s,如果30s没有向etcd发送心跳健康监测,30s过后这个key就失效了),这个holderldentity也是用了租期续约机制

 图解:

作为领导者每次做心跳的时候,会去更新租期续约的时间renew time,更新之后中间那块就会刷新,相当于作为leader就是继续续约仍然具有这个锁的权限,如果租期到了,在固定时间没有刷新这个权限,说明他当前这个leader是出问题的,接下来这些leader权限就被其他的controller竞选,谁先拿到,谁继续执行以上动作

三、节点组件kubelet

1.kubelet架构

探活
保证节点正常工作,节点守护者
cadvisor被重写了,现在看不见,是kubelet的一部分
evictionManager监听水位,如果溢出,就回驱逐pod(按照优先级)

图解:

首先最外面的api接口层, 用于开启端口,做一些信息的收发行为。比较重要的是10250端口(这是kublet真正提供服务的端口),其他还有健康监测探针的端口10248。在往下就是管理者层,

【注】:健康监测故障,基于api,就不用port,能用port,就不用ping。基于node做探测,但是基于pod做探测,就会发生故障,

probemanager:健康检查探针一个管理者,探针的探活行为是由kublet发起的,它会定期按照学过的三种方式,按照设定的行为,去向相应的目标发送探活行为,如果探活信号发送失败,并且已经超过了容灾次数,那么就会对pod进行故障处理。最直接的故障处理就是删除pod.

OOMWatcher:很多pod运行了很久,由于日志的堆压,发生pod自身的oom行为。又或者说你给你的pod被设置了cgroup规则,cgroup允许范围内,这个pod已经超过了cgroup规定的内存锁所使用的范围,那么在他即将超过的时候,对于这个进程来说,进程会对内部的线程进行杀死行为,对于进程来说就会发生oom事件,而kublet的oomwatcher会检测到这个oom事件,一旦这个pod发生了oom事件,说明你这个pod肯定有哪个进程被杀死了,已经不属于一个完全体的状态了。会第一时间以oom的事件将其out掉,先驱除掉,再看它是否有重启的条件。

GPUManager:结合一些其他的组件来一起去管理GPU,如:是否需要一些做ai的算法,是否人脸识别,跑模型

cAdvisor:通过基于cgroup,通过cgroup,获取你pod的状态,然后去监控这些pod应用的状态,如:每个pod吃了多少资源、吃了多少内存等,这些情况都是有cAdvisor进行监控和整理

DiskSpaceManager:磁盘管理,每个pod用了多少个磁盘,使用了多少个磁盘资源

statusManager:node,image等,这些信息是有statusManager去实施监控的,然后进行汇报上传。

evictionManager:当一些pod发生一些不合理行为,这个模块是用来做驱逐使用的,如:打了污点,要进行驱逐,而这个模块就是真正做这个事的

Volume Manager:结合你的CSI容器进行一些卷管理的组件。

ImageGC:清理不活跃的镜像,在运行容器中,很有可能发生容器不正常退出的情况,虽然容器已经死了,但是他的文件层还在。对于这类容器,如果积攒过多,很有可能将这个服务器资源占满,相对于你的kublet,k8s集群来说是没有任何作用的,甚至还有阻碍的作用,所以他们是被视为垃圾的。对于这类容器,是有一类处理镜像容器,以此去镜像容器层,直接将其删除。

ContainerGC:清理已死亡的镜像

ImageManager:镜像管理者

CertificateManager:kubelet签证管理,kublet本身有颁发证书的能力,它颁发证书对象主要是你的kublet和api server之间通信,通常情况下对证书颁发要求是有效期1年,有一种自动化手段,当证书过期之后,让其自动颁发证书,这就是此模块

syncl oop:控制器模块,用来watch节点上所有运行的pod。

podworker:当kublet捕捉到etcd那边传来的信息之后,由podworker将这个信息往下走,去真正启动一个容器

container runtime interface:启动容器,cri通过元上层进行调用docker进行翻译,所以可以通过CRI gRPC server这样一个插件,去调用Container Network Plugin(容器网络插件去生成网络),然后用CRI gRPC server去翻译调用docker shim调用关系,docker shim去调用docker,它就是一组命令,docker命令行的接入口,然后docker本身通过Containerd去启动容器,后来Containerd被独立开源出来,k8s可以直接通过远程调用的方式,直接使用Containerd来实现pod的启动。

在早期我们是通过docker去启动容器的,在k8s发展初期,它和docker的体量比起来太小了,所以不得不去遵从docker的规则;当k8s发展起来,它本身概念就是尽量不和任何厂商发生耦合,独属于CNCF的规范

2. kubelet管理pod核心流程

运行时不响应,relist这部分流程会导致k8s认为节点不正常
exited container的gc没做好,遍历旧容器延时过长,k8s认为节点不正常

 事件到syncloop将你的行为,进行下发,然后syncpod会计算pod要进行创建还是删除,computePodActions会计算各种各样pod的行为,通过CRI插件将pod的行为进行实现,另一边的PLEG插件就是定期用来从containerruntype中,定期获取pod的运行状态,并且进行上报。syncloop一边往下下发信息,一边监控pod的资源使用状态,通过syncloop这条管道往你的etcd中进行上报。

3. kubelet职责

kublet本身对node有一个监控作用,它会收集你当前情况下的node节点信息,并且定时向你的api server去做报告,报告你当前状态下的node是否是一个健康状态,

为什么在master中可以通过k get no看到node节点?

在进行二进制包部署k8s时,k8s的kublet在向你的节点进行部署注册时,需要再kublet中指定master到底是谁,指定完成之后,再把其他的证书文件放入一个个指定目录下,然后重启一下你的kublet,在你重启kublet的时,你的kublet会向你的master的api server去发送一个注册信息,master那边节点收到注册信息,会进行认证鉴权,如果过了认证鉴权这一步,就会将你的node信息注册到etcd中;如果你的kublet没有指定master,你需要手动在master中生成一个node信息,然后由你的master主动向kublet去监测是否有这个节点的行为,当master中在etcd注册了这个node,由master上的api server去寻址到kublet之后,这时候你的master中的etcd的信息,才和节点上的kublet发生关联绑定关系,这个时候你通过命令,才可以看到node节点

一共有两种方式:主动发现、被动发现,无论主动被动,当发生一次关联之后,kublet都会向你的master发送自身的健康心跳,kublet本身也有一个监听atl,它是你的master在发现你的kublet,发现你节点不行,然后向kublet进行探活测试时使用的

静态pod这个可以看一下配置文件,/etc/ubernetes/mainfestws中找staticPodPath 

4.pod启动流程

docker会弱化,重点是containerd这条路线
pause容器,sandbox container,(底座),sleep inifinity

图解: 

首先当你提交一个pod时,这条信息是被push到api server中,api server get到这条信息,它会做认证、鉴权、准入控制,这条流程完成之后,会将其写入到etcd,etcd发生pod一个对象的存储行为时,会弹出一个事件给api server, api  server接收到这个消息之后,会有相应的controller去监听的api server,当它监听到这个时间弹出的消息之后,会一直对这个新的对象保持watch行为,在watch的时候,它会将这个pod中的limits信息和当前已有的两个字段进行比对验证,将合适的node进行排序,去筛选谁是最合适的。当计算结果完成之后,schedule控制器会将pod status中的nodename这一项改为它绑定的节点,绑定完成之后,就由kublet去监听是否该进行创建pod。做完这一套消息之后,就有kublet下来去启容器的运行时sandbox,去配置容器,配置完容器之后,就是拉镜像,创建容器,启动容器等步骤,在删除的时候,是由你的kublet向你的api server发起一个update 信息。

如果你想走读代码,给你提供条思路
pod清单获取到pod容器启动的过程如图所示

图解: 

当你的kublet去启动pod的时候,首先会检查一个准入权限,其实对kublet来说也是有准入的,创建pod会检查网络插件信息是否合适,如果网络情况为noready,显然创建了pod,也不能提供正常的服务,在准入后,接下来是提取cgroup信息,如果有limits信息,会将这个信息放到cgroup中,去创建一些行为,完成这些行为之后,会进行创建poddatadirs,再往下就是你的容器要配置一些waitforattachandmount的东西区进行资源挂载,当其把容器的挂载层做好之后,就进行syncpod,然后就会启动一个sandbox,所有的容器都会先启pause容器(当你要启动一个pod,本身要对这个pod进行一个网络配置,资源隔离的作用,在没有任何保护措施情况下,容器反复重启是非常消耗资源的,通过sandbox启动容器,这个容器是个永不退出的容器,这个pause几乎没做什么事,就是永久性等待的行为,它的目的就是启一个最小化的行为去配置你的pod环境,这样当你pod中的每个容器发生重启的时候,不会直接将你网络环境退出清除掉,而是根据你原来已有的pause容器进行重启),所以当你的pod进到syncpod这一步的时候,首先会创建一个这样基底容器出来sandbox出来,将其创建出来之后,才会向下进行计算(对这些容器进行一个什么样的操作),generatepodboxconfig是创建你syncpod的配置文件的,根据你的配置文件去创建你pod的log日志,log日志的目录,在往下进行对这个pod有个什么样行为,如果计算结果得知,你对这个pod进行一个创建行为,你的kublet就会向下去调你的CRI,去启一个容器运行时出来,然后你的容器运行时启的时候,去配你的network,去配置你的网络环境,这时CRI就创建出来,在下面就是进行调取CNI,它会做一些网络基础环境配置

假如我启动的容器是nginx,进去之后touch一个文件.txt,刚好我的探针把我的容器给删掉了,它发生了一个重启行为,请问原来这个临时文件还在吗?

在的,如果重启方式是delete这个pod,让其rs自动启动,然后自动创建一个新的出来,此时这个.txt文件就不在了,因为根容器pause基底没了,

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值