Kubernetes 调度利器(二):污点与容忍度(压力驱逐)

作者: 王炳明 分类: Kubernetes 发布时间: 2022-03-14 08:31 热度:523

本系列教程目录(已发布):

图解 K8S(01):基于ubuntu 部署最新版 k8s 集群
图解 K8S(02):认识 K8S 中的资源对象
图解 K8S(03):从 Pause 容器理解 Pod 的本质
图解 K8S(04):吃透 Pod 中的第三类容器 — init 容器
图解 K8S(05):调度利器之标签与选择器(分组调度)
图解 K8S(06):调度利器之污点与容忍度(压力驱逐)
图解 K8S(07):调度利器之亲和与反亲和(服务容灾)

将一个 Pod 分配到某一个可以满足 Pod 资源请求的节点上,这一过程称之为调度。

理想情况下,你的集群中,有足够的资源能让你创建你期望的 Pod,如此一来,你就有理由不关心你的节点的资源还剩多少,有理由不关心 K8S 调度 Pod 的细节。

可事实上,你的集群资源是有限的,为了能让节点资源得到合理分配、有效利用,需要你对节点进行规划。

比如哪些机器是高性能的机器,哪些是普通机器,哪些是专用机器,尽量避免让普通的应用跑在高性能的机器上。

除此之外,有些应用,出于高可用的考虑,还需要应用部署多个副本,并分散开在不同的域里。

而关于这些内容,可以分成三个部分:

  • 标签与选择器
  • 污点与容忍度
  • 亲和与反亲和

上一篇文章已经介绍了 标签与选择器,本篇文章讲一下 污点与容忍度

1. 通俗理解污点

打个比方,你去医院里看病,在医生的诊断之后,了解了你的病情后,准备给你开点药。

但开药也讲究对症下药,同样是发烧,给大人吃的药和给小孩子吃的药是不一样的。

因此为了防止滥用,药店会给不同的退烧药就设定适用范围(作用等同于 K8S 中的污点):

  • 阿司匹林:适合成年人使用(做为退烧药,禁止儿童使用)
  • 布洛芬:适合儿童使用

医生根据患者的病症,诊断出是发烧了,于是就在系统中寻找退烧药(等同于 K8S 的调度过程),找出来有两种退烧药 阿司匹林 和 布洛芬。

而根据患者是儿童,那么因此会把阿司匹林给排除掉,最后选择布洛芬。

这就是污点的作用,在本例中:

  • 阿司匹林 和 布洛芬,即为 K8S 中的 Node
  • 药品上的适用范围,就是打在药品(K8S 中 Node) 上的污点
  • 而找药的需求,就是 K8S 中的 Pod

可以看出,污点是从 Node 的角度,禁止那些不能容忍这些污点的 Pod 调度过来。

2. 污点与标签区别

污点与标签有什么不同呢?这是我们在学习污点时首先要搞清楚的问题。

由于标签和污点工作原理的不同,他们的适用场景也不一样。

标签通常用于为 Pod 指定分组,规定了 Pod 只能调度到这些分组里的 node 中,这是一种强制的做法。

污点通常用于将 Node 设置为专用节点,默认情况下普通 Pod 是无法调度过来,仅当你在 Pod 中指定了对应的容忍度才能调度。

3. 容忍度与污点

污点 也是打在 Node 上,可以理解为对外公开自己的“缺点”(并非真的缺点),想调度到我这边的,请明示说出你可以容忍我的缺点的,不然是调度不过来的。

使用如下命令为 worker01 打上污点,而 worker02 却没有任何污点

kubectl taint nodes worker02 gpu=true:NoSchedule

正常你创建的 Pod,没有进行特殊的配置,是无法调度到 worker02 的,只能调度到 worker01,即使你使用了 nodeSelector

Kubernetes 调度利器(二):污点与容忍度(压力驱逐)

只有你在 Pod 上加上了如下的容忍度(在 .spec 下),才能有可能地创建到 worker01 上

tolerations:
- key: "gpu"
  operator: "Equal"
  value: "true"
  effect: "NoSchedule"

千万要注意的是,上面说的是有可能,而不是一定。

如果要标准地调度到 gpu 的机器上,还要配合前面的 2. 如何打标签?[^1]

  • 标签:实现精度地调度的需求
  • 污点:避免产生不必要地浪费

Kubernetes 调度利器(二):污点与容忍度(压力驱逐)

上面节点上的污点的,我再翻译一下,意思是该机器上有 GPU,没有指定需要 GPU 的容忍度 的 Pod ,不能调度过来。

如果不使用 GPU 的 Pod 也创建到有 GPU 的节点上,那就是浪费资源,这是不能理解也不能允许的。

而后面的容忍度,意思是,我可以调度到有 gpu 的机器上,如果没有指定这个配置,就无法调度到 GPU 的机器上。

如果要删除原有的污点,可以在上面添加污点的命令最后加个减号 -

kubectl taint nodes worker02 gpu=true:NoSchedule-

4. 容忍度的配置

容忍度,由几个关键字段组成:

  • key:键(必填)
  • value:值,当 operator 为 Equal ,value 必填,当 operator 为 Exists ,value 就不用填写
  • operator:操作,可以为 Exists (存在即可匹配) 或者 Equal (value 必须与相等才算匹配)
  • effect:影响,有三个选项:NoSchedule、PreferNoSchedule、NoExecute

其中 effect 比较难理解,这边挑出来专门说一下,要理解 effect,就要理解 容忍度与污点的过滤原理。

简单来说,一个 Node 上可以设置多个污点,一个 Pod 也可以设置多个容忍度。

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

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

5. 污点的原生用途

在原生的 kubernetes 中是如何使用污点的呢?

在 kubernetes 的每个集群节点上,都有一个 kubelet 服务,它会监控集群节点的 CPU、内存、磁盘空间和文件系统的 inode 等资源。

当这些资源中的一个或者多个达到特定的消耗水平, kubelet 会主动给节点打上一个或者多个污点标记,这些标记的 effect 为 NoExecute

比如内存比较紧张的话,会打上 node.kubernetes.io/memory-pressure

比如磁盘比较紧张的话,会打上 node.kubernetes.io/disk-pressure

比如 pid 比较紧张的话,会打上 node.kubernetes.io/pid-pressure

而如果该节点上,已有一些 Pod 在运行,并且这些 Pod 没有配置以上三种对应的容忍度,则 kubelet 会开始驱逐的流程,一个一个的驱逐,直到节点不再有存在资源压力为止,才会清除污点,结束驱逐。

通常还会带上一个 tolerationSeconds,它意思是在污点出现后,Pod 还可以正常工作多少时间,也就是延迟多久再进行驱逐。

除了以上污点之外,还有其他常见的

  • node.kubernetes.io/not-ready:节点未准备好。这相当于节点状态 Ready 的值为 “False
  • node.kubernetes.io/unreachable:节点控制器访问不到节点. 这相当于节点状态 Ready 的值为 “Unknown”。
  • node.kubernetes.io/network-unavailable:节点网络不可用。
  • node.kubernetes.io/unschedulable: 节点不可调度。

而这些污点的 effect 通常为 NoSchedule,以防新的 Pod 调度过来,却无法正常工作。

6. 污点的进阶开发

污点的原理上面已经剖析得差不多了,在实际工作中,它被广泛应用于实现节点的专有专用。

但要实现节点的专有专用,还要有标签与选择器(nodeSelector)的配合才可以。

因此,你想要将 Pod 调度到专用节点上,你要添加 容忍度的配置,还要添加 nodeSelector 的配置。

那有没有办法,将这两个步骤,再简化成一个步骤呢?

K8S 中有一个 准入控制器 的概念,它可以理解为一个定义在 api-server 组件中的插件,当你在对对象进行操作时,这些插件可以拦截 api 的请求,并进行一些操作,

根据操作的不同,这类准入插件可以分为两类:

  • MutatingAdmissionWebhook:可以变更对象的配置
  • ValidatingAdmissionWebhook:可以验证对象

准入控制过程分为两个阶段。第一阶段,运行变更准入控制器。第二阶段,运行验证准入控制器,有某些控制器既是变更准入控制器又是验证准入控制器。

Kubernetes 调度利器(二):污点与容忍度(压力驱逐)

如果任何一个阶段的任何控制器拒绝了该请求,则整个请求将立即被拒绝,并向终端用户返回一个错误。

而 MutatingAdmissionWebhook 可以变量对象的配置,这不正是我们所需求的吗?

我们可以自定义一个MutatingAdmissionWebhook ,当检查到 Pod 有如下的容忍度时

tolerations:
- key: "dedicated"
  operator: "Equal"
  value: "gpu"
  effect: "NoSchedule"

就自动往 Pod 中添加如下的选择器配置,当然如果原有的 Pod 已经有了该段配置,就可以直接覆盖或跳过。

nodeSelect:
  gpu: true

自定义的准入控制器,其实也不难,Kubernetes 其实本身自带了非常多地准入控制器,可以模仿一下,写起来并不麻烦,具体的代码在:src/k8s.io/kubernetes/plugin/pkg/admission/

要注意的是,有些准入控制器,即是MutatingAdmissionWebhook 也是 ValidatingAdmissionWebhook。

下边我挑选一个 Kubernetes 自带的准入控制器,带你了解一下 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 是怎样工作的。

7. PodNodeSelector

创建一个全新的 namespace,名字叫 iswbm

kubectl create namespace iswbm

然后再使用 kubectl edit 命令,在该 namespacce 上添加 annotation 时(或者也可以通过在 apiserver 上指定对应的配置文件

apiVersion: v1
kind: Namespace
metadata:
  annotations:
    scheduler.alpha.kubernetes.io/node-selector: env=test
  name: iswbm

有了该 annotation 后,在该 namespace 下创建的 Pod 都只能创建到有 env=test 标签的 Node 上 — 这是 MutatingAdmissionWebhook 的部分

若 Pod 自己的 nodeSelector 和 PodNodeSelector 做完交集后,没有一个 node 满足条件,则会直接拒绝 — 这是 ValidatingAdmissionWebhook 的部分

这个实现的方式就是通过 PodNodeSelector 这个准入控制器,自动给该 namespace 下的 Pod 加上 nodeSelector 。

参考文档:

  1. 污点和容忍度
  2. 使用准入控制器

文章有帮助,请作者喝杯咖啡?

发表评论