十年老IT知识分享 – 8-Kubernetes入门基础之调度器与亲和性介绍

[TOC]


0x01 Scheduler-任务调度器

Q: 什么是Scheduler?

答: Scheduler美 /ˈskedʒuːlər/是 kubernetes 中的调度器组件, 其主要任务是把指定的Pod分配到集群工作节点(或者说非污点节点)之上;
他是作为单独的程序运行的启动之后会一直和APIServer持续连接,当然有 pod 的资源清单中 Pod.Spec.NodeName 为空的时候,对每个pod都会创建一个binding,表明该 pod 应该放到哪个节点上

Tips: 由于Pod调度策略的随机性,其目的是自定义Pod调度到哪一个节点;

Q: 实现 Scheduler 调度器需要考虑的问题?

1.公平: 如何保证每个节点都能被分配.
2.资源高效利用: 集群所有资源最大化被使用.
3.效率: 调度的性能要好,能够尽快地对大批量的pod 完成调度工作.
4.灵活: 允许用户根据自己的需求控制调度的逻辑.


1.调度基础概念

(1)调度过程(构成部分)说明:

  • 1.首先是过滤掉不满足条件的节点,这个过程称为 predicate 英 /ˈprɛdɪˌkeɪt/ – v. 使……基于;
  • 2.然后对通过的节点按照优先级排序,这个过程称为priority 英 /praɪˈɒrəti/ – n. 优先;优先权;
  • 3.最后从中选择优先级最高的节点。

Tips: 如果中间任何一步骤有错误就直接返回错误;

  • Predicate 系列的算法
    • PodFitsResources :节点上剩余的资源是否大于 pod请求的资源
    • PodFitsHost :如果pod指定了NodeName,检查节点名称是否和NodeName相匹配
    • PodFitsHostPorts :节点上已经使用的port是否和 pod申请的port冲突
    • PodSelectorMatches : 过滤掉 和 pod 指定 的label 不匹配的节点
    • NoDiskConflict : 已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读

priority 选项
描述: 优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(非常重要)一般得权重越高即优先级越高,通过算法对所有的优先级项目和权重进行计算得出最终的结果;
这些优先级选项包括:

LeastRequestedPriority : 通过计算CPU和 Memory 的使用率来决定权重,使用率越低权重越高。这个优先级指标倾向于资源使用比例更低的节点;

BalancedResourceAllocation : 节点上 CPU 和 Memory 使用率越接近,权重越高,该项需要与LeastRequestedPriority一起使用不能单独使用;

# 其含义示例是表达如下:
Node1:CPU 占用 20% , Memory 占用 60 %;
Node2: CPU 占用 50% , Memory 占用 50 %;
# 此时由于Node2资源分配更加均衡此时我们的Pod将会优先调度到此节点之上;

ImageLocalityPriority : 倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高

Tips: 如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度直到有节点满足条件(后面将会进行实践)。之后如果有多个节点满足此条件就继续 priorities 过程按照优先级大小对节点排序;

2.自定义调度器

描述: 除了 kubernetes 自带的调度器开发者也可以编写自己的调度器, 通过 spec:schedulername 参数指定调度器的名字,可以为 pod 选择某个调度器进行调度;

Kubernetes也允许用户编写自己的调度器组件,并在创建资源的时候引用它。多个调度器可以同时运行和工作,只要名字不冲突, Kubernetes提供的调度器名字是default

例如: 使用某个调度器就是在Pod的spec.schedulername字段中填写上调度器的名字。如果自定义的调度器名字是my-scheduler,那么只有当spec.schedulername字段是my-scheduler才会被调度;

资源清单:

# 资源对象: pod.spec.schedulername
# 通过 spec:schedulername 参数指定调度器的名字,可以为pod 选择某个调度器进行调度
apiVersion: v1 
kind: Pod 
metadata:
  name: annotation-second-scheduler 
  labels:
    name: multischeduler-example 
spec:
  schedulername: my-scheduler      # 关键点 (选择调度器)
  containers:
  - name: pod-with-second-annotation-container 
    image: gcr.io/google_containers/pause:2.0

简单调度脚本: 它通过kubectl命令从apiserver获取未调度的Pod(spec.schedulerName 是my-scheduler,并且spec.nodeName 为空),同样地,用kubectl从apiserver获取nodes的信息,然后随机选择一个node作为调度结果,并写入到apiserver中。我们可通过kubectl describe pod pod_name查看一个Pod采用的调度器;

#!/bin/bash
SERVER='localhost:8001'
while true;
do
    for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')
;
    do
        NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))
        NUMNODES=${#NODES[@]}
        CHOSEN=${NODES[$[ $RANDOM % $NUMNODES ]]}
        curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind"
: "Node", "name": "'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
        echo "Assigned $PODNAME to $CHOSEN"
    done
    sleep 1
done


3.nodeAffinity – 节点亲和性

资源对象: pod.spec.nodeAffinity

软策略 : ·preferredDuringSchedulingIgnoredDuringExecution: 【我想要去这个节点】 - adj. 优先的;首选的
硬策略 : ·requiredDuringschedulinglgnoredDuringExecution: 【我一定要去这个节点】 - adj. 必需的;(美)必修的

什么是Pod调度的软策略以及硬策略?

1.软策略: 即希望 , 表示想到满足条件的节点上去,当不满足时候就到其它节点上去。
2.硬策略: 即强制 , 表示一定到某一个节点上去, 当不满足条件时候其哪一个节点也不去,注意其优先级高于软策略;

键值运算关系(即operator的可用值):

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

硬策略

资源清单: affinity-strong-demo.yaml

cat > affinity-strong-demo.yaml<<'EOF'
apiVersion: v1 
kind: Pod 
metadata:
  name: strong-affinity 
  labels:
    app: node-strong-affinity-pod 
spec:
  containers:
  - name: with-strong-node-affinity 
    image: harbor.weiyigeek.top/test/busbox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","sleep 700"]
  restartPolicy: Never          # 重启策略
  affinity:                     # 亲和性
    nodeAffinity:               # 节点亲和性
      requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略(必须得)
        nodeSelectorTerms:          # 节点选择条件
        - matchExpressions:         # 匹配表达式
          - key: kubernetes.io/hostname   # Key 值
            operator: NotIn          # 表达式 表示 Node主机名称不能是k8s-node-4即表示不能在node-4的节点上运行该Pod;
            values:
            - k8s-node-4             # Value 值
EOF

操作示例:

# (1) 部署资源清单
~/K8s/Day9/demo1$ kubectl create -f affinity-strong-demo.yaml
  # pod/affinity created

# (2) 查看调度器 (此时Pod将会调度到Node0-5之上)
$ kubectl get pod -o wide --show-labels
  # NAME       READY   STATUS    RESTARTS   AGE   IP            NODE           LABELS
  # affinity   1/1     Running   0          64s   10.244.2.17   k8s-node-5             app=node-strong-affinity-pod


# (3) 修改调度器
~/K8s/Day9/demo1$ kubectl delete -f affinity-strong-demo.yaml   # 先删除原来的Pod下同
  # pod "affinity" deleted
$ vim affinity-strong-demo.yaml
# spec:
#   affinity:
#     nodeAffinity:
#       requiredDuringSchedulingIgnoredDuringExecution:
#         nodeSelectorTerms:
#         - matchExpressions:
#           - key: kubernetes.io/hostname
#             operator: In   # 修改点  表示 Node主机名称是k8s-node-4即表示只能在 node-4 的节点上运行该Pod
#             values:
#             - k8s-node-4
~/K8s/Day9/demo1$ kubectl create -f affinity-strong-demo.yaml

# (4) 验证
~/K8s/Day9/demo1$ kubectl get pod -o wide --show-labels
  # NAME              READY   STATUS    RESTARTS   AGE   IP             NODE           LABELS
  # strong-affinity   1/1     Running   0          6s    10.244.1.141   k8s-node-4             app=node-strong-affinity-pod

# (5) 此时设置一个不可满足的条件时(我们根本都没有一个node-6得节点),此时Pod将会一直处于 Pending
# spec:
#   affinity:
#     nodeAffinity:
#       requiredDuringSchedulingIgnoredDuringExecution:
#         nodeSelectorTerms:
#         - matchExpressions:
#           - key: kubernetes.io/hostname # 表示 Node 主机名称
#             operator: In                # 即表示只能在 node-6 的节点上运行该Pod
#             values:
#             - k8s-node-6                # 匹配值是 k8s-node-6
~/K8s/Day9/demo1$ kubectl create -f affinity-strong-demo-2.yaml
  # pod/strong-affinity created
~/K8s/Day9/demo1$ kubectl get pod -o wide
  # NAME              READY   STATUS            RESTARTS   AGE   IP       NODE    
  # strong-affinity   0/1     Pending (关键点)  0          10s   <none>   <none>

软策略

资源清单: affinity-soft-demo.yaml

cat > affinity-soft-demo.yaml<<'EOF'
apiVersion: v1 
kind: Pod 
metadata:
  name: soft-affinity 
  labels:
    app: node-soft-affinity-pod 
spec:
  containers:
  - name: with-soft-node-affinity 
    image: harbor.weiyigeek.top/test/busbox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","sleep 700"]
  restartPolicy: Never          # 重启策略
  affinity:                     # 亲和性
    nodeAffinity:               # 节点亲和性
      preferredDuringSchedulingIgnoredDuringExecution:  # 软策略(优先级首选得)
      - weight: 1               # 权重(越高其匹配优先级越高)
        preference:             # 偏向
          matchExpressions:     # 匹配表达式
          - key: source         # Key 值
            operator: In        # 表达式  
            values: 
            - k8s-node-3        # Value 值  表示 最想匹配到node3节点
      - weight: 2               # 权重(越高其匹配优先级越高)
        preference:             # 偏向
          matchExpressions:     # 匹配表达式
          - key: source         # Key 值
            operator: NotIn        # 表达式  
            values: 
            - k8s-node-4        # Value 值  表示 不想匹配到node4节点
EOF

操作示例:

# (1) 创建部署节点
~/K8s/Day9/demo1$ kubectl create -f affinity-soft-demo.yaml
  # pod/soft-affinity created
~/K8s/Day9/demo1$ kubectl get pod -o wide  #此时将会调度到node5上面
  # NAME              READY   STATUS    RESTARTS   AGE   IP            NODE        
  # soft-affinity     1/1     Running   0          8s    10.244.2.19   k8s-node-5 

# (2) 修改权重追加如下
      # - weight: 3               # 权重(越高其匹配优先级越高)
      #   preference:             # 偏向
      #     matchExpressions:     # 匹配表达式
      #     - key: source         # Key 值
      #       operator: In        # 表达式  
      #       values: 
      #       - k8s-node-4        # Value 值表示想匹配到node4节点
~/K8s/Day9/demo1$ kubectl delete -f affinity-soft-demo-1.yaml && kubectl create -f affinity-soft-demo-1.yaml && sleep 10 && kubectl get pod -o wide
# 此时虽然想到node4节点的亲和性较高然后前面有权重为2的不希望到node4上,此时他任然不会将Pod调度在node4上;
  # NAME              READY   STATUS    RESTARTS   AGE     IP            NODE        
  # soft-affinity     1/1     Running   0          5m38s   10.244.2.22   k8s-node-5

硬软策略联使

描述 : 硬策略与软策略(required & preferred)可以组合相互进行使用;
资源清单:

cat > requiredpreferred-strategy.yaml<<'END'
apiVersion: v1 
kind: Pod 
metadata:
  name: affinity 
  labels:
    app: node-affinity-pod 
spec:
  containers:
  - name: with-node-affinity 
    image: harbor.weiyigeek.top/test/busbox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","sleep 700"]
  affinity:
    nodeAffinity:                                         # 节点亲和性
      requiredDuringSchedulingIgnoredDuringExecution:     # 硬策略
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname 
            operator: NotIn 
            values:
            - k8s-node02 
      preferredDuringSchedulingIgnoredDuringExecution:    # 软策略
      - weight: 1 
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In 
            values:
            - k8s-node03
END

Tips : 该Pod一定不能调度到k8s-node02节点,但如果k8s-node03可以被调度时便会调度到k8s-node03节点;

固定节点调度策略(fixed)

描述: 上述讲了亲和性来选择运行的节点,如果想要Pod运行在指定的节点之上就需要按照下述进行设置固定调度的节点;

主要有以下资源对象字段设置Pod运行的固定节点:

  • 1) spec.template.spec.nodeName
  • 2) pod.spec.nodeSelector

方式1.资源清单示例:

cat > fixed-node-test.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment 
metadata:
  name: fixed-node-test
  labels:
    app: fixed-node
spec:
  replicas: 5
  selector:                  # 注意它是在Deployment控制器中需要依赖的
    matchLabels:  
      app: fixed-node        # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: fixed-node
    spec:
      nodeName: k8s-node-4      # 节点绑定 (关键点)
      containers:
      - name: fixed-node 
        image: harbor.weiyigeek.top/test/busbox:latest
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh","-c","sleep 700"]
        ports:
        - containerPort: 80
EOF

操作流程:

# (1) 部署`deployment`管理的`fiexd`固定控制器
~/K8s/Day9/demo2$ kubectl create -f fixed-node-test.yaml
  # deployment.apps/fixed-node-test created

# (2) 查看Pod部署状态
~/K8s/Day9/demo2$ kubectl get pod
  # NAME                               READY   STATUS              RESTARTS   AGE
  # fixed-node-test-5f67b8645d-4c7vd   0/1     ContainerCreating   0          3s
  # fixed-node-test-5f67b8645d-bcrj7   0/1     ContainerCreating   0          3s
  # fixed-node-test-5f67b8645d-cnb5n   0/1     ContainerCreating   0          3s
  # fixed-node-test-5f67b8645d-sh5ld   0/1     ContainerCreating   0          3s
  # fixed-node-test-5f67b8645d-v5kfd   0/1     ContainerCreating   0          3s
  # taint-tolerations                  0/1     Pending             0          24m
~/K8s/Day9/demo2$ kubectl get pod -o wide
  # NAME                               READY   STATUS    RESTARTS   AGE   IP             NODE        
  # fixed-node-test-5f67b8645d-4c7vd   1/1     Running   0          7s    10.244.1.148   k8s-node-4 
  # fixed-node-test-5f67b8645d-bcrj7   1/1     Running   0          7s    10.244.1.146   k8s-node-4 
  # fixed-node-test-5f67b8645d-cnb5n   1/1     Running   0          7s    10.244.1.145   k8s-node-4 
  # fixed-node-test-5f67b8645d-sh5ld   1/1     Running   0          7s    10.244.1.147   k8s-node-4 
  # fixed-node-test-5f67b8645d-v5kfd   1/1     Running   0          7s    10.244.1.149   k8s-node-4

方式2.资源清单示例:

cat > fixed-node-test-1.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fixed-node-test-1
  labels:
    app: fixed-node-1
spec:
  replicas: 5
  selector:                    # 注意它是在Deployment控制器中需要依赖的
    matchLabels:  
      app: fixed-node-1        # 匹配的Pod标签非常重要
  template:
    metadata:
      labels:
        app: fixed-node-1
    spec:
      nodeSelector:
        nodetype: fixed           # 节点标签 (主要需要给节点打标签然后才能指定)
      containers:
      - name: fixed-node-1
        image: harbor.weiyigeek.top/test/busbox:latest
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh","-c","sleep 700"]
        ports:
        - containerPort: 80       # 容器暴露端口 
EOF

部署流程:

# (1) 节点打标签(只有打了才能生效)
~/K8s/Day9/demo2$ kubectl label node k8s-node-5 nodetype=fixed
# node/k8s-node-5 labeled
kubectl get node --show-labels # 查看打的标签
  # NAME          STATUS   ROLES    AGE   VERSION   LABELS
  # weiyigeek-ubuntu   Ready    master   18d   v1.19.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=weiyigeek-ubuntu,kubernetes.io/os=linux,node-role.kubernetes.io/master=
  # k8s-node-4    Ready    <none>   18d   v1.19.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-4,kubernetes.io/os=linux
  # k8s-node-5    Ready    <none>   7d    v1.19.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-5,kubernetes.io/os=linux,nodetype=fixed


# (2) 部署
~/K8s/Day9/demo2$ kubectl create -f fixed-node-test-1.yaml
# deployment.apps/fixed-node-test-1 created

~/K8s/Day9/demo2$ kubectl get pod -o wide  # 可以看见所有的Pod都运行在k8s-node-5上
  # NAME                                 READY   STATUS    RESTARTS   AGE   IP             NODE        
  # fixed-node-test-1-6c996c5887-67ldb   1/1     Running   0          37s   10.244.2.30    k8s-node-5 
  # fixed-node-test-1-6c996c5887-g4gz8   1/1     Running   0          37s   10.244.2.28    k8s-node-5 
  # fixed-node-test-1-6c996c5887-j9f2k   1/1     Running   0          37s   10.244.2.27    k8s-node-5 
  # fixed-node-test-1-6c996c5887-msc5g   1/1     Running   0          37s   10.244.2.29    k8s-node-5 
  # fixed-node-test-1-6c996c5887-xfb6v   1/1     Running   0          37s   10.244.2.26    k8s-node-5 

# (3) 更进一步将node4的标签更改一致然后在进行Pod的扩展操作查看有什么效果;
kubectl label node k8s-node-4 nodetype=fixed
  # node/k8s-node-4 labeled

# (4) Deployment 进行扩容
~/K8s/Day9/demo2$ kubectl scale --replicas=7 deploy fixed-node-test-1
  # deployment.apps/fixed-node-test-1 scaled

# (5) 结果验证
~/K8s/Day9/demo2$ kubectl get pod -o wide | grep "fixed-node-test-1"
  # fixed-node-test-1-6c996c5887-67ldb   1/1     Running   0          37s   10.244.2.30    k8s-node-5 
  # fixed-node-test-1-6c996c5887-g4gz8   1/1     Running   0          37s   10.244.2.28    k8s-node-5 
  # fixed-node-test-1-6c996c5887-j9f2k   1/1     Running   0          37s   10.244.2.27    k8s-node-5 
  # fixed-node-test-1-6c996c5887-msc5g   1/1     Running   0          37s   10.244.2.29    k8s-node-5 
  # fixed-node-test-1-6c996c5887-xfb6v   1/1     Running   0          37s   10.244.2.26    k8s-node-5 
  # fixed-node-test-1-6c996c5887-tbnf4   1/1     Running   0          61s   10.244.1.152   k8s-node-4   # 此时增加两个Pod运行在K8s-node-4上
  # fixed-node-test-1-6c996c5887-xfb6v   1/1     Running   0          10m   10.244.2.26    k8s-node-4


4.podAffinity – Pod 亲和性

描述: 同样PodAffinity也有软策略和硬策略其解释与节点亲和性相同;

# (1) 所属对象: pod.spec.affinity.podAffinity (亲和) / podAntiAffinity (反亲和)
软策略 :* preferredDuringSchedulinglgnoredDuringExecution
硬策略 : * requiredDuringSchedulinglgnoredDuringExecution

资源清单示例:

# (1) 根据Pod亲和性匹配该Pod运行的节点,反亲和就是不和满足条件的Pod在同一个节点运行;
cat > pod-affinity-demo-1.yaml <<'EOF'
apiVersion: v1 
kind: Pod 
metadata:
  name: pod-affinity-demo1 
  labels:
    app: pod-affinity
spec:
  containers:
  - name: pod-affinity
    image: harbor.weiyigeek.top/test/busbox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","sleep 700"]
  affinity:
    podAffinity:  # Pod 亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和
      - labelSelector:       # 标签选择器
          matchExpressions:  # 匹配规则
          - key: app   
            operator: In     # 表示 匹配标签为app=node-soft-affinity-pod 的Pod在哪一个节点之上
            values:
            - node-soft-affinity-pod  
        topologyKey: kubernetes.io/hostname 
    podAntiAffinity:  # Pod 反亲和
      preferredDuringSchedulingIgnoredDuringExecution:  # 软亲和
      - weight: 1 
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app 
              operator: In             # 表示 匹配标签为`app=node-strong-affinity-pod`的 Pod 在哪一个节点之上
              values:
              - node-strong-affinity-pod
          topologyKey: kubernetes.io/hostname
EOF

温馨提示: 什么是 topologyKey? 个人理解 pod affinity 的调度范围为 topology。

官方解释:如果该X已经在运行一个或多个满足规则Y的Pod,则该Pod应该(或者在非亲和性的情况下不应该)在 X 中运行,Y 表示为LabelSelector规则, X 是一个拓扑域,例如节点,机架,云提供者区域,云提供者区域等。您可以使用topologyKey这是系统用来表示这种拓扑域的节点标签的密钥。

操作流程:

# (1) 部署
~/K8s/Day9/demo1$ kubectl create -f pod-affinity-demo-1.yaml
  # pod/pod-affinity-demo1 created

# (2) 验证可以看见运行在 k8s-node-5 节点上,这是由于我们的Pod亲和性的强策略;
~/K8s/Day9/demo1$ kubectl get pod --show-labels -o wide
  # NAME                 READY   STATUS    RESTARTS   AGE   IP            NODE           LABELS
  # pod-affinity-demo1   1/1     Running   0          4s    10.244.2.24   k8s-node-5             app=pod-affinity
  # soft-affinity        1/1     Running   0          10s   10.244.2.23   k8s-node-5             app=node-soft-affinity-pod
  # strong-affinity      0/1     Pending   0          88m   <none>        <none>                 app=node-strong-affinity-pod

PS : 在使用Pod亲和性时有一个问题需要非常重视即,与之匹配Pod必须是RUNNING状态,否则认为不满足调度条件则Pod将会被置为Pending状态;

节点与Pod的Affinity亲和性总结:
描述: 节点与Pod亲和性/反亲和性调度策略比较如下。

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

补充说明.解决负载不均衡的问题, 可以给Pod容器设置反亲和,让这些容器平均的分布在各个节点上(不要聚在一起)

# 节点 & Pod 反亲和
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: node
          operator: In
          values:
          - work
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - podAffinityTerm:
        labelSelector:
          matchExpressions:
          - key: app.kubernetes.io/name
            operator: In
            values:
            - ingress-nginx
        topologyKey: kubernetes.io/hostname
      weight: 100

5.Priority – Pod 优先级

描述: 与前面所讲的调度优选策略中的优先级(Priorities)不同,前文所讲的优先级指的是节点优先级,而pod priority指的是Pod的优先级,高优先级的Pod会优先被调度,或者在资源不足低情况牺牲低优先级的Pod以便于重要的Pod能够得到资源部署。

为了定义Pod优先级,需要先定义PriorityClass对象,该对象没有Namespace限制,官网示例:

# (1) 
~$ kubectl api-resources | grep "priorityclasses"
priorityclasses   pc   scheduling.k8s.io   false   PriorityClass

# (2) 资源清单
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."

然后通过在Pod的spec. priorityClassName中指定已定义的PriorityClass名称即可:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

6.Preemption – Pod 抢占

描述: 当节点没有足够的资源供调度器调度Pod、导致Pod处于pending时,抢占(preemption)逻辑会被触发。Preemption会尝试从一个节点删除低优先级的Pod,从而释放资源使高优先级的Pod得到节点资源进行部署。


0x02 Taints – 污点

描述: 前面我们讲述到节点亲和性是pod的一种属性(偏好或硬性要求), 它可以使得pod运行在满足条件的特定节点之上;

Q: 那什么又是Taint(污点)?

答: Taint 恰恰与之相反, 它使节点能够排斥一类特定的pod。注意每个节点上都可以应用一个或多个taint, 如果设置了容忍的Pod将可以容忍污点的存在,可以被调度到存在污点的 Node 上;

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

# Syntax : 其中 value 可以为空 effect 描述污点的作用
kubectl taint node [nodeName] key=value:[effect]   # 每个污点有一个key和 value作为污点的标签

# 参数值
# 其中 [effect] 可取值 : [ NoSchedule | PreferNoSchedule | NoExecute ]
* NoSchedule : 表示k8s将不会将Pod 调度到具有该污点的 Node 上;
* PreferNoSchedule : 表示k8s将尽量避免将Pod调度到具有该污点的 Node 上;
* NoExecute : 表示k8s将不会将 Pod调度到具有该污点的Node上,同时会将Node上已经存在的 Pod 驱逐出去;

案例演示:

# 0) 查看指定节点taint
$ kubectl describe node master1| grep "Taints"  # 查找 Taints 字段 
  # Taints: node-role.kubernetes.io/master:NoSchedule
# 查看所有污点策略显示三个master节点都是NoSchedule
$ kubectl get no -o yaml | grep taint -A 5
#     taints:
#     - effect: NoSchedule
#       key: node-role.kubernetes.io/master
#   status:
#     addresses:
#     - address: master1的IP
# --
#     taints:
#     - effect: NoSchedule
#       key: node-role.kubernetes.io/master
#   status:
#     addresses:
#     - address: master2的IP
# --
#     taints:
#     - effect: NoSchedule
#       key: node-role.kubernetes.io/master
#   status:
#     addresses:
#     - address: master3的IP
 

# 1) 手动为master节点设置taints
$ kubectl taint nodes node1 key1=value1:NoSchedule 
$ kubectl taint nodes master1 node-role.kubernetes.io/master=:NoSchedule


# 2) 去除污点的设置
kubectl taint nodes node1 key1:NoSchedule-  # 这里的key可以不用指定value代表删除指定key所有的effect;
  # de1 key1: NoSchedule-
$ kubectl taint nodes k8s-master-1 node-role.kubernetes.io/master=:NoSchedule-
  # node/k8s-master-1untainted


# 3) 取消污点后结果
$ kubectl describe node k8s-master-1 | grep "Taints"
  # Taints:             <none>                     # 取消污点后

$ kubectl get node --show-labels
  # NAME          STATUS   ROLES    AGE   VERSION   LABELS
  # weiyigeek-ubuntu   Ready    master   18d   v1.19.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=weiyigeek-ubuntu,kubernetes.io/os=linux,node-role.kubernetes.io/master=
  # k8s-node-4    Ready    <none>   18d   v1.19.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-4,kubernetes.io/os=linux
  # k8s-node-5    Ready    <none>   7d    v1.19.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node-5,kubernetes.io/os=linux,nodetype=fixed

~/K8s/Day9/demo1$ kubectl get no k8s-node-5 -o yaml | grep "key" -C 3
  #   - 10.244.2.0/24
  #   taints:
  #   - effect: NoExecute
  #     key: node-role.kubernetes.io/master
  # status:
  #   addresses:
  #   - address: 10.10.107.215

$ kubectl taint nodes k8s-node-5 node-role.kubernetes.io/master-
  # node/k8s-node-5 untainted

补充操作:

# 1) 去除污点允许 master 节点部署 pod;
[root@master1 ~]$ kubectl taint nodes --all node-role.kubernetes.io/master-
  # node/master1 untainted
  # node/master2 untainted
  # node/master3 untainted
  # error: taint "node-role.kubernetes.io/master" not found
  
# 2) 再次查看无显示说明污点去除成功
kubectl get no -o yaml | grep taint -A 5

Tips : 为 master 设置的这个 taint 中node-role.kubernetes.io/master为 key/value 为空 effect 为NoSchedule;
Tips : 如果输入命令时你丢掉了= 符号, 写成了node-role.kubernetes.io/master:NoSchedule, 会报 error: at least one taint update is required 错误;


0x03 Toleration – 容忍

Q: 什么是Toleration容忍?

答: 即表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上, 简单的说

Tips: Taint 和 Toleration 相互配合可以用来避免pod被分配到不合适的节点上。表示对于那些不能容忍这些 taint 的 pod 是不会被该节点接受的, 但是针对 Pod 没有节点运行时可以选一台污点的节点运行Pod;

在 pod 的 spec 中设置 tolerations 字段:

# 1.当不指定key值时表示容忍所有的污点key:
tolerations:
- operator: "Exists"

# 2.当不指定 effect 值时表示容忍所有的污点作用 
tolerations:
- key: "key"
  operator: "Exists"

# 3.查看容忍
~$ kubectl describe pod
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s

Tolerations 资源清单演示:

# pod.spec.tolerations
tolerationstolerations:
- key: "key1"
  operator: "Equal"         # 其中key, vaule, effect 要与Node上设置的 taint保持一致 
  value: "value1"
  effect: "NoSchedule"
  tolerationSeconds: 3600   # 用于描述当Pod需要被驱逐时可以在 Pod上继续保留运行的时间 
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
- key: "key2"
  operator: "Exists"                     # 为Exists将会忽略value值 
  effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"  # 容忍 tolerations 主节点的 taints 
  operator: "Equal"    
  value: "" 
  effect: "PreferNoSchedule"             # 尽可能不调度Pod到该节点之上

操作实例:

# (0) 有多个Master 存在时防止资源浪费,可以如下设置
kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule

# (1) 当节点有故障需要退出时,需要将Pod从指定Node节点进行驱逐后,方可删除节点
kubectl taint nodes k8s-master-4 node-role.kubernetes.io/master=:NoExecute
  # node/k8s-node-5 tainted


# (2) 查看Pod运行节点
$ kubectl get pod -o wide
  # NAME                 READY   STATUS        RESTARTS   AGE     IP            NODE      
  # pod-affinity-demo1   1/1     Terminating   335        2d17h   10.244.2.24   k8s-node-5
~/K8s/Day9/demo1$ kubectl get no k8s-node-4 -o yaml | grep "key" -C 3
  - 10.244.1.0/24
  taints:
  - effect: NoSchedule
    key: node-role.kubernetes.io/master  # 关键点
status:
  addresses:
  - address: 10.10.107.214


# (3) 演示案例
cat > taint-Tolerations-mode.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: taint-tolerations
  labels:
    app: taint-tolerations-mode
spec:
  containers:
  - name: with-soft-node-affinity
    image: harbor.weiyigeek.top/test/busbox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","sleep 700"]
  restartPolicy: Never                # 重启策略(绝不除非移除退出)
  tolerations:                        
  - key: "kubernetes.io/hostname"
    operator: "Equal"                 # supported values: "Equal", "Exists"
    value: "k8s-node-5"
    effect: "NoExecute"
EOF

# (4) 此时Pod不会运行在k8s-node-5主机上
~/K8s/Day9/demo1$ kubectl create -f taint-Tolerations-mode.yaml
  # pod/taint-tolerations created
~/K8s/Day9/demo1$ kubectl get pod -o wide
  # NAME                READY   STATUS    RESTARTS   AGE   IP             NODE       
  # taint-tolerations   1/1     Running   0          33s   10.244.1.143   k8s-node-4 


# (5) 此时如果将node-45主机都设置成为污点则删除重建Pod将会运行k8s-node-5有污点的这台机器,这是因为上面 tolerations 容忍配置的原因;
~/K8s/Day9/demo1$ kubectl taint nodes k8s-node-4 node-role.kubernetes.io/master=:NoSchedule
~/K8s/Day9/demo1$ kubectl taint nodes k8s-node-5 node-role.kubernetes.io/master=:NoSchedule
~/K8s/Day9/demo1$ kubectl delete -f taint-Tolerations-mode.yaml
~/K8s/Day9/demo1$ kubectl get pod -o wide
  # NAME                READY   STATUS    RESTARTS   AGE   IP       NODE    
  # taint-tolerations                    1/1     Running       0          10s   10.244.2.33   k8s-node-4   <none>           <none>


0x04 Cordon – Node 隔离与恢复

描述: 在某些场景下需要对Node进行隔离,比如硬件升级或者维护,目前隔离的Node有两种方式。

# (1) 使用 kubectl cordon 命令可以对某一个Node进行隔离,在隔离后就不会向该Node节点调度Pod。
$ kubectl get node | grep "224"
  # weiyigeek-224   Ready    <none>   72d   v1.19.6
$ kubectl cordon weiyigeek-224
  # node/weiyigeek-224 cordoned
$ kubectl get node # Tips :隔离操作并不会停止或者删除正在运行的Pod需要人工进行干预,所以尽管已经对224进行隔离,但是其在上面运行的Pod状态并没有受到任何影响。
  # NAME       STATUS                     ROLES    AGE   VERSION
  # weiyigeek-224   Ready,SchedulingDisabled   <none>   72d   v1.19.6
$ ansible weiyigeek-224 -m shell -a "docker ps"
  # weiyigeek-224 | CHANGED | rc=0 >>
  # CONTAINER ID        IMAGE                                                           COMMAND                  CREATED             STATUS              PORTS               NAMES
  # 558fa21e1993        caa6655434a5                                                    "/conf/update-node.s…"   2 months ago        Up 2 months                             k8s_redis_redis-cluster-3_database_db60bf69-de70-4aa4-b8e6-84ebbb37992b_0
  # a515551a4f71        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2   "/pause"                 2 months ago        Up 2 months                             k8s_POD_redis-cluster-3_database_db60bf69-de70-4aa4-b8e6-84ebbb37992b_0
  # ffe033537640        183b53858d7d                                                    "start_runit"            2 months ago        Up 2 months                             k8s_calico-node_calico-node-7jcwh_kube-system_5c66e41b-f6d8-4ed8-a5b6-d08a5e734214_0
  # 6dbb52c66413        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2   "/pause"                 2 months ago        Up 2 months                             k8s_POD_calico-node-7jcwh_kube-system_5c66e41b-f6d8-4ed8-a5b6-d08a5e734214_0
  # 22bad44b05f4        dbcc366449b0                                                    "/usr/local/bin/kube…"   2 months ago        Up 2 months                             k8s_kube-proxy_kube-proxy-25tts_kube-system_89870e87-ecb3-48fd-b42d-2b7662631d51_0
  # d0619946b54e        registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.2   "/pause"                 2 months ago        Up 2 months                             k8s_POD_kube-proxy-25tts_kube-system_89870e87-ecb3-48fd-b42d-2b7662631d51_0


# (2) 统一的恢复方式:
~$ kubectl uncordon weiyigeek-224
  # node/weiyigeek-224 uncordoned
~$ kubectl get node | grep "224"
  # weiyigeek-224   Ready    <none>   72d   v1.19.6


FAQ – 知识扩展

Q:普通用户有自定义Pod优先级的权限吗?

A:可以,Pod优先级定义与创建普通Pod类似,并没有特别权限控制。定义Pod优先级,需要先定义kind为PriorityClass类型的资源对象,然后通过在Pod的spec. priorityClassName中指定已定义的PriorityClass名称即可。

Q:Kubernetes scheduler extender能介绍一下么?

A:extender可理解为Kubernetes调度策略和算法的扩展,属于自定义调度器的一种方式,与Kubernetes默认调度器的过程类似,主要是针对一些不算受集群本身控制的资源(比如网络),需要通过外部调用来进行调度的情况。

Q:用户使用了NodeSelector指定了Pod调度的node节点后,如果node不可用,那么scheduler会采用别的策略吗?

A:nodeSelector是目前最为简单的一种pod运行时调度限制,目前在Kubernetes 1.7.x及以下版本可用。Pod.spec.nodeSelector通过kubernetes的label-selector机制选择节点,由调度器调度策略匹配label,而后调度Pod到目标节点,该匹配规则属于强制约束,如果node不可用,Pod会一直处于pending状态。nodeAffinity具备nodeSelector的全部功能,所以未来Kubernetes会将nodeSelector废除。

Q: 如何让多个Pod均匀部署到各个节点上?

描述: 通过前面学习我们知道在K8S中kube-scheduler组件负责Pod的调度对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的节点去运行这个 Pod,那我们如何将Pod部署到各个节点上呢?

通常两种方式,首先考虑使用工作负载反亲和特性让Pod之间尽量“互斥”,其次是可以使用daemonsets.apps资源控制器管理Pod资源,这样就能尽量均匀的分布在各节点上。

# 亲和性
affinity:
  podAntiAffinity:                                      # 工作负载反亲和
    preferredDuringSchedulingIgnoredDuringExecution:    # 尽量满足如下条件
      - podAffinityTerm:
          labelSelector:                                # 选择Pod的标签,与工作负载本身反亲和
            matchExpressions:
              - key: app
                operator: In
                values:
                  - nginx
          namespaces:
            - default
          topologyKey: kubernetes.io/hostname           # 在指定节点上起作用

正文完