提升服务高可用性—Pod亲和力

提升服务高可用性-Pod亲和力

1.亲和力Affinity

Kubernetes的亲和力(Affinity)是一种调度规则,作用于Pod资源,用于控制Pod的调度行为,比如Pod应该或者尽量调度到哪些节点上、Pod不能和哪些Pod部署在一起等,Pod尽量和哪些Pod部署到一起,从而实现更灵活的资源管理和故障隔离。

Kubernetes的亲和力支持三种类型:节点亲和力(NodeAffinity)、Pod亲和力(PodAffinity)、Pod反亲和力(PodAntiAffinity)。

分别用于如下用途:

  • 节点亲和力:用于控制Pod和节点之间的关系
  • Pod亲和力:用于控制Pod和Pod之间的关系,允许和某些Pod部署在同一个位置
  • Pod反亲和力:同样用于控制Pod和Pod之间的关系,不允许和某些Pod部署在同一个位置

同时每种亲和力也分为了强制和非强制调度策略,用于实现调度规则是属于尽量满足还是必须满足

亲和力详细分类:

在配置Pod的节点亲和性时,硬亲和性(requiredDuringSchedulingIgnoredDuringExecution)和软亲和性(preferredDuringSchedulingIgnoredDuringExecution)的调度行为是有明确优先级和处理顺序的。Kubernetes调度器会首先考虑硬亲和性,然后在此基础上尽力满足软亲和性。

调度流程

  1. 硬亲和性(Hard Affinity)
    • 首先,Kubernetes调度器会检查所有符合硬亲和性条件的节点。如果没有任何节点满足硬亲和性的条件,Pod将不会被调度。
    • 硬亲和性是强制性的要求,如果没有符合条件的节点,Pod就会处于待调度状态,直到有匹配的节点可用。
  2. 软亲和性(Soft Affinity)
    • 在找到满足硬亲和性条件的节点集合后,调度器会根据软亲和性的权重(weight)对这些节点进行评分和排序。
    • 调度器会优先选择得分最高的节点来调度Pod,但如果没有任何节点满足软亲和性的条件,Pod仍然会调度到满足硬亲和性但不满足软亲和性的节点上。

2.认识拓扑域和拓扑键

拓扑键(topologyKey) 和 拓扑域(topology domain) 是 Kubernetes 中用来描述节点物理/逻辑分组的核心概念,主要用于调度(尤其是 Pod Topology Spread Constraints)、亲和性/反亲和性,以及服务流量路由等场景。

拓扑键(topologyKey)
它是节点标签(Node Label)的 key
常见标准值:

  • kubernetes.io/hostname:每个节点自己是一个独立拓扑
  • topology.kubernetes.io/zone:可用区(Availability Zone)
  • topology.kubernetes.io/region:区域(Region)

你也可以用自定义标签,比如 rack、failure-domain.beta.kubernetes.io/zone 等。

拓扑域(topology domain)
当多个节点拥有相同的 topologyKey 且 value 相同时,这些节点就被认为属于同一个拓扑域

举例:

  • 如果 topologyKey = topology.kubernetes.io/zone,那么 zone=zone-a 的所有节点构成一个拓扑域,zone=zone-b 构成另一个拓扑域。例如:k8s-node01节点和k8s-node02节点属于同一个拓扑域。

    1
    2
    3
    4
    k8s-node01节点的标签:  topology.kubernetes.io/zone=zone-a
    k8s-node02节点的标签: topology.kubernetes.io/zone=zone-a
    k8s-node03节点的标签: topology.kubernetes.io/zone=zone-b

  • 如果 topologyKey = kubernetes.io/hostname,那么每个节点本身就是一个独立的拓扑域。

关键点是:标签本身不会自动成为拓扑域,只有当某个规则把这个 Label 的 key 作为 topologyKey 使用时,才会按这个标签值划分拓扑域。

3.Pod硬亲和力配置详解

假设已经有一组 Redis Pod,它的pod标签如下:

1
2
labels:
app: redis

现在希望业务 Pod myapp 必须调度到和 Redis 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#下面是一个pod亲和性基本配置示例,在调度时,myapp 的 Pod 必须被调度到与 default 命名空间中带有 app: redis 标签的 Pod 所在的同一个节点(Node)上;如果找不到这样的 Redis Pod,myapp 的 Pod 将无法调度(处于 Pending 状态)
[root@k8s-master01 affinity]# cat myapp-affinity.yaml
apiVersion: apps/v1 # API 版本。Deployment 资源属于 apps 组,v1 是当前稳定
kind: Deployment #资源类型。声明这是一个 Deployment 对象,用于管理无状态应用
metadata: # 元数据区域,定义该资源的身份信息
name: myapp # Deployment 的名称。在同一个 namespace 下必须唯一
namespace: default # 所属命名空间。未指定时默认为 default空间
spec: # 规格定义区域,描述 Deployment 的期望状态
replicas: 2 # 期望运行的 Pod 副本数量是2
selector: # 标签选择器。Deployment 通过它来识别和管理哪些 Pod
matchLabels: # 精确匹配模式。只有标签完全匹配以下键值对的 Pod 才会被纳入管理
app: myapp # 匹配条件:Pod 必须带有 app=myapp 标签。
template: # Pod 模板。当需要创建新 Pod 时(如扩容、滚动更新),均按此模板生成
metadata: # Pod 自身的元数据
labels: # 定义Pod的标签。必须包含上面 selector.matchLabels 中定义的所有键值对
app: myapp #必须与上面selector.matchLabels对应
spec:
# affinity:定义 Pod 的亲和性/反亲和性规则
affinity:

# podAffinity:Pod 亲和力
# 含义:希望当前 Pod 与满足条件的其他 Pod 调度到相同拓扑域
podAffinity:

# requiredDuringSchedulingIgnoredDuringExecution:
# 硬性要求。
# 调度 Pod 时必须满足下面的规则,否则 Pod 不会被调度。
# IgnoredDuringExecution 表示:
# Pod 已经运行后,即使节点 Label 等发生变化,也不会把 Pod 自动迁走。
requiredDuringSchedulingIgnoredDuringExecution:

# 一个 PodAffinityTerm,定义“我要靠近谁,以及多近”
- labelSelector:

# matchLabels:选择目标 Pod
# 这里表示寻找具有 app=redis 标签的 Pod
matchLabels:
app: redis

# namespaces:
# 指定在哪些 namespace 中寻找上述 Pod。
# 如果不配置 namespaces,默认只匹配当前 Pod 所在的 namespace。
namespaces:
- default

# topologyKey:拓扑键
# Scheduler 会读取目标 Pod 所在节点的这个 Label,
# 然后要求当前 Pod 也进入相同 Label 值的拓扑域。
#
# 使用 kubernetes.io/hostname 时:
# 每个 Node 通常就是一个独立拓扑域,
# 因此效果就是“和 Redis 放到同一个 Node”。
topologyKey: kubernetes.io/hostname

containers:
- name: myapp
image: nginx:1.27

ports:
- name: http
containerPort: 80

标签匹配还可以使用正则方式匹配,例如:

当前 Pod 必须调度到:与 default 命名空间中、标签 security=S1 的 Pod 所在节点属于同一个 zone 拓扑域的节点上。

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
40
41
42
43
44
45
46
47
48
49
50
affinity:    # 定义亲和性和反亲和性规则
podAffinity: # Pod 亲和力:希望当前 Pod 靠近某类 Pod

requiredDuringSchedulingIgnoredDuringExecution:
# required:硬性要求,调度时必须满足
# DuringScheduling:Pod 调度阶段检查
# IgnoredDuringExecution:Pod 运行后,即使条件变化,也不会自动迁移

- labelSelector:
# labelSelector:定义“我要靠近哪些 Pod”

matchExpressions:
# matchExpressions:使用表达式匹配 Pod 标签
# 相比 matchLabels,可以使用 In、NotIn、Exists 等条件

- key: security
# key:要匹配的 Pod Label 的 key
# 例如目标 Pod 上需要存在:
# security=S1

operator: In
# operator:匹配运算符
# In 表示标签值必须属于 values 列表中的某个值

values:
- S1
# values:允许匹配的标签值
# 当前配置相当于:
# security 的值必须是 S1

topologyKey: topology.kubernetes.io/zone
# topologyKey:定义什么叫“靠近”
# 这里按 zone 划分拓扑域
#
# 例如:
# node01 topology.kubernetes.io/zone=zone-a
# node02 topology.kubernetes.io/zone=zone-a
# node03 topology.kubernetes.io/zone=zone-b
#
# 如果 security=S1 的 Pod 在 node01,
# 那么当前 Pod 可以调度到 node01 或 node02,
# 因为它们都属于 zone-a

namespaces:
- default
# namespaces:在哪些 namespace 中寻找符合 labelSelector 的目标 Pod
# 这里表示只查找 default namespace
#
# 如果不写 namespaces,
# 默认只查找当前 Pod 所在的 namespace

其中最关键的是:

1
2
3
4
5
6
7
8
matchExpressions:
- key: security
operator: In
values:
- S1
它等价于:
matchLabels:
security: S1

但 matchExpressions 能表达更复杂的条件。
比如:

1
2
3
4
5
6
7
8
matchExpressions:
- key: security
operator: In
values:
- S1
- S2
# 表示:security=S1 或者 security=S2
# 而 matchLabels 就不方便直接表达这种“多个值任选其一”的条件。

常见 operator 有这几个:

operator 含义 values 是否需要
In 标签值必须在指定列表中 需要
NotIn 标签值不能在指定列表中 需要
Exists 只要求这个标签存在 不需要
DoesNotExist 要求这个标签不存在 不需要

例如:表示只要 Pod 上存在 gpu 这个标签就匹配,不关心它的值是什么。

1
2
3
matchExpressions:
- key: gpu
operator: Exists

4.Pod软亲和力配置详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
affinity:   # 定义亲和性和反亲和性规则
podAffinity: # Pod 亲和性规则,定义当前 Pod 希望与哪些其他 Pod 靠近

# preferredDuringSchedulingIgnoredDuringExecution:
# 软性偏好规则。
# Scheduler 会尽量满足此规则,但如果无法满足(例如没有符合条件的 Pod 或资源不足),
# Pod 仍然会被调度到其他节点,不会导致 Pod 处于 Pending 状态。
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100 # 权重值(1-100)。
# 当有多个偏好规则时,权重越高,Scheduler 越优先满足该规则。
# 这里设为 100 表示最高优先级偏好。

podAffinityTerm: # 定义具体的亲和性匹配条件
labelSelector: # 标签选择器,用于筛选目标 Pod
matchExpressions: # 基于表达式的匹配模式,比 matchLabels 更灵活
- key: security # 目标 Pod 的标签键名
operator: In # 操作符,表示“包含在以下值中”
values: # 对应的标签值列表
- S2 # 寻找标签为 security=S2 的 Pod
topologyKey: topology.kubernetes.io/zone # 拓扑键,表示希望当前 Pod 与目标 Pod 位于相同的“可用区(Zone)”
namespaces: # 指定在哪些命名空间中寻找目标 Pod
- default # 仅在 default 命名空间中查找带有 security=S2 标签的 Pod

5.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
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default

spec:
replicas: 3

selector:
matchLabels:
app: nginx

template:
metadata:
labels:
app: nginx # 当前 Pod 的标签,反亲和规则会通过这个标签识别同类 Pod

spec:
affinity: # 定义亲和性和反亲和性规则

podAntiAffinity: # Pod 反亲和:避免当前 Pod 与指定 Pod 位于同一拓扑域

requiredDuringSchedulingIgnoredDuringExecution:
# required:硬性反亲和
# 调度时必须满足,否则 Pod 会保持 Pending
# IgnoredDuringExecution:Pod 运行后规则发生变化,不会自动驱逐已有 Pod

- labelSelector:
# labelSelector:选择“需要避开的 Pod”

matchLabels:
app: nginx
# 匹配 app=nginx 的 Pod
# 即当前 nginx Pod 要避开其他 nginx Pod

namespaces:
- default
# 在哪些 namespace 中查找目标 Pod
# 这里表示只检查 default 命名空间
# 如果不配置,默认也是当前 Pod 所在 namespace

topologyKey: kubernetes.io/hostname
# topologyKey:定义“不能处于同一个什么范围”
# kubernetes.io/hostname 表示以 Node 为拓扑域
# 即 nginx Pod 不能和其他 nginx Pod 在同一个 Node

6.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
affinity:
podAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution:
# preferred:软性规则
# 尽量满足,无法满足时仍然可以调度

- weight: 100
# 权重范围 1~100
# 数值越大,Scheduler 越倾向满足这条规则

podAffinityTerm:
# 注意:
# Pod 软亲和和 Pod 软反亲和这里都使用 podAffinityTerm

labelSelector:
matchLabels:
app: nginx
# 希望避开的 Pod

namespaces:
- default

topologyKey: kubernetes.io/hostname
# 尽量不要和 app=nginx 的 Pod 位于同一个节点

7.节点硬亲和力和软亲和力配置

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: default
spec:
replicas: 2 # 创建 2 个 Pod 副本
selector:
matchLabels:
app: myapp # Deployment 通过该标签管理 Pod
template:
metadata:
labels:
app: myapp
spec:
affinity: # affinity:定义亲和性/反亲和性调度规则

# nodeAffinity:节点亲和力
# 根据 Node 的 Label 决定 Pod 应该调度到哪些节点
nodeAffinity:
# ============================
# 1. 节点硬亲和
# ============================
requiredDuringSchedulingIgnoredDuringExecution:
# required:必须满足
# DuringScheduling:Pod 调度时检查
# IgnoredDuringExecution:
# Pod 已经运行后,即使节点 Label 发生变化,
# Kubernetes 也不会因此自动驱逐这个 Pod

nodeSelectorTerms:
# nodeSelectorTerms:定义节点筛选条件
# 这里可以配置一个或多个 NodeSelectorTerm

- matchExpressions: # matchExpressions:通过表达式匹配 Node Label
- key: disk
# key:节点 Label 的 key
# 例如节点上:
# disk=ssd

operator: In
# operator:匹配运算符
# In 表示 Label 的值必须位于 values 列表中

values: # values:允许的 Label 值
- ssd # 表示 Pod 必须调度到 disk=ssd 的节点
# ============================
# 2. 节点软亲和
# ============================
preferredDuringSchedulingIgnoredDuringExecution:
# preferred:优先满足,但不是强制要求
# 如果找不到满足条件的节点,Pod 仍然可以调度

- weight: 100
# weight:这条软亲和规则的权重
# 范围:1~100
# 数值越大,Scheduler 越倾向选择满足该条件的节点

preference:
# preference:定义具体的节点偏好条件
# 注意:Node 软亲和这里使用 preference

matchExpressions:
# 通过 Node Label 表达式匹配节点
- key: gpu
# Node Label 的 key
operator: In
# gpu 的值必须位于 values 中
values:
- A100
# 优先选择 gpu=A100 的节点
containers:
- name: myapp
image: nginx:1.27

假设节点标签如下:

1
2
3
node01   disk=ssd   gpu=A100
node02 disk=ssd gpu=H100
node03 disk=hdd gpu=A100

在调度时会先执行硬亲和筛选

1
2
3
node01  disk=ssd   ✅ 可以
node02 disk=ssd ✅ 可以
node03 disk=hdd ❌ 直接排除

然后再执行软亲和,因此在 node01node02 ,更倾向调度到 node01

1
2
node01  gpu=A100   ← 优先
node02 gpu=H100

上述案例配置的意思是:Pod 必须调度到有 disk=ssd 标签的节点上;在满足这个条件的节点中,优先选择 具有gpu=A100 标签的节点。

8.拓扑域划分

集群中有以下6个节点:

1
2
3
4
5
6
7
8
[root@k8s-master01 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready <none> 635d v1.31.4
k8s-master02 Ready <none> 635d v1.31.4
k8s-master03 Ready <none> 635d v1.31.4
k8s-node01 Ready <none> 635d v1.31.4
k8s-node02 Ready <none> 635d v1.31.4
k8s-node03 Ready <none> 635d v1.31.4

在一个超大规模的集群中,可以使用不同的区域和可用区划分拓扑,同时可以精确到数据中 心或机房,甚至某个机柜。

比如按照区域划分拓扑域:

1
2
3
4
5
6
7
8
9
10
11
12
# 把k8s-master01,k8s-master02,k8s-node01,k8s-node02四个节点划分到北京的区域
[root@k8s-master01 ~]# kubectl label nodes k8s-master01 k8s-master02 k8s-node01 k8s-node02 region=beijing
node/k8s-master01 labeled
node/k8s-master02 labeled
node/k8s-node01 labeled
node/k8s-node02 labeled


# 把k8s-master03,k8s-node03两个节点划分到南京的区域
[root@k8s-master01 ~]# kubectl label nodes k8s-master03 k8s-node03 region=nanjing
node/k8s-master03 labeled
node/k8s-node03 labeled

同时如果一个区域具备多个数据中心,也可以按照可用区进行再次划分,比如北京区域有两个数据中心位于海淀和朝阳:

1
2
3
4
5
6
7
8
9
10
# 把北京的四台机器进一步划分,k8s-master01,k8s-node01在海淀,k8s-master02和k8s-node02在朝阳
# 划分haidian
[root@k8s-master01 ~]# kubectl label nodes k8s-master01 k8s-node01 k8s-node02 zone=beijing-haidian
node/k8s-master01 labeled
node/k8s-node02 labeled
node/k8s-node01 labeled

# 划分chaoyang
[root@k8s-master01 ~]# kubectl label nodes k8s-master02 zone=beijing-chaoyang
node/k8s-master02 labeled

如果需要进一步划分,可以按照不同的标签进行划分即可,就是为不同的节点打上标签,来做逻辑上的划分,只有节点标签的key和value相同时它们才属于同一个拓扑域。

9.k8s亲和力实战

9.1pod必须部署在不同的节点

  • 亲和性(Affinity):让 Pod 尽量靠近(放在相同节点 / 相同 zone 等)。
  • 反亲和性(Anti-Affinity):让 Pod 尽量远离(放在不同节点 / 不同 zone 等)。

想让多个 Pod 必须部署到不同的宿主机上,就是典型的“排斥”需求,所以用 podAntiAffinity

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
podAntiAffinity:
# 硬性反亲和:必须满足,否则 Pod 会一直 Pending
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: my-app # 不要和自己有相同标签的pod部署到一起
topologyKey: kubernetes.io/hostname # 关键!按节点 hostname 排斥
namespaces:
- default # 不要和默认命名空间的pod部署到一起
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f my-app.yaml
deployment.apps/my-app created

查看pod的分布情况,三个副本的pod分布在不同主机上:

1
2
3
4
5
[root@k8s-master01 20-affinity]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-544c97bd6d-bfjb6 1/1 Running 0 2m49s 172.16.32.133 k8s-master01 <none> <none>
my-app-544c97bd6d-m2rvf 1/1 Running 0 2m49s 172.16.85.255 k8s-node01 <none> <none>
my-app-544c97bd6d-p7vdh 1/1 Running 0 2m49s 172.16.58.214 k8s-node02 <none> <none>

在上述案例中当副本数少于节点数量时,pod会分散在不同的节点上部署,但是当副本数大于节点数时,就会有pod一直处于Pending状态,无法部署成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 增加副本数为7,修改replicas参数为7
[root@k8s-master01 20-affinity]# kubectl edit deployments.apps my-app
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2026-09-10T16:01:59Z"
generation: 1
name: my-app
namespace: default
resourceVersion: "94941999"
uid: e6287757-34d4-4fca-88a9-0015c31302d8
spec:
progressDeadlineSeconds: 600
replicas: 7 #将副本修改为7

查看pod部署情况:

1
2
3
4
5
6
7
8
9
[root@k8s-master01 20-affinity]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-544c97bd6d-5k79n 1/1 Running 0 55s 172.16.122.178 k8s-master02 <none> <none>
my-app-544c97bd6d-8vxdl 0/1 Pending 0 55s <none> <none> <none> <none>
my-app-544c97bd6d-bfjb6 1/1 Running 0 12m 172.16.32.133 k8s-master01 <none> <none>
my-app-544c97bd6d-dtg8f 1/1 Running 0 55s 172.16.135.178 k8s-node03 <none> <none>
my-app-544c97bd6d-j8qcr 1/1 Running 0 55s 172.16.195.39 k8s-master03 <none> <none>
my-app-544c97bd6d-m2rvf 1/1 Running 0 12m 172.16.85.255 k8s-node01 <none> <none>
my-app-544c97bd6d-p7vdh 1/1 Running 0 12m 172.16.58.214 k8s-node02 <none> <none>

可以看到名字是my-app-544c97bd6d-8vxdl的pod一直处于pending,可以查看具体信息:

1
2
3
4
5
6
7
8
9
# describe指令查看详细信息
[root@k8s-master01 20-affinity]# kubectl describe po my-app-544c97bd6d-8vxdl
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m3s default-scheduler 0/6 nodes are available: 6 node(s) didn't match pod anti-affinity rules. preemption: 0/6 nodes are available: 6 No preemption victims found for incoming pod.

翻译:警告:调度失败,耗时 2 分钟 3 秒,默认调度器 0/6 个节点可用:6 个节点不符合 Pod 反亲和性规则。抢占:0/6 个节点可用:6 个节点未找到传入 Pod 的抢占目标。
也就是说6个节点都不符合定义的pod反亲和性规则,所以一直pending

所以当pod副本数大于节点数量时就要用preferredDuringSchedulingIgnoredDuringExecution软亲和性来实现,参考9.2

9.2pod尽量部署在不同的节点

当pod副本数大于节点数量时,pod应该尽量分散部署,不是强制必须,用podAntiAffinitypreferredDuringSchedulingIgnoredDuringExecution

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
[root@k8s-master01 20-affinity]# cat my-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
podAntiAffinity:
# pod软亲和性,尽量满足,不强制要求必须满足
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: kubernetes.io/hostname # 关键!按节点 hostname 排斥
namespaces:
- default # 不要和默认命名空间的pod部署到一起
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建上述资源:

1
2
[root@k8s-master01 20-affinity]# kubectl replace -f my-app.yaml
deployment.apps/my-app replaced

检查pod分散情况:

1
2
3
4
5
[root@k8s-master01 20-affinity]# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-74fdd4ccdf-qs9nn 1/1 Running 0 36s 172.16.122.157 k8s-master02 <none> <none>
my-app-74fdd4ccdf-xgx9c 1/1 Running 0 31s 172.16.32.147 k8s-master01 <none> <none>
my-app-74fdd4ccdf-zvt7k 1/1 Running 0 33s 172.16.135.131 k8s-node03 <none> <none>

此时在将pod副本数扩容至8个,在观察pod分散情况:

1
2
3
4
5
6
7
8
[root@k8s-master01 20-affinity]# kubectl edit deployments.apps my-app
spec:
progressDeadlineSeconds: 600
replicas: 8 #将replicas参数修改为8
revisionHistoryLimit: 10
selector:
matchLabels:
app: my-app
1
2
# 可以看到在k8s-node02和k8s-master01上分别部署了两个pod,此时pod都是Running状态,就没有penging的了
[root@k8s-master01 20-affinity]# kubectl get po -owide

9.3同一应用分布在不同可用域

假设集群中6个节点按照zone划分haidian和chaoyang两个可用域

1
2
3
4
5
6
7
8
9
10
11
# 划分haidian,
[root@k8s-master01 ~]# kubectl label nodes k8s-master01 k8s-node01 k8s-node02 zone=beijing-haidian
node/k8s-master01 labeled #这三个节点属于同一个可用域haidian
node/k8s-node02 labeled
node/k8s-node01 labeled

# 划分chaoyang
[root@k8s-master01 ~]# kubectl label nodes k8s-master02 zone=beijing-chaoyang
node/k8s-master02 labeled #这三个节点属于同一个可用域chaoyang
node/k8s-master03 labeled
node/k8s-node03 labeled

按照上述规划,集群划分为两个不同的可用域,分别是haidianchaoyang两个可用域,可以把应用分布在不同的可用域,以提高服务的高可用性:

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
[root@k8s-master01 20-affinity]# cat diff-zone.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
podAntiAffinity:
# 硬性反亲和:必须满足,否则 Pod 会一直 Pending
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: zone #按照zone来划分拓扑域
namespaces:
- default
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f diff-zone.yaml
deployment.apps/my-app created

查看pod分散情况:

1
2
3
4
[root@k8s-master01 20-affinity]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-c458695bc-ssrxq 1/1 Running 0 10s 172.16.122.185 k8s-master02 <none> <none>
my-app-c458695bc-xd9ht 1/1 Running 0 10s 172.16.58.226 k8s-node02 <none> <none>

可以看到两个副本的pod分别落在k8s-node02(属于haidian域)和k8s-master02(属于chaoyang域) ,因为副本是2,不同的可用域也是2个,所以刚好分散,当副本数大于不同可用域的个数时,使用硬反亲和性时会出现pod是Pending状态

扩容pod副本为4:

1
2
[root@k8s-master01 20-affinity]# kubectl edit deployments.apps my-app
replicas: 2 #将replicas的值修改为4

再次查看pod分散情况,可以看到新增的两个pod一直处于pending状态,没有满足的节点,因为上述的规则是按照可用域来分散pod:

1
2
3
4
5
6
[root@k8s-master01 20-affinity]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-app-c458695bc-99drz 0/1 Pending 0 2s <none> <none> <none> <none>
my-app-c458695bc-ssrxq 1/1 Running 0 95s 172.16.122.185 k8s-master02 <none> <none>
my-app-c458695bc-w42f9 0/1 Pending 0 2s <none> <none> <none> <none>
my-app-c458695bc-xd9ht 1/1 Running 0 95s 172.16.58.226 k8s-node02 <none> <none>

9.4应用尽量和缓存服务部署在同一个可用域

如果集群分布在不同的可用域,为了提升基础组件的使用性能,可以把应用程序尽量和缓存 服务部署在同一个可用域。首先部署一个redis缓存服务:

1
2
[root@k8s-master01 20-affinity]# kubectl create deployment redis-cache --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/redis:5.0.14
deployment.apps/redis-cache created

Pod标签如下(app=redis-cache):

1
2
3
[root@k8s-master01 20-affinity]# kubectl get po --show-labels
NAME READY STATUS RESTARTS AGE LABELS
redis-cache-79987bf7f4-hfwz4 1/1 Running 0 9m31s app=redis-cache,pod-template-hash=79987bf7f4

Pod部署在node02节点上:

如果按照zone来划分拓扑域的话,在9.3中规划的 k8s-node02k8s-node01k8s-master01属于同一个可用域beijing-haidian,所以按照预期规划应用应该部署在k8s-node02k8s-node01k8s-master01三台节点上。

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
[root@k8s-master01 20-affinity]# cat my-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
podAffinity:
# 硬性反亲和:必须满足,否则 Pod 会一直 Pending
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cache
topologyKey: zone
namespaces:
- default # 不要和默认命名空间的pod部署到一起
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建应用资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f my-app.yaml
deployment.apps/my-app created

查看pod部署位置,可以看到my-app的pod按照预期部署在对应的节点上:


上述流程需要注意的问题是:redis缓存服务没有配置任何调度的限制,在缓存更新重启时pod可能调度到其他可用域中,此时应用的pod如果不更新的话,那么它们有可能不在同一个可用域,在生产中这种情况也要在redis服务中配置亲和性,让它靠近我们的应用服务才算完整。

9.5节点亲和性-必须调度到高性能节点

假设集群中有一批机器是高性能机器,而有一些需要密集计算的服务,需要部署至这些机器,以提高计算性能,此时可以使用节点亲和力来控制 Pod 尽量或者必须部署至这些高性能节点上。

设置k8s-master02的标签为disktype=nvem,k8s-master03,k8s-node01,k8s-node02,k8s-node03的标签为disktype=ssd

1
2
3
4
5
6
7
# 给节点打上标签
kubectl label nodes k8s-master02 disktype=nvem
node/k8s-master02 labeled

# 给节点打上标签
kubectl label nodes k8s-master03,k8s-node01,k8s-node02,k8s-node03 disktype=ssd

应用资源亲和性配置如下:

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
[root@k8s-master01 20-affinity]# cat disktype.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
nodeAffinity:
# 硬性反亲和:必须满足,否则 Pod 会一直 Pending
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktpye
operator: In
values: #满足任意一个即可
- ssd
- nvme
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f disktype.yaml
deployment.apps/my-app created

按照规划pod会部署在 k8s-master02 ,k8s-master03,k8s-node01,k8s-node02,k8s-node03 当中:

9.6节点亲和性-尽量调度到高性能节点

依然使用9.5中的标签配置:

1
2
k8s-master02的标签为disktype=nvem
k8s-master03,k8s-node01,k8s-node02,k8s-node03的标签为disktype=ssd

如果不是强制要求,可以用preferredDuringSchedulingIgnoredDuringExecution参数让服务尽量部署至高性能节点:

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
nodeAffinity:
# 尽量满足
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
- nvme
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

同时还可以配置优先调度到 nvem 的节点:

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
nodeAffinity:
# 硬性反亲和:必须满足,否则 Pod 会一直 Pending
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
- weight: 90
preference:
matchExpressions:
- key: disktype
operator: In
values:
- nvem
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f disktype.yaml
deployment.apps/my-app created

查看pod部署情况,副本是1,优先部署在k8s-master02节点上:


把pod副本数扩容3时,pod才会部署在disktype=ssd的节点上:

9.7应用尽量不部署至低性能节点

假如集群中有一些机器可能性能不佳或者其他因素的影响,需要控制某个服务尽量不部署至这些机器,因为节点亲和性中没有反亲和性,所以此时只需要把 operator 改为 NotIn 即可:

假设集群中k8s-master01,k8s-master02,k8s-master03三个节点性能比较低,尽可能的不要将应用部署到这三台节点上,为三个节点打上 performance=low标签

1
2
3
4
[root@k8s-master01 20-affinity]# kubectl label nodes k8s-master01 k8s-master02 k8s-master03 performance=low
node/k8s-master01 labeled
node/k8s-master02 labeled
node/k8s-master03 labeled

定义资源:

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
vim performance.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
nodeAffinity:
# 软亲和性,尽量满足
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: performance
operator: NotIn
values:
- low
containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest

创建资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f performance.yaml
deployment.apps/my-app created

查看pod部署的节点情况,可以看到pod部署在node节点上,没有在master节点:

9.8控制Pod在集群拓扑域中均分的分布

如果你的目标是 “让 Pod 尽量均匀地分散到不同拓扑域”,更推荐用 Pod Topology Spread Constraints(Pod 拓扑分布约束),而不是主要依赖 Pod 亲和/反亲和。

两者的核心区别:

机制 更适合解决什么问题 是否能“均匀分散”
podAntiAffinity 不希望两个 Pod 出现在同一个拓扑域 可以间接实现,但控制不够精细
topologySpreadConstraints 控制不同拓扑域之间 Pod 数量差异 最适合

比如你的拓扑域是:

1
2
zone=beijing-haidian
zone=beijing-chaoyang

你希望 4 个 Pod 最终变成:

1
2
haidian   2
chaoyang 2

这种需求应该优先用:

1
topologySpreadConstraints

而软反亲和可以做到“尽量分散”,但不如 topologySpreadConstraints精确,例如:

1
2
3
4
5
6
7
8
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: my-app
topologyKey: zone

这代表:调度器尽量避免把 Pod 放到已经有 my-app 的 zone。

所以 4 个 Pod 可能接近:

1
2 / 2

但它只是一个调度偏好。

不能明确约束:

1
两个 zone 数量差不能超过 1

topologySpreadConstraints 可以,配置示例:

1
2
3
4
5
6
7
8
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app
  • topologySpreadConstraints:拓扑域约束配置,多个副本均匀分布在不同的域中
  • maxSkew:指定允许的最大偏差。例如,如果 maxSkew 设置为 1,那么在任何拓扑域 中,副本的数量最多只能相差 1
  • whenUnsatisfiable:指定当无法满足拓扑约束时的行为
    • DoNotSchedule:不允许调度新的 Pod,直到满足约束
    • ScheduleAnyway:即使不满足约束,也允许调度新的 Pod
  • topologyKey:指定拓扑域的键
  • labelSelector:指定要应用拓扑约束的 Pod 的标签选择器,通常配置为当前 Pod 的标 签

9.8应用均匀部署在不同的机房

Kubernetes 的 topologySpreadConstraints(拓扑域约束) 是一种高级的调度策略,用于确 保工作负载的副本在集群中的不同拓扑域(如节点、可用区、区域等)之间均匀分布。

节点规划如下:

1
2
3
region=beijing的有:k8s-master01,k8s-master02,k8s-node01,k8s-node02
zone=beijing-haidian的有:k8s-master01,k8s-node01,k8s-node02
zone=beijing-chaoyang的有:k8s-master02

需求时pod必须部署在beijing,并且在beijing-haidianbeijing-chaoyang之间均匀的分布

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
[root@k8s-master01 20-affinity]# vim spread.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 8
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app #定义的pod标签时app=myapp
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: region
operator: In
values:
- beijing

topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-app

containers:
- name: app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest
# 注意:topologySpreadConstraints和affinity可以分开单独使用

创建资源:

1
2
[root@k8s-master01 20-affinity]# kubectl create -f spread.yaml
deployment.apps/my-app created

查看pod分布情况,可以看到8个副本的pod在zone=beijing-chaoyang的k8s-master02节点上部署了4个,而另外4个副本分散在zone=beijing-haidian节点上,只是zone=beijing-chaoyang的节点就一个,但是按照zone来均匀分散的目的已经完成。