Kubernetes控制平面组件:Scheduler调度器
一个好的调度器是k8s集群的服务能稳定、高效运行的保证
文章目录
建议:看下https://time.geekbang.org/column/article/69890
一、简介
kube-scheduler 负责把 pod 调度到node上,它监听 kube-apiserver,查询node的健康,资源状况,然后根据调度策略为这些 Pod 分配节点(更新 pod 的 NodeName 字段)
。
kube-scheduler 会有集群所有节点的计算资源的全局视图,也会读取pod的配置信息。
调度器需要充分考虑诸多的因素:
- 公平调度:同一优先级会公平,高的优先级也可以插队(见下方优先级调度)
- 资源高效利用:用户申请资源时都会超量申请
- Qos:支持超卖,满足pod的基本需求
- affinity 和 anti-affinity
- 数据本地化 (data locality):pod所需的镜像在别的node上已经存在了,就可以直接调度到这个node上,节省磁盘资源。
- 内部负载干扰 (inter-workload interference)
- deadlines。
二、调度流程
kube-scheduler 调度分为两个阶段,predicate 和 priority:
predicate
:过滤不符合条件的节点;(filter)
priority
:打分排序,选择score得分最高的节点。(score)
1、predicate(过滤不符合节点)
predicate插件及其功能(默认开启):
当然也可以编写自己的策略。
工作原理:
package scheduler
// predicate插件逻辑
type Node struct {
name string
taints string
addresses string
allocatableCPU int
allocatableMEM int
allocatedCPU int
allocatedMEM int
}
type Pod struct {
name string
namespace string
requestsCPU int
requestsMEM int
}
func podFitsResources(pod *Pod, nodeList []Node) (passNodeList []node) {
// 插件检查逻辑,并生成合格的node列表
for _, n := range nodeList {
if checkNodeResource(
n,
pod.requestsCPU,
pod.requestsMEM,
) {
passNodeList = append(passNodeList, n)
}
}
return
}
func checkNodeResource(n Node, requestCPU, requestMEM int) bool {
needCPU := requestCPU + n.allocatedCPU
needMEM := requestMEM + n.allocatedMEM
if needCPU > n.allocatableCPU || needMEM > n.allocatableMEM {
return true
}
return false
}
2、priority(节点打分排序)
priority打分插件:
(1)资源需求
【1】CPU
- requests
Kubernetes 调度 Pod 时,会判断当前节点正在运行的 Pod 的 CPU Request 的总和,再加上当前调度 Pod 的 CPU request,计算其是否超过节点的 CPU 的可分配资源。最小资源。m是cpu的单位,1000m就是1个cpu。
- limits
配置 cgroup 以限制资源上限。实际需求资源和cgroup中的limits资源不一致,就达到了超卖的效果。比如机器有4个cpu,如果按照limits去调度只能调度4个pod,但是如果按照reques 100m调度就可以调度40个pod。
【2】内存
- requests
判断节点的剩余内存是否满足 Pod 的内存请求量,以确定是否可以将 Pod 调度到该节点。
- limits
配置 cgroup 以限制资源上限
。
(2)磁盘
容器临时存储 (ephemeral storage) 包含日志和可写层数据,可以通过定义 Pod Spec 中的 limits.ephemeral-storage 和 requests.ephemeral-storage 来申请。
Pod 调度完成后,计算节点对临时存储的限制不是基于 cgroup 的,而是由 kubelet 定时获取容器的日志和容器可写层的磁盘使用情况,如果超过限制,则会对 Pod 进行驱逐
。
(3)init container
init容器在主容器前运行,负责完成一些初始化工作。比如istio中就会负责完成iptables的配置;主容器需要认证token,init容器就会去获取token然后保存到本地目录,再把volume挂载到主容器,这样主容器就可以使用这个token了。
当 kube-scheduler 调度带有多个 init 容器的 Pod 时,只计算 cpu.request 最多的 init 容器,而不是计算所有的 init 容器总和。
由于多个 init 容器按顺序执行,并且执行完成立即退出,所以申请最多的资源 init 容器中的所需资源,即可满足所有 init 容器需求。
kube-scheduler 在计算该节点被占用的资源时,init 容器的资源依然会被纳入计算。因为 init容器在特定情况下可能会被再次执行,比如由于更换镜像而引起 Sandbox 重建时
。
(4)LimitRange
有些时候用户不想定义pod需要多少资源,又希望给一个默认资源;或者希望一个namespace中的pod都有一个limits,这时就可以使用LimitRange。
cat limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi # limit mem
defaultRequest:
memory: 256Mi # request mem
type: Container
k apply -f limitrange.yaml
k get limtrange
这时候如果启动一个不带资源控制的deployment就会加入上面的内存信息。而且即使是init container、sidecar都会带上,即LimitRange是对所有container生效的,而不是所有pod
,因为init container基本上不用什么资源,因此采用这个会比较浪费,原因是initContainer只会在最开始消耗资源,后面就被销毁了,但是调度器是认为你给initContainer设置了资源那么它还是占用着的,调度器是根据request计算机器剩余资源进行调度的
。
(5)优先级调度
集群中的业务肯定有重要程度区分,在集群资源紧张时,就可能发生资源争抢。优先级调度就可以让让高优先级的业务优先调度
。
从v1.8 开始,kube-scheduler 支持定义 Pod 的优先级,从而保证高优先级的 Pod 优先调度。开启方法为:
- apiserver 配置 --feature-gates=PodPriority=true和–runtime-config=scheduling.k8s.io/vlalphal=true
- kube-scheduler 配置 --feature-gates=PodPriority=true
使用方法:
在指定 Pod 的优先级之前需要先定义一个 PriorityClass
(非 namespace 资源)。
value越大优先级越高。如果globalDefault: true
,那么这个集群中所有没有打PriorityClass的pod都是这个优先级。
而且PriorityClass还会抢占,当优先级高的pod处于pending状态时,会驱逐其他低优pod,掠夺资源直至它起来!
cat PriorityClass.yaml
apiversion: v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This is a priority class test.”
为pod设置PriorityClass:
cat priority.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
priorityClassName: high-priority
三、多调度器
如果默认的调度器不满足要求,比如需要批处理调度。那么可以部署自定义的调度器
。并且,在整个集群中还可以同时运行多个调度器实例,通过 pod.Spec.schedulerName 来选择使用哪一个调度器
(默认使用内置的基于event的调度器)。比如腾讯的TKE,华为的Volcano等。
四、多租户集群实现
k8s集群一般是共享集群,用户无需关心节点细节。但是有些用户加入集群时自带了计算资源并且要求隔离,这应该怎么实现呢?
- 将要隔离的计算节点打上 Taints;
- 在用户创建创建 Pod 时,定义 tolerations 来指定要调度到 node taints。
但是这个方案是有漏洞的,比如其他用户如果可以 get nodes 或者 pods,可以看到 taints 信息,也可以用相同的 tolerations 偷用其他租户的硬件资源。怎
么解决?
- 不让用户 get node detail
- 不让用户 get 别人的 pod detail
- 企业内部,也可以通过审计查看统计数据看谁占用了哪些 Node;
存在哪些问题:
- 集群中有一个node上的runtime有问题,应用起不来,但是节点状态是正常的。这时新建pod调度到这个node上,起不来。由于scheduler没有熔断机制,用户删除pod后又调度到这个node上还起不来,用户就会认为整个集群不可用了。
- 应用炸弹: 用户新建了一个pod,但是这个pod里面一直fock子进程,可能pid/打开文件句柄/连接数过多。
由于容器是共享主机内核的
,它就是一个运行在namespace中的进程,如果不加以限制,很可能把node上的资源耗尽,导致node状态变为notready,然后node上的pod就会被驱逐掉。然后pod被调度到一个好的node上,又把这个好的node弄坏,循环往复,整个集群的节点就都不可用了。
五、scheduler源码
- filter预处理:遍历pod所有的init container和主container,计算pod的总资源需求。
- filter阶段:遍历所有节点,过滤掉所有不符合需求的节点。
- 处理扩展 predicate plugin(自定义)
- score:处理弱亲和性
- 为节点打分
- 处理扩展 priority plugin(自定义)
- 选择节点
- 假定选中pod(去为这个pod reserve一些资源)
- 绑定pod
两个扩展的plugin相当于是在扩展scheduler了。多调度器这个不是extend scheduler,这是一个完全新的调度器
更多推荐
所有评论(0)