说白了,一个调度过程就是把pod放到合适的node上。
Kube-ApiServer <------⑴------> Controllers (Validate/Admit)
| ↑
⑶| ↑_ _ _ ⑵_ _ _ _ Kube-Scheduler (Bind NodeName)
_____|____
/ \
↓ ↓
(Kubelet) (Kubelet)
Node1 Node2
当向集群提交一个创建Pod的yaml文件时,会先发往kube-ApiServer,apiserver再将请求路由到webhooks的Controllers进行校验。
在通过校验之后,ApiServer会在集群里面生成一个pod,但此时生成的pod,它的nodeName是空的,且它的phase是Pending状态(一个pod正常运行时的状态为Running)。在生成了这个pod后,kube-Scheduler及kubelet都能watch 到这个pod的生成事件,kube-Scheduler发现这个pod的nodeName是空的之后,会认为这个pod是处于未调度状态。
然后,kube-Scheduler会对Pod进行调度。通过一系列的调度算法,包括一系列的过滤和打分的算法后,Schedule会选出一台最合适的节点,并把这一台节点的名称绑定在这个pod的spec上,完成一次调度的过程。
此时会看到,pod.spec上,nodeName已经更新成了名为Node1这个node,更新完nodeName之后,在Node1上的这台kubelet会watch到这个pod是属于自己节点上的一个pod。
然后它会把这个pod拿到节点上进行操作,包括创建一些容器storage及network,最后等所有的资源都准备完成,kubelet会把状态更新为Running,这样一个完整的调度过程就结束了。
主要是指Pod与Pod之间、Pod与Node之间的关系调度。
场景:比如某个Pod要调度到指定Node上运行。
这是一种常规性调度方式,通过 pod.spec.nodeSelector 或 pod.spec.nodeName 来将 Pod 调度到指定的节点上。
示例1:指定nodeSelector,将pod调度到带有k1=v1标签的Node上
# pod-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
nodeSelector:
k1: v1
# kubectl create -f pod-demo.yaml
# 可手动为node添加上此标签
# kubectl label nodes node1 k1=v1
示例2:指定nodeName,将pod调度到IP为172.24.11.212的Node上
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: zjmns
spec:
containers:
- image: ikubernetes/myapp:v1
name: myapp
nodeName: 172.24.11.212
亲和性又分为 硬亲和性 和 软亲和性 这两种调度,通过 pod.spec.affinity.nodeAffinity 字段指定,作用是根据Node上的标签来约束Pod可以调度到哪些节点上。
requiredDuringSchedulingIgnoredDuringExecution:
硬亲和性,意思是将Pod调度至节点上时,挑选出来的Node,必须要满足这个字段定义的条件,如果集群中找不到满足这样条件的Node时,Pod会调度失败。
preferredDuringSchedulingIgnoredDuringExecution:
软亲和性,意思是将Pod调度至节点上时,会优先挑选出满足条件的Node,如果集群中找不到满足这样条件的Node时,也没关系,Pod仍然可以被调度出去。
示例1:硬亲和性调度,Pod只会被调度至带有zone=foo或zone=bar标签的node上(若不存在这样的Node,则会导致调度失败)
# pod-nodeaffinity-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-demo
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: #硬亲和性
nodeSelectorTerms:
- matchExpressions: #匹配表达式条件,Pod只会被调度至带有zone=foo或zone=bar标签的node上,若不存在这样的Node,会导致调度失败
- key: zone
operator: In
values:
- foo
- bar
验证一下:
$ kubectl apply -f pod-nodeaffinity-demo.yaml
# 刚创建完Pod后,因为在集群中没有找到拥有此标签的节点,所以pod处于pending状态,没有被调度出去
$ kubectl get pods pod-affinity-demo
NAME READY STATUS RESTARTS AGE
pod-affinity-demo 0/1 Pending 0 23s
$ kubectl describe pod pod-affinity-demo | grep -A 4 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 47s (x8 over 9m18s) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
# 在集群中找到一个几点,为节点添加上此标签,比如node1
$ kubectl label nodes node1 zone=bar
# 可以看到,Pod马上就被调度出去了,由pending变成Running状态
$ kubectl get pods pod-affinity-demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-affinity-demo 1/1 Running 0 36m 10.244.1.34 node1
示例2:软亲和性调度,Pod优先会被调度至带有zone=foo2或zone=bar2标签的node上(若不存在这样的Node时,也没关系,也能被调度出去)
# pod-nodeaffinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-demo-2
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution: #软亲和性
- preference:
matchExpressions: #匹配表达式条件,Pod优先会被调度至带有zone=foo2或zone=bar2标签的node上,若不存在这样的Node,也没关系,仍能被调度出去
- key: zone
operator: In
values:
- foo2
- bar2
验证一下:
$ kubectl apply -f pod-nodeaffinity-demo-2.yaml
# 创建好Pod后,Pod马上就能被调度出去了。因为是优先会调度至满足条件的Node上,不满足时也没关系,所以pod能被调度出去
$ kubectl get pods pod-affinity-demo-2
NAME READY STATUS RESTARTS AGE
pod-affinity-demo-2 1/1 Running 0 15s
场景:比如一个Pod必须要与另一个Pod调度在一起,或不能放在一起,这种关系可以通过POD的Affinity(亲和性)和AntiAffinity(反亲和性)实现。
通过 pod.spec.affinity.podAffinity 字段指定:
requiredDuringSchedulingIgnoredDuringExecution:硬性要求,必须和某些POD调度在一起
preferredDuringSchedulingIgnoredDuringExecution:软性要求,优先和某些POD调度在一起
示例:硬亲和性,创建两个Pod,这两个pod具有亲和性,必须放在同一个位置或节点上
# pod-required-affinity-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app: podaffinity-myapp1
tier: frontend1
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
---
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: podaffinity-myapp2
tier: frontend2
spec:
containers:
- name: myapp2
image: ikubernetes/myapp:v2
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: #选择与哪个POD具有亲和性
matchExpressions: #匹配表达式
- key: app
operator: In
values:
- podaffinity-myapp1 #表示这个pod要跟一个带有app:podaffinity-myapp1标签的pod放在同一个位置上
topologyKey: kubernetes.io/hostname #通过节点kubernetes.io/hostname标签来判断节点是否在同一个位置
验证一下:
$ kubectl apply -f pod-required-affinity-demo.yaml
# 两个POD都运行在node2节点上
$ kubectl get pods pod-first pod-second -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-first 1/1 Running 0 13s 10.244.2.39 node2
pod-second 1/1 Running 0 13s 10.244.2.40 node2
通过 pod.spec.affinity.podAntiAffinity 字段指定:
requiredDuringSchedulingIgnoredDuringExecution:硬性要求,禁止和某些POD调度在一起
preferredDuringSchedulingIgnoredDuringExecution:软性要求,优先不跟某些POD调度在一起
示例:硬反亲和性,创建两个Pod,这两个pod具有反亲和性,不能放在同一位置上
# pod-required-anti-affinity-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: anti-affinity-pod1
labels:
name: podaffinity-myapp1
tier: front1
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
---
apiVersion: v1
kind: Pod
metadata:
name: anti-affinity-pod2
labels:
name: podaffinity-myapp2
tier: front2
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: #选择跟哪个POD不亲和
matchExpressions: #匹配表达式
- key: name
operator: In
values:
- podaffinity-myapp1 #与带有name:podaffinity-myapp1标签的pod具有反亲和性,不能放在同一个位置上
topologyKey: zone #通过节点的zone标签判断是否为同一个位置
验证一下:
# 给node1和node2打上同一个标签zone=foo(后面的topologyKey字段会以此标签来判断是否属于同一个位置)
kubectl label node node1 zone=foo
kubectl label node node2 zone=foo
$ kubectl apply -f pod-required-anti-affinity-demo.yaml
# 由于node1与node2都有zone标签,且值为foo,所以这两个节点都会被判断为同一个位置
# 当第一个pod运行在这上面后,第二个pod就不能运行在这上面了,因此第二个pod会处于pending状态
kubectl get pods --show-labels -l tier
NAME READY STATUS RESTARTS AGE LABELS
anti-affinity-pod1 1/1 Running 0 80s name=podaffinity-myapp1,tier=front1
anti-affinity-pod2 0/1 Pending 0 80s name=podaffinity-myapp2,tier=front2
还有一种类型调度就是,通过给Node打一些标记,来限制Pod调度到某些Node上,这些标记被称为Taints(污点),通过node.spec.taints字段设置。
当Node被打上标记或污点后,除非这个Pod明确声明能够容忍这些污点,否则Pod是无法在这个有污点的node上运行的,这个容忍是通过pod.spec.tolerations字段定义的。
node.spec.taints: 给node设置污点,每个污点有一个key和value作为标签,其中value可以为空,effect描述污点的作用
key: 污点标签名称。The taint key to be applied to a node.
value: 污点标签对应的值
effect: 污点作用,有三个选项 NoSchedule, PreferNoSchedule, NoExecute
pod.spec.tolerations:
effect: 匹配taint effect,若为空表示匹配所有effect
key: 匹配taint key
operator: 有Exists与Equal两个值,Exists表示只要key在就可以调度,Equal(等值比较)必须是value也要相同。可以为空,匹配所有。
value: 匹配taint value。当operator为Exists时,value可以为空
effect:
NoSchedule: 表示不会将Pod调度到带有该污点的Node上, 但是已经存在的pod不会被驱逐
PreferNoSchedule: 表示尽量避免将Pod调度到带有该污点的Node上,但这不是必需的
NoExecute:表示不会将Pod调度到带有该污点的Node上,同时在该Node上已经存在的Pod,若没有对应的tolerations,也会被驱逐出去
示例:
第一步,给node打上污点(新Pod要容忍这个污点,才能调度到这个节点上)
# node-taint-demo.yaml
apiVersion: v1
kind: Node
metadata:
name: node1
spec:
taints:
- key: "k1"
value: "v1"
effect: "NoSchedule"
第二步,创建一个Pod,并设置Pod的容忍度(能容忍node的污点,污点标签是k1=v1)
apiVersion: v1
kind: Pod
metadata:
name: myweb
spec:
containers:
- name: myweb
image: nginx
tolerations:
- key: "k1"
operator: "Exists"
value: "v1"
effect: "NoSchedule"
命令补充:
# 设置污点
kubectl taint nodes key=value:effect
# 删除污点
kubectl taint nodes key:effect-
# 为node1打上污点标签
kubectl taint nodes node1 k1=v1:NoSchedule
# 查看节点taint
kubectl describe nodes node1 | grep Taints
高级调度能力包括优先级(Priority)和抢占(Preemption)机制,它解决的是Pod调度失败时该怎么办的问题,或集群资源不够时也能合理调度POD的问题。
在kubernetes v1.14版本中该功能已经变稳定了,且PodPriority和Preemption默认都是开启的。
集群资源的合理利用,有两种策略: FIFO(先到先得策略)和 Priority(优先级策略)。
在实际生产中,如果使用FIFO策略,是一种不公平的策略,因为公司业务里面肯定是有高优先级的业务和低优先级的业务,所以Priority策略会比FIFO策略更能够符合日常公司业务特点。
优先级调度:
Pod被创建时会被放入一个队列中等待调度,若启用了优先级调度,会对POD调度顺序会有影响。
在队列中,高优先级的POD会被放在低优先级的POD前面,所以在调度时,高优先级的POD就会优先被调度,而当不满足高优先级的POD的调度需求,或无法调度时,调度器会继续尝试调度其他较低优先级的Pod。
当无法调度时,这个高优先级的POD会被暂时“搁置”起来,直到Pod被更新,或集群状态发生变化,调度器才会对这个Pod进行重新调度。
抢占机制:
Pod被创建时会被放入一个队列中等待调度。调度器从队列中选择Pod,尝试将其调度到某Node上。
当找不到能够满足这个高优先级Pod所设置需求的Node时,且启用了抢占功能,就会触发抢占流程,会重新尝试找到一个Node,并将该Node上一些低优先级的Pod驱逐出去,然后高优先级的POD就能调度到这个Node上了。
低优先级的Pod被逐出后,会回到等待队列,或等业务重新提交。
示例:
第一步,定义一个优先级
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high
value: 10000
globalDefault: false
第二步,为POD指定一个优先级
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
spec:
containers:
- name: nginx
image: nginx
priorityClassName: high