服务器K8s亲和性配置:从节点选择到Pod互斥的落地实践
先弄清亲和性到底解决什么问题
K8s亲和性配置的本质,是告诉调度器「这个Pod更愿意去哪些节点」或「必须和哪些Pod待在一起」。
默认调度器只看资源是否够用,不看业务逻辑;
而亲和性让你能用标签把节点和Pod关联起来,实现定向调度。
它主要分三类:
- nodeAffinity:Pod和节点之间的匹配,比如只调度到带
disktype=ssd标签的机器。 - podAffinity:Pod和Pod靠近,比如前端服务尽量和缓存服务在同一节点。
- podAntiAffinity:Pod和Pod互斥,比如同一应用的三个副本不要挤在一台机器上。
适用条件:集群至少有两个可调度节点,且节点或已有Pod带有可用于区分的标签。
单节点集群测不出效果,建议在测试环境先验证。
准备标签和测试环境
先确认节点状态和标签情况:
kubectl get nodes --show-labels
如果节点没有业务标签,手动打一个:
kubectl label nodes node-1 disktype=ssd
kubectl label nodes node-2 disktype=hdd
再准备一个测试用的Deployment,镜像用 nginx:alpine 即可,方便观察Pod落在哪个节点:
kubectl create deployment affinity-demo --image=nginx:alpine --replicas=2
查看调度结果:
kubectl get pods -o wide
输出中的 NODE 列就是实际落点,后续每一步都靠它验证。
nodeAffinity:把Pod绑到指定节点
节点亲和分两种写法,区别在「必须」还是「尽量」:
requiredDuringSchedulingIgnoredDuringExecution:硬性要求,不满足就Pending。preferredDuringSchedulingIgnoredDuringExecution:软性偏好,不满足也会调度到其他节点。
下面这个YAML要求Pod必须调度到 disktype=ssd 的节点:
apiVersion: v1
kind: Pod
metadata:
name: nginx-ssd
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx:alpine
保存为 node-affinity.yaml 后执行:
kubectl apply -f node-affinity.yaml
kubectl get pod nginx-ssd -o wide
如果 NODE 显示的是带 ssd 标签的节点,说明生效。
若长时间 Pending,用 kubectl describe pod nginx-ssd 看 Events,通常会提示 node(s) didn't match node affinity。
软性写法只需把 requiredDuringScheduling... 换成 preferredDuringScheduling...,
并在 matchExpressions 同级加 weight:(取值1-100),
1
表示偏好权重。
podAntiAffinity:避免副本扎堆
生产环境更常用反亲和,让同一Deployment的Pod分散到不同节点,降低单点故障风险。
关键点是 topologyKey,它定义「以什么为隔离单位」。
常用 kubernetes.io/hostname 表示按节点隔离:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-spread
spec:
replicas: 3
selector:
matchLabels:
app: web-spread
template:
metadata:
labels:
app: web-spread
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-spread
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:alpine
应用后查看分布:
kubectl apply -f web-spread.yaml
kubectl get pods -l app=web-spread -o wide
三个Pod应落在不同节点。注意:如果节点数少于副本数,多余的Pod会一直Pending,这是反亲和的硬约束导致的,属于预期行为而非故障。
podAffinity的写法和它对称,把 podAntiAffinity 换成 podAffinity 即可,效果是尽量和匹配的Pod同节点。
容易踩的坑和调度失败排查
- 标签写错或大小写不一致:K8s标签区分大小写,
App和app是两个不同的键,用kubectl get nodes --show-labels核对。 - required太严格导致Pending:硬约束一旦无节点满足,Pod永远起不来。优先用preferred过渡,再按需要收紧。
- topologyKey缺失或拼错:podAffinity和podAntiAffinity必须写
topologyKey,写错会报invalid value或调度异常。 - 节点被污点隔离:即使亲和性匹配,节点有
NoSchedule污点也会拒绝调度,用kubectl describe node <节点名>查看Taints。 - 改了标签但Pod没重新调度:亲和性只在调度时刻生效,修改节点标签不会自动迁移已运行的Pod,需重建Pod触发重新调度。
排查通用命令:
kubectl describe pod | tail -20
kubectl get events --sort-by=.lastTimestamp
kubectl describe node <节点名>
验证配置是否真正生效
配置完成后别只看 kubectl get pods 是否Running,要确认落点符合预期:
- 用
kubectl get pods -o wide检查每个Pod的NODE列。 - 用
kubectl describe pod查看Node-Selectors和Events,确认调度决策来源。 - 对反亲和场景,统计每个节点上的Pod数量,确认没有超出预期。
- 临时给节点打错标签,观察新Pod是否被拒绝调度,反向验证规则确实在起作用。
几个常见疑问
nodeSelector和nodeAffinity怎么选?
nodeSelector只能做等值匹配,写法简单;nodeAffinity支持 In、NotIn、Exists 等操作符和软硬约束,表达能力更强。简单场景用nodeSelector就够了,需要复杂逻辑时用nodeAffinity。
podAffinity的required和preferred有什么区别?
required是硬性要求,找不到匹配Pod就不调度;preferred是尽量满足,找不到也会调度到其他节点。生产环境建议对可用性敏感的服务用preferred,避免因约束过严导致Pod无法启动。
亲和性会影响滚动更新吗?
会。滚动更新时新Pod要重新参与调度,如果反亲和约束太严或节点资源不足,新Pod可能Pending,导致更新卡住。更新前建议确认节点数量和资源余量。
如何让Pod尽量分散但不强制?
把 requiredDuringSchedulingIgnoredDuringExecution 改成 preferredDuringSchedulingIgnoredDuringExecution,并加上 weight 值,这样调度器会尽量分散,节点不够时也不会阻塞。