3.3 调度利器(三):亲和与反亲和(服务容灾)¶
将一个 Pod 分配到某一个可以满足 Pod 资源请求的节点上,这一过程称之为调度。
理想情况下,你的集群中,有足够的资源能让你创建你期望的 Pod,如此一来,你就有理由不关心你的节点的资源还剩多少,有理由不关心 K8S 调度 Pod 的细节。
可事实上,你的集群资源是有限的,为了能让节点资源得到合理分配、有效利用,需要你对节点进行规划。
比如哪些机器是高性能的机器,哪些是普通机器,哪些是专用机器,尽量避免让普通的应用跑在高性能的机器上。
除此之外,有些应用,出于高可用的考虑,还需要应用部署多个副本,并分散开在不同的域里。
而关于这些内容,可以分成三个部分:
标签与选择器
污点与容忍度
亲和与反亲和
前面两篇文章已经介绍了 标签与选择器 与 污点与容忍度,本篇文章讲一下 亲和与反亲和。
1. 通俗理解亲和性¶
按照惯例,解释一个新名词前,我会拿生活中的例子做类比,方便大家轻松上手。
公司组织员工出去团建,带头人策划了一个小游戏,这个小游戏会将成员分成几个不同的小组进行 PK,最终以团队的比分做为排名依据。
员工可以自由选择队友,这时候就有两种选择的标准:
第一种:我有社交恐惧症,只选自己熟悉的同事,自己容易融入。这种就是亲和性原则
第二种:我有社交牛逼症,只选自己陌生的同事,能交到新朋友。这种就是反亲和原则
这里的员工就是 K8S 中的 Pod,而“熟悉” 和 “陌生” 就是 Pod 上的标签。
要注意的是在这里有一点点不一样,因为对于每个人来说这里的标签值是不一样的,而在 Pod 上标签是固定值
2. 亲和性调度与 nodeSelector¶
以你目前的知识储备来看,应该会认为上面的亲和性做法,和之前学习过的 nodeSelector 很像吧?
仅以上面的例子来看,确实亲和性做法,就是 nodeSelector。
但实际上亲和性调度,远比 nodeSelector 强大许多,还是以上面的亲和性做法来举例
若以 nodeSelector 来实现上面的亲和性原则来组队,那 nodeSelector 就是脑子一根筋,只选自己熟悉的同事,不熟悉的,一概不选。
这么一来,就有可能,所有你熟悉的同事已经被别人捷足先登抢先拉拢了,而最后只剩你一个人孤零零的。
换成亲和性调度,就变得灵活许多,他可以设置两种策略:
对于亲和性和反亲和性,都可以设置:
preferredDuringSchedulingIgnoredDuringExecution ==> 软策略
requiredDuringSchedulingIgnoredDuringExecution ==> 硬策略
硬策略的做法,就是换个模式的 nodeSelector,它是强制性的,不满足就调度失败。
软策略的做法,则更灵活,可以选择满足条件的,要是真没有满足条件的,就调度到其他节点上(选择自己陌生的同事)
3. 亲和性的三个种类¶
对比 nodeSelector 来说,亲和性调度除非了上面可以选择软策略之外,还有更多强大的功能。
亲和性调度器定义在 .spec.affinity
字段里,通过 explain
命令可以查看其字段
KIND: Pod
VERSION: v1
RESOURCE: affinity <Object>
DESCRIPTION:
If specified, the pod's scheduling constraints
Affinity is a group of affinity scheduling rules.
FIELDS:
nodeAffinity <Object>
Describes node affinity scheduling rules for the pod.
podAffinity <Object>
Describes pod affinity scheduling rules (e.g. co-locate this pod in the
same node, zone, etc. as some other pod(s)).
podAntiAffinity <Object>
Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod
in the same node, zone, etc. as some other pod(s)).
可以看到亲和性调度器,有如下三种:
nodeAffinity(node 亲和性):该 Pod 喜欢调度到什么样的 Node 上
podAffinity(pod 亲和性) :该 Pod 喜欢和某些 Pod 调度在一起
podAntiAffinity(pod 反亲和性):该 Pod 不喜欢和某些 Pod 调度在一起
上面三种亲和性调度,无论是哪一种,都要依赖标签才能起作用,只是不同的亲和性调度方法,亲和性调度器匹配标签的对象不同
node 亲和性:检查的是亲和性调度器与 node 标签的匹配
pod (反)亲和性:检查的是亲和性调度器与 pod 标签的匹配
4. 亲和性调度示例¶
4.1 node 亲和性 + 硬策略¶
如下是一个使用 node 亲和性调度器的简单示例,并且使用的是硬策略。
该段配置的意思是,当 kube-scheduler 在判断一个节点是否能通过筛选时,会先取出 node 上的 kubernetes.io/hostname 标签,当该标签的值为 worker01 时,则不允许调度。
一句话总结,就是不允许调度到 worker01 上
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- worker01
4.2 node 亲和性 + 软策略¶
如下是一个使用 node 亲和性调度器的简单示例,并且使用的是软策略。
该段配置的意思是,当 kube-scheduler 在判断一个节点是否能通过筛选时,会先取出 node 上的 disktype 标签,当该标签的值为 ssd 时, 该节点的权重 +100,反之标签值不为 ssd,则节点的权重值 +0
一句话总结,就是尽量 调度到有 ssd 的节点上。
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 100
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
4.3 Pod 亲和性 + 硬策略¶
假设你在集群中部署有两个服务,分别为 S1 和 S2,其中 S1 使用 S2 的服务。
为了减少他们之间的网络延迟(或其他原因),会考虑将 S1 和 S2 的Pod 部署在同一拓扑域中
这就是依赖 Pod 的亲和性实现的
如下是一个简单的示例
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: "kubernetes.io/hostname"
在这个示例中,它要求该 Pod 要调度与有标签键为 security 且值为 S1 的 Pod 同一个域上,其中域的 key 为 kubernetes.io/hostname,则域的范围就是节点级。
4.4 Pod 亲和性 + 软策略¶
还是以 4.3 的例子来说明,若想让 S1 和 S2 尽量调度到一起,当集群资源不那么充裕时,不调在一起也可以时,就要使用软策略。
具体配置如下
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: "kubernetes.io/hostname"
4.5 Pod 反亲和 + 硬策略¶
当使用 Deployment 创建多副本的 Pod 时,这些多副本是有可能创建到同一个域(或节点)上的。
若多个副本创建到同一个域(或节点)上,当该域(或节点)发生故障,就会有多个副本无法工作,原来的副本就失去了意义。
因此,我们希望能让副本能打散调度到不同的域(或节点)上,这就要用到反亲和调度器。
如下是一个反亲和调度器的简单示例,在这个示例中,Deployment 创建了三副本的 Pod,而这些 Pod 不能创建在同一个域(本示例上,域为节点)上
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
spec:
selector:
matchLabels:
app: store
replicas: 3
template:
metadata:
labels:
app: store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: redis-server
image: redis:3.2-alpine
4.6 Pod 反亲和 + 软策略¶
还是以 4.5 的例子来说明,当集群资源不那么充裕时,不打散也能接受的话,就要使用软策略。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
spec:
selector:
matchLabels:
app: store
replicas: 3
template:
metadata:
labels:
app: store
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: redis-server
image: redis:3.2-alpine
5. 域和匹配方式¶
在上面的诸多示例中,为了方便:
域都是使用的是
kubernetes.io/hostname
并且都是使用的
matchExpressions
关于第一个问题,实际上K8S 有内置三种域的 key,分别是
kubernetes.io/hostname
和 topology.kubernetes.io/region
和
topology.kubernetes.io/zone
,最常用的就是
kubernetes.io/hostname
,大家可根据自身需求进行选择。
关于第二个问题,上面使用 matchExpressions 是更通用、更灵活的方式,因为 matchExpressions 可以利用操作符(operator)做更多复杂的判断
下面是操作的可选项及其含义:
In:label 的值在某个列表中
NotIn:label 的值不在某个列表中
Gt:label 的值大于某个值
Lt:label 的值小于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在
不同调度器支持的操作符不太一样,可参考下面表格
若只是单纯的 security=S1,可以直接使用 matchLabels ,书写更加快捷,也更容易理解
因此下面两种方法在效果上是等价的
# 第一种写法:使用 matchExpressions
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: "kubernetes.io/hostname"
# 第二种写法:使用 matchLabels
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
labelSelector:
matchLabels:
security: S1
topologyKey: "kubernetes.io/hostname"