K8s里pod的调度笔记

概述

k8s里对pod的调度的过程简单说就是:当Scheduler通过API server的watch接口监听到新建Pod副本的信息后,它会检查所有符合该Pod要求的Node列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上,目标Node上的kubelet服务进程接管后继工作,负责Pod生命周期的后半生。同时Scheduler并将绑定信息传给API server写入etcd中。

Kubernetes Scheduler提供的调度流程分三步:

  1. 预选策略(predicate):遍历nodelist,选择出符合要求的候选节点,Kubernetes内置了多种预选规则供用户选择。
  2. 优选策略(priority):在选择出符合要求的候选节点中,采用优选规则计算出每个节点的积分,最后选择得分最高的。
  3. 选定(select):如果最高得分有好几个节点,select就会从中随机选择一个节点。

而具体方案有以下几种:
pod对pod:podAffinity和podantiAffinity
pod对node:nodeName、nodeSelector、Taint和Toleration、nodeAffinity和nodeantiAffinity、DeamonSet

强指定nodeName

先看一下当前node的情况:
paradin

最简单无脑的把pod分配到node上的方法就是nodeName,这个方法是不走schedule分配的,而是直接到对应的node上由kubelet创建pod,举个例子,写一个pod.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod-manual-schedule
namespace: default
name: pod-manual-schedule
spec:
nodeName: "node2" #这里填写的是NAME段,指定这个pod去node2节点上
#nodeSelector:
# cloudnil.com/role: dev #指定调度节点为带有label标记为:cloudnil.com/role=dev的node节点
#schedulerName: default-scheduler #调度器可以自己确定,一般来说这一行不写,即默认的那个调度器
containers:
- name: my-pod
image: nginx:1.11.9

kubectl apply -f pod.yaml之后,查看一下效果,果然分配到了node2上:
paradin

kubectl delete -f 1.yaml删掉该pod。

nodeName差不多功效的是nodeSelector,只不过后者靠label而不是名字来给pod指定node。

但是要注意!!!如果在创建deployment/pod的时候指定了nodeName,那么k8s是不会做调度的,也就是说不管该node上的剩余资源是否满足request,都会硬往里塞。那么如果node上的剩余资源不足,就会发现pod处于outofcpu/outofmem

亲和性nodeaffinity

通过nodeAffinity调度pod就多了一丝动态选择的味道,不像nodeName那么死板了。首先kubectl label nodes node3 has-eip=yes先给node3打上has-eip=yes这样的label,然后写一个pod.yaml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVrsion: v1
kind: Pod
metadata:
labels:
run: node-affinity
namespace: default
name: node-affinity
spec:
containers:
- name: my-pod
image: nginx:1.11.9
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: has-eip
operator: In
values:
- "yes"

kubectl apply -f pod.yaml之后,查看一下效果,果然分配到了node3上:
paradin

如果说就是不信邪,要把nodeAffinity和nodeName/nodeSelector一起用,而且还不是指向同一个node,那么这个pod就会调度失败,卡在Predicate MatchNodeSelector failed这个阶段。

谈谈topologyKey

众所周知,为了照顾pod之间的超亲密关系,k8s还有一个podAffinity,比如一个yaml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod-affinity
namespace: default
name: pod-affinity
spec:
containers:
- name: my-pod
image: nginx:1.11.9
imagePullPolicy: IfNotPresent
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: run
operator: In
values:
- "node-affinity"
topologyKey: kubernetes.io/hostname

这里面有一个元素叫topologyKey,对它的讲解并不多,很多资料也是一句带过。我这里打算研究一番:

topologyKey是拓扑域,即一个范围。这个范围可大可小,可以是地区、机房也可以是一个node。它实际对应的还是一个node的标签,也就是说,其实topologyKey就是用于筛选Node的。

结合我上面那个yaml,这个yaml意思就是要在default这个命名空间里创建一个叫pod-affinity的pod,它要求必须与run:node-affinity这样的pod在同一个node里,而且这个node还要有kubernetes.io/hostname这样的label。

那么如何判断一个node的label里有没有kubernetes.io/hostname?使用kubectl get nodes -o wide --show-labels 看一下:
paradin

可见图里三个node都有kubernetes.io/hostname,但是如何结合拓扑域呢?举个例子:Pod1在k8s.io/hostname=node1的Node上,Pod2在k8s.io/hostname=node2的Node上,Pod3在k8s.io/hostname=node1的Node上,则Pod2和Pod1、Pod3不在同一个拓扑域,而Pod1和Pod3在同一个拓扑域。

注意!原则上,topologyKey可以是任何合法的标签Key。但是出于性能和安全原因,对topologyKey有一些限制:

  1. 于亲和性和 requiredDuringSchedulingIgnoredDuringExecution的Pod反亲和性,topologyKey不能为空。
  2. 对于requiredDuringSchedulingIgnoredDuringExecution 的Pod反亲和性,引入LimitPodHardAntiAffinityTopology准入控制器来限制topologyKey只能是 kubernetes.io/hostname。如果要使用自定义拓扑域,则可以修改准入控制器,或者直接禁用它。
  3. 对于 preferredDuringSchedulingIgnoredDuringExecution 的Pod反亲和性,空的topologyKey 表示所有拓扑域。截止v1.12版本,所有拓扑域还只能是kubernetes.io/hostnamefailure-domain.beta.kubernetes.io/zonefailure-domain.beta.kubernetes.io/region的组合。
  4. 除上述情况外,topologyKey可以是任何合法的标签key。

timeAdded的问题

在Node的taint的配置里,可能会有timeAdded这样的字眼:

1
2
3
4
5
taints:
- effect: NoSchedule
key: dedicated
timeAdded: null
value: master

这个timeAdded是啥?很少有人介绍,就有人误认为效果是类似toleration里的tolerationSeconds,其实是不对的。

timeadded只有在effect:NoExecute里才有具体的值,其余时候都是null,这个字段的设计用途是:“考虑到不同的node可能时间不一致,如果k8s组件出现时间误差的时候,通过timeAdded + pod.tolerationSeconds小于等于time.Now来保证taint能成功的把pod转移到其他的node去”。

不过这个字段据说要慢慢被淘汰掉了,所以可以不用太关注它。

参考资料

http://bazingafeng.com/2019/03/31/k8s-affinity-topologykey/
https://github.com/kubernetes/kubernetes/issues/42394
https://segmentfault.com/a/1190000018446858

paradin

感谢您请我喝咖啡~O(∩_∩)O,如果要联系请直接发我邮箱chenx1242@163.com,我会回复你的
-------------本文结束感谢您的阅读-------------