k8s调度: 亲和与反亲和

大番茄 2020年12月25日 1,562次浏览

https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node/#%E4%BA%B2%E5%92%8C%E4%B8%8E%E5%8F%8D%E4%BA%B2%E5%92%8C

v1.19.5

亲和就是在一起运行的配置。
反亲和就是不在一起的配置。

节点亲和用来配置pod运行在哪些节点,强制或者优先。
pod亲和用来配置pod之间的亲和关系,强制或者优先。

都是通过匹配label来实现的。

关键字描述

affinity

亲和性配置主关键字。

nodeAffinity

设置节点亲和, 节点的反亲和也是这个,只不过条件设置成不匹配罢了。

podAffinity

设置pod间亲和。

podAntiAffinity

设置pod间反亲和。
与节点相比有单独的反亲和关键字,是因为节点反亲和某节点,就是亲和其它节点,没啥影响。但是pod就不行了,如一个pod不想跟nginx运行在一起, app!=nginx,但是没有其它app标签了,结果就会匹配失败。

requiredDuringSchedulingIgnoredDuringExecution

preferredDuringSchedulingIgnoredDuringExecution

第一个是强制的,如果没有符合条件的就不调度,pod就一直是Pending状态。
第二个不是强制的,没有符合条件的也会调度,还可以为匹配条件设置不同优先级,实现pod在同一个服务器,同一个机柜,同一个机房运行。

如果一起写,它们是AND的关系,只不过第二个不匹配也没关系,是一个节点偏好。

关键字有点长,主要由三部分组成:

requiredpreferred

表示调度方式,强制还是优先。

DuringScheduling

表示调度期间,没啥好理解的,跟下面的一对比就明白了。

IgnoredDuringExecution

表示忽略执行期间。 就是在pod正常运行的情况下,一些条件不在满足时,pod不会被驱逐,还在原来节点正常运行。

官方网站说以后会添加requiredDuringSchedulingRequiredDuringExecution,跟requiredDuringSchedulingIgnoredDuringExecution 差不多,只是会在条件不符合以后,pod会被驱逐。

preferred调度相关

匹配出来的节点权重分数越大,优先级越高, 越偏向于哪个节点。
因为不是强制性的,会根据多个算法,如安全性、性能相关的,综合判断出调度到哪些节点。只要实例够多,就算是完全不匹配的节点,也会运行实例。甚至同样的配置每次应用,分配的节点,节点运行的实例数量,都是不同的。

节点亲和

节点亲和性相对来说容易配置,因为功能也是比较单一。
注意里面匹配的都是节点的label。

官方的例子:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

节点亲和用到的关键字也就这么多。
例子的意思是:
节点必须满足kubernetes.io/e2e-az-name 等于 (e2e-az1e2e-az2)。 强制条件。
如果节点也能满足another-node-label-key 等于 another-node-label-value 则优先考虑。

requiredDuringSchedulingIgnoredDuringExecution

nodeSelectorTerms

是固定的。 包含一个用来匹配节点的列表。

DESCRIPTION:
     Required. A list of node selector terms. The terms are ORed.

     A null or empty node selector term matches no objects. The requirements of
     them are ANDed. The TopologySelectorTerm type implements a subset of the
     NodeSelectorTerm.

FIELDS:
   matchExpressions	<[]Object>
     A list of node selector requirements by node's labels.

   matchFields	<[]Object>
     A list of node selector requirements by node's fields.
matchExpressions

匹配label,这里也是固定的只能用这个匹配label,没有matchLabels
还有一个用来匹配字段的matchFields,用的少。

preferredDuringSchedulingIgnoredDuringExecution

包含一个列表,里面是不同权重的节点匹配规则。

FIELDS:
   preference	<Object> -required-
     A node selector term, associated with the corresponding weight.

   weight	<integer> -required-
     Weight associated with matching the corresponding nodeSelectorTerm, in the
     range 1-100.
preference

包含一个node selector的配置,与nodeSelectorTerms不同的是,没有包含一个配置列表,因为需要跟weight权重相关联。 跟权重一起相当于是一组配置。

weight

权重,数字越大权重越高。 取值范围 1-100.

调度规则

摘的官网的,就上面网页里面的。

如果你同时指定了 nodeSelector 和 nodeAffinity,两者必须都要满足,才能将 pod 调度到候选节点上。


如果你指定了多个与 nodeAffinity 类型关联的 nodeSelectorTerms,则如果其中一个 nodeSelectorTerms 满足的话,pod将可以调度到节点上。

也就是OR的关系, 例子3


如果你指定了多个与 nodeSelectorTerms 关联的 matchExpressions,则只有当所有 matchExpressions 满足的话,pod 才会可以调度到节点上。

也就是AND的关系, 例子4


如果你修改或删除了 pod 所调度到的节点的标签,pod 不会被删除。换句话说,亲和选择只在 pod 调度期间有效。


preferredDuringSchedulingIgnoredDuringExecution 中的 weight 字段值的范围是 1-100。对于每个符合所有调度要求(资源请求,RequiredDuringScheduling 亲和表达式等)的节点,调度器将遍历该字段的元素来计算总和,并且如果节点匹配对应的MatchExpressions,则添加“权重”到总和。然后将这个评分与该节点的其他优先级函数的评分进行组合。总分最高的节点是最优选的

例子2


requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution 是 AND 的关系,虽然preferred不是强制的,但逻辑关系确实是AND, 或者是相加的关系。


例子1

只匹配node1
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 50
            preference:
              matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode1']

node2与node3也有实例。

[root@k8smaster1 ~]# kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
nginx-7f69fbf687-2ktrh   1/1     Running   0          23s   10.1.0.10    k8snode1   <none>           <none>
nginx-7f69fbf687-5d5sj   1/1     Running   0          25s   10.1.0.9     k8snode1   <none>           <none>
nginx-7f69fbf687-6cg5g   1/1     Running   0          25s   10.1.0.238   k8snode2   <none>           <none>
nginx-7f69fbf687-6t6sp   1/1     Running   0          23s   10.1.0.11    k8snode1   <none>           <none>
nginx-7f69fbf687-8jx49   1/1     Running   0          25s   10.1.1.75    k8snode3   <none>           <none>
nginx-7f69fbf687-96m4z   1/1     Running   0          25s   10.1.0.8     k8snode1   <none>           <none>
nginx-7f69fbf687-hxd7z   1/1     Running   0          23s   10.1.0.12    k8snode1   <none>           <none>
nginx-7f69fbf687-nr8rf   1/1     Running   0          22s   10.1.0.13    k8snode1   <none>           <none>
nginx-7f69fbf687-rdxnb   1/1     Running   0          25s   10.1.0.7     k8snode1   <none>           <none>
nginx-7f69fbf687-rtzkd   1/1     Running   0          23s   10.1.1.76    k8snode3   <none>           <none>

例子2

第一个匹配k8snode1; 第二三个都是匹配k8snode2, 总的优先级权重比第一个高。
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 50
            preference:
              matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode1']
          - weight: 30
            preference:
              matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode2']
          - weight: 30
            preference:
              matchExpressions:
              - key: rule
                operator: In
                values: ['dmz']

node2比较多,完全不匹配的node3也有实例运行:

NAME                    READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATES
nginx-f68fb4b64-2gbbd   1/1     Running   0          3m27s   10.1.0.3     k8snode1   <none>           <none>
nginx-f68fb4b64-475fm   1/1     Running   0          3m26s   10.1.0.4     k8snode1   <none>           <none>
nginx-f68fb4b64-55zpw   1/1     Running   0          3m27s   10.1.0.231   k8snode2   <none>           <none>
nginx-f68fb4b64-8zlrx   1/1     Running   0          3m26s   10.1.0.232   k8snode2   <none>           <none>
nginx-f68fb4b64-bk956   1/1     Running   0          3m28s   10.1.0.229   k8snode2   <none>           <none>
nginx-f68fb4b64-btj9n   1/1     Running   0          3m29s   10.1.1.71    k8snode3   <none>           <none>
nginx-f68fb4b64-dcrt6   1/1     Running   0          3m29s   10.1.0.228   k8snode2   <none>           <none>
nginx-f68fb4b64-jj7ff   1/1     Running   0          3m27s   10.1.0.230   k8snode2   <none>           <none>
nginx-f68fb4b64-k69np   1/1     Running   0          3m29s   10.1.0.126   k8snode1   <none>           <none>
nginx-f68fb4b64-vgtw7   1/1     Running   0          3m28s   10.1.0.2     k8snode1   <none>           <none>

例子3

nodeSelectorTerms多个元素是OR的关系

不是说有多个nodeSelectorTerms关键字, 而是这个关键字里有多个元素。毕竟nodeSelectorTerms可是字典的key,写多了也没用,下面的会覆盖上面的。

多个matchExpressions是OR的关系。

    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode1']
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode2']
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode3']

3个节点都有实例。

NAME                    READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
nginx-fd4679bbf-2nx9z   1/1     Running   0          14s   10.1.0.249   k8snode2   <none>           <none>
nginx-fd4679bbf-44zs4   1/1     Running   0          11s   10.1.0.26    k8snode1   <none>           <none>
nginx-fd4679bbf-57vz9   1/1     Running   0          14s   10.1.0.250   k8snode2   <none>           <none>
nginx-fd4679bbf-98ssf   1/1     Running   0          14s   10.1.0.251   k8snode2   <none>           <none>
nginx-fd4679bbf-9cdtk   1/1     Running   0          12s   10.1.0.252   k8snode2   <none>           <none>
nginx-fd4679bbf-9k5d4   1/1     Running   0          11s   10.1.1.97    k8snode3   <none>           <none>
nginx-fd4679bbf-kqf9h   1/1     Running   0          12s   10.1.0.253   k8snode2   <none>           <none>
nginx-fd4679bbf-m9rnk   1/1     Running   0          14s   10.1.0.25    k8snode1   <none>           <none>
nginx-fd4679bbf-sp5lh   1/1     Running   0          14s   10.1.1.96    k8snode3   <none>           <none>
nginx-fd4679bbf-xtf2k   1/1     Running   0          10s   10.1.0.27    k8snode1   <none>           <none>

例子4

matchExpressions 关键字的元素有多个

是AND的关系, 必须同时满足才行。

    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode1']
              - key: kubernetes.io/hostname
                operator: NotIn
                values: ['k8snode2']
              - key: kubernetes.io/hostname
                operator: In
                values: ['k8snode3']

因为第三个元素不满足,所以pod无法调度。

NAME                    READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
nginx-7dd9d85d6-246wh   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-hbnjp   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-n2s2b   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-qqb2n   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-s8mfw   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-sjplc   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-swf2q   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-xn6h6   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-xxn4j   0/1     Pending   0          6s    <none>   <none>   <none>           <none>
nginx-7dd9d85d6-zd48w   0/1     Pending   0          6s    <none>   <none>   <none>           <none>

pod间亲和与反亲和

注意匹配的是pod的label。

podAffinity: 亲和性配置
podAntiAffinity: 反亲和性配置

亲和与反亲和除了podAffinitypodAntiAffinity关键字不一样,其它的关键字都一样。


Pod 间亲和与反亲和需要大量的处理,这可能会显著减慢大规模集群中的调度。我们不建议在超过数百个节点的集群中使用它们。

Pod 反亲和需要对节点进行一致的标记,即集群中的每个节点必须具有适当的标签能够匹配 topologyKey。如果某些或所有节点缺少指定的 topologyKey 标签,可能会导致意外行为。


pod亲和与反亲和的过程:

  1. 匹配目标pod label。确定目标pod所在节点。
  2. 确定目标节点指定的label的值。这个label就是topologyKey指定的。
  3. 确定拥有相同label值得节点。
  4. 根据亲和还是反亲和,调度到确定的节点还是其它节点。

官方例子:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0

上面的意思是:
亲和性设置: 与security等于S1的POD在同一区域(topology.kubernetes.io/zone)的节点,强制性的。
反亲和性设置:尽可能避开security等于S2的POD区域(topology.kubernetes.io/zone)的节点。


labelSelector: 表示开始匹配pod label,包含的是匹配的方式(matchExpressions, matchLabels)
topologyKey : 一致性参照。

例子中不包含的关键字:
matchLabels: 基于等式的Label匹配,包含一个label匹配的字典, 一般资源里用的都是这个。matchExpressions`是基于集合的。

namespaces: 与labelSelector同级。 指定namespace, 默认跟当前pod一样。pod的匹配只匹配对应namespace的。


topologyKey限制

原则上,topologyKey 可以是任何合法的标签键。然而,出于性能和安全原因,topologyKey 受到一些限制:

对于亲和与 requiredDuringSchedulingIgnoredDuringExecution 要求的 pod 反亲和,topologyKey 不允许为空。

对于 requiredDuringSchedulingIgnoredDuringExecution 要求的 pod 反亲和,准入控制器 LimitPodHardAntiAffinityTopology 被引入来限制 topologyKey 不为 kubernetes.io/hostname。如果你想使它可用于自定义拓扑结构,你必须修改准入控制器或者禁用它。

对于 preferredDuringSchedulingIgnoredDuringExecution 要求的 pod 反亲和,空的 topologyKey 被解释为“所有拓扑结构”(这里的“所有拓扑结构”限制为 kubernetes.io/hostname,topology.kubernetes.io/zone 和 topology.kubernetes.io/region 的组合)。

除上述情况外,topologyKey 可以是任何合法的标签键。

调度规则


所有与 requiredDuringSchedulingIgnoredDuringExecution 亲和与反亲和关联的 matchExpressions 必须满足,才能将 pod 调度到节点上。

总之就是没有OR关系的, 全部都是AND关系。

preferredDuringSchedulingIgnoredDuringExecution 这个里面其实也都是AND关系,只不过不是强制的,但也都是会计算。


总结就是:pod亲和与反亲和里面都是AND关系。必须满足所有条件才行。


例子1:

运行在同一节点, 当前部署的deployment。
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: ['nginx']
            topologyKey: kubernetes.io/hostname

例子2:

分散部署pod, 不能在同一节点.
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: ['nginx']
            topologyKey: kubernetes.io/hostname

例子3:

必须在同一节点,必须在有ssd盘的节点。

  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: ['nginx']
            topologyKey: kubernetes.io/hostname
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: disk
                operator: In
                values: ['ssd']

例子4:

多个labelSelector,需要都满足,AND关系。

下面的例子无法调度.

  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: ['nginx']
            topologyKey: kubernetes.io/hostname
          - labelSelector:
              matchExpressions:
              - key: app
                operator: NotIn
                values: ['nginx']
            topologyKey: kubernetes.io/hostname

例子5:

matchExpressions 多个元素也是都要满足

AND关系。下面的例子无法调度.

  template:
    metadata:
      labels:
        app: nginx
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: ['nginx']
              - key: app
                operator: NotIn
                values: ['nginx']
            topologyKey: kubernetes.io/hostname

其它


affinity下面的podAffinity, podAntiAffinity, nodeAffinity之间也是AND关系。


第二个pod亲和第一个pod, 从而跟第一个pod运行在了一个节点。 而后第一个pod删除重建了之后一般情况下还会回到那个节点。 以为有第二个pod的关系在,调度时候的分数也大。 除非是因为资源不够用了,或者是其它方面的影响。