抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Kubernetes 节点调度

image-20220525094307190

标签概述

标签是Kubernetes极具特色的功能之一,它能够附加于Kubernetes的任何资源对象之上

​ 简单来说,标签就是“键值”类型的数据,可以在资源创建时直接指定,也可以随时按需添加到活动对象中,而后即可由标签选择器进行匹配度检查从而完成资源挑选,一个对象可拥有不止一个标签,而同一个标签也可以被添加到至多个资源之上

标签操作

下面我们来演示下如何操作标签

准备工作
编辑POD资源清单

我们需要先在我们的服务上面启动一个POD

1
vi nginx-pod.yml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
创建POD
1
2
kubectl apply -f nginx-pod.yml
kubectl get pod -o wide

image-20220527155055093

查看标签

通过该命令可以查看POD的标签

1
kubectl get pods --show-labels

image-20220527155150093

过滤标签

我们可以通过过滤标签来查看我们感兴趣的POD

1
2
3
kubectl get pods -l app
kubectl get pods -l app=nginx
kubectl get pods -l app=tomcat

因为我们没有创建app=tomcat的标签

image-20220527155815101

查看标签信息

我们可以通过使用以下命令来查看标签的内容

1
kubectl get pods -L app

image-20220527155917008

添加标签

有时候我们需要给POD增加一个标签可以通过如下的方式

1
2
kubectl label pod nginx env=prod
kubectl get pods -L env

这条命令给nginx的pod增加了一个env=prod的标签

image-20220527160207086

多条件过滤

有时候过滤标签可能是多个条件同时满足,我们可以使用逗号进行分割

1
2
kubectl get pods -l app,env
kubectl get pods -l app=nginx,env=prod

image-20220527160322166

修改标签

修改已有的标签的值,同上面添加标签一样,只是添加–overwrite参数

1
2
kubectl label pod nginx env=dev --overwrite
kubectl get pods -L env

image-20220527160422657

删除标签

删除标签和添加标签类型,只需要在需要删除的标签名字后面加上-

1
2
kubectl label pod nginx env-
kubectl get pods -L env

image-20220527160538880

标签选择器

标签选择器用于选择标签的查询条件或选择标准

kubernetes API目前支持两个选择器:基于等值关系以及基于集合关系,例如,env=productionenv!=qa是基于等值关系的选择器,而tier in(frontend,backend)则是基于集合关系的选择器。

选择器逻辑

使用标签选择器时还将遵循以下逻辑:

  • 同时指定的多个选择器之间的逻辑关系为“与”操作
  • 使用空值的标签选择器意味着每个资源对象都将被选中
  • 空的标签选择器将无法选出任何资源。
选择器类型
等值选择器

​ “=”、“==”和“!=”三种,其中前两个意义相同,都表示等值关系;最后一个表示不等关系

集合选择器
  • KEY in(VALUE1,VALUE2,…):指定的健名的值存在于给定的列表中即满足条件

  • KEY notin(VALUE1,VALUE2,…):指定的键名的值不存在与给定的列表中即满足条件

  • KEY:所有存在此健名标签的资源。

  • !KEY:所有不存在此健名标签的资源

演示示例
等值关系

查询标签app等于nginx的pod

1
kubectl get pods -l app=nginx

查询标签app不等于nginx的pod

1
kubectl get pods -l app!=nginx

image-20220527161225260

集合关系示例

查询标签app包含nginx的pod

1
kubectl get pods -l "app in(nginx)"

查询标签app不包含nginx的pod

1
kubectl get pods -l "app notin(nginx)"

image-20220527161253193

节点选择器

pod节点选择器是标签及标签选择器的一种应用,它能够让pod对象基于集群中工作节点的标签来挑选倾向运行的目标节点。

作用

​ 在定义pod资源清单时,可以通过nodeName来指定pod运行的节点,或者通过nodeSelector来挑选倾向的节点

节点选择器操作

下面我们演示下节点选择器的一般操作

查看节点默认的标签

通过下面命令我们可以查看节点选择器的默认标签有哪些

1
kubectl get nodes --show-labels

image-20220527161505323

节点添加标签

我们可以通过如下命令给节点添加标签

1
2
kubectl label nodes node01 service=nginx
kubectl get nodes -L service

image-20220527165840697

节点修改标签

如上面,修改标签只需要在添加的基础上添加–overwrite参数

1
2
kubectl label nodes node01 service=tomcat --overwrite
kubectl get nodes -L service

image-20220527165953363

删除节点标签

删除标签和添加标签类型,只需要在需要删除的标签名字后面加上-

1
2
kubectl label nodes node01 service-
kubectl get nodes -L service

image-20220527170106814

Pod调度器

​ kube-scheduler 是 kubernetes 的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理、更加充分的利用集群的资源,这也是我们选择使用 kubernetes 一个非常重要的理由。

什么是调度

调度,可以说是Kubernetes最核心的功能之一了

​ Pod是Kubernetes中最小的调度单元,而Pod又是运行在Node之上的,所谓调度,简单来说就是为一个新创建出来的 Pod,寻找一个最适合它运行的Node

调度流程

默认调度器

kube-scheduler负责分配调度Pod 到集群内的节点上,它监听kube-apiserver,查询还未分配Node的Pod,然后根据调度策略为这些Pod分配节点(更新Pod 的NodeName字段)

​ 默认情况下,kube-scheduler 提供的默认调度器能够满足我们绝大多数的要求,我们前面和大家接触的示例也基本上用的默认的策略,都可以保证我们的 Pod 可以被分配到资源充足的节点上运行,但是在实际的线上项目中,可能我们自己会比 kubernetes 更加了解我们自己的应用,比如我们希望一个 Pod 只能运行在特定的几个节点上,或者这几个节点只能用来运行特定类型的应用,这就需要我们的调度器能够可控。

​ kube-scheduler 的主要作用就是根据特定的调度算法和调度策略将 Pod 调度到合适的 Node 节点上去,是一个独立的二进制程序,启动之后会一直监听 API Server,获取到 PodSpec.NodeName 为空的 Pod,对每个 Pod 都会创建一个 binding

kube-scheduler structrue

需要考虑的因素

这个过程在我们看来好像比较简单,但在实际的生产环境中,需要考虑的问题就有很多了:

  • 如何保证全部的节点调度的公平性?要知道并不是所有节点资源配置一定都是一样的
  • 如何保证每个节点都能被分配资源?
  • 集群资源如何能够被高效利用?
  • 集群资源如何才能被最大化使用?
  • 如何保证 Pod 调度的性能和效率?
  • 用户是否可以根据自己的实际需求定制自己的调度策略
调度过程

调度主要分为以下几个部分

  • 首先是预选过程,过滤掉不满足条件的节点,这个过程称为 Predicates(过滤)
  • 然后是优选过程,对通过的节点按照优先级排序,称之为 Priorities(打分)
  • 最后从中选择优先级最高的节点,如果中间任何一步骤有错误,就直接返回错误

Predicates 阶段首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的节点将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试。

​ 所以我们在部署应用的时候,如果发现有 Pod 一直处于 Pending 状态,那么就是没有满足调度条件的节点,这个时候可以去检查下节点资源是否可用。

Priorities 阶段即再次对节点进行筛选,如果有多个节点都满足条件的话,那么系统会按照节点的优先级(priorites)大小对节点进行排序,最后选择优先级最高的节点来部署 Pod 应用。

Pod调度策略

一般而言pod的调度都是通过RC、Deployment等控制器自动完成,但是仍可以通过手动配置的方式进行调度,目的就是让pod的调度符合我们的预期

​ 但是在实际过程中,这并不满足需求,因为很多情况下,我们想控制某些pod到达某些节点上,那么应该怎么做呢?

调度策略分类

这就要求了解k8s对pod的调度规则,k8s提供了四大类调度方式:

  • 自动调度:运行在哪个节点上完全由scheduler经过一系列的算法得出
  • 定向调度:nodename、nodeselector
  • 亲和性调度:nodeaffinity、podaffinity、podantiaffinity
  • 污点(容忍)调度:Taints、toleration

调度原理

​ Kubernetes Scheduler 根据调度算法将 pod 调度到最优的节点上,和 OpenStack 和 Mesos 等非常类似,kube-scheduler 首先过滤不符合要求的节点,然后从符合要求的节点中根据权重选出最优节点,这两个步骤在 K8S 中分别被称为 predicates 和 priorities

predicates 类型

Predicates 主要有以下类型:

  • PodFitsResources:节点 CPU,内存资源是否充足。
  • 节点是否压力大:节点 CPU,内存,磁盘资源是否存在压力。
  • Volume 相关调度:节点是否支持相应的云厂商,卷的数量是否超出上限等。
  • MatchNodeSelector:节点亲和性调度,即 node affinity。
  • MatchInterPodAffinity:容器之间的亲和性调度,即 pod affinity。
  • 其它类型性

定向调度

定向调度,指的是利用在pod上声明nodename或者nodeselector,以此将pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度目标node不存在,也会向上面进行调度,只不过pod运行失败而已

nodename调度

nodename用于强制约束将pod调度到指定的name的pod节点上,这种方式,其实是直接跳过scheduler的调度逻辑,直接写入podlist表

创建资源清单

下面的配置是将一个pod强制调度到node1节点

1
vi pod-node-dispatch.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeName: node01 #指定调度节点为node01节点
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
应用配置

下面我们应用配置文件

1
2
kubectl apply -f pod-node-dispatch.yml
kubectl get pod -o wide

image-20220527171138460

删除pod

删除pod应用,pod控制器会重建pod

1
2
kubectl delete pod nginx-deployment-596b9b7fb6-4b55h
kubectl get pod -o wide

我们发现节点删除后,重新的应用还是落在了node1节点上,说明我们的节点调度生效的

image-20220527171246789

调度不存在的节点

我们尝试将节点调度到一个不存在的节点,配置nodeName: node03

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeName: node03 #定向调度到一个不存在的节点
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80

应用配置

1
2
kubectl apply -f pod-node-dispatch.yml
kubectl get pod -o wide

我们发现pod一直处于Pending状态,并没有成功创建,并且调度的节点是node03

image-20220527171732664

可以看见虽然被指定在了node3,但是由于node3不存在,pod无法启动

nodeselector

nodeselector用于将pod调度到添加了指定标签的node节点上,它是通过k8s的label-selector机制实现的,也即是说,在pod创建之前,会由,scheduler使用matchnodeselector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束

​ 此时需要首先给指定的node打上标签,并在pod中设置nodeSelector属性以完成pod的指定调度。

创建标签

给node2添加上disk=ssd的标签

1
2
kubectl label nodes node02 disk=ssd
kubectl get nodes -L disk

image-20220527172215649

创建资源清单

我们创建一个节点调度的资源清单

1
vi pod-node-selector-dispatch.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2 #设置副本数量为2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeSelector:
disk: ssd # pod部署在节点是disk:ssd的标签
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
应用配置
1
2
kubectl apply -f pod-node-selector-dispatch.yml
kubectl get pod -o wide

我们发现两个POD现在都处于Node02的节点上

image-20220527172511270

删除pod

删除pod应用,pod控制器会重建pod,我们通过标签将两个POD都给删除掉

1
2
kubectl delete pod -l app=nginx
kubectl get pod -o wide

我们发现POD重启后还是会被强制调度到node02节点上

image-20220527172706290

亲和性调度

​ 前面我们了解了 kubernetes 调度器的调度流程,我们知道默认的调度器在使用的时候,经过了 predicates 和 priorities 两个阶段,但是在实际的生产环境中,往往我们需要根据自己的一些实际需求来控制 Pod 的调度,这就需要用到 nodeAffinity(节点亲和性)、podAffinity(pod 亲和性) 以及 podAntiAffinity(pod 反亲和性)

亲和策略

亲和性调度可以分成软策略硬策略两种方式,对于亲和性和反亲和性都有这两种规则可以设置:

  • preferredDuringSchedulingIgnoredDuringExecution:软亲和

  • requiredDuringSchedulingIgnoredDuringExecution:硬亲和

软策略

软策略就是如果现在没有满足调度要求的节点的话,Pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓

硬策略

硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然就不干了

节点亲和策略

定义节点亲和性规则的关键点有两个,一是为节点配置合乎需求的标签,另一个是为Pod对象定义合理的标签选择器,从而能够基于标签选择出符合期望的目标节点。

​ 不过preferredDuringSchedulingIgnoredDuringExecutionrequiredDuringSchedulingIgnoredDuringExecution名字中的后半段字符串IgnoredDuringExecution隐含的意义所指,在Pod资源基于节点亲和性规则调度至某节点之后,节点标签发生了改变而不再符合此节点亲和性规则时,调度器不会将Pod对象从此节点上移除,因为它仅对新建的Pod对象生效。

​ 相比nodeSelector节点亲和可以匹配有更多的逻辑组合,不只是字符串的完全相等,支持的操作符有:In、NotIn、Exists、DoesNotExist、Gt、Lt

调度策略

调度分为软策略和硬策略,而不是硬性要求

  • 硬(required):必须满足
  • 软(preferred):尝试满足,但不保证
标签匹配逻辑

这里的匹配逻辑是 label 标签的值在某个列表中,现在 Kubernetes 提供的操作符有下面的几种:

  • In:label 的值在某个列表中 (这里的操作符,我们一般只用到in就足够了;)
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在

但是需要注意的是如果 nodeSelectorTerms 下面有多个选项的话,满足任何一个条件就可以了;如果 matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 Pod。

演示案例
创建资源清单

创建一个资源清单

1
vi node-affinity.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-affinity
labels:
app: node-affinity
spec:
replicas: 4
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
name: nginx
affinity: #定义亲和性
nodeAffinity: #节点亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- master #相当于只能调度到node01和node02节点,默认就不会调度到master节点
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: role
operator: In
values:
- web # 相当于尽量调度到 role=web 的节点上,如果找不到则使用默认调度策略

该资源清单包含两个亲和调度策略

  • 节点硬亲和:不能调度到master节点,只能调度到node01,node02节点上
  • 节点软亲和:尽量调度到 role=web 的节点上如果找不到则使用默认调度策略
查看节点的信息

我们先看下节点的kubernetes.io/hostname标签的信息

1
kubectl get node -L kubernetes.io/hostname

image-20220529102044535

查看下节点的role标签信息

1
kubectl get node -L role

我们发现当前节点没有role标签

image-20220529102208382

应用配置

应用配置后并查看节点信息

1
2
kubectl apply -f node-affinity.yaml
kubectl get pod -o wide

我们发现四个节点,并且没有被调度到master节点上,说明说明硬亲和策略生效了

image-20220529102939901

添加标签

因为没添加role=web的标签,软亲和没有生效,我们给node02添加上相应的标签

1
kubectl label nodes node02 role=web

image-20220529103236328

查看节点分布情况,发现节点没有发生变化,说明节点亲和策略只针对于创建pod生效,已有的pod不会变化

image-20220529103305161

重新应用配置

因为亲和策略只针对于创建有效,我们先删除pod然后在自动创建

1
2
kubectl delete pod -l app=nginx
kubectl get pod -o wide

我们发现重新创建标签后,节点的软亲和策略已经生效了,都已经调度到了node02节点上

POD 亲和策略

​ Pod 亲和性(podAffinity)主要解决 Pod 可以和哪些 Pod 部署在同一个拓扑域中的问题(其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等),而 Pod 反亲和性主要是解决 Pod 不能和哪些 Pod 部署在同一个拓扑域中的问题,它们都是处理的 Pod 与 Pod 之间的关系。

​ 比如一个 Pod 在一个节点上了,那么我这个也得在这个节点,或者你这个 Pod 在节点上了,那么我就不想和你待在同一个节点上。

这个是很重要的,线上业务基本要配置这种podAntiAffinity

演示案例

由于我们这里只有一个集群,并没有区域或者机房的概念,所以我们这里直接使用主机名来作为拓扑域,把 Pod 创建在同一个主机上面

创建busybox

因为我们的的调度用到了busybox容器,所以我们先创建一个busybox的POD

1
vi pod-busybox.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: busybox-pod
labels:
app: busybox-pod
spec:
containers:
- name: busybox
image: busybox:1.35.0
command:
- "/bin/sh"
- "-c"
- "sleep 3600"

并启动运行

1
2
kubectl apply -f pod-busybox.yaml
kubectl get pod -o wide

image-20220529105911126

创建资源清单

我创建一个POD硬亲和的资源清单

1
vi pod-affinity.yaml

这里我们创建的是一个硬亲和,调度到的节点必须要有app=busybox-pod标签的pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
name: nginx
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector: #去选择具有app in ["busybox-pod"]的pod所在的hostname这个域。
matchExpressions:
- key: app
operator: In
values:
- busybox-pod
topologyKey: kubernetes.io/hostname # 调度的拓扑域,我们没有配置集群,所以用hostname代理集群

应用配置

应用配置并查看POD的部署情况

1
2
kubectl apply -f pod-affinity.yaml
kubectl get pod -o wide

我们发现创建的三个POD都处于node02节点上,这个就是pod亲和

image-20220529110528162

删除busybox-pod

我们删除掉busybox-pod在检查看POD的情况

1
2
kubectl delete pod -l app=busybox-pod
kubectl get pod -o wide

我们删除节点后,发现硬亲和的pod并没有收到影响,说明POD亲和调度也是在创建的时候才生效

image-20220529110830545

重建Nginx

我们对nginx节点进行重建,然后看看情况,前提是busybox-pod已经被删除了

1
2
kubectl delete pod -l app=nginx
kubectl get pod -o wide

我们发现节点一直处于Pending状态,说明没有找到busybox-pod,不能进行创建nginx的pod,这个就是硬亲和

image-20220529111105318

查看POD的详细信息

1
kubectl describe pod pod-affinity-ff5d5f6cc-67lqf

告诉我们没有可用的节点

image-20220529111357676

拓扑域

我们这个地方使用的是 kubernetes.io/hostname 这个拓扑域,意思就是我们当前调度的 Pod 要和目标的 Pod 处于同一个主机上面,因为要处于同一个拓扑域下面。

​ 为了说明这个问题,我们把拓扑域改成 beta.kubernetes.io/os,同样的我们当前调度的 Pod 要和目标的 Pod 处于同一个拓扑域中,目标的 Pod 是拥有 beta.kubernetes.io/os=linux 的标签,而我们这里所有节点都有这样的标签,这也就意味着我们所有节点都在同一个拓扑域中,所以我们这里的 Pod 可以被调度到任何一个节点。

查看拓扑域
1
2
kubectl get node -L kubernetes.io/hostname
kubectl get node -L beta.kubernetes.io/os

image-20220529113603661

演示案例

我们重新运行上面的 app=busybox-pod 的 Pod,然后再更新下我们这里的资源对象:

修改资源清单
1
vi pod-affinity.yaml

我们修改资源清单,将

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
name: nginx
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector: #去选择具有app in ["busybox-pod"]的pod所在的hostname这个域。
matchExpressions:
- key: app
operator: In
values:
- busybox-pod
topologyKey: beta.kubernetes.io/os #
应用配置

应用修改的配置

1
2
kubectl apply -f pod-affinity.yaml
kubectl get pod -o wide

可以看到现在是分别运行在2个节点下面的,因为他们都属于 beta.kubernetes.io/os 这个拓扑域(而busybox-pod也刚好在这个域下,因此符合硬策略要求)。

image-20220529113832881

​ 这里需要注意下:通过上面这个实验可以看到,这2个node节点都属于beta.kubernetes.io/os这个拓扑域,但只有node1上有app=pod-affitity这个标签的pod,从结果可以看到也是可以调度到node2上的。

POD反亲和策略

Pod 反亲和性(podAntiAffinity)则是反着来的,比如一个节点上运行了某个 Pod,那么我们的模板 Pod 则不希望被调度到这个节点上面去了。

演示案例
运行busybox-pod
1
2
kubectl apply -f pod-busybox.yaml
kubectl get pod -o wide
修改资源清单

我们把上面的 podAffinity 直接改成 podAntiAffinity

1
vi pod-antiaffinity.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-affinity
labels:
app: pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: pod-affinity
template:
metadata:
labels:
app: pod-affinity
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
name: nginx
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
- labelSelector: #去选择具有app in ["busybox-pod"]的pod所在的hostname这个域。
matchExpressions:
- key: app
operator: In
values:
- busybox-pod
topologyKey: kubernetes.io/hostname # 调度的拓扑域,我们没有配置集群,所以用hostname代理集群

​ 这里的意思就是如果一个节点上面有一个 app=busybox-pod 这样的 Pod 的话,那么我们的 Pod 就别调度到这个节点上面来,也就是app=busybox-podapp=nginx在一个节点上不能共存,上面我们把app=busybox-pod 这个 Pod 固定到了 node02 这个节点上面的,所以正常来说我们这里的 Pod 不会出现在该节点上

应用配置

我们创建POD

1
2
kubectl apply -f pod-antiaffinity.yaml
kubectl get pod -o wide

我们发现app=busybox-podapp=nginx处于两个不同的节点上不能部署在一起,这个就是反亲和

image-20220529113002270

污点与容忍

污点(taints)是定义节点之上的键值型属性数据,用于让节点拒绝将Pod调度运行于其上,除非该Pod对象具有接纳节点污点的容忍度。

​ 而容忍度(tolerations)是定义在Pod对象上的键值型属性数据,用于配置其可容忍的节点污点,而且调度器仅能将Pod对象调度至其能够容忍该节点污点的节点之上,如下图所示:

img

​ 上面的节点选择器(nodeSelector)和节点亲和性(nodeAffinity)两种调度方式都是通过在Pod对象上添加标签选择器来完成对特定类型节点标签的匹配,它们实现的是由Pod选择节点的机制。

​ 而污点和容忍度则是通过向节点添加污点信息来控制Pod对象的调度结果,从而赋予了节点控制何种Pod对象能够调度于其上的主控权。简单来说,节点亲和性使得Pod对象被吸引到一类特定的节点,而污点则相反,提供了让节点排斥特定Pod对象的能量。

Kubernetes使用PodToleratesNodeTaints预选策略和TaintTolerationPriority优选函数来完成此种类型的高级调度机制。

应用场景

  • 专用节点:根据业务线将Node分组管理,希望在默认情况下不调度该节点,只有配置了污点容忍才允许分配
  • 配备特殊硬件:部分Node配有SSD硬盘、GPU,希望在默认情况下不调度该节点,只有配置了污点容忍才允许分配
  • 基于Taint的驱逐
  • 对于 nodeAffinity 无论是硬策略还是软策略方式,都是调度 Pod 到预期节点上。而污点(Taints)恰好与之相反,如果一个节点标记为 Taints ,除非 Pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 Pod。

  • 比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 Pod,则污点就很有用了,Pod 不会再被调度到 taint 标记过的节点,我们使用 kubeadm 搭建的集群默认就给 master 节点添加了一个污点标记,所以我们看到我们平时的 Pod 都没有被调度到 master 上去。

容忍度定义

污点定义在节点的nodeSpec中,容忍度定义在PodpodSpec中,都是键值型数据,都额外支持(effect)标记,语法格式为”key=value:effect”,其中keyvalue的用法及格式与资源注解信息相似,而effect则用于定义对Pod对象的排斥等级,

容忍度类型

容忍度主要包含以下三种类型:

  • NoSchedule:不能容忍此污点的新Pod对象不可调度至当前节点,属于强制型约束关系,节点上现存的Pod对象不受影响。
  • PreferNoScheduleNoSchedule的柔性约束版本,即不能容忍此污点的新Pod对象尽量不要调度至当前节点,不过无其它节点可供调度时也允许接受相应的Pod对象,节点上现存的Pod对象不受影响。
  • NoExecute:不能容忍此污点的新Pod对象不可调度至当前节点,属于强制型约束关系,而且节点上现存的Pod对象因节点污点变动或Pod容忍度变动而不再满足匹配规则时,Pod对象将被驱逐。
容忍度操作符

Pod对象上定义容忍度时,支持两种操作符:

  • 等值比较(Equal),表示容忍度与污点必须在keyvalueeffect三者之上完全匹配;

  • 存在性判断(Exists),表示二者的keyeffect必须完全匹配,而容忍度中的value字段要使用空值。

污点定义

污点:其实是一个label标签,只不过它是一个特殊的label标签。

污点匹配规则

一个节点可以配置使用多个污点,一个Pod对象也可以有多个容忍度,不过二者在进行匹配检查时遵循如下逻辑:

  1. 首先处理每个有着与之匹配的容忍度的污点。
  2. 不能匹配到的污点上,如果存在了一个使用NoSchedule效用标识,则拒绝调度Pod对象至此节点。
  3. 不能匹配到的污点上,若没有任何一个使用了NoSchedule效用标识,但至少有一个使用了PreferNoScheduler,则应尽量避免将Pod对象调度至此节点。
  4. 如果至少有一个不匹配的污点使用了NoExecute效用标识,则节点将立即驱逐Pod对象,或者不予调度至给定节点;另外,即便容忍度可以匹配到使用了NoExecute效用标识的污点,若在定义容忍度时还同时使用tolerationSeconds属性定义了容忍时限,则超出时限后期也将被节点驱逐。
默认污点

使用kubeadm部署的Kubernetes集群,其Master节点将自动添加污点信息以阻止不能容忍此污点的Pod对象调度至此节点,因此,用户手动创建的未特意添加容忍此污点容忍度的Pod对象将不会被调度至此节点:

1
kubectl describe node master |grep Taints

image-20220530090717675

污点管理

污点定义规则

任何符合其键值规范要求的字符串均可用于定义污点信息:仅可使用字母、数字、连接符、点号和下划线,且仅能以字母或数字开头,其中键名长度上限为253个字符,值最长为63个字符。

​ 可以用污点用来描述具体的部署规划,键名比如node-type、node-role、node-project或node-geo等,还可以在必要时戴上域名以描述其额外的信息,如node-type.ilinux.io等。

添加污点

需要注意的是,即便是同一个键值数据,若其效用标识不同,则其也分属于不同的污点信息

添加NoSchedule污点

我们给node01添加一个 node-type=production的污点,并且这个污点属于NoSchedule类型

1
kubectl taint nodes node01 node-type=production:NoSchedule

此时,node01上已有的Pod对象不受影响,但新建的Pod若不能容忍此污点将不能再被调度至此节点

image-20220530091508070

添加NoExecute污点

我们给node01添加一个 node-type=production的污点,并且这个污点属于NoExecute类型

1
kubectl taint nodes node01 node-type=production:NoExecute

此时,node01上已有的Pod将会被驱逐出去

image-20220530092907794

查看污点信息

我们可以通过如下命令来查看污点信息

1
kubectl get nodes node01 -o go-template={{.spec.taints}}

image-20220530093829201

删除污点

删除污点,仍通过kubectl taint命令进行,但是要使用如下的命令格式,省略效用标识则表示删除使用指定键名的所有污点,否则就只是删除指定健名上对应效用标识的污点:

命令格式
1
kubectl taint nodes <node-name> <key>[:<effect>]-
删除单个污点

我们把刚刚创建的污点删除掉

1
2
kubectl taint nodes node01 node-type:NoSchedule-
kubectl get nodes node01 -o go-template={{.spec.taints}}

image-20220530093902280

删除所有污点

若要删除使用指定健名的所有污点,则在删除命令中省略效用标识即能实现

1
2
kubectl taint nodes node01 node-type-
kubectl get nodes node01 -o go-template={{.spec.taints}}

image-20220530093925502

POD容忍度

Pod对象的容忍度可通过其spec.tolerations字段进行添加,根据使用的操作符不同,主要有两种可用的形式:

  • 一种是与污点信息完全匹配的等值关系;

  • 另一种是判断污点信息存在性的匹配方式。

使用Equal操作符的示例如下所示,其中tolerationSeconds用于定义延迟驱逐当前Pod对象的时长。

tolerations属性

对于 tolerations 属性的写法,其中的 key、value、effect 与 Node 的 Taint 设置需保持一致, 还有以下几点说明:

  • 如果 operator 的值是 Exists,则 value 属性可省略
  • 如果 operator 的值是 Equal,则表示其 key 与 value 之间的关系是 equal(等于)
  • 如果不指定 operator 属性,则默认值为 Equal

两个特殊值

  • 空的 key 如果再配合 Exists 就能匹配所有的 key 与 value,也就是是能容忍所有节点的所有 Taints
  • 空的 effect 匹配所有的 effect
NoSchedule容忍度
创建资源清单
1
vi deploy-taint-deployment.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-taint-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80

我们创建了一个普通的Deployment,然后应用配置

1
2
kubectl apply -f deploy-taint-deployment.yml
kubectl get pod -o wide

image-20220530095609057

创建污点

我们在node01节点上创建一个NoSchedule的污点

1
2
kubectl taint node node01 node-type=production:NoSchedule
kubectl get nodes node01 -o go-template={{.spec.taints}}

image-20220530095819013

查看POD部署

我们再来查看下POD的部署情况

1
kubectl get pod -o wide

我们发现POD没有任何变化,说明NoSchedule不针对已经部署的服务

image-20220530095900862

删除POD

下面我们删除掉POD,然后再查看POD的部署情况

1
2
kubectl delete pod -l app=nginx
kubectl get pod -o wide

我们发现新创建的POD都已经部署到了node02节点,说明NoSchedule只针对于新创建的POD

image-20220530100107555

配置容忍度

现在我们改写配置文件,增加POD的容忍度,让POD可以容忍NoSchedule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-taint-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoSchedule"
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80

这里我们配置了POD的容忍度,让POD可以在node-type=production:NoSchedule的node上生存

1
2
kubectl apply -f deploy-taint-deployment.yml
kubectl get pod -o wide

我看看到一旦配置容忍度后,我们的pod就被调度到了node1节点上,这个就是pod的容忍度

image-20220530102651143

NoExecute容忍度
查看POD部署

我们现在先看下服务的部署情况

1
kubectl get pod -o wide

我们看下当前我们的POD运行在node01和node02节点上

image-20220530104019090

创建污点

我们在node02节点上创建一个NoExecute的污点

1
2
kubectl taint node node02 node-type=production:NoExecute
kubectl get nodes node02 -o go-template={{.spec.taints}}

image-20220530104130504

查看POD部署

现在我们在看下POD的部署情况

1
kubectl get pod -o wide

我们发现node02上面的POD已经被驱逐出去了,说明NoExecute不允许运行没有容忍度的POD

image-20220530104217066

配置容忍度

我们编辑资源清单,并让POD的容忍度支持NoExecute

1
vi deploy-taint-deployment.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-taint-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoExecute"
tolerationSeconds: 30
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80

这里我们配置了POD的容忍度,让POD可以在node-type=production:NoExecute的node上生存

1
2
kubectl apply -f deploy-taint-deployment.yml
kubectl get pod -o wide

我们设置容忍度后,就会被调度到了node02节点,但是这个容忍度不会被调度到了node01节点上

image-20220530105010615

Exists容忍度

我们可以通过Exists进行判断,修改资源配置清单如下,下面这段表示,只要有污点key为node-type,不论值为什么且是什么类型的污点,都能容忍

编辑资源清单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-taint-deployment
spec:
replicas: 4
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
tolerations:
- key: "node-type"
operator: "Exists" #配置为Exists容忍度
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
应用配置

因为我们两个节点的污点都配置了node-type=production,而我们配置的是Exists容忍度,则可以将pod部署在node01,node02

1
2
kubectl apply -f deploy-taint-deployment.yml
kubectl get pod -o wide

image-20220530110113185

评论