Kubernetes(k8s)亲和性调度

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes(k8s)亲和性调度相关的知识,希望对你有一定的参考价值。

参考技术A 一般情况下我们部署的 Pod 是通过集群的自动调度策略来选择节点的,默认情况下调度器考虑的是资源足够,并且负载尽量平均,但是有的时候我们需要能够更加细粒度的去控制 Pod 的调度,比如我们内部的一些服务 gitlab 之类的也是跑在Kubernetes集群上的,我们就不希望对外的一些服务和内部的服务跑在同一个节点上了,担心内部服务对外部的服务产生影响;但是有的时候我们的服务之间交流比较频繁,又希望能够将这两个服务的 Pod 调度到同一个的节点上。这就需要用到 Kubernetes 里面的一个概念:亲和性和反亲和性。

亲和性有分成节点亲和性(nodeAffinity)和 Pod 亲和性(podAffinity)。

在了解亲和性之前,我们先来了解一个非常常用的调度方式:nodeSelector。我们知道label是kubernetes中一个非常重要的概念,用户可以非常灵活的利用 label 来管理集群中的资源,比如最常见的一个就是 service 通过匹配 label 去匹配 Pod 资源,而 Pod 的调度也可以根据节点的 label 来进行调度。

我们可以通过下面的命令查看我们的 node 的 label:

现在我们先给节点node02增加一个com=youdianzhishi的标签,命令如下:

我们可以通过上面的--show-labels参数可以查看上述标签是否生效。当 node 被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在 Pod 的spec字段中添加nodeSelector字段,里面是我们需要被调度的节点的 label 即可。比如,下面的 Pod 我们要强制调度到 node02 这个节点上去,我们就可以使用 nodeSelector 来表示了:(node-selector-demo.yaml)

然后我们可以通过 describe 命令查看调度结果:

可以看到 Events 下面的信息,我们的 Pod 通过默认的 default-scheduler 调度器被绑定到了node02节点。不过需要注意的是nodeSelector属于强制性的,如果我们的目标节点没有可用的资源,我们的 Pod 就会一直处于 Pending 状态,这就是nodeSelector的用法。

通过上面的例子我们可以感受到nodeSelector的方式比较直观,但是还够灵活,控制粒度偏大,接下来我们再和大家了解下更加灵活的方式:节点亲和性(nodeAffinity)。

之前了解了 kubernetes 调度器的一个调度流程,我们知道默认的调度器在使用的时候,经过了 predicates 和 priorities 两个阶段,但是在实际的生产环境中,往往我们需要根据自己的一些实际需求来控制 pod 的调度,这就需要用到 nodeAffinity(节点亲和性)、podAffinity(pod 亲和性) 以及 podAntiAffinity(pod 反亲和性)。

亲和性调度可以分成软策略和硬策略两种方式:

软策略就是如果你没有满足调度要求的节点的话,pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓了的策略
硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不干的策略。
对于亲和性和反亲和性都有这两种规则可以设置: preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution,前面的就是软策略,后面的就是硬策略。

nodeAffinity

节点亲和性主要是用来控制 pod 要部署在哪些主机上,以及不能部署在哪些主机上的。它可以进行一些简单的逻辑组合了,不只是简单的相等匹配。

比如现在我们用一个 Deployment 来管理3个 pod 副本,现在我们来控制下这些 pod 的调度,如下例子:(node-affinity-demo.yaml)

上面这个 pod 首先是要求不能运行在 node03 这个节点上,如果有个节点满足com=youdianzhishi的话就优先调度到这个节点上。

下面是我们测试的节点列表信息:

可以看到 node02 节点有com=youdianzhishi这样的 label,按要求会优先调度到这个节点来的,现在我们来创建这个 pod,然后使用descirbe命令查看具体的调度情况是否满足我们的要求。

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

In:label 的值在某个列表中
NotIn:label 的值不在某个列表中
Gt:label 的值大于某个值
Lt:label 的值小于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在
如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件就可以了;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 POD。

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

由于我们这里只有一个集群,并没有区域或者机房的概念,所以我们这里直接使用主机名来作为拓扑域,把 pod 创建在同一个主机上面。

同样,还是针对上面的资源对象,我们来测试下 pod 的亲和性:(pod-affinity-demo.yaml)

上面这个例子中的 pod 需要调度到某个指定的主机上,至少有一个节点上运行了这样的 pod:这个 pod 有一个app=busybox-pod的 label。

我们查看有标签app=busybox-pod的 pod 列表:

我们看到这个 pod 运行在了 node02 的节点上面,所以按照上面的亲和性来说,上面我们部署的3个 pod 副本也应该运行在 node02 节点上:

如果我们把上面的 test-busybox 和 affinity 这个 Deployment 都删除,然后重新创建 affinity 这个资源,看看能不能正常调度呢:

我们可以看到处于Pending状态了,这是因为现在没有一个节点上面拥有busybox-pod这个 label 的 pod,而上面我们的调度使用的是硬策略,所以就没办法进行调度了,大家可以去尝试下重新将 test-busybox 这个 pod 调度到 node03 这个节点上,看看上面的 affinity 的3个副本会不会也被调度到 node03 这个节点上去?

我们这个地方使用的是kubernetes.io/hostname这个拓扑域,意思就是我们当前调度的 pod 要和目标的 pod 处于同一个主机上面,因为要处于同一个拓扑域下面,为了说明这个问题,我们把拓扑域改成beta.kubernetes.io/os,同样的我们当前调度的 pod 要和目标的 pod 处于同一个拓扑域中,目标的 pod 是不是拥有beta.kubernetes.io/os=linux的标签,而我们这里3个节点都有这样的标签,这也就意味着我们3个节点都在同一个拓扑域中,所以我们这里的 pod 可能会被调度到任何一个节点:

这就是 pod 亲和性的用法,而 pod 反亲和性则是反着来的,比如一个节点上运行了某个 pod,那么我们的 pod 则希望被调度到其他节点上去,同样我们把上面的 podAffinity 直接改成 podAntiAffinity,(pod-antiaffinity-demo.yaml)

这里的意思就是如果一个节点上面有一个app=busybox-pod这样的 pod 的话,那么我们的 pod 就别调度到这个节点上面来,上面我们把app=busybox-pod这个 pod 固定到了 node03 这个节点上面来,所以正常来说我们这里的 pod 不会出现在 node03 节点上:

这就是 pod 反亲和性的用法。

运维实战 容器部分 Kubernetes调度

运维实战 容器部分 Kubernetes调度

需求简介

  • 生产环境有对Pod调度规划的真实需求, 比如端口冲突的业务不能部署在同一物理机如何实现, 比如某些业务尽可能部署在同一物理机如何实现
  • 调度器通过K8Swatch机制来发现集群中新创建且尚未被调度到 Node 上的 Pod. 调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行.
  • 集群默认使用kube-scheduler作为调度器, 可以自行编写调度组件替换原有的kube-scheduler
  • 也可以在资源清单中指定使用的调度策略
  • 做调度分配时需要考虑许多要素, 如: 单独和整体的资源请求, 硬件/软件/策略限制, 亲和以及反亲和要求, 数据局限性, 负载间的干扰等等

具体调度方法

nodeName

  • nodeName是最简单的节点约束方法, 通常来说不推荐使用
  • 通过在资源清单中指定Node, Pod会被自动部署到该Node上, 但如果Node不存在, 集群也会尝试这么做, 因而导致Pending

局限性

  • 指定的节点不存在时不会只能解决

  • 节点存在, 但不满足物理需求(如资源/空间不足), 调度也会失败

  • 在生产环境中Node的名称并不总是稳定/可预测的

  • nodeName.yaml文件内容

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: server3
  • 测试结果
[root@Server2 Schedule]# kubectl apply -f nodeName.yaml 
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          10s   10.244.141.226   server3   <none>           <none>
  • 如果集群中没有符合条件的Node, 比如上面的server3改成server10
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: server10
  • 则会出现调度失败, 并不会只能解决
  • 因为集群中不存在server10而你又要求调度到server10上, 就会一直Pending
[root@Server2 Schedule]# kubectl delete Pod nginx 
Pod "nginx" deleted
[root@Server2 Schedule]# kubectl get Pod -o wide
No resources found in default namespace.
[root@Server2 Schedule]# kubectl apply -f nodeName.yaml 
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP       NODE       NOMINATED NODE   READINESS GATES
nginx   0/1     Pending   0          1s    <none>   server10   <none>           <none>

nodeSelector

想必你也注意到了, nodeName并不能指定一类机器, 而只能指定固定机器

如果我们想定义一类具有同样特点的机器, 如这些机器都使用固态硬盘, 可以通过对节点打tag的方式来实现

与之对应, nodeSelector是通过tag进行调度筛选的机制, 也是节点选择约束的最简单推荐形式

  • nodeSelector.yaml文件内容
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd
  • 测试流程
[root@Server2 Schedule]# kubectl apply -f nodeSelector.yaml 
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
nginx   0/1     Pending   0          9s    <none>   <none>   <none>           <none>
  • 此时, 所有结点上都没有disktype: ssd标签所以Pending
  • server3附加disktype=ssd的标签后, 调度正确实现
[root@Server2 Schedule]# kubectl label nodes server3 disktype=ssd
node/server3 labeled
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          67s   10.244.141.227   server3   <none>           <none>

亲和与反亲和

  • 实现了 “硬要求” 和 “软需求” 并存, 如"必须满足A, 尽量满足B(B不满足也可以)"这类需求, 这极大扩展了表达约束的类型
  • 亲和和反亲和不止支持节点标签, 也支持Pod标签, 如B服务只能放在A服务不存在的Node上这种需求

节点亲和

参数含义
requiredDuringSchedulingIgnoredDuringExecution必须满足
preferredDuringSchedulingIgnoredDuringExecution倾向满足

虽然这个参数很长, 但实际上是由两部分构成的, 前半部分表示软/硬

后半部分的IgnoreDuringExecution表示如果在Pod运行期间Node的标签发生变化, 导致亲和性策略不能满足, 则继续运行当前的Pod, 而不直接驱离

nodeaffinity还支持多种规则匹配条件的配置

参数含义
Inlabel的值在列表内
NotInlabel的值不在列表内
Gtlabel的值大于设置的值, 不支持Pod亲和性
Ltlabel的值小于设置的值, 不支持Pod亲和性
Exists设置的label存在
DoesNotExist设置的label不存在
  • 测试用node-affinity.yaml文件内容
apiVersion: v1
kind: Pod
metadata:
  name: node-affinity
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
           nodeSelectorTerms:
           - matchExpressions:
             - key: disktype
               operator: In
               values:
                 - ssd
  • 这里的需求是 满足disktype=ssd的节点, key + operator + values共同完成了这一指名

  • 测试流程, 可以看到测试用的node-affinity被调度到了server3

[root@Server2 Schedule]# kubectl apply -f nodeAffinity_required.yaml 
Pod/node-affinity created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
node-affinity   1/1     Running   0          5s    10.244.141.228   server3   <none>           <none>

倾向满足的案例

apiVersion: v1
kind: Pod
metadata:
  name: node-affinity
spec:
  containers:
  - name: nginx
    image: nginx
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
           nodeSelectorTerms:
           - matchExpressions:
             - key: kubernetes.io/hostname
               operator: NotIn
               values:
               - server3
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd     

在这个资源清单中存在两条要求:

  • 必须满足 不能调度到server3
  • 倾向满足 Node包含disktype=ssdtag

这样, 即使server4上并没有这个tag, 也可以调度到server4上了

[root@Server2 Schedule]# kubectl apply -f nodeAffinity_preferred.yaml Pod/node-affinity created[root@Server2 Schedule]# kubectl get Pod -o wideNAME            READY   STATUS    RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATESnode-affinity   1/1     Running   0          27s   10.244.22.24   server4   <none>           <none>

Pod亲和

  • PodAffinity主要解决Pod可以和哪些Pod部署在同一个拓扑域中的问题(拓扑域用主机标签实现, 可以是单个主机, 也可以是多个主机组成的cluster, zone等.) 与之效能相反的PodAntiAffinity主要解决Pod不能和哪些Pod部署在同一个拓扑域中的问题. 它们处理的是Kubernetes集群内部PodPod之间的关系.
  • Pod 间亲和与反亲和在与更高级别的集合(例如ReplicaSets,StatefulSets, Deployments 等)一起使用时, 它们可能更加有用. 可以轻松配置一组应位于相同定义拓扑(例如, 节点)中的工作负载.
  • Pod 间亲和与反亲和需要大量的处理, 这可能会显著减慢大规模集群中的调度. \\

测试样例

  • 创建一个自主的包含nginx服务的Pod
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  • 创建一个包含Pod亲和需求的资源清单
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  containers:
  - name: mysql
    image: mysql
    env:
     - name: "MYSQL_ROOT_PASSWORD"
       value: "westos"
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - nginx
        topologyKey: kubernetes.io/hostname
[root@Server2 Schedule]# kubectl apply -f nodeName.yaml
Pod/nginx created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATESnginx   1/1     Running   0          11s   10.244.141.230   server3   <none>           <none>

[root@Server2 Schedule]# kubectl apply -f PodAffinity.yaml
Pod/demo created
[root@Server2 Schedule]# kubectl get Pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATESdemo    1/1     Running   0          6s    10.244.22.25     server4   <none>           <none> nginx   
 1/1     Running   0          54s   10.244.141.230   server3   <none>           <none>

可以看到, 两个Pod分别被调度到了2个不同的Node

Taints

NodeAffinity节点亲和性的目的是让Pod可以按照我们的需求调度到一个或一类Node上;

Taints与之相反, 它能让Node拒绝运行Pod, 甚至驱离已经在该Node上运行的Pod

Taints(污点)是Node的一个属性, 设置了Taints后, K8S集群就不会把Pod调度到这个Node上了

如果想让Pod调度到有Taints的节点, 就需要给Pod设置Tolerations(容忍)属性

主节点上天生具有Taints, 因此才有了默认不会调度到主节点上的说法

  • 相关命令
##为node1增加一个NoSchedule(不允许调度)的污点
kubectl taint nodes node1 key=value:NoSchedule

##查询server1上的污点情况
kubectl describe nodes  server1 |grep Taints

##删除node1上的NoSchedule(不允许调度)污点属性
$ kubectl taint nodes node1 key:NoSchedule-
  • 一个标准的Deployment控制器部署nginx的清单
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
  • 主节点默认不允许调度
[root@Server2 Schedule]# kubectl describe nodes  server2 |grep TaintsTaints:
node-role.kubernetes.io/master:NoSchedule
  • 为Server4增加驱离属性
[root@Server2 Schedule]# kubectl taint node server4 key1=v1:NoExecute
node/server4 tainted
[root@Server2 Schedule]# kubectl get Pod -o wide
  • 如果设置正确, 你将只会在server3上看到Pod, 因为server2server4都不能用于部署

  • 增加容忍设置的版本

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
      tolerations:
      - key: "key1"
        operator: "Equal"
        value: "v1"
        effect: "NoExecute"

Taints设置的取值

参数含义
NoSchedule不会被调度到标记节点
PreferNoScheduleNoSchedule 的软策略版本
NoExecute不会调度到标记节点, 并且该节点内的Pod如无对应的Tolerate设置还会被驱逐

Tolerations相关设置

Tolerations中定义的key, value, effect, 要与node上设置的taint保持一致, 但并不是都要填写

  • operatorExists时, 可以省略value
  • operatorEqual时, keyvalue之间的关系必须相等
  • 如果不指定operator属性, 则默认值为Equal
  • 当不指定key, 再配合Exists就能匹配所有的keyvalue, 可以容忍所有污点
  • 当不指定effect , 则匹配所有的effect
tolerations:
  - key: "key"
    operator: "Equal"
    value: "value"
    effect: "NoSchedule"
对满足key=value 且污点策略为NoSchedule的节点进行容忍

tolerations:
  - operator: "Exists"
    effect: "NoSchedule"
容忍所有污点并不驱离

以上是关于Kubernetes(k8s)亲和性调度的主要内容,如果未能解决你的问题,请参考以下文章

Kubernetes 调度使用介绍(亲和反亲和污点容忍)

Kubernetes 调度使用介绍(亲和反亲和污点容忍)

Kubernetes 调度使用介绍(亲和反亲和污点容忍)

K8S调度之pod亲和性

Linux企业运维——Kubernetesk8s调度

K8S之list-watch机制+节点以及亲和性调度