文章目录

  • 集群调度
  • 调度过程
  • 自定义调度器
  • 固定节点调度
  • 通过主机名固定
  • 通过标签固定
  • 调度亲和性
  • node亲和性
  • 硬策略
  • 软策略
  • 硬策略和软策略
  • Pod亲和性和Pod反亲和性
  • podAffinity与podAntiAffinity
  • 资源清单模板
  • 污点(Taint)和容忍(Toleration)
  • 污点
  • 容忍


集群调度

Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:

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

Scheduler 是作为单独的程序运行的,启动之后会一直持续连接 API Server,获取 PodSpec.NodeName(节点名)为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上

调度过程

调度分为几个部分:

  • 首先是过滤掉不满足条件的节点,这个过程称为 predicate (预选);
  • 然后对通过的节点按照优先级排序,这个是 priority (优选);
  • 最后从中选择优先级最高的节点。

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

Predicate 会依次执行选择算法:

var (
   predicatesOrdering = []string{
       CheckNodeConditionPred,     
       CheckNodeUnschedulablePred,
       GeneralPred, 
       HostNamePred, 
       PodFitsHostPortsPred,
       MatchNodeSelectorPred, 
       PodFitsResourcesPred, 
       NoDiskConflictPred,
       PodToleratesNodeTaintsPred, 
       PodToleratesNodeNoExecuteTaintsPred, 
       CheckNodeLabelPresencePred,
       CheckServiceAffinityPred, 
       MaxEBSVolumeCountPred, 
       MaxGCEPDVolumeCountPred, 
       MaxCSIVolumeCountPred,
       MaxAzureDiskVolumeCountPred, 
       CheckVolumeBindingPred, 
       NoVolumeZoneConflictPred,
       CheckNodeMemoryPressurePred, 
       CheckNodePIDPressurePred, 
       CheckNodeDiskPressurePred, 
       MatchInterPodAffinityPred}
)

比如:

  • HostName :如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配;
  • PodFitsHostPorts :节点上已经使用的 port 是否和 pod 申请的 port 冲突;
  • MatchNodeSelector :过滤掉和 pod 指定的 label 不匹配的节点;
  • PodFitsResources :节点上剩余的资源是否大于 pod 请求的资源;
  • NoDiskConflict :已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读;
  • PodToleratesNodeTaintsPred:检测pod的容忍度能否容忍这个node 上的污点;
  • CheckServiceAffinit:检查亲和度。

这个顺序是可以被配置文件覆盖的,用户可以指定类似于这样的配置文件修改预选算法执行顺序:

{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
    {"name" : "PodFitsHostPorts", "order": 2},
    {"name" : "PodFitsResources", "order": 3},
    {"name" : "NoDiskConflict", "order": 5},
    {"name" : "PodToleratesNodeTaints", "order": 4},
    {"name" : "MatchNodeSelector", "order": 6},
    {"name" : "PodFitsHost", "order": 1}
    ],
"priorities" : [
    {"name" : "LeastRequestedPriority", "weight" : 1},
    {"name" : "BalancedResourceAllocation", "weight" : 1},
    {"name" : "ServiceSpreadingPriority", "weight" : 1},
    {"name" : "EqualPriority", "weight" : 1}
    ],
"hardPodAffinitySymmetricWeight" : 10
}

如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程: 按照优先级大小对节点排序。

优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:

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

通过算法对所有的优先级项目和权重进行计算,得出最终的结果。

自定义调度器

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

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

固定节点调度

通过主机名固定

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

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 7	# 副本数
  template:
    metadata:
      labels:
        app: myweb
    spec:
      nodeName: k8s-node01		# 指定 Node 节点的名字
      containers:
        - name: myweb
          image: wangyanglinux/myapp:v1
          ports:
            - containerPort: 80
通过标签固定

Pod.spec.nodeSelector:通过 kuberneteslabel-selector 机制选择节点,由调度器调度策略匹配 label,而后调度 Pod 到目标节点,该匹配规则属于强制约束。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 5
  template:
    metadata:
      labels:
        app: myweb
    spec:
      nodeSelector:		# 节点标签选择
        disk: ssd		# 只有节点存在指定的标签,才能运行该 pod
      containers:
        - name: myweb
          image: wangyanglinux/myapp:v1
          ports:
            - containerPort: 80

调度亲和性

亲和性调度是指通过配置的形式,实现优先选择满足条件的Node(高亲和度)进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。

node亲和性

节点亲和性就是说,假如有 k8s 集群有两个节点A、B,某个 Pod 调度时,更倾向于去 A 节点;这时节点亲和性也有两种级别,一种是 Pod 一定要去 A 节点(硬策略),另一种是 Pod 想去 A 节点,不去也行(软策略)。

节点亲和通过 pod.spec.nodeAffinity 来描述:

  • requiredDuringSchedulingIgnoredDuringExecution:硬策略,必须满足,不能满足则 Pod 会一直处于 Pending 状态;
  • preferredDuringSchedulingIgnoredDuringExecution:软策略,可以不满足,但尽量满足。

主要是通过节点标签来完成策略选择

  1. 查看所有节点标签:
# 查看所有节点的标签
kubectl get node --show-labels

# 查看指定节点的标签
# kubectl get node 节点名 --show-labels
kubectl get node k8s-node01 --show-labels
  1. 添加节点标签:
# kubectl label node 节点名 标签键值对
kubectl label node k8s-node01 app=node01
  1. 修改节点标签:
# kubectl label node 节点名 新的标签键值对 --overwrite=true
kubectl label node k8s-node01 app=app01 --overwrite=true
  1. 删除节点标签:
# kubectl label node {节点名} {标签键}-
kubectl label node k8s-node01 app-
硬策略
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
    - name: with-node-affinity
      image: wangyanglinux/myapp:v1
  affinity:		# 亲和性
    nodeAffinity:		# Node 亲和性
      requiredDuringSchedulingIgnoredDuringExecution:	# 硬策略
        nodeSelectorTerms:		# Node 选择方案
          - matchExpressions:
              # 优先选择:节点标签键 kubernetes.io/hostname 对应的值不在 values列表["k8s-node02"] 中的节点
              # kubectl get node --show-labels 查看所有节点标签
              # kubernetes.io/hostname 标签存储的是节点 hostname
              - key: kubernetes.io/hostname		# 键名,Node 节点的标签
                operator: NotIn					# 匹配条件
                values:							# 值
                  - k8s-node02

说明:

  • 如果 nodeSelectorTerms 下面有多个选项的话,满足任何一个条件就可以了
  • 如果 matchExpressions 有多个选项的话,则必须同时满足这些条件才能正常调度 Pod
  • 键值运算关系:
  • In:label 的值在某个列表中;
  • NotIn:label 的值不在某个列表中;
  • Gt:label 的值大于某个值;
  • Lt:label 的值小于某个值;
  • Exists:某个 label 存在;
  • DoesNotExist:某个 label 不存在
软策略
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
    - name: with-node-affinity
      image: wangyanglinux/myapp:v1
  affinity:		# 亲和性
    nodeAffinity:		# Node 亲和性
      preferredDuringSchedulingIgnoredDuringExecution:	# 软策略
        - weight: 1		# 权重,数值越大,越亲和
          preference:
            matchExpressions:
              - key: kubernetes.io/hostname		# 键名,Node 节点的标签
                operator: In					# 匹配条件
                values:							# 值
                  - k8s-node03
硬策略和软策略

硬策略与软策略可以同时指定,此时要先满足硬策略,在满足硬策略的基础上,再尽量满足软策略。

apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
    - name: with-node-affinity
      image: wangyanglinux/myapp:v1
  affinity:
    nodeAffinity:   # 节点亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
        nodeSelectorTerms:    # Node 选择方案
          - matchExpressions:
              - key: kubernetes.io/hostname   # 键名,Node 节点的标签
                operator: In         # 匹配条件
                values:       
                  - k8s-node01      # 值
                  - k8s-node02
      preferredDuringSchedulingIgnoredDuringExecution:  # 软策略
        - weight: 1   # 权重,数值越大,越亲和
          preference:
            matchExpressions:
              - key: source
                operator: In
                values:
                  - qikqiak
Pod亲和性和Pod反亲和性

Pod 亲和性就是说,假如有 k8s 集群有两个节点A、B,有两个 Pod 分别是 M 和 N,当 M 接受调度时,更倾向于去 Pod N 所在的节点;这时调度也存在两种情况,一种是 Pod M 一定要去 Pod N 所在的节点(硬策略),另一种是 Pod M 想去 Pod N 所在的节点,不去也行(软策略)。

Pod 亲和通过 pod.spec.affinity.podAffinity/podAntiAffinity 来描述:

  • requiredDuringSchedulingIgnoredDuringExecution:硬策略,必须满足,不能满足则 Pod 会一直处于 Pending 状态
  • preferredDuringSchedulingIgnoredDuringExecution:软策略,可以不满足,但尽量满足。

主要是通过Pod标签来完成策略选择

操作和节点标签操作相同,只要把命令中的 node 换成 pod 即可。

  1. 查看Pod标签。
# 查看所有 Pod 的标签
kubectl get pod --show-labels

# 查看指定 Pod 的标签
# kubectl get pod pod名 --show-labels
kubectl get pod pod-1 --show-labels
  1. 添加Pod标签。
# kubectl label pod pod名 标签键值对
kubectl label pod pod-1 app=node01
  1. 修改Pod标签。
# kubectl label pod pod名 新的标签键值对 --overwrite=true
kubectl label pod pod-1 app=app01 --overwrite=true
  1. 删除Pod标签。
# kubectl label pod {pod名} {标签键}-
kubectl label pod pod-1 app-
podAffinity与podAntiAffinity

Pod的亲和性有两种选择:podAffinity与podAntiAffinity。它们的区别:

拓扑域:当多个节点拥有相同的标签时,如果通过这个标签来判断,则这几个节点就处于同一拓扑域内。

调度策略

匹配标签

操作符

拓扑域支持

调度目标

nodeAffinity

主机

In, NotIn, Exists,DoesNotExist, Gt, Lt


指定主机

podAffinity

Pod

In, NotIn, Exists,DoesNotExist


Pod 与指定 Pod 在同一拓扑域

podAnitAffinity

Pod

In, NotIn, Exists,DoesNotExist


Pod 与指定 Pod 不在同一拓扑域

其实podAffinity与podAntiAffinity就是一个相反的意思。

资源清单模板
apiVersion: v1
kind: Pod
metadata:
  name: affinity-pod
  labels:
    app: pod-affinity-pod
spec:
  containers:
    - name: pod-affinity
      image: wangyanglinux/myapp:v1
  affinity:
    podAffinity:	# pod 亲和性,两个 pod 运行在同一拓扑域内
      requiredDuringSchedulingIgnoredDuringExecution:	# 硬策略
        # 一定要和标签 app=pod-1 的 Pod 运行在同一拓扑域内
        - labelSelector:	# 标签选择
            matchExpressions:
              - key: app		# pod 的标签
                operator: In	# 匹配策略
                values:			# 标签值
                  - pod-1
          # 指定通过哪个节点标签判断 pod 是否在同一个拓扑域上(多个节点拥有相同的标签,则这几个节点就处于同一拓扑域)
          topologyKey: kubernetes.io/hostname
    podAntiAffinity:	# Pod 亲和性,两个 pod 不运行在同一拓扑域内
      preferredDuringSchedulingIgnoredDuringExecution:	# 软策略
        # 希望不要和标签 app=pod-2 的 Pod 运行在同一拓扑域内
        - weight: 1		# 权重,数值越大,越亲和
          podAffinityTerm:
            labelSelector:
              matchExpressions:
                - key: app			# pod 的标签
                  operator: In		# 匹配策略
                  values:			# 标签值
                    - pod-2
            # 指定通过哪个节点标签判断 pod 是否在同一个拓扑域上(多个节点拥有相同的标签,则这几个节点就处于同一拓扑域)
            topologyKey: kubernetes.io/hostname

污点(Taint)和容忍(Toleration)

节点亲和性,是 pod 的一种属性(偏好或硬性要求),它使 pod 被吸引到一类特定的节点。Taint 则相反,它使节点能够 排斥 一类特定的 pod。

TaintToleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod 上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上。

如果某个 Node 有个污点,Pod 不能容忍,则 Pod 一定不会运行在这个 Node 上;如果 Pod 能容忍这个污点,Pod 也不一定会运行在这个节点上。

污点

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

每个污点的组成如下:

key=value:effect
  • 每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空;
  • effect 描述污点的作用。当前 taint effect 支持如下三个选项:
  • NoSchedule :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上;
  • PreferNoSchedule :表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上;
  • NoExecute :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去

k8s 主节点中默认存在一个污点:node-role.kubernetes.io/master:NoSchedule,所以 Pod 不会被分配在主节点上。

污点的创建、查看、去除:

# 设置污点
kubectl taint nodes k8s-node01 key1=value1:NoSchedule

# 节点说明中,查找 Taints 字段
kubectl describe node k8s-node01

# 去除污点
kubectl taint nodes k8s-node01 key1:NoSchedule-
容忍

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

通过 pod.spec.tolerations 来配置 Pod 的容忍:

apiVersion: v1
kind: Pod
metadata:
  name: pod-1
  labels:
    app: pod-1
spec:
  containers:
    - name: with-node-affinity
      image: wangyanglinux/myapp:v1
  tolerations:
    - key: "key1"
      operator: "Equal"
      value: "value1"
      effect: "NoExecute"
      tolerationSeconds: 3600		# 当 Pod 被驱逐时可以在节点上继续保留运行的时间
    - key: "key1"
      operator: "Equal"
      value: "value1"
      effect: "NoExecute"
    - key: "key2"
      operator: "Exists"
      effect: "NoSchedule"
  • 容忍的 key, vaule, effect 要与 Node 上设置的 taint 保持一致;
  • operator 的值为 Exists 将会忽略 value 值;
  • 当 effect 为 NoExecute 时,可以通过设置 tolerationSeconds 来指定 Pod 被驱逐时可以在 Node 上继续保留运行的时间;
  • 当不指定 key 值时,表示容忍所有的污点 key:
tolerations:
  - operator: "Exists"
  • 当不指定 effect 值时,表示容忍所有的污点的作用:
tolerations:
  - key: "key"
    operator: "Exists"
  • 有多个 Master 存在时,防止资源浪费,可以如下设置:
kubectl taint nodes MasterNode-Name node-role.kubernetes.io/master=:PreferNoSchedule