Kubernetes使用节点亲缘性将POD调度到特定节点上

  节点污点可以用来让pod远离特定的节点,尽量在不修改已有pod信息的前提,通过在节点添加污点信息,来拒绝pod在某些节点上的部署。

  而现在介绍一种叫做节点亲缘性,通过明确的在pod中添加的信息,来决定一个pod可以或者不可以被调度到哪些节点上。

  对比节点亲缘性和节点选择器

  在早期版本的Kubernetes中,初始的节点亲缘性机制,就是pod描述中的nodeSelector字段。节点必须包含所有pod对应字段中的指定label,才能成为pod调度的目标节点。

  节点选择器实现简单,但是它不能满足你的所有需求。正因为如此,一种更强大的机制被引入。节点选择器最终会被弃用,所以现在了解新的节点亲缘性机制就变得重要起来。

  与节点选择器类似,每个pod可以定义自己的节点亲缘性规则。这些规则可以允许指定硬性限制或者偏好。如果指定一种偏好的话,你将告知Kubernetes对于某个特定的pod,它更倾向于调度到某些节点上,之后Kubernetes将尽量把这个pod调度到这些节点上面。如果没法实现的话,pod将被调度到其他某个节点上。

  检查默认的节点标签

  节点亲缘性根据节点的标签来进行选择,这点跟节点选择器是一致的。现在检查一个Google Kubernetes引擎集群(GKE)中节点的标签,来看一下它们默认的标签是什么,如以下代码所示。

#代码16.7 GKE节点的默认标签
$ kubectl describe node gke-kubia-default-pool-db274c5a-mjnf
Name:  gke-kubia-default-pool-db274c5a-mjnf
Role:
Labels:  beta.kubernetes.io/arch=amd64
       beta.kubernetes.io/fluentd-ds-ready=true
       beta.kubernetes.io/instance-type=fl-micro
       beta.kubernetes.io/os=linux
       cloud.google.com/gke-nodepool=default-pool
       failure-domain.beta.kubernetes.io/region=europe-west1               #最后这三个标签对于节点亲缘性来说最为重要
       failure-domain.beta.kubernetes.io/zone=europe-west1-d
       kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-mjnf

  这个节点有很多标签,但涉及节点亲缘性和pod亲缘性时,最后三个标签是最重要的。这三个标签的含义如下:

    • failure-domain.beta.kubernetes.io/region表示该节点所在的地理地域。
    • failure-domain.beta.kubernetes.io/zone表示该节点所在的可用性区域(availability zone)。
    • kubernetes.io/hostname很显然是该节点的主机名。

  这三个以及其他标签,将被用于pod亲缘性规则。在第三章中,你己经学会 如何给一个节点添加自定义标签,并且在pod的节点选择器中使用它。可以通过给 pod加上节点选择器的方式,将节点部署到含有这个自定义标签的节点上。现在, 你将学习到怎么用节点亲缘性规则实现同样的功能。

 

1.指定强制性节点亲缘性规则

  有这么一个例子,使用了节点选择器使得需要GPU的pod只被调度到有GPU的节点上。包含了nodeSelector字段的pod描述如以下代码所示。

#代码16.8 使用了节点选择器的pod : kubia-gpu-nodeselector.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  nodeSelector:
    gpu: "true"
  containers:
  - image: luksa/kubia        #这个pod会被调度到包含了gpu=true标签的节点上
    name: kubia

  nodeSelector字段表示,pod只能被部署在包含了gpu=true标签的节点上。 如果将节点选择器替换为节点亲缘性规则,pod定义将会如以下代码清单所示。

#代码16.9 使用了节点亲缘性规则的pod : kubia-gpu-nodeaffinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"
  containers:
  - image: luksa/kubia
    name: kubia

  首先会注意到的是,这种写法比简单的节点选择器要复杂得多,但这是因为它的表达能力更强,再详细看一下这个规则。

  较长的节点亲缘性属性名的意义

  正如你所看到的,这个pod的描述包含了affinity字段,该字段又包含了nodeAffinity字段,这个字段有一个极其长的名字,所以,先重点关注这个。

  把这个名字分成两部分,然后分别看下它们的含义:

    • requiredDuringScheduling...表明了该字段下定义的规则,为了让pod能调度到该节点上,明确指出了该节点必须包含的标签。
    • ...IgnoredDuringExecution表明了该字段下定义的规则,不会影响己经在节点上运行着的pod。

  目前,当你知道当前的亲缘性规则只会影响正在被调度的pod,并且不会导致己经在运行的pod被剔除时,情况可能会更简单一些。这就是为什么目前的规则都是以IgnoredDuringExecution结尾的。最终,Kubernetes也会支持RequiredDuringExecution,表示如果去除掉节点上的某个标签,那些需要节点包含该标签的pod将会被剔除,但是,Kubernetes目前还不支持次特性。所以,可以暂时不去关心这个长字段的第二部分。

  了解节点选择器条件

  记住上一节所解释的内容,将更容易理解nodeSelectorTerms和matchExpressions字段,这两个字段定义了节点的标签必须满足哪一种表达式,才能满足pod调度的条件。样例中的单个表达式比较容易理解,节点必须包含一个叫作gpu的标签,并且这个标签的值必须是true。

  因此,这个pod只会被调度到包含gpu=true的节点上。

  现在,更有趣的部分来了,节点亲缘性也可以在调度时指定节点的优先级,将在接下来的部分看到。

 

2.调度pod时优先考虑某些节点

  最近介绍的节点亲缘性的最大好处就是,当调度某一个pod时,指定调度器可以优先考虑哪些节点,这个功能是通过preferredDuringSchedulingIgnoredDuringExecution字段来实现的。

  想象一下拥有一个跨越多个国家的多个数据中心,每一个数据中心代表了一个单独的可用性区域。在每个区域中,有一些特定的机器,只提供给你自己或者你的合作公司使用。现在,你想要部署一些pod,希望将pod优先部署在区域zone1,并且是为你公司部署预留的机器上。如果你的机器没有足够的空间用于这些pod,或者出于其他一些重要的原因不希望这些pod调度到上面,那么就会调度到其他区域的其他机器上面,这种情况你也是可以接受的。节点亲缘性就可以实现这样的功能。

  给节点加上标签

  首先,节点必须加上合适的标签。每个节点需要包含两个标签,一个用于表示所在的这个节点所归属的可用性区域,另一个用于表示这是一个独占的节点还是一个共享的节点。

  在接下来假设双节点的集群环境,将使用这个集群中的两个工作节点,当然也可以使用GKE或者其他多节点的集群。

  首先,给这些节点加上标签,如以下代码所示。

#代码16.10 给节点加上标签
$ kubectl label node node1.k8s  availability-zone=zone1
node "nodel.k8s" labeled 
$ kubectl label node nodel.k8s share-type=dedicated
node "nodel.k8s" labeled 
$ kubectl label node node2.k8s availability-zone=zone2
node "node2.k8s" labeled 
$ kubectl label node node2.k8s share-type=shared 
node "node2.k8s" labeled
$ kubectl get node -L availability-zone -L share-type
NAME  STATUS AGE  VERSION  AVAILABILITY-ZONE SHARE-TYPE
master.k8s READY 4d  v1.6.4  <none>    <none>
node1.k8s READY 4d  v1.6.4  zone1    dedicated
node2.k8s READY 4d  v1.6.4  zone2    shared

  指定优先级节点亲缘性规则

  当这些节点的标签设置好,现在可以创建一个Deployment,其中优先选择zone1中的dedicated节点。下面的代码清单显示了这个Deployment的描述。

#代码16.11 含有优先级节点亲缘性规则的Deployment: preferred-deployment.yamI
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: pref
spec:
  replicas: 5
  template:
    metadata:
      labels:
        app: pref
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:  #指定优先级,这不是必需的
          - weight: 80                                #这块是节点有限调度到zone1,这是最重要的偏好
            preference:
              matchExpressions:
              - key: availability-zone
                operator: In
                values:
                - zone1
          - weight: 20                                 #同时优先调度pod到独占节点,但是该优先级为zone1优先级的1/4
            preference:
              matchExpressions:
              - key: share-type
                operator: In
                values:
                - dedicated
      containers:
      - args:
        - sleep
        - "99999"
        image: busybox
        name: main

  查看上面的代码清单,定义了一个节点亲缘性优先级,而不是强制要求(后面会讲解)。想要pod被调度到包含标签availability-zone=zone1以及share-type=dedicated的节点上。第一个优先级规则是相对重要的,因此将其weight设置为80,而第二个优先级规则就不那么重要(weight设置为20)。

  了解节点优先级是如何工作的

  如果集群包含多个节点,当调度上面的代码中的Deployment pod时,节点将会分成4个组,。那些包含availability-zone以及share-type标签,并且匹配pod亲缘性的节点,将排在最前面。然后,由于pod的节点亲缘性规则配置的权重,接下来是zone1的shared节点,然后是其他区域的dedicated节点,优先级最低的是剩下的其他节点。

  在一个包含两个节点的集群中部署节点

  如果在一个包含两个节点的集群中创建该部署,看到的最多的应该是pod被部署在了node1上面。检查下面的代码清单看情况是否属实。

#代码16.12 查看pod调度情况
$kubectl get po -o wide
NAME          READY  STATUS  RESTARTS  AGE   IP       NODE
pref-607515-1rnwv  1/1   Running   0      4m   10.47.0.1  node2.k8s
pref-607515-27wp0  1/1   Running   0      4m   10.44.0.8  node1.k8s
pref-607515-5xd0z  1/1   Running   0      4m   10.44.0.5  node1.k8s
pref-607515-jx9wt  1/1   Running   0      4m   10.44.0.4  node1.k8s
pref-607515-m1gqm  1/1   Running   0      4m   10.44.0.6  node1.k8s

  5个pod被创建,其中4个部署在了node1,1个部署在了node2。为什么会有1个pod会被调度到node2而不是node1?原因是除了节点亲缘性的优先级函数,调度器还是使用其他的优先级函数来决定节点被调度到哪。其中之一就是Selector SpreadPriority函数,这个函数确保了属于同一个ReplicaSet或者Service的pod,将分散部署在不同节点上,以避免单个节点失效导致这个服务也宕机。这就是有1个pod被调度到node2的最大可能。

  可以去试着扩容部署至20个实例或更多,将看到大多数的pod被调度到node1。如果没有设置任何节点亲缘性优先级,pod将会被均匀地分配在两个节点上面。

 

3.更多的亲缘性匹配规则及语法

  上面也简单介绍了简单的原理以及过程,现在介绍更多的规则以及案例。

  节点亲和调度分成软策略(soft)和硬策略(hard),在软策略下,如果没有满足调度条件的节点,pod会忽略这条规则,继续完成调度。

  • requiredDuringSchedulingIgnoredDuringExecution

表示pod必须部署到满足条件的节点上,如果没有满足条件的节点,就不停重试。其中IgnoreDuringExecution表示pod部署之后运行的时候,如果节点标签发生了变化,不再满足pod指定的条件,pod也会继续运行。

  • requiredDuringSchedulingRequiredDuringExecution

表示pod必须部署到满足条件的节点上,如果没有满足条件的节点,就不停重试。其中RequiredDuringExecution表示pod部署之后运行的时候,如果节点标签发生了变化,不再满足pod指定的条件,则重新选择符合要求的节点。

  • preferredDuringSchedulingIgnoredDuringExecution

  表示优先部署到满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。

  • preferredDuringSchedulingRequiredDuringExecution

  表示优先部署到满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。其中RequiredDuringExecution表示如果后面节点标签发生了变化,满足了条件,则重新调度到满足条件的节点。

  再来一个官方示例

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: gcr.io/google_containers/pause:2.0

  这个pod同时定义了requiredDuringSchedulingIgnoredDuringExecution和 preferredDuringSchedulingIgnoredDuringExecution 两种 nodeAffinity。第一个要求 pod 运行在特定AZ的节点上,第二个希望节点最好有对应的another-node-label-key:another-node-label-value 标签。

  这里的匹配逻辑是label在某个列表中,可选的操作符有:

    • In: label的值在某个列表中
    • NotIn:label的值不在某个列表中
    • Exists:某个label存在
    • DoesNotExist:某个label不存在
    • Gt:label的值大于某个值(字符串比较)
    • Lt:label的值小于某个值(字符串比较)

  如果nodeAffinity中nodeSelector有多个选项,节点满足任何一个条件即可;如果matchExpressions有多个选项,则节点必须同时满足这些选项才能运行pod 。

Kubernetes使用节点亲缘性将POD调度到特定节点上

上一篇:转:JAVA ANNOTATION详解


下一篇:微信录音接口的调用以及amr文件转码MP3文件的实现