Tip
  • kube-scheduler负责调度Pod到集群内的节点上,它监听kube-apiserver,查询还未分配Node的Pod
  • 然后根据调度策略为这些Pod分配节点(最终体现为更新Pod的spec.nodeName字段)

kube-scheduler

pkg/scheduler/scheduler.go#New

snapshot := internalcache.NewEmptySnapshot()

1 nodeName

Important

直接指定节点名字,跳过调度器, 实际就没有走调度流程

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01

2 nodeSelector

Tip

直接通过键值对将Pod调度到具有特定label的Node上

nodeSelector

k get no
# 给节点打上标签
k label nodes node1 disk-type=ssd
# 查看节点标签
k get nodes --show-labels
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disk-type: ssd

3 Affinity

3.1 基础介绍

Tip
  • 亲和: 亲近
  • requiredDuringSchedulingIgnoredDuringExecution 硬亲和
    • requiredDuringScheduling
      • 调度时必须满足, 比如你设置了想要调度到有标签是app=web的节点,那么节点上必须有这样的标签
    • IgnoredDuringExecution
      • 执行时忽略, 比如你pod已经调度完在节点上运行了, 然后现在将节点上的标签app=web 删除了, 那么不会影响这个pod
  • preferredDuringSchedulingIgnoredDuringExecution 软亲和
    • preferredDuringScheduling
      • 调度时最好满足, 首选这样的节点.
    • IgnoredDuringExecution
      • 同硬亲和
查看说明
k explain pod.spec.affinity.nodeAffinity
k explain pod.spec.affinity.podAffinity
k explain pod.spec.affinity.podAntiAffinity

3.2 节点亲和性

Tip
  • NodeSelector 的升级版,也是用labels来约束, 支持更丰富的配置规则,使用更灵活
  • 如果 nodeSelector 和 nodeAffinity 两者都指定,那 node 需要两个条件都满足,pod 才能调度

节点亲和性

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
1      requiredDuringSchedulingIgnoredDuringExecution:
2        nodeSelectorTerms:
3        - matchExpressions:
          - key: disk-type
4            operator: In
            values:
            - ssd
5      preferredDuringSchedulingIgnoredDuringExecution:
6      - weight: 1
7        preference:
          matchExpressions:
          - key: label-1
            operator: In
            values:
            - key-1
      - weight: 50
        preference:
          matchExpressions:
          - key: label-2
            operator: In
            values:
            - key-2
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
1
硬亲和力配置,必须符合
2
节点选择器配置, 可以配置多个matchExpressions,满足一个即可
3
可以配置多个key, 必须都满足
4
这里表示节点必须包含键名为 disk-type 的标签,并且其值为ssd
5
软亲和力配置,表示最好符合
6
软亲和力的权重, 权重越高优先级越大,范围1-100
7
软亲和力配置项,和weight同级

如果存在两个候选节点,都满足 requiredDuringSchedulingIgnoredDuringExecution 规则,其中一个节点具有标签 label-1:key-1,另一个节点具有标签 label-2:key-2, 调度器会考察各个节点的 weight 取值,并将该权重值添加到节点的其他得分值之上

3.3 pod间 亲和性和反亲和性

Tip
  • PodAffinity : 想要和含有某些标签的pod 部署在”一起” (一个topology中)
  • podAntiAffinity: 与PodAffinity 相反

pod-affinity

  • 拓扑域,主要针对节点进行区域的划分,比如服务器有华东区这类的,{==用节点的label进行判断==},key相同且value也相同才是属于同一个的拓扑域
  • 对于 Pod 亲和性而言,在 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution 中,topologyKey 不允许为空
  • 对于 requiredDuringSchedulingIgnoredDuringExecution 要求的 Pod 反亲和性, 准入控制器 LimitPodHardAntiAffinityTopology 要求 topologyKey 只能是 kubernetes.io/hostname. 如果你希望使用其他定制拓扑逻辑,你可以更改准入控制器或者禁用之
k explain pod.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.topologyKey
# 节点默认都有一个 标签
k get no --show-labels

#我们能看到各个节点应该都有这样一个标签
kubernetes.io/hostname=xxx
#  我们可以给不同的节点设置相同的标签, 后续可以用以表示它们在同一个topology
k label no node1 node2 area=north
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine
  1. 反亲和性规则表示: Pod 不能被调度到这样的节点(集群/域)中
    1. 具有标签kubernetes.io/hostname=xxx的节点们(集群),或说在这样一个名字叫kubernetes.io/hostname=xxx的域中
    2. 且 集群(或域)中至少有一个节点运行着一个带有 app=store 标签的 Pod
  2. 首先第一个redis副本, 因为现在虽然每个节点都有kubernetes.io/hostname的标签,但是没有运行着 app=store标签 的pod,所以随便选一个节点部署第一个redis副本,比如node1
  3. 部署第一个redis副本后, 这个时候出现了这样的节点
    1. 运行着带有 app=store 标签的 Pod,且节点具有标签kubernetes.io/hostname=node1
  4. 部署第二个redis副本, 这个时候发现 node1 是不符合的, 因为有 app=store 的pod 且节点有标签是kubernetes.io/hostname,那么就在其他节点上随便选一个
  5. 部署第三个同理, 这样就会将3个redis 部署到不同的节点上去了.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine
  1. 现在我们可以马上理解,根据反亲和性规则 ,它们3个副本肯定会部署到不同的节点上
  2. 接着根据亲和性规则: pod要被调度到这样的节点(集群/域)中
    1. 具有标签kubernetes.io/hostname=xxx的节点们(集群),或说在这样一个名字叫kubernetes.io/hostname=xxx的域中
    2. 且 集群(或域)中至少有一个节点运行着一个带有 app=store 标签的 Pod
  3. 根据前面的redis副本, 现在3个节点是 有app=store标签的pod运行,然后节点标签具有kubernetes.io/hostname=xxx,它们符合亲和性规则
  4. 第一个副本随机选择一个 redis所在的节点, 然后第二个根据反亲和会选择另外一个 redis所在的节点, 第三个同理

最后的结果

node-1 node-2 node-3
web-server-1 web-server-2 web-server-3
redis-cache-1 redis-cache-2 redis-cache-3

4 Taint与Toleration

4.1 基础介绍

Tip
  • Taint: 污点
  • Toleration: 容忍
  • 原理:一旦某个节点被加上了一个Taint(理解为被打上了”污点”),那么正常情况下所有Pod都嫌这样的节点”脏”,都不会在这样的节点上运行
  • 除非有个别的Pod声明自己能”容忍”这个”污点”,即声明了Toleration, 这样它可以在这个节点上运行
  • Toleration是让Pod容忍节点上设置的污点Taint
  • pod有容忍不是说你就会调度到有个污点的节点, 只是说你这个pod可以调度到有这个污点的节点
  • 节点可以有多个污点, pod必须全部容忍这些污点才能够调度到该节点
Note

就像找男/女朋友, 不能容忍对方的缺点,pass, 可以容忍,ok

污点类型说明
Taint 效果(类型) 解释
NoSchedule
  1. 节点打上污点后,禁止调度到该节点
  2. 已经在该节点上的Pod不受影响,容忍这个污点的pod可以被调度到这个节点
NoExecute
  1. 节点打上污点后,禁止调度到该节点
  2. 已经在该节点上的,但没有容忍这个污点的pod,会立马被驱逐
  3. 如果pod容忍了污点但是设置了tolerationSeconds,则表示pod容忍含这个污点的节点tolerationSeconds秒后(容忍也有个限度),被驱逐
  4. 如果pod容忍了污点,且没有设置tolerationSeconds,那么可以被调度到该节点,不会被驱逐
  5. 单独的pod被驱逐后不会重新调度
PreferNoSchedule
  1. 节点被打上key=value:PreferNoSchedule 的污点, 你想要pod调度到这样的节点, pod是无需有对这个污点的容忍 |
  2. 这个PreferNoSchedule表示尽量避免将Pod调度到这样的节点上,如果没有更合适的节点,可以部署到该节点

4.2 污点操作命令

Important

key 和effect一起 才能表示 是否是同一个 污点

#创建一个污点(一个节点可以有多个污点):
# TAINT_VALUE可以不写, 就是 =TAINT_VALUE  不写
k taint nodes NODE_NAME TAINT_KEY=TAINT_VALUE:EFFECT
k taint nodes node01 ssd=true:PreferNoSchedule
k describe no node1 |grep taint -iA 10
k describe no master1 |grep taint -iA 10
    # master 默认是不会被调度到的.
    Taints:             node-role.kubernetes.io/master:NoSchedule
k taint no node1 hello=tomcat:NoSchedule-
k taint no node1 hello:NoSchedule- # 这样就可以了. 因为同样的key只会有一个
k taint no node1 hello-  #删除这个key的所有污点 (不同的effect)
# key 和effect 作为 区分不同的 污点
# 修改 key为hello effect为NoSchedule 的污点的 value值, 没有就创建该污点
k taint no node1 hello=world:NoSchedule --overwrite

4.3 污点相关配置

4.3.1 内置污点

node.kubernetes.io/not-ready:节点未准备好,相当于节点状态Ready的值为False。
node.kubernetes.io/unreachable:Node Controller访问不到节点,相当于节点状态Ready的值为Unknown。
node.kubernetes.io/out-of-disk:节点磁盘耗尽。
node.kubernetes.io/memory-pressure:节点存在内存压力。
node.kubernetes.io/disk-pressure:节点存在磁盘压力。
node.kubernetes.io/network-unavailable:节点网络不可达。
node.kubernetes.io/unschedulable:节点不可调度。
node.cloudprovider.kubernetes.io/uninitialized:如果Kubelet启动时指定了一个外部的cloudprovider,它
将给当前节点添加一个Taint将其标记为不可用。在cloud-controller-manager的一个controller初始化这个 节点后,Kubelet将删除这个Taint。

4.3.2 不可用多久才会被打上污点

kube-controller-manager 参数

/etc/kubernetes/manifests/kube-controller-manager.yaml 相关配置
spec:
  containers:
  - command:
    - kube-controller-manager
    - ...
1    - --node-monitor-grace-period=40s
2    - --node-monitor-period=5s
    - ...
1
添加这个 默认是40s, 表示40s 内一直不可用,就会被打上污点
2
5s会去探测一次节点是否可用
Caution
  • 你的节点发生故障, pod需要等待40s+pod对污点的容忍时间tolerationSeconds
  • 可以准备2个节点,将node1 关机, 查看 k get no 看状态, 大概40s 才会看到 not_ready

4.4 pod默认容忍

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    imagePullPolicy: IfNotPresent
# 创建后
k get pod nginx2 -o yaml|grep tolerations: -iA 10
  tolerations:
  - effect: NoExecute
1    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
1
节点没有ready时会打上这样的污点,在节点上的pod(被控制器管理的pod)默认会等待5m中才会被驱逐,重新调度
Caution
  • 你可能会疑问, 出了问题, pod 5m后才会被重新调度, 是不是有点长了.
  • 这个设置的原因是一般情况下节点是短暂的网络中断或临时故障,时间不会太长,这样做的目的是避免在节点暂时不可用的情况下频繁地驱逐和重建 Pod,以减少不必要的服务中断和资源浪费
如果你确定节点会故障较长一段时间
toleration-patch.yaml
spec:
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 60
# 更新
k patch po nginx2 --patch "$(cat toleration-patch.yaml)"

4.5 pod容忍度操作命令

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoExecute"
1    tolerationSeconds: 3600
1
如果有这个设置,表示容忍这个节点3600s后,驱逐
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "key1"
    operator: "Exists"
    effect: "NoSchedule"
Note

无需效果effect 匹配

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "key1"
    operator: "Exists"
apiVersion: v1
kind: Pod
metadata:
  name: nginx
labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - operator: "Exists"

4.6 问题答疑

指定有污点的nodeName的pod 会如何?
  • 没有设置容忍的情况下,也会先创建pod,因为指定了nodeName就会跳过调度器,然后才会根据节点上污点的effect做相关行为
  • 比如
    1. node1有 k=v:NoExecute, pod 设置nodeName=node1
    2. 通过describe pod 可以看到pod 创建 并Started , 后马上就被驱逐了(但是describe 看不到)
    3. 可以给pod 设置一个preStop sleep 20 这样可以看到…
Back to top