Kubernetes基本概念之Affinity,Taints和Tolerations

为了使Pods分配到特定的Nodes上运行,K8s提出了两种解决方案:nodeSelector和nodeAffinity。

nodeSelector

nodeSelector是一种最简单的指定Pod运行在哪一个Node上的方式。它的使用简单粗暴,直接给node打上一个label,然后在Pod的定义中添加一个nodeSelector即可。

  1. 为node-1打上一个"disktype=ssd"的label
kubectl label nodes node-1 disktype=ssd
  1. 在Pod的定义中添加一个nodeSelector
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd    # 对应node-1的label

通过label选择器的方式,这样这个名为nginx的Pod就会调度到拥有"disktype=ssd"的label的Node上运行。如果K8s集群找不到任何一个拥有"disktype=ssd"的label的Node,那么这个Pod就会处于Pending状态(即无法运行成功)。

nodeAffinity

nodeAffinity提供的功能与nodeSelector类似,都是用于指定Pod运行在哪些Node上。但是nodeAffinity拥有更加复杂的语法和更加强大的功能。下面是一个例子:

apiVersion: v1  
kind: Pod  
metadata:  
  name: database
spec:  
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: role
              operator: In
              values: [ "database-require" ]
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          preference:
            matchExpressions:
              - key: role
                operator: In
                values: [ "database-prefer" ]

目前nodeAffinity支持两种模式:requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution

  • requiredDuringSchedulingIgnoredDuringExecution:可以分为两部分requiredDuringScheduling和IgnoredDuringExecution。requiredDuringScheduling意味着K8s调度器必须将这个Pod调度到目标Node(这个目标Node由matchExpressions决定)上,否则像nodeSelector一样,Pod的状态将处于Pending。IgnoredDuringExecution意味着这个node affinity的过程仅仅作用于Pod调度阶段,它不适用与已经处于运行中的Pod。也就是说,如果一个Pod被分配到node1之后,此时node1的label被修改了(修改后不满足这个Pod的nodeAffinity了),此时K8s并不会重新调度这个Pod。

  • preferredDuringSchedulingIgnoredDuringExecution:同样的,preferredDuringScheduling意味着一种倾向性,K8s调度器首先尝试将Pod分配到满足node affinity的目标Node上,如果找不到这样的Node,那么允许将其分配到其他Node上。

  • matchExpressions中的operator可选值包括:In, NotIn, Exists, DoesNotExist, Gt, Lt。可以使用NotIn和DoesNotExist实现node anti-affinity,或者使用taints(稍后会提到)来实现node anti-affinity。

  • 如果一个nodeAffinity中包含了多个nodeSelectorTerms,那么Pod可以被分配到任意一个满足了nodeSelectorTerms的Node上。

  • 如果一个nodeSelectorTerms包含了多个matchExpressions,那么Pod分配的Node必须完全满足所有的atchExpressions。

  • preferredDuringSchedulingIgnoredDuringExecution中的weight字段,其取值范围为1-100,用于定义每一个preference的权重。每满足一个preference,该Node的得分就会增加(增加的值即为这个weight)。最终K8s会统计每一个Node的得分,从而选择得分最高的Node作为目标Node。

podAffinity和podAntiAffinity

podAffinity用于根据已经在Node上运行的Pod的标签来限制Pod调度在哪个Node上。例如有些时候出于性能考虑,我们希望将某2个Pod分配在同一个Node上,此时我们就能够使用nodeAffinity。下面是一个例子:

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

例如上面的例子中,requiredDuringSchedulingIgnoredDuringExecution中的定义表示,必须将这个Pod调度到一个运行着拥有label:security=S1的Pod的Node上。也就是说,当且仅当某个Node上运行着一个Pod,并且这个Pod拥有security=S1的label,那么这个例子中Pod才能分配到这个Node上。

podAntiAffinity则刚好相反,如果某个Node拥有了满足matchExpressions的Pod,那么久不会将这个Pod分配到这个Node上。

注意到podAffinity和podAntiAffinity还包含一个属性:topologyKey。topologyKey本身代表着一个label的key,它用于限定Node范围。因此nodeAffinity的规则是:如果 X 已经运行一个或多个符合规则 Y 的 pod,那么这个 pod 应该运行在 X 上。这里的Y就是matchExpressions定义的规则。而X则是这里的topologyKey,例如上面的kubernetes.io/hostname将Node的范围限定在拥有"kubernetes.io/hostname"这个label的Node中。通常情况下,"kubernetes.io/hostname" 是Node默认就拥有的Label,Node默认拥有的其他Label包括:failure-domain.beta.kubernetes.io/zone,failure-domain.beta.kubernetes.io/region,topology.kubernetes.io/zone,topology.kubernetes.io/region,beta.kubernetes.io/instance-type,node.kubernetes.io/instance-type,kubernetes.io/os,kubernetes.io/arch。(参考资料:https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#built-in-node-labels)

Taints和Tolerations

如果说我们可以使用nodeSelector和nodeAffinity来表达Pod对于Node的亲和性,那么Taints和Tolerations就是用于表达Pod对于Node的反亲和性。换种方式说,nodeSelector/nodeAffinity是Pod的一个属性,将其吸引到一个特定的Node集合上;而Taint则刚好相反,它允许Node排斥Pod。

Taint中文译为污点,它作用于Node,当为Node打上污点后,任何没有显示声明能够容忍这个污点的Pod都不会被调度到这个Node上。Pod通过定义Tolerations来声明自己能够容忍某些污点(Taints)。下面是Taints的使用例子:

kubectl taint nodes node1 key=value:NoSchedule

一个Taint由三部分组成:key,value和effect。格式为 key=value:effect。key/value类似于label的使用,而effect包含三个可选值:

  • NoSchedule:打上这种effect的taint的Node,一定不会被调度上没有声明对应Toleration的Pod
  • PreferNoSchedule:打上这种effect的taint的Node,尽可能不会被调度上没有声明对应Toleration的Pod
  • NoExecute:不仅不会调度,还会驱逐Node上已有的没有声明对应Toleration的Pod

为Pod添加Toleration:

apiVersion: v1  
kind: Pod  
metadata:  
  name: database
spec:  
  tolerations:
    - key: 
      operator: "Equal"
      value: 
      effect: "NoSchedule"

声明了Toleration的Pod表示其可以容忍该污点,可以被调度到有该污点的Pod上。

可以添加多个 taint 到一个 node 上,也能添加多个 toleration 到一个 pod 上。 Kubernetes以过滤器的形式来处理多个taint和toleration的情况: 遍历 node 的所有 taint,如果 pod 拥有匹配的 toleration 则将 taint 忽略掉;最后剩下的没有被忽略的 taint 将作用于这个 pod。

我们提到了 NoExecute taint 的效果,它将对正在运行的 pod 产生如下影响:

  • 不能容忍对应 taint 的 pod 将立即被驱逐
  • 能够容忍对应 taint 但是没有在toleration specification中指定tolerationSeconds则将继续保持在原节点上
  • 能够容忍对应 taint 并且指定 tolerationSeconds 则在原节点上保持指定的时间,指定时间到达后将被驱逐

参考文章

  • https://k8smeetup.github.io/docs/concepts/configuration/assign-pod-node/#interlude-built-in-node-labels

你可能感兴趣的:(Kubernetes基本概念之Affinity,Taints和Tolerations)