一、什么污点和容忍度?

污点(Taint) 是应用在节点之上的,从这个名字就可以看出来,是为了排斥pod 所存在的。

容忍度(Toleration)是应用于 Pod 上的,允许(但并不要求)Pod 调度到带有与之匹配的污点的节点上。

二、污点和容忍度的作用?

Taint(污点)和 Toleration(容忍)可以作用于node和 pod 上,其目的是优化pod在集群间的调度,这跟节点亲和性类似,只不过它们作用的方式相反具有taint的node和pod是互斥关系,而具有节点亲和性关系的node和pod是相吸的。另外还有可以给node节点设置label,通过给pod设置nodeSelector将pod调度到具有匹配标签的节点上。

Taint 和 toleration 相互配合,可以用来避免pod被分配到不合适的节点上。每个节点上都可以应用一个或多个taint,这表示对于那些不能容忍这些taint的 pod,是不会被该节点接受的。如果将toleration应用于pod上,则表示这些pod可以(但不要求)被调度到具有相应taint的节点上。

三、给节点打污点 

给节点node1增加一个污点,它的键名是key1,键值是value1,效果是NoSchedule。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到node1这个节点。

kubectl taint nodes node1 applicatioin.xxx.flink=NoSchedule

若要移除上述命令所添加的污点,你可以执行:

kubectl taint nodes node1 applicatioin.xxx.flink=NoSchedule-

四、在pod 中定义容忍度

可以在 PodSpec 中定义 Pod 的容忍度。 下面两个容忍度均与上面例子中使用kubectl taint命令创建的污点相匹配, 因此如果一个 Pod 拥有其中的任何一个容忍度都能够被分配到node1

tolerations:
- key: applicatioin.xxx.flink
  operator: Equal
  effect: NoSchedule
tolerations:
- key: "applicatioin.xxx.flink"
  operator: "Exists"
  effect: "NoSchedule"

 实例

kind: Deployment
apiVersion: apps/v1
metadata:
  name: xxx-xxx-outbound-stream-xxx
  namespace: xxx-xxx-prd
  labels:
    app: xxx-xxx-outbound-stream-xxx
    component: jobmanager
    dce.daocloud.io/app: xxx-flink
    type: flink-native-kubernetes
  annotations:
    deployment.kubernetes.io/revision: '1'
    flinkdeployment.flink.apache.org/generation: '10'
spec:
  replicas: 1
  selector:
    matchLabels:
      app: xxx-xxx-outbound-stream-xxx
      component: jobmanager
      type: flink-native-kubernetes
  template:
    metadata:
      name: job-manager-pod-template
      creationTimestamp: null
      labels:
        app: xxx-xxx-outbound-stream-xxx
        component: jobmanager
        dce.daocloud.io/app: xxx-flink
        type: flink-native-kubernetes
      annotations:
        flinkdeployment.flink.apache.org/generation: '10'
    spec:
      volumes:
        - name: cacerts
          secret:
            secretName: confluent-kafka-secret
            defaultMode: 420
        - name: flink-jar
          persistentVolumeClaim:
            claimName: flink-jar-pvc-nfs
        - name: flink-config-volume
          configMap:
            name: flink-config-xxx-xxx-outbound-stream-xxx
            items:
              - key: log4j-console.properties
                path: log4j-console.properties
              - key: flink-conf.yaml
                path: flink-conf.yaml
            defaultMode: 420
        - name: pod-template-volume
          configMap:
            name: pod-template-xxx-xxx-outbound-stream-xxx
            items:
              - key: taskmanager-pod-template.yaml
                path: taskmanager-pod-template.yaml
            defaultMode: 420
      containers:
        - name: flink-main-container
          image: 'x.x.x.x/flink/flink:1.13'
          command:
            - /docker-entrypoint.sh
          args:
            - bash
            - '-c'
            - kubernetes-jobmanager.sh kubernetes-session
          ports:
            - name: rest
              containerPort: 8081
              protocol: TCP
            - name: jobmanager-rpc
              containerPort: 6123
              protocol: TCP
            - name: blobserver
              containerPort: 6124
              protocol: TCP
          env:
            - name: KUBERNETES_MAX_CONCURRENT_REQUESTS
              value: '300'
            - name: _POD_IP_ADDRESS
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.podIP
          resources:
            limits:
              cpu: 800m
              memory: 3Gi
            requests:
              cpu: 800m
              memory: 3Gi
          volumeMounts:
            - name: cacerts
              mountPath: /opt/flink/kafkaSSL
            - name: flink-jar
              mountPath: /opt/flink/webUpload
            - name: flink-config-volume
              mountPath: /opt/flink/conf
            - name: pod-template-volume
              mountPath: /opt/flink/pod-template
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          imagePullPolicy: Always
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      dnsPolicy: ClusterFirst
      nodeSelector:
        application.xxx.flink: 'true'
      serviceAccountName: flink-xxx-prod
      serviceAccount: flink-xxx-prod
      securityContext: {}
      schedulerName: default-scheduler
      tolerations:
        - key: applicatioin.xxx.flink
          operator: Equal
          effect: NoSchedule
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

五、多个污点的匹配原则 

可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。

Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点,特别是以下情况:

  • 如果未被过滤的污点中存在至少一个 effect 值为 NoSchedule 的污点, 则 Kubernetes 不会将 Pod 分配到该节点。
  • 如果未被过滤的污点中不存在 effect 值为 NoSchedule 的污点, 但是存在 effect 值为 PreferNoSchedule 的污点, 则 Kubernetes 会 尝试 不将 Pod 分配到该节点。
  • 如果未被过滤的污点中存在至少一个 effect 值为 NoExecute 的污点, 则 Kubernetes 不会将 Pod 分配到该节点(如果 Pod 还未在节点上运行), 或者将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。

例如,假设您给一个节点添加了如下污点:

kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

 假定有一个 Pod,它有两个容忍度:

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"

上述 Pod 不会被分配到 node1 节点,因为其没有容忍度和第三个污点相匹配。

但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的

六、设置NoExcute 的驱逐时间

通常情况下,如果给一个节点添加了一个 effect 值为NoExecute的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐, 任何可以忍受这个污点的 Pod 都不会被驱逐。

但是,如果 Pod 存在一个 effect 值为NoExecute的容忍度指定了可选属性tolerationSeconds的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。 如果在此之前上述污点被删除了,则 Pod 不会被驱逐。 

六、使用案例

通过污点和容忍度,可以灵活地让 Pod避开某些节点或者将 Pod 从某些节点驱逐。

1、专用节点

如果您想将某些节点专门分配给特定的一组用户使用,您可以给这些节点添加一个污点(即, kubectl taint nodes nodename dedicated=groupName:NoSchedule), 然后给这组用户的 Pod 添加一个相对应的 toleration。

拥有上述容忍度的 Pod 就能够被分配到上述专用节点,同时也能够被分配到集群中的其它节点。

如果您希望这些 Pod 只能被分配到上述专用节点,那么您还需要给这些专用节点另外添加一个和上述 污点类似的 label (例如:dedicated=groupName),同时,还要在上述准入控制器中给 Pod 增加节点亲和性要求上述 Pod 只能被分配到添加了 dedicated=groupName 标签的节点上。

2、配备了特殊硬件的节点

在部分节点配备了特殊硬件(比如 GPU)的集群中, 我们希望不需要这类硬件的 Pod 不要被分配到这些特殊节点,以便为后继需要这类硬件的 Pod 保留资源。

要达到这个目的,可以先给配备了特殊硬件的节点添加 taint (例如 kubectl taint nodes nodename special=true:NoSchedule 或 kubectl taint nodes nodename special=true:PreferNoSchedule), 然后给使用了这类特殊硬件的 Pod 添加一个相匹配的 toleration。

和专用节点的例子类似,添加这个容忍度的最简单的方法是使用自定义准入控制器。 比如,我们推荐使用扩展资源来表示特殊硬件,给配置了特殊硬件的节点添加污点时包含扩展资源名称, 然后运行一个ExtendedResourceToleration准入控制器。此时,因为节点已经被设置污点了,没有对应容忍度的 Pod 不会被调度到这些节点。但当你创建一个使用了扩展资源的 Pod 时, ExtendedResourceToleration 准入控制器会自动给 Pod 加上正确的容忍度, 这样 Pod 就会被自动调度到这些配置了特殊硬件件的节点上。 这样就能够确保这些配置了特殊硬件的节点专门用于运行需要使用这些硬件的 Pod, 并且您无需手动给这些 Pod 添加容忍度。

3、基于污点的驱逐

这是在每个 Pod 中配置的在节点出现问题时的驱逐行为。

前文提到过污点的 effect 值 NoExecute会影响已经在节点上运行的 Pod

  • 如果 Pod 不能忍受 effect 值为 NoExecute 的污点,那么 Pod 将马上被驱逐
  • 如果 Pod 能够忍受 effect 值为 NoExecute 的污点,但是在容忍度定义中没有指定 tolerationSeconds,则 Pod 还会一直在这个节点上运行。
  • 如果 Pod 能够忍受 effect 值为 NoExecute 的污点,而且指定了 tolerationSeconds, 则 Pod 还能在这个节点上继续运行这个指定的时间长度。

当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:

  • node.kubernetes.io/not-ready:节点未准备好。这相当于节点状态 Ready 的值为 "False"。
  • node.kubernetes.io/unreachable:节点控制器访问不到节点. 这相当于节点状态 Ready 的值为 "Unknown"。
  • node.kubernetes.io/memory-pressure:节点存在内存压力。
  • node.kubernetes.io/disk-pressure:节点存在磁盘压力。
  • node.kubernetes.io/pid-pressure: 节点的 PID 压力。
  • node.kubernetes.io/network-unavailable:节点网络不可用。
  • node.kubernetes.io/unschedulable: 节点不可调度。
  • node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 启动时指定了一个 "外部" 云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。

在节点被驱逐时,节点控制器或者 kubelet 会添加带有NoExecute效应的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。

实例

一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段较长的时间, 愿意等待网络恢复以避免被驱逐。在这种情况下,Pod 的容忍度可能是下面这样的:

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

说明:

Kubernetes 会自动给 Pod 添加一个 key 为 node.kubernetes.io/not-ready 的容忍度 并配置 tolerationSeconds=300,除非用户提供的 Pod 配置中已经已存在了 key 为 node.kubernetes.io/not-ready 的容忍度。

同样,Kubernetes 会给 Pod 添加一个 key 为 node.kubernetes.io/unreachable 的容忍度 并配置 tolerationSeconds=300,除非用户提供的 Pod 配置中已经已存在了 key 为 node.kubernetes.io/unreachable 的容忍度。

这种自动添加的容忍度意味着在其中一种问题被检测到时 Pod 默认能够继续停留在当前节点运行 5 分钟。

注意:DaemonSet 中的 Pod 被创建时, 针对以下污点自动添加的 NoExecute 的容忍度将不会指定 tolerationSeconds

  • node.kubernetes.io/unreachable
  • node.kubernetes.io/not-ready

这保证了出现上述问题时 DaemonSet 中的 Pod 永远不会被驱逐。

4、基于节点的状态添加污点

控制平面使用节点控制器 自动创建 与 节点状况 对应的带有NoSchedule效应的污点。

调度器在进行调度时检查污点,而不是检查节点状况。这确保节点状况不会直接影响调度。

如果DiskPressure节点状况处于活跃状态,则控制平面 添加node.kubernetes.io/disk-pressure污点并且不会调度新的 pod 到受影响的节点。

如果MemoryPressure节点状况处于活跃状态,则控制平面添加node.kubernetes.io/memory-pressure污点。

对于新创建的 Pod,可以通过添加相应的 Pod 容忍度来忽略节点状况。 控制平面还在具有除BestEffort之外的QoS类的 pod 上 添加node.kubernetes.io/memory-pressure容忍度。 这是因为 Kubernetes 将GuaranteedBurstableQoS 类中的 Pod(甚至没有设置内存请求的 Pod) 视为能够应对内存压力,而新创建的BestEffortPod 不会被调度到受影响的节点上。

DaemonSet 控制器自动为所有守护进程添加如下 NoSchedule 容忍度以防 DaemonSet 崩溃:

  • node.kubernetes.io/memory-pressure
  • node.kubernetes.io/disk-pressure
  • node.kubernetes.io/pid-pressure (1.14 或更高版本)
  • node.kubernetes.io/unschedulable (1.10 或更高版本)
  • node.kubernetes.io/network-unavailable (只适合主机网络配置)

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐