k8s篇-POD调度详解(亲和性/污点容忍/优先级调度)

一、Kubernetes调度过程

说白了,一个调度过程就是把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之间、Pod与Node之间的关系调度。

Pod与Node的关系调度:

场景:比如某个Pod要调度到指定Node上运行。

1、节点选择器调度:nodeSelector/nodeName

这是一种常规性调度方式,通过 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

2、节点亲和性调度:NodeAffinity

亲和性又分为 硬亲和性 和 软亲和性 这两种调度,通过 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必须要与另一个Pod调度在一起,或不能放在一起,这种关系可以通过POD的Affinity(亲和性)和AntiAffinity(反亲和性)实现。

1、Pod亲和性调度:PodAffinity

通过 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

2、Pod反亲和性调度:PodAntAffinity

通过 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污点容忍调度:

还有一种类型调度就是,通过给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

三、高级调度(POD优先级与抢占机制)

高级调度能力包括优先级(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

你可能感兴趣的:(Kubernetes,kubernetes)