Kubernetes / Linux Note / 运维笔记

Kubernetes 亲和性及反亲和性

Einic Yeo · 4月16日 · 2018年 ·

经常听到K8S里亲和性与反亲和性, 这其实是更复杂的调度策略, 用在需要更细力度的控制节点调度的场景.比如不希望对外的一些服务和内部的服务跑在同一个节点上了,害怕内部服务对外部的服务产生影响;但是有的时候我们的服务之间交流比较频繁,又希望能够将这两个服务的 Pod 调度到同一个的节点上.

这就需要用到 Kubernetes 里面的一个概念: 亲和性和反亲和性

还是要提出非常重要的一点,在K8S的调度流程:

  • (预选)先排除完全不符合pod运行要求的节点
  • (优先)根据一系列算法,算出node的得分,最高没有相同的,就直接选择
  • 如果有相同的话,就随机选一个

亲和性与反亲和性也遵循以上规律.

在说亲和性特点之前,版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!有必要再说一说nodeselect.

nodeSelect

大多数情况下, 如果应用没有特殊要求, 也不会去设定Nodeselect, 因为大体上容器所运行的机器都具有相同的属性, 同时, nodeselect也是一种非常常用的调度方式, 比如某些应用对磁盘IO要高要求, 切好有一批机器是SSD硬盘, 那么这个时候就可以给这些机器打上tag, 在k8s里称之为label, 这样的话,我在调度POD的时候通过这个label来进行机器的选择, 我们可以使用–show-labels也查看node节点所具有的标签.

$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready master 147d v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=master,node-role.kubernetes.io/master=
node02 Ready <none> 67d v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,course=k8s,kubernetes.io/hostname=node02
node03 Ready <none> 127d v1.10.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,jnlp=haimaxy,kubernetes.io/hostname=node03

我们再来看看一个标准的deployment的yaml文件

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: busybox-pod
  name: test-busybox
spec:
  containers:
  - command:
    - sleep
    - "3600"
    image: busybox
    imagePullPolicy: Always
    name: test-busybox
  nodeSelector:
    com: youdianzhishi

这里提定了nodeselect, 即这个deployment会调度到带有com的label上, 如果所有node里没有该属性的node, 则我们的 Pod 就会一直处于 Pending 状态, 直到有为此.

所以, nodeSelect是一个硬限制

亲和性(Affinity)

那如果我想做到某些密切调用的应用部署到同一台机器上,这样可以减少网络传输等消耗过程, 有好办法吗?

当时, 使用nodeselect肯定是可以的, 只要在deployment中使用相同的label即可. 但是如果我想更精确的控制POD的调度行为呢?

亲和性/反亲和性特性就是一种更细粒度的控制策略

亲和性又可以分为:

  • 节点亲和性(nodeAffinity): 节点亲和性主要是用来控制 pod 要部署在哪些主机上,以及不能部署在哪些主机上的
  • pod亲和性(podAffinity): pod 亲和性主要解决 pod 可以和哪些 pod 部署在同一个拓扑域中的问题

同时, 亲和性在执行程度上,又可分为:

  • 软策略(preferredDuringSchedulingIgnoredDuringExecution): 如果你没有满足调度要求的节点的话,pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓了的策略
  • 硬策略(requiredDuringSchedulingIgnoredDuringExecution): 比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不干的策略
  • RequiredDuringSchedulingRequiredDuringExecution: 在调度期间要求满足亲和性或者反亲和性规则,如果不能满足规则,则POD不能被调度到对应的主机上. 在之后的运行过程中,如果因为某些原因(比如修改label)导致规则不版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!能满足,系统会尝试把POD从主机上删除

节点亲和性

如上所说, 节点亲和性限定的是pod需要调度到什么资源上, 是node与pod的调度关系,同时它支持一些逻辑运算, 比如in,not in之类的.

比如说,现在我们用一个 Deployment 来管理3个 pod 副本,现在我们来控制下这些 pod 的调度

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: NotIn
                values:
                - node03
          preferredDuringSchedulingIgnoredDuringExecution:  # 软策略
          - weight: 1
            preference:
              matchExpressions:
              - key: com
                operator: In
                values:
                - youdianzhishi

上面的deployment里同时指定了硬策略与软策略,满足的需求为:该节点的label不能是(硬性条件)kubernetes.io/hostname=node03, 除此之外, 如果存在com=youdianzhishi的节点,则尽可能调度(也不一定能成功).

$ kubectl get nodes --show-labels
NAME      STATUS    ROLES     AGE       VERSION   LABELS
master    Ready     master    154d      v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=master,node-role.kubernetes.io/master=
node02    Ready     <none>    74d       v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,com=youdianzhishi,course=k8s,kubernetes.io/hostname=node02
node03    Ready     <none>    134d      v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,jnlp=haimaxy,kubernetes.io/hostname=node03

调度情况如下:

$ kubectl create -f node-affinity-demo.yaml
deployment.apps "affinity" created
$ kubectl get pods -l app=affinity -o wide
NAME                        READY     STATUS    RESTARTS   AGE       IP             NODE
affinity-7b4c946854-5gfln   1/1       Running   0          47s       10.244.4.214   node02
affinity-7b4c946854-l8b47   1/1       Running   0          47s       10.244.4.215   node02
affinity-7b4c946854-r86p5   1/1       Running   0          47s       10.244.4.213   node02

从结果可以看出 pod 都被部署到了 node02,其他节点上没有部署 pod,这里的匹配逻辑是 label 的值在某个列表中,现在Kubernetes提供的操作符有下面的几种:

  • In: label 的值在某个列表中
  • NotIn: label 的值不在某个列表中
  • Gt: label 的值大于某个值
  • Lt: label 的值小于某个值
  • Exists: 某个 label 存在
  • 版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!
  • DoesNotExist: 某个 label 不存在

同时对于nodeSelectorTerms与matchExpressions

  • 如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件就可以了
  • 如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 POD

node的反亲和性则只要使用上面列出的逻辑运算就可以很好的先排除一些不符合条件的节点.

POD亲和性

pod 亲和性主要解决 pod 可以和哪些 pod 部署在同一个拓扑域中的问题(其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等),而 pod 反亲和性主要是解决 pod 不能和哪些 pod 部署在同一个拓扑域中的问题,它们都是处理的pod 与 pod 之间的调度,比如一个 pod 在一个节点上了,那么我这个也得在这个节点,或者你这个 pod 在节点上了,那么我就不想和你待在同一个节点上

$ kubectl get nodes --show-labels
NAME      STATUS    ROLES     AGE       VERSION   LABELS
master    Ready     master    154d      v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=master,node-role.kubernetes.io/master=
node02    Ready     <none>    74d       v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,com=youdianzhishi,course=k8s,kubernetes.io/hostname=node02
node03    Ready     <none>    134d      v1.10.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,jnlp=haimaxy,kubernetes.io/hostname=node03
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - busybox-pod
            topologyKey: kubernetes.io/hostname

上面的deployment里指定了硬策略,topologykey表示某个拓扑域, 节点满足的需求为必须包含app=busybox-pod.

目前只有node02上面有app=busybox-pod的label,所以理想情况是3个pod都 应该在node02上才符合预期

$ kubectl get pods -o wide -l app=busybox-pod
NAME           READY     STATUS    RESTARTS   AGE       IP             NODE
test-busybox   1/1       Running   164        7d        10.244.4.205   node02

查看pod调度情况

$ kubectl get pods -o wide -l app=affinity
NAME                        READY     STATUS    RESTARTS   AGE       IP             NODE
affinity-564f9d7db9-lzzvq   1/1       Running   0          3m        10.244.4.216   node02
affinity-564f9d7db9-p79cq   1/1       Running   0          3m        10.244.4.217   node02
affinity-564f9d7db9-spfzs   1/1       Running   0          3m        10.244.4.218   node02

我们尝试把node02上的app=busybox-pod这个pod删掉,然后再进行调度

$ kubectl delete -f node-selector-demo.yaml
pod "test-busybox" deleted
$ kubectl delete -f pod-affinity-demo.yaml
deployment.apps "affinity" deleted
$ kubectl create -f pod-affinity-demo.yaml
deployment.apps "affinity" created
$ kubectl get pods -o wide -l app=affinity
NAME                        READY     STATUS    RESTARTS   AGE       IP        NODE
affinity-564f9d7db9-fbc8w   0/1       Pending   0          2m        <none>    <none>
affinity-564f9d7db9-n8gcf   0/1       Pending   0          2m        <none>    <none>
affinity-564f9d7db9-qc7x6   0/1       Pending   0          2m        <none>    <none>

会发现3个pod都是Pending状态, 原因就是没有任何一个no版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!de符合app=bus版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!ybox-pod这个条件, pod则会无限期pending.

反亲和性(antiAffinity)

当时node是没有nodeaAntiAffinity的, 对于node来说, 反亲和就是使用not 取反就行.

podAntiAffinity则是说如果一个节点上运行了某个 pod,那么我们的 pod 则希望被调度到其他节点上去.

比如还是上面的例子, 我修改一下

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - busybox-pod
            topologyKey: kubernetes.io/hostname

只是把podAffinity变成 了podAntiAffinity,则达到的效果为如果某个节点上有app=busybox-pod,则不会调度到该node上.

$ kubectl create -f pod-antiaffinity-demo.yaml
deployment.apps "affinity" created
$ kubectl get pods -o wide
NAME                                      READY     STATUS      RESTARTS   AGE       IP             NODE
affinity-bcbd8854f-br8z8                  1/1       Running     0          5s        10.244.4.222   node02
affinity-bcbd8854f-cdffh                  1/1       Running     0          5s        10.244.4.223   node02
affinity-bcbd8854f-htb52                  1/1       Running     0          5s        10.244.4.224   node02
test-busybox                              1/1       Running     0          23m       10.244.2.10    node03

当然这里也可以In换成NotIn.

上边的例子中可以通过修改topologyKey来限制拓扑域的范围,实现把相关服务部署在不同的容灾域等其它功能

总结

最后总结一下三种亲和性和反亲和性策略的比较如下表所示:

支持的操作符:

策略名称匹配目标支持的操作符支持拓扑域 设计目标
nodeAffinity主机标签In,NotIn,Exists,DoesNotExist,Gt,Lt不支持决定Pod可以部署在哪些主机上
podAffinityPod标签In,NotIn,Exists,DoesNotExist支持决定Pod可以和哪些Pod部署在同一拓扑域
PodAntiAffinityPod标签In,NotIn,Exists,DoesNotExist支持决定Pod不可以和哪些Pod部署在同一拓扑域

场景:

**nodeAffinity使用场景 **:

  • 将S1服务的所有Pod部署到指定的符合标签规则的主机上
  • 将S1服务的所有Pod部署到除部分主机外的其他主机上

**podAffinity使用场景 **:

  • 将某一特定服务的pod部署在同一拓扑域中,不用指定具体的拓扑域
  • 如果S1服务使用S2服务,为了减少它们之间的网络延迟(或其它原因),把S1服务的POD和S2服务的pod部署在同一拓扑域中

podAntiAffinity使用场景:

  • 将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性
  • 给POD对于一个节点的独占访问权限来保证资源隔离,保证不会有其它pod来分享节点资源
  • 把可能会相互影响的服务的POD分散在不同的主机上
0 条回应