亲和和反亲和 - Kubernetes(k8s)

原创
半兽人 发表于: 2019-08-08   最后更新时间: 2021-10-25 15:45:48  
{{totalSubscript}} 订阅, 5,770 游览

亲和(affinity)和 反亲和(anti-affinity)

nodeSelector是提供了一种非常简单的方法,可以将pod限制到特定标签的节点上。而亲和/反亲和将极大地扩展了你可以表达的约束类型。其关键的改进是:

  1. 不仅仅是“精确匹配”
  2. 你可以指出规则是倾向/优先,而不是硬性要求。所以如果调度程序不能满足它,那么也仍然会被调度。
  3. 你可以不依赖于node的标签,可以针对pod之间的标签进行亲和或反亲和。

亲和特征由两种类型的亲和组成,“Node亲和”和“Pod之间亲和”。

  • Node亲和性类似于现有的nodeSelector(但具有上面列出的前两个优点)
  • 而pod之间的亲和性/反亲和限制了pod标签而不是Node标签

提示:nodeSelector最终会被弃用,因为节点亲和可以表达nodeSelector可以表达的所有内容。

节点亲和(Node affinity)

节点亲和在概念上类似于nodeSelector - 根据节点上的标签约束你的pod调度到符合条件的节点上。

目前有两种类型的节点亲和

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution

你可以将它们分别视为“硬”和“软”:

  • “硬”指定了要将pod调度到节点上必须满足的规则(就像nodeSelector),
  • 而“软”指定调度程序将优先尝试调度,但不保证是首选的。

上面两种类型的名称里都包含了 “IgnoredDuringExecution” ,这意味着,如果Node上的标签更改了,不再满足pod上的亲和规则,则已经运行的pod仍将继续在节点上运行(与nodeSelector类似)。

在未来,我们计划提供requiredDuringSchedulingRequiredDuringExecution,跟requiredDuringSchedulingIgnoredDuringExecution一样,但是它会将不再满足pods节点亲和要求的pod驱逐。

举个例子

满足以下2个条件:

  • 仅在具有Intel CPU的节点上运行pod。(使用requiredDuringSchedulingIgnoredDuringExecution

  • 并且将尝试在失败区域XYZ中运行此组pod,但如果不行,则允许一部分在其他节点运行。(通过preferredDuringSchedulingIgnoredDuringExecution

PodSpecaffinity字段添加nodeAffinity来实现节点亲和,如下:

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

上面例子中,pod只能放置在节点标签key为kubernetes.io/e2e-az-name,其value为e2e-az1e2e-az2上。 另外,在满足该条件的节点中,优先选择具有其key为another-node-label-key且value为another-node-label-value的标签节点。

示例中operatorIn。也支持以下operator:

  • In
  • NotIn
  • Exists
  • DoesNotExist
  • Gt
  • Lt

您可以使用NotInDoesNotExist来实现节点反亲和,或使用污点来排除特定节点的pod。

如果同时指定nodeSelectornodeAffinity,则必须满足两者。

如果指定nodeAffinity类型关联的多个nodeSelectorTerms,则可以在满足其中一个nodeSelectorTerms的情况下将pod调度到该节点上。

如果指定与nodeSelectorTerms关联的多个matchExpressions,则只有在可以满足所有matchExpressions的情况下才能将pod调度到该节点上。

如果删除或变更节点标签,则不会删除该pod。换句话说,亲和选择仅在调度pod时起作用。

preferredDuringSchedulingIgnoredDuringExecution中的weight(权重)范围在1-100内。 对于满足所有调度要求的每个节点(资源请求,RequiredDuringScheduling亲和等),如果节点与相应的MatchExpressions匹配,则调度程序将通过迭代此字段的元素并将“weight”添加来计算总和。然后将该得分与节点的其他优先级函数的得分组合起来,总得分最高的节点是最优先选择的。

Pod的之间亲和反亲和

Pod之间亲和和反亲和,你可以通过已经在节点上运行的Pod上的标签来约束你的Pod调度到哪些节点上(而不是通过节点上的标签)。也就是说,如果X上已经在运行一个或多个满足规则Y的Pod,则意味着 “此pod就应该在 X 上运行(如果是反亲和,则不应该)”。 Y表示为LabelSelector,带有可选的关联namespace列表;

与节点不同,因为pod是namespace(其实pod上的标签是隐式的namespace),pod标签选择器必须指定选择器应该应用到哪些namespace上。从概念上,X 是一个拓扑域的概念,如它可以是noderackcloud provider zonecloud provider region等。你都可以使用topologyKey来表达它,是系统用来标识这些资源对象。

注意:Pod之间亲和和反亲和需要大量处理,这会显着减慢大型集群中的调度。我们不建议在大于几百个节点的群集中使用它们。

注意:Pod 反亲和要求节点始终要有标签,即群集中的每个节点都必须具有适当的标签匹配topologyKey。如果某些或所有节点缺少指定的topologyKey标签,则可能导致意外行为。

与节点亲和一样,目前Pod也是两种亲和和反亲和,requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution,也是

举个例子: ”将pod Apod B放到相同的区域,因为它们之间通讯频繁”,并结合反亲和”希望Pod不要调到标签有S2的区域”。

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

例子中,pod上的定义了一个亲和规则(podAffinityrequiredDuringSchedulingIgnoredDuringExecution)和一个反亲和规则(podAntiAffinitypreferredDuringSchedulingIgnoredDuringExecution)。

pod中亲和规则表示,只有当该节点上,至少有一个运行的pod(相同区域)时,并且该pod带有“security”和“S1”的标签,才能将该pod调度到这个节点上。

pod反亲和规则表示如果该节点已经在运行具有键“security”和值“S2”的标签的pod,则该pod不希望被调度到这个节点上。

pod亲和和反亲和可选有 “in”、“notin”、“exists”、“doesnotexist”。

原则上,topologyKey可以是任何合法的标签key。 但是,出于性能和安全性的原因,对TopologyKey有一些限制:

  1. 对于亲和非亲和(RequiredDuringSchedulingIgnoredDuringExecution),不允许使用空的topologyKey

  2. 对于反亲和(requiredDuringSchedulingIgnoredDuringExecution),引入了许可控制器LimitPodHardAntiAffinityTopology对 topologyKey 限制为kubernetes.io/hostname。 如果你想用于自定义,你可以修改许可控制器,或者禁用它。

  3. 对于pod反亲和(preferredDuringSchedulingIgnoredDuringExecution),空topologyKey被解释为“所有”(此处的“所有”现在仅限于kubernetes.io/hostname / failure-domain.beta.kubernetes.io/zonefailure-domain.beta.kubernetes.io/region的组合)。

  4. 除上述情况外,topologyKey可以是任何合法的label-key

除了labelSelectortopologyKey之外,你还可以通过labelSelector指定相匹配的namespace列表(这与labelSelector和topologyKey的定义属于同一级别)。如果省略或为空,则默认为显示亲和/非亲和定义的pod的命名空间。

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

更实用的场景

当Pod之间的Affinity和AntiAffinity与更高级别的类型(如ReplicaSet,StatefulSets,Deployments等)一起使用时,会更加有用。可以轻松配置一组应用程序运行到同一个节点上。

始终在同一个节点上

假设,在三节的集群中,Web应用程序需要使用缓存(例如redis)。我们希望Web应用程序尽可能与缓存放在一起。

这是一个简单的redis部署的yaml片段,三个副本和选择器标签app=store。通过PodAntiAffinity以确保调度程序在每个节点上只运行一个。

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

以下web应用的yaml片段,配置podAntiAffinitypodAffinity告知调度程序其所有副本将与具有选择器标签app=store的pod放一起。这还将确保每个Web程序的副本不在单个节点上共存。

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.12-alpine

如果我们创建上述两个部署,我们的三个节点集群应如下所示。

node-1 node-2 node-3
webserver-1 webserver-2 webserver-3
cache-1 cache-2 cache-3

如上所示,web-server的所有3个副本都按预期自动与缓存共存。

kubectl get pods -o wide

输出类似于:

NAME                           READY     STATUS    RESTARTS   AGE       IP           NODE
redis-cache-1450370735-6dzlj   1/1       Running   0          8m        10.192.4.2   kube-node-3
redis-cache-1450370735-j2j96   1/1       Running   0          8m        10.192.2.2   kube-node-1
redis-cache-1450370735-z73mh   1/1       Running   0          8m        10.192.3.1   kube-node-2
web-server-1287567482-5d4dz    1/1       Running   0          7m        10.192.2.3   kube-node-1
web-server-1287567482-6f7v5    1/1       Running   0          7m        10.192.4.3   kube-node-3
web-server-1287567482-s330j    1/1       Running   0          7m        10.192.3.2   kube-node-2

(完)

更新于 2021-10-25

查看kubernetes更多相关的文章或提一个关于kubernetes的问题,也可以与我们一起分享文章