Kubernetes之集群调度

1、调度说明

1.1 调度器简介

  • Scheduler是Kubernetes的调度器,主要的任务是把定义的pod分配到集群的节点上,需要考虑以下问题:
    • 公平:如何保证每个节点都能被分配资源。
    • 资源高效利用:集群所有资源最大化被使用。
    • 效率:调度的性能要好,能够尽快地对大批量的pod完成调度工作。
    • 灵活:允许用户根据自己的需求控制调度的逻辑。
  • Scheduler是作为单独的程序运行的,启动之后会一直连接apiserver获取PodSpec.NodeName为空的pod,对每个pod都会创建一个binding,表明该pod应该放到哪个节点上。

1.2 调度过程

  • 调度分为几个部分:

    • 首先是过滤掉不满足条件的节点,这个过程称为predicate(预选)
    • 然后对通过的节点按照优先级排序,这个是priority(优选)
    • 最后从中选择优先级最高的节点。
  • Predicate有一系列的算法可以使用:

    • PodFitsResources:节点上剩余的资源是否大于pod请求的资源。
    • PodFitsHost:如果pod指定了NodeName,检查节点名称是否和NodeName匹配。
    • PodFitsHostPorts:节点上已经使用的port是否和pod申请的port冲突。
    • PodSelectorMatches:过滤掉和pod指定的label不匹配的节点。
    • NoDiskConflict:已经mount的volume和pod指定的volume不冲突,除非它们都是只读。
  • 如果在predicate过程中没有合适的节点,pod会一直在pending状态(等待),不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续priorities过程:按照优先级大小对节点排序。优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重,这些优先级选项包括:

    • LeastRequestedPriority:通过计算CPU和Memory的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点。
    • BalancedResourceAllocation:节点上CPU和Memory使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用。
    • ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高。
  • 通过算法对所有的优先级项目和权重进行计算,得出最终的结果。

1.3 自定义调度器

  • 除了K8S自带的调度器,可以自定义调度器。通过spec:schedulername参数指定调度器的名字,可以为pod选择某个调度器进行调度。比如下面的pod选择my-scheduler进行调度,而不是默认的default-scheduler

2、调度亲和性

2.1 pod与node的亲和性

  • spec.affinity.nodeAffinity

  • preferredDuringSchedulingIgnoredDuringExecution(优先执行计划):软策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    [root@master ~]# kubectl get node --show-labels
    NAME STATUS ROLES AGE VERSION LABELS
    master Ready master 14d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/master=
    node1 Ready <none> 14d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node1,kubernetes.io/os=linux
    node2 Ready <none> 14d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux
    [root@master ~]# vim node-preferred-pod.yaml
    [root@master ~]# cat node-preferred-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: affinity
    labels:
    app: node-affinity-pod
    spec:
    containers:
    - name: with-node-affinity
    image: nginx:v1
    affinity:
    nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
    preference:
    matchExpressions:
    - key: kubernetes.io/hostname
    operator: In
    values:
    - node3
    [root@master ~]# kubectl create -f node-preferred-pod.yaml
    pod/affinity created
    # 因为配置的是软策略,而node3节点并没有,那就只能在其它节点找
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    affinity 1/1 Running 0 12s 10.244.1.75 node1 <none> <none>
  • requiredDuringSchedulingIgnoredDuringExecution(要求执行计划):硬策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    [root@master ~]# kubectl get node --show-labels
    NAME STATUS ROLES AGE VERSION LABELS
    master Ready master 14d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/master=
    node1 Ready <none> 14d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node1,kubernetes.io/os=linux
    node2 Ready <none> 14d v1.15.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=node2,kubernetes.io/os=linux
    [root@master ~]# vim node-required-pod.yaml
    [root@master ~]# cat node-required-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: affinity2
    labels:
    app: node-affinity-pod
    spec:
    containers:
    - name: with-node-affinity
    image: nginx:v1
    affinity: #亲和性
    nodeAffinity: #node亲和性
    requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchExpressions:
    - key: kubernetes.io/hostname
    operator: In
    values:
    - node3
    [root@master ~]# kubectl create -f node-required-pod.yaml
    pod/affinity2 created
    # 因为指定了该pod必须要在node3节点运行的硬策略,而node3并不存在,所以只能不运行
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    affinity2 0/1 Pending 0 8s <none> <none> <none> <none>
    [root@master ~]# kubectl describe pod affinity2
    …………
    Events:
    Type Reason Age From Message
    ---- ------ ---- ---- -------
    Warning FailedScheduling 85s (x2 over 85s) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
  • 键值运算关系:

    说明
    In label 的值在某个列表中
    NotIn label 的值不在某个列表中
    Gt label 的值大于某个值
    Lt label 的值小于某个值
    Exists 某个 label 存在
    DoesNotExist 某个 label 不存在

2.2 pod与pod的亲和性

  • pod.affinity.podAffinity/podAntiAffinity

    • preferredDuringSchedulingIgnoredDuringExecution(优先执行计划):软策略
    • requiredDuringSchedulingIgnoredDuringExecution(要求执行计划):硬策略
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    [root@master ~]# vim pod1.yaml
    [root@master ~]# cat pod1.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: node01
    labels:
    app: node01
    spec:
    containers:
    - name: with-node-affinity
    image: nginx:v1
    [root@master ~]# kubectl create -f pod1.yaml
    pod/node01 created
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    node01 1/1 Running 0 36s 10.244.1.76 node1 <none> <none>
    [root@master ~]# kubectl get pod --show-labels
    NAME READY STATUS RESTARTS AGE LABELS
    node01 1/1 Running 0 3m13s app=node01
    [root@master ~]# vim pod-required.yaml
    [root@master ~]# cat pod-required.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: pod-3
    labels:
    app: pod-3
    spec:
    containers:
    - name: pod-3
    image: nginx:v1
    affinity:
    podAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
    matchExpressions:
    - key: app
    operator: In
    values:
    - node01
    topologyKey: kubernetes.io/hostname
    [root@master ~]# kubectl create -f pod-required.yaml
    pod/pod-3 created
    # 上方指定了pod-3与label为app=node01的pod亲和硬策略,由于满足条件的node01在node1节点上运行,所以pod-3也会一起运行在node1节点
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    node01 1/1 Running 0 4m33s 10.244.1.76 node1 <none> <none>
    pod-3 1/1 Running 0 20s 10.244.1.77 node1 <none> <none>
    [root@master ~]# kubectl delete pod pod-3
    pod "pod-3" deleted
    # 将podAffinity改成podAntiAffinity
    [root@master ~]# vim pod-required.yaml
    [root@master ~]# cat pod-required.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: pod-3
    labels:
    app: pod-3
    spec:
    containers:
    - name: pod-3
    image: nginx:v1
    affinity:
    podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
    matchExpressions:
    - key: app
    operator: In
    values:
    - node01
    topologyKey: kubernetes.io/hostname
    [root@master ~]# kubectl create -f pod-required.yaml
    pod/pod-3 created
    # 由于podAnitAffinity指定了不在同一拓扑域,即pod3必须和label为app=node01的pod不在同一个节点,所以pod-3运行在了node2而不是node1
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    node01 1/1 Running 0 63m 10.244.1.76 node1 <none> <none>
    pod-3 1/1 Running 0 16s 10.244.2.68 node2 <none> <none>
    [root@master ~]# kubectl delete pod pod-3
    pod "pod-3" deleted
    # 将podAnitAffinity重新改成podAffinity,并且label为app=node01改成app=node02
    [root@master ~]# vim pod-required.yaml
    [root@master ~]# cat pod-required.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: pod-3
    labels:
    app: pod-3
    spec:
    containers:
    - name: pod-3
    image: nginx:v1
    affinity:
    podAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
    matchExpressions:
    - key: app
    operator: In
    values:
    - node02
    topologyKey: kubernetes.io/hostname
    [root@master ~]# kubectl create -f pod-required.yaml
    pod/pod-3 created
    # 由于设置了pod3必须要运行在label为app=node02的pod所在的节点上,而目前没有,所以不运行
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    node01 1/1 Running 0 69m 10.244.1.76 node1 <none> <none>
    pod-3 0/1 Pending 0 8s <none> <none> <none> <none>
    [root@master ~]# kubectl get pod --show-labels
    NAME READY STATUS RESTARTS AGE LABELS
    node01 1/1 Running 0 71m app=node01
    pod-3 0/1 Pending 0 2m25s app=pod-3
    # 将node01的label改成满足配置条件后,pod-3即可正常运行
    [root@master ~]# kubectl label pod node01 app=node02 --overwrite=true
    pod/node01 labeled
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    node01 1/1 Running 0 77m 10.244.1.76 node1 <none> <none>
    pod-3 1/1 Running 0 8m38s 10.244.1.78 node1 <none> <none>
  • 亲和性/反亲和性调度策略比较如下:

    调度策略 匹配标签 操作符 拓扑域支持 调度目标
    nodeAffinity 主机 In, NotIn, Exists,DoesNotExist, Gt, Lt 指定主机
    podAffinity POD In, NotIn, Exists,DoesNotExist POD与指定POD同一拓扑域
    podAnitAffinity POD In, NotIn, Exists,DoesNotExist POD与指定POD不在同一拓扑域

3、污点

3.1 Taint和Toleration

  • 节点亲和性,是pod的一种属性(偏好或硬性要求),它使pod被吸引到一类特定的节点。Taint则相反,它使节点能够排斥一类特定的pod。
  • Taint和toleration相互配合,可以用来避免pod被分配到不合适的节点上。每个节点上都可以应用一个或多个taint,这表示对于那些不能容忍这些taint的pod,是不会被该节点接受的。如果将toleration应用于pod上,则表示这些pod可以(但不要求)被调度到具有匹配taint的节点上。

3.1.1 Taint

  • 污点(Taint)的组成:

    • 使用kubectl taint命令可以给某个Node节点设置污点,Node被设置上污点之后就和Pod之间存在了一种相斥的关系,可以让Node拒绝Pod的调度执行,甚至将Node已经存在的Pod驱逐出去,每个污点的组成如下:key=value:effect

    • 每个污点有一个key和value作为污点的标签,其中value可以为空,effect描述污点的作用。当前taint effect支持如下三个选项:

      • NoSchedule:K8S将不会将Pod调度到具有该污点的Node上。

        1
        2
        3
        4
        5
        6
        7
        # 例如master节点是由于设置了NoSchedule,所以才不会有pod运行在master节点上
        [root@master ~]# kubectl describe node master
        …………
        CreationTimestamp: Wed, 06 Oct 2021 13:31:13 +0800
        Taints: node-role.kubernetes.io/master:NoSchedule
        Unschedulable: false
        …………
      • PreferNoSchedule:K8S将尽量避免将Pod调度到具有该污点的Node上。

      • NoExecute:K8S将不会将Pod调度到具有该污点的Node上,同时会将Node上已经存在的Pod驱逐出去。

  • 污点的设置、查看和去除:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #查看节点污点,查找 Taints 字段:kubectl describe node node-name
    [root@master ~]# kubectl describe node master
    …………
    CreationTimestamp: Wed, 06 Oct 2021 13:31:13 +0800
    Taints: node-role.kubernetes.io/master:NoSchedule
    Unschedulable: false
    …………

    # 设置污点:kubectl taint nodes node1 key1=value1:effect
    [root@master ~]# kubectl get pod
    NAME READY STATUS RESTARTS AGE
    node01 1/1 Running 1 24h
    pod-3 1/1 Running 1 22h
    [root@master ~]# kubectl taint nodes node1 check=zhu:NoExecute
    node/node1 tainted
    [root@master ~]# kubectl get pod
    No resources found.

    # 去除污点:kubectl taint nodes node1 key1=value1:effect-
    [root@master ~]# kubectl taint nodes node1 check=zhu:NoExecute-
    node/node1 untainted

3.1.2 Tolerations

  • 设置了污点的Node将根据taint的effect和Pod之间产生互斥的关系,Pod将在一定程度上不会被调度到Node上。但我们可以在Pod上设置容忍 (Toleration) ,设置了容忍的Pod将可以容忍污点的存在,可以被调度到存在污点的Node上。

  • toleration的配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    spec:
    tolerations:
    - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoSchedule"
    tolerationSeconds: 3600
    - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoExecute"
    - key: "key2"
    operator: "Exists"
    effect: "NoSchedule"

    [root@master ~]# kubectl taint nodes node1 check=zhu:NoExecute
    node/node1 tainted
    [root@master ~]# vim test-toleration.yaml
    [root@master ~]# cat test-toleration.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: test
    labels:
    app: test
    spec:
    containers:
    - name: test
    image: nginx:v1
    tolerations:
    - key: "check"
    operator: "Equal"
    value: "zhu"
    effect: "NoExecute"
    tolerationSeconds: 3600
    [root@master ~]# kubectl create -f test-toleration.yaml
    pod/test created
    # 由于设置了该pod可以容忍check=zhu:NoExecute的污点,因此可以在node1节点上正常运行
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    test 1/1 Running 0 2m30s 10.244.1.81 node1 <none> <none>
    • 其中key、vaule、effect要与Node上设置的taint保持一致。
    • operator的值为Exists将会忽略value值。
    • tolerationSeconds:当Pod需要被驱逐时可以在Pod上继续保留运行的时间。
  • ①当不指定key值时,表示容忍所有的污点key。

    1
    2
    tolerations:
    - operator: "Exists"
  • ②当不指定effect值时,表示容忍所有的污点作用。

    1
    2
    3
    tolerations:
    - key: "key"
    operator: "Exists"
  • ③有多个Master存在时,防止资源浪费,可以如下设置:

    1
    kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule

4、固定节点

  • Pod.spec.nodeName将Pod直接调度到指定的Node节点上,会跳过Scheduler的调度策略,该匹配规则是强制匹配:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    [root@master ~]# vim test-fixnode.yaml
    [root@master ~]# cat test-fixnode.yaml
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: myweb
    spec:
    replicas: 7
    template:
    metadata:
    labels:
    app: myweb
    spec:
    nodeName: node1
    containers:
    - name: myweb
    image: nginx:v1
    ports:
    - containerPort: 80
    [root@master ~]# kubectl apply -f test-fixnode.yaml
    deployment.extensions/myweb created
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    myweb-6565474876-2lgjf 1/1 Running 0 12s 10.244.1.86 node1 <none> <none>
    myweb-6565474876-7nsx8 1/1 Running 0 12s 10.244.1.85 node1 <none> <none>
    myweb-6565474876-kbzxh 1/1 Running 0 12s 10.244.1.83 node1 <none> <none>
    myweb-6565474876-m7nmg 1/1 Running 0 12s 10.244.1.82 node1 <none> <none>
    myweb-6565474876-nkzw9 1/1 Running 0 12s 10.244.1.87 node1 <none> <none>
    myweb-6565474876-rwkxh 1/1 Running 0 12s 10.244.1.84 node1 <none> <none>
    myweb-6565474876-wrw7k 1/1 Running 0 12s 10.244.1.88 node1 <none> <none>
  • Pod.spec.nodeSelector:通过kubernetes的label-selector机制选择节点,由调度器调度策略匹配label,而后调度Pod到目标节点,该匹配规则属于强制约束。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    [root@master ~]# vim test-fixnode2.yaml 
    [root@master ~]# cat test-fixnode2.yaml
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: myweb
    spec:
    replicas: 2
    template:
    metadata:
    labels:
    app: myweb
    spec:
    nodeSelector:
    disk: ssd
    containers:
    - name: myweb
    image: nginx:v1
    ports:
    - containerPort: 80
    [root@master ~]# kubectl apply -f test-fixnode2.yaml
    deployment.extensions/myweb created
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    myweb-74586cff8f-2srj6 0/1 Pending 0 21s <none> <none> <none> <none>
    myweb-74586cff8f-kqn5v 0/1 Pending 0 21s <none> <none> <none> <none>
    [root@master ~]# kubectl label node node1 disk=ssd
    node/node1 labeled
    [root@master ~]# kubectl get pod -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    myweb-74586cff8f-2srj6 1/1 Running 0 69s 10.244.1.90 node1 <none> <none>
    myweb-74586cff8f-kqn5v 1/1 Running 0 69s 10.244.1.89 node1 <none> <none>