K8S调度利器(二):污点与容忍度(压力驱逐)
理想情况下,你的集群中,有足够的资源能让你创建你期望的 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
只有你在 Pod 上加上了如下的容忍度(在 .spec
下),才能有可能地创建到 worker01 上
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
千万要注意的是,上面说的是有可能,而不是一定。
如果要标准地调度到 gpu 的机器上,还要配合前面的 2. 如何打标签?[1]
标签:实现精度地调度的需求
污点:避免产生不必要地浪费
上面节点上的污点的,我再翻译一下,意思是该机器上有 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:可以验证对象
准入控制过程分为两个阶段。第一阶段,运行变更准入控制器。第二阶段,运行验证准入控制器,有某些控制器既是变更准入控制器又是验证准入控制器。
如果任何一个阶段的任何控制器拒绝了该请求,则整个请求将立即被拒绝,并向终端用户返回一个错误。
而 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 。
本系列目录(已发布)