各位听众,各位看官,大家好!我是你们的老朋友——一位在 Kubernetes (K8s) 的世界里摸爬滚打多年的“老码农”。今天,咱们不谈高深的架构,不聊复杂的源码,就来聊聊 K8s 里一个听起来高大上,但实际上非常实用,而且能让你的应用跑得更稳、更舒服的玩意儿:亲和性与反亲和性调度策略。
这名字听起来是不是有点像相亲节目?没错,K8s 的调度器就像一个媒婆,负责把你的应用(Pod)介绍给合适的“家庭”(Node)。而亲和性和反亲和性,就是媒婆在牵线搭桥时需要考虑的重要因素。
为什么要考虑亲和性和反亲和性?
想象一下,如果你有两个好兄弟,一个爱吃辣,一个爱吃甜。你请他们吃饭,总不能把他们安排在一家只有辣菜的餐馆吧?或者,如果你有两个死对头,让他们坐在一起,恐怕还没等菜上来,就要打起来了。
K8s 也是一样。有些 Pod 之间天生就应该“亲近”,比如,数据库的 Master 和 Slave,最好部署在不同的 Node 上,这样可以避免单点故障。而有些 Pod 之间则应该“疏远”,比如,同一个应用的多个实例,最好部署在不同的 Node 上,以提高应用的可用性。
所以,亲和性和反亲和性,就是为了让我们的应用在集群中找到最合适的“归宿”,保证应用的性能、可用性和稳定性。
一、亲和性:让 Pod 们“亲上加亲”
亲和性,顾名思义,就是让 Pod 们尽可能地部署在一起。这就像给 Pod 们找对象,条件是“门当户对”、“志趣相投”。
K8s 提供了两种类型的亲和性:
- 节点亲和性 (Node Affinity):基于 Node 的标签进行匹配,指定 Pod 只能部署在满足特定标签的 Node 上。
- Pod 间亲和性 (Pod Affinity):基于 Pod 的标签进行匹配,指定 Pod 只能部署在与特定 Pod 部署在同一个 Node 上。
咱们一个个来分析:
1. 节点亲和性 (Node Affinity)
想象一下,你有一台 GPU 服务器,专门用来跑深度学习任务。你肯定不希望你的深度学习 Pod 被调度到没有 GPU 的 Node 上吧?这时候,就可以使用节点亲和性。
首先,给你的 GPU 服务器打上一个标签,比如 gpu=true
:
kubectl label node <node-name> gpu=true
然后,在你的 Pod 定义文件中,指定节点亲和性:
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: your-gpu-image
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- "true"
这段 YAML 的意思是:
requiredDuringSchedulingIgnoredDuringExecution
:表示在调度时必须满足亲和性规则,但是在 Pod 运行期间,如果 Node 的标签发生变化,Pod 不会被驱逐。nodeSelectorTerms
:定义了 Node 选择的条件。matchExpressions
:定义了具体的匹配规则。key: gpu
:表示要匹配的标签的 key 是gpu
。operator: In
:表示匹配的操作符是In
,也就是 Node 必须包含gpu=true
标签。values: ["true"]
:表示匹配的值是true
。
简单来说,这段 YAML 就是告诉 K8s:这个 Pod 只能调度到拥有 gpu=true
标签的 Node 上。
节点亲和性还有另外两种策略:
preferredDuringSchedulingIgnoredDuringExecution
:表示在调度时尽量满足亲和性规则,但是如果找不到合适的 Node,也可以调度到不满足规则的 Node 上。requiredDuringSchedulingRequiredDuringExecution
:和requiredDuringSchedulingIgnoredDuringExecution
类似,但是如果 Node 的标签发生变化,导致 Pod 不再满足亲和性规则,Pod 会被驱逐。
策略 | 描述 |
---|---|
requiredDuringSchedulingIgnoredDuringExecution |
必须满足,调度后忽略。Pod 必须调度到满足条件的 Node 上,调度后即使 Node 标签改变,Pod 也不会被驱逐。 |
preferredDuringSchedulingIgnoredDuringExecution |
尽量满足,调度后忽略。Pod 尽量调度到满足条件的 Node 上,如果没有满足条件的 Node,也可以调度到其他 Node 上。调度后即使 Node 标签改变,Pod 也不会被驱逐。 |
requiredDuringSchedulingRequiredDuringExecution |
必须满足,调度后也必须满足。Pod 必须调度到满足条件的 Node 上,调度后如果 Node 标签改变,导致 Pod 不再满足条件,Pod 会被驱逐。 |
2. Pod 间亲和性 (Pod Affinity)
有时候,我们需要让两个 Pod 部署在同一个 Node 上,比如,一个 Web 应用和一个数据库,它们之间需要频繁的通信,如果部署在不同的 Node 上,会增加网络延迟。
这时候,就可以使用 Pod 间亲和性。
假设你有一个 Web 应用 Pod,标签是 app=web
,你想让数据库 Pod 也部署在同一个 Node 上。你可以这样定义数据库 Pod:
apiVersion: v1
kind: Pod
metadata:
name: db-pod
labels:
app: db
spec:
containers:
- name: db-container
image: your-db-image
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "web"
topologyKey: kubernetes.io/hostname
这段 YAML 的意思是:
podAffinity
:表示使用 Pod 间亲和性。requiredDuringSchedulingIgnoredDuringExecution
:和节点亲和性中的含义一样。labelSelector
:定义了要匹配的 Pod 的标签。matchExpressions
:定义了具体的匹配规则。key: app
:表示要匹配的标签的 key 是app
。operator: In
:表示匹配的操作符是In
,也就是要匹配的 Pod 必须包含app=web
标签。values: ["web"]
:表示匹配的值是web
。
topologyKey: kubernetes.io/hostname
:表示亲和性的范围是 Node。也就是说,只有和app=web
的 Pod 部署在同一个 Node 上的,才能满足亲和性规则。
简单来说,这段 YAML 就是告诉 K8s:这个数据库 Pod 只能调度到和 app=web
的 Pod 部署在同一个 Node 上。
二、反亲和性:让 Pod 们“保持距离”
反亲和性,和亲和性正好相反,就是让 Pod 们尽可能地不要部署在一起。这就像给 Pod 们找对象,条件是“八字不合”、“水火不容”。
K8s 也提供了两种类型的反亲和性:
- 节点反亲和性 (Node Anti-Affinity):基于 Node 的标签进行匹配,指定 Pod 不能部署在满足特定标签的 Node 上。
- Pod 间反亲和性 (Pod Anti-Affinity):基于 Pod 的标签进行匹配,指定 Pod 不能部署在与特定 Pod 部署在同一个 Node 上。
咱们也一个个来分析:
1. 节点反亲和性 (Node Anti-Affinity)
假设你有一些老旧的服务器,性能比较差,你想避免重要的 Pod 调度到这些服务器上。你可以给这些服务器打上一个标签,比如 legacy=true
,然后使用节点反亲和性。
apiVersion: v1
kind: Pod
metadata:
name: important-pod
spec:
containers:
- name: important-container
image: your-important-image
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: legacy
operator: DoesNotExist
这段 YAML 的意思是:这个 Pod 不能调度到拥有 legacy=true
标签的 Node 上。 DoesNotExist
是一个操作符,表示 Node 不能包含指定的标签。
2. Pod 间反亲和性 (Pod Anti-Affinity)
这可能是最常用的反亲和性策略了。为了提高应用的可用性,我们通常会运行多个相同的 Pod 实例。但是,如果这些 Pod 实例都部署在同一个 Node 上,一旦这个 Node 发生故障,整个应用就挂了。
所以,我们需要使用 Pod 间反亲和性,让这些 Pod 实例尽可能地部署在不同的 Node 上。
假设你有一个 Web 应用 Pod,标签是 app=web
,你想让同一个应用的多个实例不要部署在同一个 Node 上。你可以这样定义 Web 应用 Pod:
apiVersion: v1
kind: Pod
metadata:
name: web-pod
labels:
app: web
spec:
containers:
- name: web-container
image: your-web-image
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "web"
topologyKey: kubernetes.io/hostname
这段 YAML 的意思是:
podAntiAffinity
:表示使用 Pod 间反亲和性。requiredDuringSchedulingIgnoredDuringExecution
:和亲和性中的含义一样。labelSelector
:定义了要匹配的 Pod 的标签。matchExpressions
:定义了具体的匹配规则。key: app
:表示要匹配的标签的 key 是app
。operator: In
:表示匹配的操作符是In
,也就是要匹配的 Pod 必须包含app=web
标签。values: ["web"]
:表示匹配的值是web
。
topologyKey: kubernetes.io/hostname
:表示反亲和性的范围是 Node。也就是说,只有和app=web
的 Pod 部署在同一个 Node 上的,才不满足反亲和性规则。
简单来说,这段 YAML 就是告诉 K8s:同一个 app=web
的 Pod 实例,不能部署在同一个 Node 上。
三、实战案例:打造高可用的 Web 应用
现在,让我们通过一个实战案例,来巩固一下我们今天学到的知识。
假设我们要部署一个高可用的 Web 应用,包含以下组件:
- Web 应用 (web):提供用户界面,需要部署多个实例,保证高可用。
- 数据库 (db):存储用户数据,需要部署 Master 和 Slave,保证数据安全。
- 缓存 (cache):缓存热点数据,提高访问速度。
我们的目标是:
- Web 应用的多个实例尽可能部署在不同的 Node 上。
- 数据库的 Master 和 Slave 部署在不同的 Node 上。
- 缓存和 Web 应用部署在同一个 Node 上,减少网络延迟。
我们可以这样定义这些组件:
1. Web 应用 (web)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web-container
image: your-web-image
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "web"
topologyKey: kubernetes.io/hostname
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "cache"
topologyKey: kubernetes.io/hostname
这里使用了 Pod 间反亲和性,保证同一个 Web 应用的多个实例不会部署在同一个 Node 上。同时,使用了 Pod 间亲和性(preferredDuringSchedulingIgnoredDuringExecution
),尽量和缓存部署在同一个节点,提高了性能。weight: 100
表示优先级,值越大,优先级越高。
2. 数据库 Master (db-master)
apiVersion: v1
kind: Pod
metadata:
name: db-master
labels:
app: db
role: master
spec:
containers:
- name: db-container
image: your-db-image
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "db"
topologyKey: kubernetes.io/hostname
这里使用了 Pod 间反亲和性,保证数据库的 Master 和 Slave 不会部署在同一个 Node 上。
3. 数据库 Slave (db-slave)
apiVersion: v1
kind: Pod
metadata:
name: db-slave
labels:
app: db
role: slave
spec:
containers:
- name: db-container
image: your-db-image
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "db"
topologyKey: kubernetes.io/hostname
和 Master 一样,也使用了 Pod 间反亲和性。
4. 缓存 (cache)
apiVersion: v1
kind: Pod
metadata:
name: cache
labels:
app: cache
spec:
containers:
- name: cache-container
image: your-cache-image
缓存组件没有特别的亲和性或反亲和性配置,因为 Web 应用已经配置了亲和性,会尽量和缓存部署在同一个节点。
通过以上配置,我们就可以打造一个高可用的 Web 应用,保证应用的性能、可用性和稳定性。
四、总结与注意事项
今天,我们一起学习了 K8s 中的亲和性与反亲和性调度策略。希望通过今天的学习,大家能够更好地理解和运用这些策略,让你的应用在 K8s 集群中跑得更稳、更舒服。
最后,给大家几点注意事项:
- 过度使用亲和性和反亲和性可能会导致调度失败。如果你的集群资源不足,或者亲和性规则过于严格,可能会导致 Pod 无法调度。
- 亲和性和反亲和性只是尽力而为,不能保证 100% 的效果。K8s 的调度器会尽力满足你的要求,但是如果实在无法满足,可能会忽略你的规则。
- 合理使用亲和性和反亲和性可以提高应用的性能、可用性和稳定性。但是,也需要根据实际情况进行调整,找到最适合你的应用的配置。
希望今天的分享对大家有所帮助!如果你还有任何问题,欢迎随时提问。咱们下期再见! 🎉