十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kubernetes topologySpreadConstraints 实战:把副本均匀打散,别让一个节点挂了全崩

Kubernetes topologySpreadConstraints 实战:把副本均匀打散,别让一个节点挂了全崩 Kubernetes topologySpreadConstraints 实战:把副本均匀打散,别让一个节点挂了全崩你把 Deployment 的副本数从 1 调到 3,以为高可用就有了。结果某天一个节点宕机,kubectl get pods一看——三个副本全在那个节点上,服务瞬间归零。副本数是够了,但它们全挤在一起,一荣俱荣一损俱损。调度器默认只关心「哪个节点资源够、能塞下」,并不保证副本分散。要真正做到「一个节点/一个可用区挂了,服务还活着」,得显式告诉 K8s:把这些 Pod 摊开。topologySpreadConstraints就是干这个的。先看默认调度的问题用一个最朴素的 Deployment 复现:apiVersion:apps/v1kind:Deploymentmetadata:name:webspec:replicas:3selector:matchLabels:app:webtemplate:metadata:labels:app:webspec:containers:-name:webimage:nginx:1.27resources:requests:cpu:100mmemory:128Mi在一个各节点资源都很宽裕的集群里 apply 它,很可能三个 Pod 都落在同一个节点上——因为调度器发现某节点资源最充足,就一股脑往那儿塞。用这条命令看副本落在哪:kubectl get pods-lappweb-owide\--sort-by.spec.nodeName|awk{print $1, $7}# web-xxx node-1# web-yyy node-1# web-zzz node-1 - 全在 node-1,node-1 一挂全没副本数 ≠ 高可用。分布才是。用 topologySpreadConstraints 强制打散在 Pod 模板的spec里加约束,告诉调度器按「节点」这个拓扑维度均匀分布:spec:topologySpreadConstraints:-maxSkew:1# 任意两个节点上的 Pod 数量差不超过 1topologyKey:kubernetes.io/hostname# 按节点打散whenUnsatisfiable:DoNotSchedule# 满足不了就别调度(硬约束)labelSelector:matchLabels:app:web# 只统计带这个标签的 Podcontainers:-name:webimage:nginx:1.27resources:requests:cpu:100mmemory:128Mi四个字段逐个说清楚,这是用对它的关键:topologyKey:按哪个维度打散。它是节点标签的 key。kubernetes.io/hostname表示按节点分;换成topology.kubernetes.io/zone就是按可用区分。调度器把有相同标签值的节点归为一个「拓扑域」。labelSelector:统计哪些 Pod 时算数。通常要和该 Deployment 自己的标签一致,不然它会把别的应用的 Pod 也算进「分布」里,算歪。maxSkew:最大允许的不均衡度。maxSkew: 1表示任意两个拓扑域里,匹配的 Pod 数量之差不能超过 1。3 副本 3 节点时,结果必然是每节点 1 个。whenUnsatisfiable:满足不了约束怎么办。DoNotSchedule(硬约束) 宁可 Pending 也不违反;ScheduleAnyway(软约束) 尽量满足,实在不行也先跑起来。apply 后再看分布:kubectl get pods-lappweb-owide|awkNR1 {print $7}|sort|uniq-c# 1 node-1# 1 node-2# 1 node-3 - 均匀摊开,挂任意一个只损失 1/3硬约束的坑:节点不够会卡 PendingDoNotSchedule用起来爽,但有个必须知道的副作用:当可用拓扑域不足以满足 maxSkew 时,多出来的 Pod 会一直 Pending,不会将就。比如你只有 2 个可调度节点,却要跑 5 个副本、maxSkew: 1、DoNotSchedule。理想分布是 32(差 1,合法),能放下。但如果是 2 个节点跑 3 副本,21 合法,没问题;真正卡住的场景是节点被污点/资源挤占,可落脚的域变少。这时你会看到:kubectl get pods-lappweb# web-aaa 1/1 Running# web-bbb 1/1 Running# web-ccc 0/1 Pending - 放上去就违反 maxSkew,硬约束下宁可不放kubectl describe pod web-ccc|grep-A3Events# Warning FailedScheduling ... didnt match pod topology spread constraints排查思路:看到didnt match pod topology spread constraints就是被自己的打散约束卡住了。关键判断——这个服务是「一致性优先」还是「可用性优先」:数据面/核心服务,宁可少跑也要摊开 → 保留DoNotSchedule,同时确保节点数够。无状态 Web,先跑起来比绝对均匀重要 → 改成ScheduleAnyway,让它尽量打散但不硬卡:topologySpreadConstraints:-maxSkew:1topologyKey:kubernetes.io/hostnamewhenUnsatisfiable:ScheduleAnyway# 软约束:尽量均匀,放不下也先跑labelSelector:matchLabels:app:web进阶:节点 可用区双层打散生产里更稳的做法是同时约束两个维度:先保证跨可用区(容灾),再保证同区内跨节点(容单机故障)。多个约束是同时生效、取交集的:topologySpreadConstraints:-maxSkew:1topologyKey:topology.kubernetes.io/zone# 第一层:跨可用区whenUnsatisfiable:DoNotSchedulelabelSelector:matchLabels:app:web-maxSkew:1topologyKey:kubernetes.io/hostname# 第二层:同区内跨节点whenUnsatisfiable:ScheduleAnywaylabelSelector:matchLabels:app:web这样 6 个副本会先按可用区尽量 222 摊开(容灾),再在每个区内尽量落到不同节点(容单机)。跨区用硬约束保容灾底线,区内用软约束避免因单节点资源紧张而卡住——「硬保容灾、软保均匀」是一条实用的默认策略。验证跨区分布:kubectl get pods-lappweb-ojson|\jq-r.items[] | .spec.nodeName|\whilereadn;dokubectl getnode$n\-ojsonpath{.metadata.labels.topology\.kubernetes\.io/zone}{\n};done|\sort|uniq-c# 2 zone-a# 2 zone-b# 2 zone-c - 三个区各 2 个,挂一个区还剩 2/3和 podAntiAffinity 的区别:别再用旧套路老文章里打散副本用的是podAntiAffinity(反亲和)。它能实现「不要和同类 Pod 待一起」,但表达力有限:requiredDuringScheduling的反亲和本质是「同一节点最多 1 个」,一旦节点数少于副本数就直接放不下,不能表达「差不超过 N」这种柔性均衡。topologySpreadConstraints是它的进化版,专为「均匀分布」设计,能精确控制不均衡度(maxSkew),软硬可选,还能多维度叠加。新项目直接上 topology spread,别再用反亲和硬怼——除非你的需求确实是「绝对不能同处一个节点」这种一刀切。小结副本数够不等于高可用,副本全挤一个节点时,单节点故障就是全量故障。topologySpreadConstraints让调度器按拓扑维度把 Pod 摊开:topologyKey选维度(节点/可用区)、maxSkew控不均衡度、labelSelector圈统计范围、whenUnsatisfiable定硬软。DoNotSchedule(硬)会在拓扑域不足时让 Pod Pending,describe看到didnt match pod topology spread constraints即是;ScheduleAnyway(软)只尽量均匀不硬卡。生产推荐「跨区硬约束 区内软约束」双层打散:硬保容灾底线,软避免因单点资源紧张卡调度。它是podAntiAffinity的进化版,能表达柔性均衡,新项目优先用它。一句话记忆点:高可用不看你有几个副本,看它们摊得够不够开。
返回列表