节点污点可以用来让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 。