亲和(affinity)和 反亲和(anti-affinity)
nodeSelector
是提供了一种非常简单的方法,可以将pod限制到特定标签的节点上。而亲和
/反亲和
将极大地扩展了你可以表达的约束类型。其关键的改进是:
- 不仅仅是“精确匹配”
- 你可以指出规则是
倾向
/优先
,而不是硬性要求。所以如果调度程序不能满足它,那么也仍然会被调度。 - 你可以不依赖于node的标签,可以针对pod之间的标签进行亲和或反亲和。
亲和特征由两种类型的亲和组成,“Node亲和”和“Pod之间亲和”。
- Node亲和性类似于现有的nodeSelector(但具有上面列出的前两个优点)
- 而pod之间的亲和性/反亲和限制了pod标签而不是Node标签
提示:nodeSelector最终会被弃用,因为
节点亲和
可以表达nodeSelector可以表达的所有内容。
节点亲和(Node affinity)
节点亲和
在概念上类似于nodeSelector
- 根据节点上的标签约束你的pod调度到符合条件的节点上。
目前有两种类型的节点亲和
requiredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
你可以将它们分别视为“硬”和“软”:
- “硬”指定了要将pod调度到节点上必须满足的规则(就像nodeSelector)
- 而“软”指定调度程序将优先尝试调度,但不保证是首选的。
上面两种类型的名称里都包含了 “IgnoredDuringExecution” ,这意味着,如果Node上的标签更改了,不再满足pod上的亲和规则,则已经运行的pod仍将继续在节点上运行(与nodeSelector类似)。
在未来,我们计划提供
requiredDuringSchedulingRequiredDuringExecution
,跟requiredDuringSchedulingIgnoredDuringExecution
一样,但是它会将不再满足pods节点亲和要求的pod驱逐。
举个例子
满足以下2个条件:
仅在具有Intel CPU的节点上运行pod。(使用
requiredDuringSchedulingIgnoredDuringExecution
)并且将尝试在失败区域XYZ中运行此组pod,但如果不行,则允许一部分在其他节点运行。(通过
preferredDuringSchedulingIgnoredDuringExecution
)
在PodSpec
中affinity
字段添加nodeAffinity
来实现节点亲和
,如下:
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/e2e-az-name
operator: In
values:
- e2e-az1
- e2e-az2
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: k8s.gcr.io/pause:2.0
上面例子中,pod只能放置在节点标签key为kubernetes.io/e2e-az-name
,其value为e2e-az1
或e2e-az2
上。 另外,在满足该条件的节点中,优先选择具有其key为another-node-label-key
且value为another-node-label-value
的标签节点。
示例中operator
为In
。也支持以下operator:
- In
- NotIn
- Exists
- DoesNotExist
- Gt
- Lt
您可以使用NotIn
和DoesNotExist
来实现节点反亲和
,或使用污点来排除特定节点的pod。
如果同时指定nodeSelector
和nodeAffinity
,则必须满足两者。
如果指定nodeAffinity类型关联的多个nodeSelectorTerms
,则可以在满足其中一个nodeSelectorTerms
的情况下将pod调度到该节点上。
如果指定与nodeSelectorTerms
关联的多个matchExpressions
,则只有在可以满足所有matchExpressions
的情况下才能将pod调度到该节点上。
如果删除或变更节点标签,则不会删除该pod。换句话说,亲和选择仅在调度pod时起作用。
preferredDuringSchedulingIgnoredDuringExecution
中的weight(权重)
范围在1-100
内。 对于满足所有调度要求的每个节点(资源请求,RequiredDuringScheduling亲和等),如果节点与相应的MatchExpressions匹配,则调度程序将通过迭代此字段的元素并将“weight”添加来计算总和。然后将该得分与节点的其他优先级函数的得分组合起来,总得分最高的节点是最优先选择的。
Pod的之间亲和
和反亲和
Pod之间亲和和反亲和,你可以通过已经在节点上运行的Pod上的标签来约束你的Pod调度到哪些节点上(而不是通过节点上的标签)。也就是说,如果X
上已经在运行一个或多个满足规则Y
的Pod,则意味着 “此pod就应该在 X 上运行(如果是反亲和,则不应该)”。 Y表示为LabelSelector
,带有可选的关联namespace列表;
与节点不同,因为pod是namespace(其实pod上的标签是隐式的namespace),pod标签选择器
必须指定选择器应该应用到哪些namespace上。从概念上,X 是一个拓扑域的概念,如它可以是node
、rack
、cloud provider zone
、cloud provider region
等。你都可以使用topologyKey来表达它,是系统用来标识这些资源对象。
注意:Pod之间亲和和反亲和需要大量处理,这会显着减慢大型集群中的调度。我们不建议在大于几百个节点的群集中使用它们。
注意:Pod 反亲和要求节点始终要有标签,即群集中的每个节点都必须具有适当的标签匹配topologyKey。如果某些或所有节点缺少指定的topologyKey标签,则可能导致意外行为。
与节点亲和一样,目前Pod也是两种亲和和反亲和,requiredDuringSchedulingIgnoredDuringExecution
和preferredDuringSchedulingIgnoredDuringExecution
,也是硬
和软
。
举个例子: ”将pod A
和pod B
放到相同的区域,因为它们之间通讯频繁”,并结合反亲和”希望Pod不要调到标签有S2的区域”。
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: failure-domain.beta.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: failure-domain.beta.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: k8s.gcr.io/pause:2.0
例子中,pod上的定义了一个亲和规则(podAffinity
是requiredDuringSchedulingIgnoredDuringExecution
)和一个反亲和规则(podAntiAffinity
是preferredDuringSchedulingIgnoredDuringExecution
)。
pod中亲和规则表示,只有当该节点上,至少有一个运行的pod(相同区域)时,并且该pod带有“security”和“S1”的标签,才能将该pod调度到这个节点上。
pod反亲和规则表示如果该节点已经在运行具有键“security”和值“S2”的标签的pod,则该pod不希望被调度到这个节点上。
pod亲和和反亲和可选有 “in
”、“notin
”、“exists
”、“doesnotexist
”。
原则上,topologyKey
可以是任何合法的标签key。 但是,出于性能和安全性的原因,对TopologyKey有一些限制:
对于
亲和
和非亲和(RequiredDuringSchedulingIgnoredDuringExecution)
,不允许使用空的topologyKey
。对于
反亲和(requiredDuringSchedulingIgnoredDuringExecution)
,引入了许可控制器LimitPodHardAntiAffinityTopology
对 topologyKey 限制为kubernetes.io/hostname
。 如果你想用于自定义,你可以修改许可控制器,或者禁用它。对于
pod反亲和(preferredDuringSchedulingIgnoredDuringExecution)
,空topologyKey被解释为“所有”(此处的“所有”现在仅限于kubernetes.io/hostname
/failure-domain.beta.kubernetes.io/zone
和failure-domain.beta.kubernetes.io/region
的组合)。除上述情况外,topologyKey可以是任何合法的
label-key
。
除了labelSelector
和topologyKey
之外,你还可以通过labelSelector指定相匹配的namespace列表(这与labelSelector和topologyKey的定义属于同一级别)。如果省略或为空,则默认为显示亲和/非亲和定义的pod的命名空间。
必须满足requiredDuringSchedulingIgnoredDuringExecution
亲和和反亲的所有matchExpressions
匹配,才能将pod调度到该节点上。
更实用的场景
当Pod之间的Affinity和AntiAffinity与更高级别的类型(如ReplicaSet,StatefulSets,Deployments等)一起使用时,会更加有用。可以轻松配置一组应用程序运行到同一个节点上。
始终在同一个节点上
假设,在三节的集群中,Web应用程序需要使用缓存(例如redis)。我们希望Web应用程序尽可能与缓存放在一起。
这是一个简单的redis部署的yaml片段,三个副本和选择器标签app=store
。通过PodAntiAffinity
以确保调度程序在每个节点上只运行一个。
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
以下web应用的yaml片段,配置podAntiAffinity
和podAffinity
告知调度程序其所有副本将与具有选择器标签app=store
的pod放一起。这还将确保每个Web程序的副本不在单个节点上共存。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
app: web-store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: "kubernetes.io/hostname"
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-app
image: nginx:1.12-alpine
如果我们创建上述两个部署,我们的三个节点集群应如下所示。
node-1 | node-2 | node-3 |
---|---|---|
webserver-1 | webserver-2 | webserver-3 |
cache-1 | cache-2 | cache-3 |
如上所示,web-server
的所有3个副本都按预期自动与缓存共存。
kubectl get pods -o wide
输出类似于:
NAME READY STATUS RESTARTS AGE IP NODE
redis-cache-1450370735-6dzlj 1/1 Running 0 8m 10.192.4.2 kube-node-3
redis-cache-1450370735-j2j96 1/1 Running 0 8m 10.192.2.2 kube-node-1
redis-cache-1450370735-z73mh 1/1 Running 0 8m 10.192.3.1 kube-node-2
web-server-1287567482-5d4dz 1/1 Running 0 7m 10.192.2.3 kube-node-1
web-server-1287567482-6f7v5 1/1 Running 0 7m 10.192.4.3 kube-node-3
web-server-1287567482-s330j 1/1 Running 0 7m 10.192.3.2 kube-node-2
(完)