Node亲和性的调度策略主要通过Affinity来实现,用于替换NodeSelector的全新调度策略。
为什么要使用亲和/反亲和性调度
nodeSelector 定向调度提供了非常简单的方法来将Pod约束到具有特定标签的节点上。
而亲和/反亲和功能极大地扩展了可以表达的约束类型
主要增加了以下功能
- 语言更具表现力。
- 可以发布软限制,而非硬性要求,即便没有期望的节点,也可以调度该Pod。
- 可以使用节点上的Pod的标签来约束,而非使用节点本身标签,来规定哪些Pod可以放在一起。
配置结构如下
type PodSpec struct {
// Pod的亲和配置,通过该配置可以将节点调度到指定的环境中
Affinity *Affinity `json:"affinity,omitempty"`
}
type Affinity struct {
// 描述了Pod与Node之间的亲和性调度规则
NodeAffinity *NodeAffinity `json:"nodeAffinity,omitempty"`
// 描述了Pod与Pod之间的亲和性调度规则
PodAffinity *PodAffinity `json:"podAffinity,omitempty"`
// 描述了Pod与Pod之间的反亲和性规则
PodAntiAffinity *PodAntiAffinity `json:"podAntiAffinity,omitempty"`
}
配置简介
通过配置结构体来学习一下配置项详情
节点亲和
类似于 nodeSelector ,然而可以指定硬性要求和软性要求。
配置结构如下
type NodeAffinity struct {
// 如果调度时节点未满足Pod的亲和性要求,Pod将不会被调度到此节点上
// 如果在Pod运行时节点突然不满足亲和要求,系统可能不会(尝试)将其从节点上驱逐
RequiredDuringSchedulingIgnoredDuringExecution *NodeSelector `json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty"`
// 与RequiredDuringSchedulingIgnoredDuringExecution类似
// 不同的在于即使节点与Pod的要求不满足或部分不满足,Pod也可能会被调度到节点上
// 具体选择是根据满足的条件的权重和来计算的
PreferredDuringSchedulingIgnoredDuringExecution []PreferredSchedulingTerm `json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty"`
}
type NodeSelector struct {
// NodeSelector的规则定义
NodeSelectorTerms []NodeSelectorTerm `json:"nodeSelectorTerms"`
}
type PreferredSchedulingTerm struct {
// 权重配置,范围为1-100
Weight int32 `json:"weight"`
// NodeSelector的规则定义
Preference NodeSelectorTerm `json:"preference"`
}
type NodeSelectorTerm struct {
// 匹配节点的Labels
MatchExpressions []NodeSelectorRequirement `json:"matchExpressions,omitempty"`
// 匹配节点的字段
MatchFields []NodeSelectorRequirement `json:"matchFields,omitempty"`
}
type NodeSelectorRequirement struct {
// label或者field的Key
Key string `json:"key"`
// 匹配关系,包括In、NotIn、Exists、DoesNotExist、Gt、Lt
Operator NodeSelectorOperator `json:"operator"`
// 匹配的Key所对应的Value
// operator是In或NotIn,该值必须不为空。在使用过程中发现,该值也只能有一个值。
// 否则会报错 ... must be only one value when `operator` is 'In' or 'NotIn' for node field selector
// opertor是Exists或DoesNotExist,该值必须为空
// operator是Gt或Lt,该值必须有且只能有一个值
Values []string `json:"values,omitempty"`
}
type NodeSelectorOperator string
const (
NodeSelectorOpIn NodeSelectorOperator = "In"
NodeSelectorOpNotIn NodeSelectorOperator = "NotIn"
NodeSelectorOpExists NodeSelectorOperator = "Exists"
NodeSelectorOpDoesNotExist NodeSelectorOperator = "DoesNotExist"
NodeSelectorOpGt NodeSelectorOperator = "Gt"
NodeSelectorOpLt NodeSelectorOperator = "Lt"
)
Pod亲和与反亲和
Pod间的亲和与反亲和配置可以通过已经在节点上运行的Pod来约束Pod可以调度到的节点上。
规则的格式为
如果 X 节点上已经运行了一个或多个 满足规则 Y 的pod,则这个 pod 应该(或不应该)运行在 X 节点上
// Pod亲和配置,要求与满足条件的Pod部署在同个节点上
type PodAffinity struct {
// 如果调度时节点上运行的Pod未满足该Pod的亲和性要求,Pod将不会被调度到此节点上
// 如果在Pod运行时节点上的Pod突然不满足亲和要求,系统可能不会(尝试)将其从节点上驱逐
// 必须列表中所有的要求都满足才可以
RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm
// 与PreferredDuringSchedulingIgnoredDuringExecution类似
// 不同的在于即使节点上Pod的条件与Pod的要求不满足或部分不满足,Pod也可能会被调度到节点上
// 具体选择是根据满足的条件的权重和来计算的
PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm
}
// Pod互斥配置,拒绝与满足条件的Pod部署在同一个节点上
type PodAntiAffinity struct {
// 硬限制,必须全部满足才会达成目标,不会驱逐运行中的Pod
RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm `json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty"`
// 软限制,必须全部满足才会达成目标,不会驱逐运行中的Pod
PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm `json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty"`
}
type PodAffinityTerm struct {
// 标签选择器,列出Pod要满足的条件
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
// LabelSelector所作用的命名空间。空列表或者未定义则为当前Pod所在命名空间
Namespaces []string `json:"namespaces,omitempty"`
// 目标节点所属的拓扑域,不可为空。
TopologyKey string `json:"topologyKey"`
}
type LabelSelector struct {
// 要求Pod的Labels完全包含此处定义的值
MatchLabels map[string]string `json:"matchLabels,omitempty"`
// 与MatchLabels类似,只是匹配功能上更丰富
MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty"`
}
type LabelSelectorRequirement struct {
// Pod定义的Label的Key
Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key"`
// 运算方法,包括In、NotIn、Exists、DoesNotExist
Operator LabelSelectorOperator `json:"operator"`
// operator是In或NotIn时,values不可为空
// operator为Exists或DoesNotExist时,values必须为空
Values []string `json:"values,omitempty"`
}
type LabelSelectorOperator string
const (
LabelSelectorOpIn LabelSelectorOperator = "In"
LabelSelectorOpNotIn LabelSelectorOperator = "NotIn"
LabelSelectorOpExists LabelSelectorOperator = "Exists"
LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist"
)
type WeightedPodAffinityTerm struct {
// 权重,满足条件后计入分数 1-100
Weight int32 `json:"weight"`
// 达成目标所需要的条件
PodAffinityTerm PodAffinityTerm `json:"podAffinityTerm"`
}
TopologyKey
Toplogy翻译为拓扑,所以可以理解为所属拓扑域的标记。
拓扑域为一个范围概念,比如一个节点、机柜、机房,或者同属于一个系列的机器,比如都是SSD的机器、都是Intel CPU的机器。总而言之是一个范围。在K8S中,它表现为节点上的一个标签。
如果设置了topologyKey为 kubernetes.io/hostname ,则表示拓扑域的范围为 kubernetes.io/hostname 范围,即CPU架构范围。
那么 kubernetes.io/hostname 对应的值不同就是不同的拓扑域。
例如此时有3个Pod, kubernetes.io/hostname 的值分别是 a 、 b 、 c ,则这三个Pod属于3个不同的拓扑域。
注意事项
原则上,topologyKey 可以是任何合法的标签键。然而,出于性能和安全原因,topologyKey 受到一些限制:
- 对于具有 Pod的亲和性要求与Pod的硬性反亲和性要求 的配置,topologyKey不允许为空。
- 对于具有 Pod的硬性反亲和性要求 的配置,准入控制器LimitPodHardAntiAffinityTopology被引入来限制topologyKey不为kubernetes.io/hostname。如果你想使它可用于自定义拓扑结构,你必须修改准入控制器或者禁用它。
- 对于具有 Pod的软性反亲和性要求 的配置,空的topologyKey被解释为“所有拓扑域”。 这里的“所有拓扑结构”限制为kubernetes.io/hostname,failure-domain.beta.kubernetes.io/zone和failure-domain.beta.kubernetes.io/region的组合。
- 除上述情况外,topologyKey可以是任何合法的标签键。
*硬性要求指的是requiredDuringSchedulingIgnoredDuringExecution
*软性要求指的是preferredDuringSchedulingIgnoredDuringExecution
配置示例
先定义一个Pod,配置文件affinity-nginx.yaml如下
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 必须满足,硬限制
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: NotIn
values:
- work-node-abc
preferredDuringSchedulingIgnoredDuringExecution: # 尽量满足,软限制
- weight: 20 # 权重
preference: # 节点偏好设置
matchExpressions:
- key: disk
operator: NotIn
values:
- ssd
- weight: 30 # 权重
preference: # 节点偏好设置
matchExpressions:
- key: disk
operator: In
values:
- ssd
podAffinity: # Pod亲和性配置,要求与type=log的Pod在一个节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: type
operator: In
values:
- log
topologyKey: proxy
podAntiAffinity: # Pod反亲和性配置,拒绝与type=app的Pod在一个节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: type
operator: In
values:
- app
topologyKey: proxy
这个配置文件规定
- Pod中容器的情况
- Pod
只能部署在metadata.name不为work-node-abc的节点上 - Pod
尽量部署在Labels中disk不包含ssd的节点上,权重30 - Pod
尽量部署在Labels中disk包含ssd的节点上,权重20 - Pod
只能部署在同时满足条件节点带有proxy标签 且 节点上有标签type值为log的Pod的节点上 - Pod
不能部署在同时满足条件节点带有kubernetes.io/hostname标签 且 节点上有标签type值为app的Pod的节点上
先配置节点标签信息
- 为A、B节点添加disk标签,一个值为ssd,一个值为hdd
- 为A、B节点添加proxy标签,值为nginx
- 为A、B节点部署
type=log标签的Pod - 为A节点部署
type=app标签的Pod
A节点tx,B节点ks
为节点打标签
$ kubectl label node tx proxy=nginx
node/tx labeled
$ kubectl label node ks proxy=nginx
node/ks labeled
$ kubectl label node tx disk=ssd
node/tx labeled
$ kubectl label node ks disk=hdd
node/ks labeled
部署协助试验的Pod
apiVersion: v1
kind: Pod
metadata:
name: log-aaa
labels:
type: log
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
kubernetes.io/hostname: tx
---
apiVersion: v1
kind: Pod
metadata:
name: log-bbb
labels:
type: log
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
kubernetes.io/hostname: ks
---
apiVersion: v1
kind: Pod
metadata:
name: app-aaa
labels:
type: app
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
kubernetes.io/hostname: tx
对比Pod的配置要求
- Pod中容器的情况 (忽略)
- Pod
只能部署在metadata.name不为kube-system的节点上 (所有节点满足) - Pod
尽量部署在Labels中disk不包含ssd的节点上,权重20 (B节点20) - Pod
尽量部署在Labels中disk包含ssd的节点上,权重30 (A节点30) - Pod
只能部署在同时满足条件节点带有proxy标签 且 节点上有标签type值为log的Pod的节点上 (A、B节点都满足要求) - Pod
不能部署在同时满足条件节点带有kubernetes.io/hostname标签 且 节点上有标签type值为app的Pod的节点上(A上有type=app的Pod,B上没有)
所以会部署在节点B上
$ kubectl delete pod nginx && kubectl create -f affinity-nginx.yaml && sleep 3 && kubectl get pods -o wide
pod "nginx" deleted
pod/nginx created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app-aaa 1/1 Running 0 16m 10.46.0.1 tx <none> <none>
log-bbb 1/1 Running 0 16m 10.32.0.1 ks <none> <none>
log-aaa 1/1 Running 0 16m 10.46.0.0 tx <none> <none>
nginx 0/1 ContainerCreating 0 3s <none> ks <none> <none>
删除掉 podAntiAffinity Pod的反亲和性配置块,此时所有节点都满足需求,则亲和性中的 preferredDuringSchedulingIgnoredDuringExecution 配置将会起作用
此时将会部署在节点A上
$ kubectl delete pod nginx && kubectl create -f affinity-nginx.yaml && sleep 3 && kubectl get pods -o wide
pod "nginx" deleted
pod/nginx created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app-aaa 1/1 Running 0 33m 10.46.0.1 tx <none> <none>
log-bbb 1/1 Running 0 33m 10.32.0.1 ks <none> <none>
log-aaa 1/1 Running 0 33m 10.46.0.0 tx <none> <none>
nginx 0/1 ContainerCreating 0 3s <none> tx <none> <none>
修改权重值,调大B的权重到50(将配置中低权重20改为50),则会部署在节点B上
$ kubectl delete pod nginx && kubectl create -f affinity-nginx.yaml && sleep 3 && kubectl get pods -o wide
pod "nginx" deleted
pod/nginx created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app-aaa 1/1 Running 0 35m 10.46.0.1 tx <none> <none>
log-bbb 1/1 Running 0 35m 10.32.0.1 ks <none> <none>
log-aaa 1/1 Running 0 35m 10.46.0.0 tx <none> <none>
nginx 0/1 ContainerCreating 0 3s <none> ks <none> <none>
最后删除所有标签,已经运行的Pod并不会受到任何影响,而重新加入时则会发现条件已经不满足了
$ kubectl label node ks disk-
node/ks labeled
$ kubectl label node tx disk-
node/tx labeled
$ kubectl label node ks proxy-
node/ks labeled
$ kubectl label node tx proxy-
node/tx labeled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-aaa 1/1 Running 0 36m
log-bbb 1/1 Running 0 36m
log-aaa 1/1 Running 0 36m
nginx 1/1 Running 0 72s
$ kubectl delete pod nginx && kubectl create -f a.yaml && sleep 3 && kubectl get pods -o wide
pod "nginx" deleted
pod/nginx created
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app-aaa 1/1 Running 0 66m 10.46.0.1 tx <none> <none>
log-bbb 1/1 Running 0 66m 10.32.0.1 ks <none> <none>
log-aaa 1/1 Running 0 66m 10.46.0.0 tx <none> <none>
nginx 0/1 Pending 0 3s <none> <none> <none> <none>