k8s 的污点与容忍度
污点和容忍度是相互匹配的关系,在node上定义一个污点,pod上定义容忍度,如果pod能容忍这个污点,就会被调度到拥有这个污点的节点上,不能容忍这个污点就不会调度到拥有这个污点的节点上,如果node节点上没有定义污点,那么任何pod都会调度到这个节点上
给节点添加污点
[root@master-1 ~]# kubectl taint node node-1 key=cx:NoSchedule node/node-1 tainted
删除污点
[root@master-1 ~]# kubectl taint node node-1 key:NoSchedule- node/node-1 untainted
可以在 PodSpec 中为容器设定容忍标签。以下两个容忍标签都与上面的 kubectl taint 创建的污点“匹配”, 因此具有任一容忍标签的Pod都可以将其调度到 node-1 上:
tolerations: - key: "key" operator: "Equal" value: "value" effect: "NoSchedule" tolerations: - key: "key" operator: "Exists" effect: "NoSchedule"
Equal 相当于等于号key 与 effect 还有 value精确匹配检查
Exists 相当于等于号key 与 effect 可以没有value 不检查value
如果一个 toleration 的 effect 为空,则 key 值与之相同的相匹配 taint 的 effect 可以是任意值。
tolerations: - key: "key" operator: "Exists"
使用到的 effect 的一个值 NoSchedule,也可以使用另外一个值 PreferNoSchedule。这是“优化”或“软”版本的 NoSchedule ——系统会 尽量 避免将 pod 调度到存在其不能容忍 taint 的节点上,但这不是强制的。effect 的值还可以设置为 NoExecute,下文会详细描述这个值
- 如果未被过滤的 taint 中存在一个以上 effect 值为 NoSchedule 的 taint,则 Kubernetes 不会将 pod 分配到该节点。
- 如果未被过滤的 taint 中不存在 effect 值为 NoSchedule 的 taint,但是存在 effect 值为 PreferNoSchedule 的 taint,则 Kubernetes 会 尝试 将 pod 分配到该节点。
- 如果未被过滤的 taint 中存在一个以上 effect 值为 NoExecute 的 taint,则 Kubernetes 不会将 pod 分配到该节点(如果 pod 还未在节点上运行),或者将 pod 从该节点驱逐(如果 pod 已经在节点上运行)
kubectl taint nodes node-1 key1=value1:NoSchedule kubectl taint nodes node-1 key1=value1:NoExecute kubectl taint nodes node-1 key2=value2:NoSchedule 然后存在一个 pod,它有两个 toleration: tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" # 未运行是不分配 - key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" #不会将 pod 分配到该节点(如果 pod 还未在节点上运行),或者将 pod 从该节点驱逐(如果 pod 已经在节点上运行)。
上述 pod 不会被分配到上述节点,因为其没有 toleration 和第三个 taint 相匹配。但是如果在给节点添加上述 taint 之前,该 pod 已经在上述节点运行,那么它还可以继续运行在该节点上,因为第三个 taint 是三个 taint 中唯一不能被这个 pod 容忍的。通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute 的 taint,则任何不能忍受这个 taint 的 pod 都会马上被驱逐,任何可以忍受这个 taint 的 pod 都不会被驱逐。但是,如果 pod 存在一个 effect 值为 NoExecute 的 toleration 指定了可选属性 tolerationSeconds 的值,则表示在给节点添加了上述 taint 之后,pod 还能继续在节点上运行的时间。
tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" tolerationSeconds: 3600
这表示如果这个 pod 正在运行,然后一个匹配的 taint 被添加到其所在的节点,那么 pod 还将继续在节点上运行 3600 秒,然后被驱逐。如果在此之前上述 taint 被删除了,则 pod 不会被驱逐。
- 专用节点:如果你想将某些节点专门分配给特定的一组用户使用,你可以给这些节点添加一个 taint(即, kubectl taint nodes nodename dedicated=groupName:NoSchedule),然后给这组用户的 pod 添加一个相对应的 toleration(通过编写一个自定义的 admission controller,很容易就能做到)。拥有上述 toleration 的 pod 就能够被分配到上述专用节点,同时也能够被分配到集群中的其它节点。如果你希望这些 pod 只能被分配到上述专用节点,那么你还需要给这些专用节点另外添加一个和上述 taint 类似的 label (例如:dedicated=groupName),同时 还要在上述 admission controller 中给 pod 增加节点亲和性要求上述 pod 只能被分配到添加了 dedicated=groupName 标签的节点上。
- 配备了特殊硬件的节点:在部分节点配备了特殊硬件(比如 GPU)的集群中,我们希望不需要这类硬件的 pod 不要被分配到这些特殊节点,以便为后继需要这类硬件的 pod 保留资源。要达到这个目的,可以先给配备了特殊硬件的节点添加 taint(例如 kubectl taint nodes nodename special=true:NoSchedule or kubectl taint nodes nodename special=true:PreferNoSchedule),然后给使用了这类特殊硬件的 pod 添加一个相匹配的 toleration。和专用节点的例子类似,添加这个 toleration 的最简单的方法是使用自定义 admission controller。比如,我们推荐使用 Extended Resources 来表示特殊硬件,给配置了特殊硬件的节点添加 taint 时包含 extended resource 名称,然后运行一个 ExtendedResourceToleration admission controller。此时,因为节点已经被 打上taint 了,没有对应 toleration 的 Pod 会被调度到这些节点。但当你创建一个使用了 extended resource 的 Pod 时。ExtendedResourceToleration admission controller 会自动给 Pod 加上正确的 toleration ,这样 Pod 就会被自动调度到这些配置了特殊硬件件的节点上。这样就能够确保这些配置了特殊硬件的节点专门用于运行 需要使用这些硬件的 Pod,并且你无需手动给这些 Pod 添加 toleration。
- 基于 taint 的驱逐: 这是在每个 pod 中配置的在节点出现问题时的驱逐行为
-
- 如果 pod 不能忍受 effect 值为 NoExecute 的 taint,那么 pod 将马上被驱逐
- 如果 pod 能够忍受 effect 值为 NoExecute 的 taint,但是在 toleration 定义中没有指定 tolerationSeconds,则 pod 还会一直在这个节点上运行。
- 如果 pod 不能够忍受 effect 值为 NoExecute 的 taint,而且指定了 tolerationSeconds,则 pod 还能在这个节点上继续运行这个指定的时间长度。
此外,Kubernetes 1.6+ 已经支持(alpha阶段)节点问题的表示。换句话说,当某种条件为真时,node controller会自动给节点添加一个 taint。当前内置的 taint 包括:
- node.kubernetes.io/not-ready:节点未准备好。这相当于节点状态 Ready 的值为 “False”。
- node.kubernetes.io/unreachable:node controller 访问不到节点. 这相当于节点状态 Ready 的值为 “Unknown”。
- node.kubernetes.io/out-of-disk:节点磁盘耗尽。
- node.kubernetes.io/memory-pressure:节点存在内存压力。
- node.kubernetes.io/disk-pressure:节点存在磁盘压力。
- node.kubernetes.io/network-unavailable:节点网络不可用。
- node.kubernetes.io/unschedulable: 节点不可调度。
- node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 启动时指定了一个 “外部” cloud provider,它将给当前节点添加一个 taint 将其标志为不可用。在 cloud-controller-manager 的一个 controller 初始化这个节点后,kubelet 将删除这个 taint。
在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute 效应的相关污点。如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。
注意:
为了保证由于节点问题引起的 pod 驱逐rate limiting行为正常,系统实际上会以 rate-limited 的方式添加 taint。在像 master 和 node 通讯中断等场景下,这避免了 pod 被大量驱逐。
使用这个功能特性,结合 tolerationSeconds,pod 就可以指定当节点出现一个或全部上述问题时还将在这个节点上运行多长的时间。
比如,一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段较长的时间,愿意等待网络恢复以避免被驱逐。在这种情况下,pod 的 toleration 可能是下面这样的
tolerations: - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" tolerationSeconds: 6000
注意,Kubernetes 会自动给 pod 添加一个 key 为 node.kubernetes.io/not-ready 的 toleration 并配置 tolerationSeconds=300,除非用户提供的 pod 配置中已经已存在了 key 为 node.kubernetes.io/not-ready 的 toleration。同样,Kubernetes 会给 pod 添加一个 key 为 node.kubernetes.io/unreachable 的 toleration 并配置 tolerationSeconds=300,除非用户提供的 pod 配置中已经已存在了 key 为 node.kubernetes.io/unreachable 的 toleration。
这种自动添加 toleration 机制保证了在其中一种问题被检测到时 pod 默认能够继续停留在当前节点运行 5 分钟。这两个默认 toleration 是由 DefaultTolerationSeconds admission controller添加的。
DaemonSet 中的 pod 被创建时,针对以下 taint 自动添加的 NoExecute 的 toleration 将不会指定 tolerationSeconds:
- node.kubernetes.io/unreachable
- node.kubernetes.io/not-ready
这保证了出现上述问题时 DaemonSet 中的 pod 永远不会被驱逐。
Node 生命周期控制器会自动创建与 Node 条件相对应的带有 NoSchedule 效应的污点。 同样,调度器不检查节点条件,而是检查节点污点。这确保了节点条件不会影响调度到节点上的内容。用户可以通过添加适当的 Pod 容忍度来选择忽略某些 Node 的问题(表示为 Node 的调度条件)。
自 Kubernetes 1.8 起, DaemonSet 控制器自动为所有守护进程添加如下 NoSchedule toleration 以防 DaemonSet 崩溃:
- node.kubernetes.io/memory-pressure
- node.kubernetes.io/disk-pressure
- node.kubernetes.io/out-of-disk (只适合 critical pod)
- node.kubernetes.io/unschedulable (1.10 或更高版本)
- node.kubernetes.io/network-unavailable (只适合 host network)
-
添加上述 toleration 确保了向后兼容,你也可以选择自由的向 DaemonSet 添加 toleration。
测试
[root@master-1 pod]# kubectl taint node node-1 cx=cx-node:NoSchedule node/node-1 tainted [root@master-1 pod]# kubectl taint node node-2 cx=cx-node:NoSchedule node/node-2 tainted [root@master-1 pod]# cat pod-1.yaml # apiVersion: v1 kind: Pod metadata: name: nginx-test spec: containers: - name: nginx image: nginx [root@master-1 pod]# kubectl apply -f pod-1.yaml pod/nginx-test created [root@master-1 pod]# kubectl get pod NAME READY STATUS RESTARTS AGE nginx-test 0/1 Pending 0 9s [root@master-1 pod]# kubectl describe pods nginx-test Name: nginx-test Namespace: default Priority: 0 Node: <none> Labels: <none> Annotations: <none> Status: Pending IP: IPs: <none> Containers: nginx: Image: nginx Port: <none> Host Port: <none> Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-w7bhz (ro) Conditions: Type Status PodScheduled False Volumes: default-token-w7bhz: Type: Secret (a volume populated by a Secret) SecretName: default-token-w7bhz Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 2m8s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) had taint {cx: cx-node}, that the pod didn't tolerate. Warning FailedScheduling 2m8s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) had taint {cx: cx-node}, that the pod didn't tolerate. #pod不容忍污点 [root@master-1 pod]# kubectl delete pods nginx-test pod "nginx-test" deleted [root@master-1 pod]# cat pod-1.yaml apiVersion: v1 kind: Pod metadata: name: nginx-test spec: containers: - name: nginx image: nginx tolerations: #定义pod容忍 - key: cx value: cx-node effect: NoSchedule [root@master-1 pod]# kubectl apply -f pod-1.yaml pod/nginx-test created [root@master-1 pod]# kubectl get pod NAME READY STATUS RESTARTS AGE nginx-test 1/1 Running 0 7s [root@master-1 pod]# kubectl describe pods nginx-test Name: nginx-test Namespace: default Priority: 0 Node: node-1/192.168.10.30 Start Time: Sun, 29 Jan 2023 09:31:00 +0800 Labels: <none> Annotations: cni.projectcalico.org/podIP: 10.244.84.136/32 cni.projectcalico.org/podIPs: 10.244.84.136/32 Status: Running IP: 10.244.84.136 IPs: IP: 10.244.84.136 Containers: nginx: Container ID: docker://6007930c71983df9ba71b0ef27d59ffc663c659aab1bec18277136175c42ac0e Image: nginx Image ID: docker-pullable://nginx@sha256:b8f2383a95879e1ae064940d9a200f67a6c79e710ed82ac42263397367e7cc4e Port: <none> Host Port: <none> State: Running Started: Sun, 29 Jan 2023 09:31:06 +0800 Ready: True Restart Count: 0 Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-w7bhz (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-w7bhz: Type: Secret (a volume populated by a Secret) SecretName: default-token-w7bhz Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: cx=cx-node:NoSchedule node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 80s default-scheduler Successfully assigned default/nginx-test to node-1 Normal Pulling 79s kubelet Pulling image "nginx" Normal Pulled 74s kubelet Successfully pulled image "nginx" in 5.427330105s Normal Created 74s kubelet Created container nginx Normal Started 74s kubelet Started container nginx [root@master-1 pod]# kubectl taint node node-1 cx:NoSchedule- #删除污点 node/node-1 untainted