定向调度,指的是利用在 Pod 上声明的 nodeName 或 nodeSelector ,以此将 Pod 调度到期望的 Node 节点上。
注意:这里的调度是强制的,这就意味着即使要调度的目标 Node 不存在,也会向上面进行调度,只不过 Pod 运行失败而已。
nodeName
是节点选择约束的最简单方法,但是由于其自身限制,通常不使用它。 nodeName
是 PodSpec 的一个字段。 如果它不为空,调度器将忽略 Pod,并且给定节点上运行的 kubelet 进程尝试执行该 Pod。 因此,如果 nodeName
在 Pod 的 Spec 中指定了,则它优先于 nodeSelector 。
使用 nodeName
来选择节点的一些限制:
示例如下:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
nodeName: k8s-node1 # 指定调度到k8s-node1节点上
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
nodeSelector
是节点选择约束的最简单推荐形式。nodeSelector
是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行,该节点的标签中 必须包含这里的每个键值对(它也可以具有其他标签)。 最常见的用法的是一对键值对。
除了自己 添加 的标签外,节点还预制了一组标准标签。 参见这些常用的标签,注解以及污点:
kubernetes.io/hostname
failure-domain.beta.kubernetes.io/zone
failure-domain.beta.kubernetes.io/region
topology.kubernetes.io/zone
topology.kubernetes.io/region
beta.kubernetes.io/instance-type
node.kubernetes.io/instance-type
kubernetes.io/os
kubernetes.io/arch
注意:这些标签的值是特定于云供应商的,因此不能保证可靠。 例如,kubernetes.io/hostname
的值在某些环境中可能与节点名称相同, 但在其他环境中可能是一个不同的值。
示例:
打标签:
# 给 k8s-node2 打上标签
kubectl label node k8s-node2 nodeevn=pro
pod的yaml:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
nodeSelector:
nodeevn: pro # 指定调度到 nodeevn = pro 标签的 Node 节点上
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
虽然定向调度的两种方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的 Node,那么 Pod 将不会被运行,即使在集群中还有可用的 Node 列表也不行,这就限制了它的使用场景。
基于上面的问题,Kubernetes 还提供了一种亲和性调度(Affinity)。它在 nodeSelector 的基础之上进行了扩展,可以通过配置的形式,实现优先选择满足条件的 Node 进行调度,如果没有,也可以调度到不满足条件的节点上,使得调度更加灵活。
Affinity 主要分为三类:
亲和性和反亲和性的使用场景的说明:
nodeAffinity 概念上类似于 nodeSelector
,它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点。 但是比nodeSelector更加强大。
支持硬性过滤 和 软性评分:
RequiredDuringSchedulingIgnoredDuringExecution
:硬亲和力,即支持必须部署在指定的节点上,也支持必须不部署在指定的节点上。支持多条件之间的逻辑或运算。PreferredDuringSchedulingIgnoredDuringExecution
:软亲和力:尽量部署在满足条件的节点上,或尽量不要部署在被匹配的节点上。支持设置条件权重。支持运算符:
In
:部署在满足条件的节点上NotIn
:匹配不在条件中的节点,实现节点反亲和性Exists
:只要存在 key 名字就可以,不关心值是什么DoesNotExist
:匹配指定 key 名不存在的节点,实现节点反亲和性Gt
:value 为数值,且节点上的值小于指定的条件Lt
:value 为数值,且节点上的值大于指定条件nodeAffinity 的可选配置项:
pod.spec.affinity.nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution # Node节点必须满足指定的所有规则才可以,硬性过滤
nodeSelectorTerms # 节点选择列表
matchFields # 按节点字段列出的节点选择器要求列表
matchExpressions # 按节点标签列出的节点选择器要求列表(推荐)
key # 键
values # 值
operator # 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
preferredDuringSchedulingIgnoredDuringExecution # 优先调度到满足指定的规则的Node,软性评分 (倾向)
preference # 一个节点选择器项,与相应的权重相关联
matchFields # 按节点字段列出的节点选择器要求列表
matchExpressions # 按节点标签列出的节点选择器要求列表(推荐)
key # 键
values # 值
operator # 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
weight # 倾向权重,在范围1-100。
注意:
如果我们修改或删除 Pod 调度到节点的标签,Pod 不会被删除;换言之,亲和调度只是在 Pod 调度期间有效。
如果同时定义了 nodeSelector 和 nodeAffinity ,那么必须两个条件都满足,Pod 才能运行在指定的 Node 上。
如果 nodeAffinity 指定了多个 nodeSelectorTerms ,那么只需要其中一个能够匹配成功即可。
如果一个 nodeSelectorTerms 中有多个 matchExpressions ,则一个节点必须满足所有的才能匹配成功。
硬性过滤示例:
打标签
# 给 k8s-node1 和 k8s-nodes 打上标签
kubectl label node k8s-node1 disktype=ssd
kubectl label node k8s-node2 disktype=hdd
pod的yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
affinity:
nodeAffinity:
# DuringScheduling(调度期间有效)IgnoredDuringExecution(执行期间忽略,执行期间:Pod 运行期间)
requiredDuringSchedulingIgnoredDuringExecution: # 硬性过滤:Node 节点必须满足指定的所有规则才可以
nodeSelectorTerms:
- matchExpressions: # 所有 matchExpressions 满足条件才行
- key: disktype # 节点标签的key
operator: In # key在values种的才满足体检
values:
- ssd
- hdd
restartPolicy: Always
软性评分示例:
打标签:
# 给 k8s-node1 和 k8s-nodes 打上标签
kubectl label node k8s-node1 disk=50 gpu=1000
kubectl label node k8s-node2 disk=30 gpu=5000
pod的yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: http
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
affinity:
nodeAffinity:
# DuringScheduling(调度期间有效)IgnoredDuringExecution(执行期间忽略,执行期间:Pod 运行期间)
preferredDuringSchedulingIgnoredDuringExecution: # 软性评分:优先调度到满足指定的规则的 Node
- weight: 90 # 权重,权重高的优先调度
preference: # 一个节点选择器项,与相应的权重相关联
matchExpressions:
- key: disk # 标签key
operator: Gt # 等于
values:
- "40"
- weight: 10 # 权重
preference: # 一个节点选择器项,与相应的权重相关联
matchExpressions:
- key: gpu
operator: Gt
values:
- "4000"
restartPolicy: Always
podAffinity 主要实现以运行的 Pod 为参照,实现让新创建的 Pod 和参照的 Pod 在一个区域的功能。
podAntiAffinity 主要实现以运行的 Pod 为参照,让新创建的 Pod 和参照的 Pod 不在一个区域的功能。
PodAffinity 的可选配置项:
pod.spec.affinity.podAffinity
requiredDuringSchedulingIgnoredDuringExecution # 硬限制
namespaces # 指定参照pod的namespace
topologyKey # 指定调度作用域
labelSelector # 标签选择器
matchExpressions # 按节点标签列出的节点选择器要求列表(推荐)
key # 键
values # 值
operator # 关系符 支持In, NotIn, Exists, DoesNotExist.
matchLabels # 指多个matchExpressions映射的内容
preferredDuringSchedulingIgnoredDuringExecution # 软限制
podAffinityTerm # 选项
namespaces
topologyKey
labelSelector
matchExpressions
key # 键
values # 值
operator
matchLabels
weight # 倾向权重,在范围0-1
注意:topologyKey 用于指定调度的作用域,例如:
如果指定为 kubernetes.io/hostname(可以通过 kubectl get node --show-labels 查看),那就是以 Node 节点为区分范围。
如果指定为 kubernetes.io/os,则以 Node 节点的操作系统类型来区分。
示例:在一个两节点的集群中,部署一个使用 redis 的 WEB 应用程序,并期望 web-server 尽可能和 redis 在同一个节点上。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deploy
namespace: default
labels:
app: redis-deploy
spec:
selector:
matchLabels:
app: store
replicas: 2
template:
metadata:
labels:
app: store
spec:
affinity: # 亲和性配置
podAntiAffinity: # Pod 反亲和性,符合以下指定条件不会被调度过去
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。
# node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
# 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
containers:
- name: redis-server
image: redis:5.0.14-alpine
resources:
limits:
memory: 500Mi
cpu: 1
requests:
memory: 250Mi
cpu: 500m
ports:
- containerPort: 2375
name: redis
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: default
labels:
app: nginx-deploy
spec:
selector:
matchLabels:
app: web-store
replicas: 2
template:
metadata:
labels:
app: web-store
spec:
affinity: # 亲和性配置
podAffinity: # Pod 亲和性,符合以下指定条件会被调度过去
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。
# node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
# 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
podAntiAffinity: # Pod 反亲和性,符合以下指定条件不会被调度过去
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: kubernetes.io/hostname # 拓扑键,划分逻辑区域。
# node 节点以 kubernetes.io/hostname 为拓扑网络,如果 kubernetes.io/hostname 相同,就认为是同一个东西。
# 亲和就是都放在这个逻辑区域,反亲和就是必须避免放在同一个逻辑区域。
containers:
- name: nginx
image: nginx:1.20.2
resources:
limits:
cpu: 200m
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
ports:
- containerPort: 80
name: nginx
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
restartPolicy: Always
上面yaml中定义了两个Deployment,来完成两个pod的部署。其中第一个是redis应用,它的标签是app: store,设置了一个反亲和性,标签也是app: store,这是为了让redis尽量部署在不同节点。第二个是一个web应用,它的标签是app: web-store,其中设置了一个反亲和性,标签是app: web-store,为了使web部署在不同的node,还有一个亲和性标签app: store,则是为了和redis尽量部署在同一节点。
前面的调度方式都是站在 Pod 的角度上,通过在 Pod 上添加属性,来确定 Pod 是否要调度到指定的 Node 上,其实我们也可以站在 Node 的角度上,通过在 Node 上添加污点(Taint),来决定是否运行 Pod 调度过来。
Node 被设置了污点之后就和 Pod 之间存在了一种相斥的关系,进而拒绝 Pod 调度进来,甚至可以将已经存在的 Pod 驱逐出去。
容忍度(Toleration)是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。
污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。
应用场景:
kubectl taint nodes nodename dedicated=groupName:NoSchedule
), 然后给这组用户的 Pod 添加一个相对应的容忍度 (通过编写一个自定义的准入控制器, 很容易就能做到)。 拥有上述容忍度的 Pod 就能够被调度到上述专用节点,同时也能够被调度到集群中的其它节点。 如果你希望这些 Pod 只能被调度到上述专用节点, 那么你还需要给这些专用节点另外添加一个和上述污点类似的 label(例如:dedicated=groupName
), 同时还要在上述准入控制器中给 Pod 增加节点亲和性要求,要求上述 Pod 只能被调度到添加了 dedicated=groupName
标签的节点上。kubectl taint nodes nodename special=true:NoSchedule
或 kubectl taint nodes nodename special=true:PreferNoSchedule
), 然后给使用了这类特殊硬件的 Pod 添加一个相匹配的容忍度。 和专用节点的例子类似,添加这个容忍度的最简单的方法是使用自定义 准入控制器。污点的格式为:
key=value:effect
# key 和 value 是污点的标签及对应的值
# effect 描述污点的作用
effect 支持如下的三个选项:
PreferNoSchedule
:Kubernetes 将尽量避免把 Pod 调度到具有该污点的 Node 上,除非没有其他节点可以调度;换言之,尽量不要来,除非没办法。NoSchedule
:Kubernets 将不会把 Pod 调度到具有该污点的 Node 上,但是不会影响当前 Node 上已经存在的 Pod ;换言之,新的不要来,在这的就不要动。NoExecute
:Kubernets 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐;换言之,新的不要来,这这里的赶紧走。注意:NoExecute 一般用于实际生产环境中的 Node 节点的上下线。
污点语法:
# 设置污点
kubectl taint node xxx key=value:effect
# 去除污点
kubectl taint node xxx key:effect-
# 去除所有污点
kubectl taint node xxx key-
# 查看污点
kubectl describe node xxx | grep -i taints
kubeadm 安装的集群上 k8s-master 自带有污点:node-role.kubernetes.io/mater:Noschedule。这是因为master节点用来管理集群,一般不用来直接部署pod。
污点演示(为了演示效果更为明显,暂时停止 k8s-node2 节点,现在只有k8s-node1 节点):
tag=xudaxian:PreferNoSchedule
,然后创建 Pod1 (Pod1 可以运行)tag=xudaxian:NoSchedule
,然后创建Pod2(Pod1 可以正常运行,Pod2 失败)。tag=xudaxian:NoExecute
,然后创建 Pod3(Pod1、Pod2、Pod3失败)。上面介绍了污点的作用,我们可以在 Node上 添加污点用来拒绝 Pod 调度上来,但是如果就是想让一个 Pod 调度到一个有污点的 Node 上去,这时候应该怎么做?这就需要使用到容忍。
污点就是拒绝,容忍就是忽略,Node 通过污点拒绝 Pod 调度上去,Pod 通过容忍忽略拒绝。
容忍的详细配置:
kubectl explain pod.spec.tolerations
......
FIELDS:
key # 对应着要容忍的污点的键,空意味着匹配所有的键
value # 对应着要容忍的污点的值
operator # key-value的运算符,支持Equal和Exists(默认)
effect # 对应污点的effect,空意味着匹配所有影响
tolerationSeconds # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间
当满足如下条件的时候,Kubernetes 认为污点和容忍匹配:
特殊情况:
示例:
设置污点
kubectl taint node k8s-node1 tag=xudaxian:NoExecute
pod的yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-toleration
spec:
containers: # 容器配置
- name: nginx
image: nginx:1.20.2
imagePullPolicy: IfNotPresent
ports:
- name: nginx-port
containerPort: 80
protocol: TCP
tolerations: # 容忍
- key: "tag" # 要容忍的污点的key
operator: Equal # 操作符
value: "xudaxian" # 要容忍的污点的value
effect: NoExecute # 添加容忍的规则,这里必须和标记的污点规则相同
可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点。需要注意以下情况:
NoSchedule
的污点, 则 Kubernetes 不会将 Pod 调度到该节点。NoSchedule
的污点, 但是存在至少一个 effect 值为 PreferNoSchedule
的污点, 则 Kubernetes 会 尝试 不将 Pod 调度到该节点。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 不会被调度到上述节点,因为其没有容忍度和第三个污点相匹配。 但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。
通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute
的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐,任何可以忍受这个污点的 Pod 都不会被驱逐。 但是,如果 Pod 存在一个 effect 值为 NoExecute
的容忍度指定了可选属性 tolerationSeconds
的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。例如,
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。 如果在此之前上述污点被删除了,则 Pod 不会被驱逐。