IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
在第 36 篇中,我们学会了用 Requests 和 Limits 控制 Pod 的资源使用,Scheduler 会依据资源请求自动将 Pod 调度到合适的节点。但资源充足只是调度的最低要求。在生产环境中,你往往需要更精细地决定 Pod 应该落在哪些节点上------比如,Redis 这类磁盘敏感的服务,能否强制调度到挂载了 SSD 的节点?多个 Flask 副本,能否让它们分布在不同的节点以避免单点故障?某些节点是否为 GPU 计算专用,普通业务 Pod 不得进入?
Docker Compose 完全不需要考虑这些问题,因为它只在一台机器上运行。而 Kubernetes 管理的是一个节点池,调度策略直接影响资源利用率、容灾能力和运维复杂度。今天这篇,我们就把 K8s 调度的三大利器------亲和性、污点与容忍------彻底搞懂,并将它们应用到贯穿案例的 Flask + Redis 应用中。
一、节点亲和性(Node Affinity):吸引 Pod 到指定节点
1.1 为什么需要节点亲和性?
默认情况下,Scheduler 会根据 Requests 与节点的可分配资源进行匹配,Pod 可能落在任何符合条件的节点上。但如果你需要"让 Redis 始终使用 SSD 磁盘的节点",或者"让某些服务只在指定的 GPU 节点上运行",就需要节点亲和性。
节点亲和性像是一种吸引力规则 ------节点上带有特定的标签,Pod 则通过 affinity 声明"我喜欢带有这些标签的节点"。
1.2 节点标签
首先,你需要给节点打上标签:
bash
kubectl label node minikube disktype=ssd
查看标签:
bash
kubectl get node minikube --show-labels
# NAME STATUS ROLES AGE VERSION LABELS
# minikube Ready control-plane 1d v1.31.0 ...,disktype=ssd
1.3 硬亲和(required)与软亲和(preferred)
节点亲和性分为两种:
-
requiredDuringSchedulingIgnoredDuringExecution (硬亲和):必须满足。如果没有任何节点符合条件,Pod 将无法调度,一直处于 Pending。
-
preferredDuringSchedulingIgnoredDuringExecution (软亲和):倾向满足。Scheduler 会优先调度到符合条件的节点,但如果没有,也可以落在其他节点上。
两者的名字中都有 IgnoredDuringExecution,意思是"一旦 Pod 已经运行,节点标签的变化不会驱逐正在运行的 Pod"。如果需要在标签变化时强制驱逐,需要更复杂的机制(如自定义控制器)。
1.4 示例:将 Redis 强制调度到 SSD 节点
bash
apiVersion: v1
kind: Pod
metadata:
name: redis-ssd
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: redis
image: redis:alpine
解释:
-
matchExpressions:可以有多个条件,它们之间是 AND 关系。 -
operator: In:标签的值必须在values列表中。还有NotIn、Exists、DoesNotExist、Gt(大于)、Lt(小于)等。 -
如果 Minikube 只有单节点并已打上
disktype=ssd,Pod 会正常调度。
部署后查看调度:
bash
kubectl get pod redis-ssd -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# redis-ssd 1/1 Running 0 10s 10.244.1.15 minikube
如果没有匹配节点,Pod 会 Pending:
bash
kubectl describe pod redis-ssd | grep -A5 Events
# Warning FailedScheduling ... 0/1 nodes are available: 1 node(s) didn't match node selector.
1.5 软亲和示例
bash
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
weight 是权重(1-100),Scheduler 会根据权重对节点打分。如果集群中有多个节点,权重越高的条件越优先被满足。
二、Pod 亲和性与反亲和性(Pod Affinity/Anti-affinity):Pod 之间的拓扑关系
2.1 什么是 Pod 亲和性?
节点亲和性解决了"Pod 与节点的关系",但实际中更常见的是"Pod 与 Pod 的关系"。例如:
-
Pod 亲和性:把 Web 应用调度到缓存所在节点的附近,降低延迟。
-
Pod 反亲和性:将同一服务的多个副本分散到不同节点上,避免单节点故障导致服务全部不可用。
Pod 亲和性与反亲和性通过 podAffinity 和 podAntiAffinity 实现,规则基于 Pod 的标签,并且可以根据拓扑域 (topologyKey)来控制亲和性的粒度。常见的 topologyKey 有 kubernetes.io/hostname(节点级别)、topology.kubernetes.io/zone(可用区级别)。
2.2 示例:让 Flask 靠近 Redis(Pod 亲和性)
假设你有一个 Redis Pod 带着标签 app=redis,你希望 Flask Pod 尽量与它在同一个节点上。
首先创建 Redis Pod 并打标签(如果还未创建):
bash
kubectl run redis --image=redis:alpine --labels=app=redis
现在创建一个 Flask Deployment,声明 Pod 亲和性:
bash
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-affinity
spec:
replicas: 2
selector:
matchLabels:
app: flask-counter
template:
metadata:
labels:
app: flask-counter
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
containers:
- name: flask
image: flask-redis-counter:3.0
ports:
- containerPort: 5000
这里 requiredDuringSchedulingIgnoredDuringExecution 要求 Flask Pod 必须与拥有标签 app=redis 的 Pod 调度在同一个 hostname 上。如果 Redis Pod 不存在或没有相同拓扑的节点,Flask Pod 无法调度。
在 Minikube 单节点中,这始终成立。在多节点集群中,你会看到 Flask Pod 被拉到 Redis 所在的节点。
2.3 示例:让 Flask 副本分散部署(Pod 反亲和性)
为了高可用,我们希望多个 Flask 副本不要挤在同一个节点上。使用 Pod 反亲和性:
bash
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- flask-counter
topologyKey: kubernetes.io/hostname
这段配置要求:如果某节点上已存在带有 app=flask-counter 标签的 Pod,则新 Pod 不能再调度到同一 hostname 上。这就强制了每个节点最多只运行一个副本。
-
优点:彻底的故障隔离,一个节点宕机只会影响一个副本。
-
缺点 :如果你的副本数超过节点数,多余的 Pod 将无法调度,处于 Pending 状态。此时应考虑使用软反亲和性(
preferredDuringSchedulingIgnoredDuringExecution),允许在必要时放宽限制。
2.4 topologyKey 的选择
-
kubernetes.io/hostname:节点级分布。 -
topology.kubernetes.io/zone:可用区级分布(需要云平台支持),防止整个可用区故障。 -
自定义标签:如
rack、failure-domain。
在多副本有状态应用(如数据库集群)中,反亲和性结合 topologyKey: hostname 可以确保每个副本都独占一个节点,避免资源竞争和单点故障。
三、污点与容忍(Taints and Tolerations):排斥与准入
3.1 污点的概念
如果说亲和性是"吸引",那么污点就是"排斥"。节点上的污点(Taint)会阻止没有相应容忍(Toleration)的 Pod 被调度到该节点上,甚至驱逐已在运行的 Pod。
污点有三个要素:key、value、effect。
effect 有三种:
-
NoSchedule:不调度新的 Pod,但不影响已在运行的 Pod。
-
PreferNoSchedule:尽量不调度新的 Pod,但不强制。
-
NoExecute :不仅不调度新的 Pod,还会驱逐已经运行的、没有相应容忍的 Pod。
3.2 给节点添加污点
bash
# 给节点 minikube 添加污点:专用 GPU
kubectl taint node minikube gpu=true:NoSchedule
查看污点:
bash
kubectl describe node minikube | grep Taints
# Taints: gpu=true:NoSchedule
现在,普通 Pod 将无法调度到这个节点上(除非它们有容忍)。这可以用来保护专用节点。
3.3 Pod 容忍污点
如果想让某个 Pod 能够调度到有污点的节点,需要在 Pod 中配置容忍:
bash
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
你也可以容忍所有污点:
bash
tolerations:
- operator: "Exists"
但谨慎使用,可能让 Pod 跑到不合适的节点上。
3.4 污点的典型场景
-
GPU 专用节点 :给节点打上
gpu=true:NoSchedule,只有请求了 GPU 资源的 Pod 配置了对应容忍。 -
控制平面隔离 :默认 master 节点有污点
node-role.kubernetes.io/control-plane:NoSchedule,防止普通业务 Pod 调度上去。 -
节点维护 :在维护前给节点添加
NoExecute污点,Pod 会自动被驱逐迁移。
3.5 移除污点
bash
kubectl taint node minikube gpu=true:NoSchedule-
注意末尾的 - 表示移除污点。
四、实战:为 Flask + Redis 配置调度策略
现在将亲和性和反亲和性应用到贯穿案例中。我们需要:
-
确保 Redis 调度到有标签
tier=backend的节点(节点亲和性)。 -
确保 Flask 副本尽量与 Redis 在同一可用区(Pod 亲和性),但副本之间要分散在节点上(Pod 反亲和性)。
-
为 Redis 节点添加污点,只允许 Redis 类 Pod 调度(可选)。
4.1 准备节点
Minikube 单节点做不了完整的多节点演示,但我们可以练习配置。先给节点加标签和污点(用于学习,不要影响其他 Pod,可使用 PreferNoSchedule)。
bash
kubectl label node minikube tier=backend
kubectl taint node minikube tier=backend:PreferNoSchedule
4.2 创建 Redis Deployment 带节点亲和性和容忍
bash
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-backend
spec:
replicas: 1
selector:
matchLabels:
app: redis
tier: backend
template:
metadata:
labels:
app: redis
tier: backend
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: tier
operator: In
values:
- backend
tolerations:
- key: "tier"
operator: "Equal"
value: "backend"
effect: "PreferNoSchedule"
containers:
- name: redis
image: redis:alpine
4.3 创建 Flask Deployment 带 Pod 亲和性和反亲和性
bash
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-backend
spec:
replicas: 3
selector:
matchLabels:
app: flask-counter
template:
metadata:
labels:
app: flask-counter
spec:
affinity:
# 亲和性:靠近 Redis
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: tier
operator: In
values:
- backend
topologyKey: kubernetes.io/hostname
# 反亲和性:副本分散
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- flask-counter
topologyKey: kubernetes.io/hostname
containers:
- name: flask
image: flask-redis-counter:3.0
ports:
- containerPort: 5000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
我们使用了 preferredDuringSchedulingIgnoredDuringExecution(软亲和)来避免单节点环境中因为满足不了硬条件而无法调度。在生产多节点环境中,可以切换为 required。
部署后查看 Pod 分布(单节点全部在 minikube):
4.4 验证亲和性和容忍效果
检查事件,确认调度器考虑了亲和性:
bash
kubectl describe pod <flask-pod> | grep -A5 "Affinity"
由于 Minikube 单节点,我们难以看到分散效果,但配置可以无缝迁移到多节点集群。
4.5 清理污点
bash
kubectl taint node minikube tier=backend:PreferNoSchedule-
五、与 Docker Compose 的对比
Compose 没有调度概念,所有容器都在同一台宿主机上,你可以通过 depends_on 控制启动顺序,但不能控制容器在主机内的分布(单机不存在分布问题)。K8s 的亲和性和污点机制为多节点集群提供了细粒度的调度控制,这是编排平台走向大规模生产的必然需求。
六、命令速查表
七、本篇总结
-
节点亲和性 :通过
nodeAffinity将 Pod 调度到带有特定标签的节点上,支持硬性和软性两种策略。 -
Pod 亲和性与反亲和性 :通过
podAffinity和podAntiAffinity控制 Pod 之间的拓扑关系,实现拉近或分散部署,提高性能或可用性。 -
污点与容忍 :通过
Taint和Toleration实现节点的专属访问控制,防止无关 Pod 调度,支持NoSchedule、PreferNoSchedule和NoExecute效果。 -
调度策略的实际价值:合理的亲和性和反亲和性配置是实现高可用部署、资源隔离和性能优化的关键手段,也是 K8s 集群运维中常被忽略的细节。
通过本篇,你获得了控制 Pod 落点的完整工具集。下一篇文章------第 38 篇:安全:RBAC 与 ServiceAccount 实战------我们将进入 K8s 安全领域,学习如何通过基于角色的访问控制来保护集群资源。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !