亚马逊云科技 2026 年 9 月 28 日公开 EKS 实测:1000 个 Pod 的软拓扑约束在单区可用性缺口后偏移成 500/500/0 且 16 小时 45 分钟不自愈,用 Kubernetes descheduler 两轮 183 次驱逐回到 333/334/333,前置部署 Forward Deployed Engineer(FDE)可照抄 PDB 先行、topologyBalanceNodeFit 兜底的两条安全设置
2026 年 9 月 28 日亚马逊云科技公开 EKS 实测:1000 个 Pod 偏移成 500/500/0,容量恢复后 16 小时 45 分钟不自愈。
前置部署 Forward Deployed Engineer(FDE)在客户自有 Kubernetes 集群里交付时,最容易被漏掉的一类风险是集群的运行状态与清单里写的意图已经不一致,而所有组件都健康、没有任何告警。这篇复盘把软拓扑约束 topologySpreadConstraints 在节点可用性缺口后产生的偏移量、恢复所需驱逐次数与可用性代价,量到了单次驱逐的粒度。
前置部署 FDE 之所以要读它,是因为它给出的不是「漂移会发生」这种结论,而是一份可照抄的修复配方:默认只处理硬约束的 RemovePodsViolatingTopologySpreadConstraint 插件、以及让它安全地作用于软约束的两个参数 topologyBalanceNodeFit 与 PodDisruptionBudget。
前置部署 FDE 把这份配方搬进客户环境时,要面对的正是同一个问题:清单里写的 maxSkew 是 1,而线上跑出来的正是 500/500/0。下面按漂移成因、实测数字、修复机制、走查步骤与长期运维顺序展开。
原文《Fix pod distribution drift in Amazon EKS with the Kubernetes descheduler》发布于 2026 年 9 月 28 日,署名 Ramya D、Himanshu Bansal(均为亚马逊云科技 AWS Professional Services 交付顾问)与 Tushar Mishra(亚马逊云科技云支持工程师),归类为 Technical How-to 高级篇。
实验环境是 Amazon EKS 1.36、descheduler Helm chart 0.36.0、kube-prometheus-stack 90.0.0,21 台 m5.2xlarge 节点按三个可用区 7/7/7 均分;按需实例计价,这个节点组每小时约 8 美元,作者明确提醒走查要在一次会话里做完并尽快删除。
一、漂移的成因:kube-scheduler 只在 Pod 创建时评估一次拓扑分布
原文把最关键的机制单独强调了一遍:调度器只在 Pod 创建那一刻评估 topologySpreadConstraints,把它作为打分算法的一部分,一旦 Pod 落到某个节点,它的位置在该 Pod 的整个生命周期里固定不变,调度器不会再回头看运行中的 Pod。
三条字段决定这个约束的行为。topologyKey 是定义域的节点标签,按可用区分布时取 topology.kubernetes.io/zone;maxSkew 是繁忙域与最闲域之间允许的最大 Pod 数差;whenUnsatisfiable 决定超出 maxSkew 时调度器怎么办,也就是软约束与硬约束的分界。
取值为 ScheduleAnyway 时是软约束,分布只是打分偏好:偏好域此刻塞不下 Pod,Pod 就落到塞得下的域,工作负载保持可用,代价是分布吸收了这份不平衡。取值为 DoNotSchedule 时是硬约束,分布是过滤器:放不进 maxSkew 的 Pod 停在 Pending。
漂移需要两个成分同时具备,而两者都很日常。第一个成分是 Pod 流动,来自自动扩缩:实验里三个 Deployment(web-frontend 的 maxReplicas 为 500、web-api 为 300、web-orders 为 200)由 Horizontal Pod Autoscaler 驱动去追随机 HTTP 负载,35 分钟基线里 Pod 总数在大约 300 到 717 之间震荡,快速扩容时 skew 一度冲到 45,每次又都自行回到 maxSkew 以内;缩容同样不均匀,后面一次下降过程里 skew 瞬时到过 82。在健康状态下,流动既制造漂移也修正漂移。
第二个成分是一个域暂时没有可调度的容量。托管节点组升级、Karpenter 整合、Auto Scaling 组替换实例、Spot 实例中断、维护窗口里的 cordon、扩容时的 InsufficientInstanceCapacity 响应,这些常规运维动作都会让节点变为不可调度(即 node.spec.unschedulable 字段被置位)或者直接消失。
两个成分叠加,漂移就会很严重。实验用最接近实例级故障的方式复现:cordon 掉某一个可用区的全部 7 台节点,并删除落在这些节点上的演示 Pod。
二、实测:90 秒的可用区缺口,把 1000 个 Pod 变成 500/500/0,并且钉住 16 小时 45 分钟
基线快照是三个可用区分别 335、334、331 个 Pod,每工作负载 skew 在 1 到 2。缺口出现后,整个集群在约 90 秒内重排成 500/500/0,第三个可用区掉到 0 个 Pod。
可用性没有被破坏,这恰恰是软约束存在的意义,也正是漂移不会触发任何告警的原因:1000 个 Pod 全部保持运行,unready 始终为 0,Pending 只在单个 30 秒采样窗口里摸到过 1。
恢复容量后作者什么都不做,分布原地保持 500/500/0 整整 97 分钟。接下来发生的事情是整个实验里最有说明力的数据:HPA 恰好缩容又扩容,web-frontend 因为 Pod 在流动,新 Pod 落进了刚恢复的可用区,它的 skew 从 250 降到约 23;而 web-api 与 web-orders 已经顶到 maxReplicas,不再创建新 Pod,就一直停在 skew 150 与 100,在恢复后的可用区里始终是 0 个 Pod。整个集群随后在容量完全可用的状态下,把 skew 227 保持了 16 小时 45 分钟。
结论是漂移会棘轮式累积:缺口期间做出的每一次放置都会留下。有流动的工作负载会顺带恢复,而处于稳态的工作负载——在很多生产集群里这占大多数——会无限期地保持偏移。
三、成本:首要代价是韧性,其次是跨区流量费与子网 IP 压力
这里的可用区 Availability Zone 指云上的独立故障域;跨可用区数据传输费按进出两个方向分别计费,区内的同侧流量本来不产生这笔费用。
最主要的影响是韧性:一个工作负载一半 Pod 在两个区、第三个区一个都没有,它对单可用区故障的承受力远低于它自己的清单所描述的样子,而清单仍然写着 maxSkew 为 1。
次级后果会悄悄累积三项:本可留在区内的流量产生了双向计费的跨可用区数据传输费;子网 IP 压力集中到存活的那几个区;节点利用率不均,进而扭曲自动扩缩信号与资源右调决策。
那为什么不直接用 DoNotSchedule 一次性杜绝?原文的回答很克制:这是一种有效的设计选择,只是失败模式不同,不是错误答案。硬约束会把静默的偏移换成可见的 Pending Pod;在模拟缺口期间,硬约束集群会拒绝向存活的区扩容,超出 maxSkew 的副本会一直排队到该区回来。如果区域均衡是正确性要求,这个代价就是对的,同时必须对 Pending Pod 数与调度失败告警,让这份代价始终可见。反过来,如果对外提供服务比精确放置更重要,软约束就是对的基础,问题也随之为「怎样修正它允许的漂移」。
原文给了一张三列对照:软约束(ScheduleAnyway)从不在缺口期间挤掉已有 Pod,新 Pod 落在存活的区,长期分布会漂移、只能靠流动修正,对工作负载没有额外要求,要监控的是每区 Pod 数与 skew;硬约束(DoNotSchedule)同样不挤掉已有 Pod,但新 Pod 会停在 Pending,长期分布始终在 maxSkew 内,要求工作负载能容忍容量下降,要监控的是 Pending Pod 与 FailedScheduling 事件;软约束加 descheduler 会主动挤掉已有 Pod、由 PDB 控制规模,缺口期间新 Pod 照常落在存活的区,每次缺口之后分布都能被恢复,代价是工作负载要能容忍重启,要监控的是 skew、驱逐数与 PDB 余量。
四、机制:descheduler 只负责驱逐,重新放置仍然交给 kube-scheduler
Kubernetes descheduler 是 Kubernetes SIG 的独立项目,按周期循环运行。它不是准入控制器,也不会自己放置 Pod:它对照策略评估集群的运行状态,驱逐违反策略的 Pod(驱逐是一次受控的 Pod 终止,会触发重新调度),再依赖 kube-scheduler 去放置替代者。因为它走 Eviction API,PodDisruptionBudget 会生效。
针对漂移的插件是 RemovePodsViolatingTopologySpreadConstraint。它默认只处理硬约束,需要在它的 constraints 参数里显式列出 ScheduleAnyway 才会作用于软约束,而这恰恰是大多数生产工作负载实际使用的配置。
两个参数承担安全职责,作者都做了实测。第一个是 topologyBalanceNodeFit: true,它禁止在不存在更好落点的情况下驱逐:在受影响的那个可用区仍处于 cordon 状态时跑一轮,它跳过了 100 个 web-api Pod 与 67 个 web-orders Pod,每一个都记录了「ignoring pod for eviction as it does not fit on any other node」。这两个数字正好等于两个 Deployment 各自需要的全部修正量——唯一能接收这些 Pod 的区不可调度,于是 descheduler 拒绝了每一次这样的驱逐。
第二个是 PDB 约束。只要驱逐会违反预算,descheduler 就不会执行:实验里三个 Deployment 的 PDB 都是 maxUnavailable: 10%,分别允许 50、30、20 个并发中断。PDB 限制的是并发中断数,保证整个再平衡过程里始终有足够 Pod 可用;而 maxNoOfPodsToEvictPerNamespace 限制的是每轮的总驱逐数,实验取值 200。两者回答的是不同问题,descheduler 两个都遵守。
五、修正数据:183 次驱逐对理论下限约 180,162 次集中在第一轮
三个区都恢复可调度后,作者把 CronJob 挂起、用手动 Job 逐轮独立计数。跑第一轮之前先从目标分布算出理论最少移动量,约 180 次。
实际结果分四轮:缺口期间(区仍被 cordon)那一轮驱逐了 225 个,全部落在 web-frontend,让 frontend 单独先到达目标;第一轮正式修正驱逐 162 个,分布变为 345/340/315,其中 web-frontend 15 个、web-api 91 个、web-orders 56 个;第二轮驱逐 21 个(1/9/11),分布收敛到 333/334/333,skew 降到 1;第三轮是验证轮,驱逐 0 个,说明已经稳态。四轮合计 183 次驱逐,对预测下限约 180。
第一轮那 162 次驱逐全部在同一轮里完成,虽然 PDB 只允许 50/30/20 的并发中断——原因是驱逐是串行的,而替代 Pod 变为 Ready 之后 PDB 余量会恢复。换句话说,PDB 限制的是速率,不是总量。
可用性代价有两个口径。Grafana 看板按 30 秒采样,整个过程中 unready 始终为 0;Prometheus 采样更细,在第一轮 162 次驱逐期间捕捉到 5 个 Pending Pod,占集群的 0.5 percent,持续时间不到一个抓取周期。
作者主动给这个数字加了两条限定,这一点很值钱。一是镜像启动速度:实验用的是 Kubernetes HPA 走查里的 hpa-example 镜像,启动很快;如果应用要预热缓存、同步状态或重放日志,同样的驱逐量会带来更宽的 unready 窗口,所以应当按需下调驱逐上限、上调 PDB 预算。二是余量:同样的实验换成 3 节点集群,Pending 峰值按比例明显更大,因为更密的集群没有多少地方放替代 Pod。
还有一个必须写明的代价:驱逐终究是干扰。每一个被修正的 Pod 都是一次 Pod 重启,长连接会断开重连、缓存从冷开始、慢就绪探针会拉长降容窗口。PDB 限制的是并发,不是消除干扰。拿到的韧性和花掉的重启要放在一起权衡。
原文另给了一张完整的分阶段分布表:最大负载基线 335/334/331、每应用 skew 2;流动基线各区不等、skew 在 0 到 45 之间波动且能自愈;1c 区出现节点可用性缺口时变成 500/500/0、skew 500,替代 Pod 落在存活的区;容量恢复后稳定在 409/409/182、skew 227,且保持 16 小时 45 分钟,只有流动中的工作负载自愈;跑完 descheduler 后是 334/333/333、skew 1,收敛到配置的 maxSkew。
六、照抄的走查配方:五步,带具体命令与四个实测坑
第一步配容量。要在建节点组之前打开 VPC CNI 的前缀委派,这样托管节点组会自动把 m5.2xlarge 的最大 Pod 数算成 110(前缀委派分配 /28 前缀,子网需要足够的连续 IP 空间):kubectl set env daemonset aws-node -n kube-system 后接 ENABLE_PREFIX_DELEGATION=true 与 WARM_PREFIX_TARGET=1 两个键值对。节点组用 ng-drift-1000,scaling-config 取 minSize=21、maxSize=24、desiredSize=21,实例类型 m5.2xlarge,磁盘 30 GB。动手之前先确认节点在三个区是 7/7/7 均匀分布、每台节点显示 110 个可分配 Pod。
第二步配监控,用 kube-prometheus-stack 的 90.0.0 版本,再应用配套的分布看板与 recording rule。这里有三个实测坑。
第一个坑:kube-state-metrics 默认不导出可用区标签,必须把 topology.kubernetes.io/zone 加进它的 metric-labels-allowlist,否则所有按区查询都会返回空。
第二个坑:Prometheus 要给这个规模留内存。chart 默认内存上限是 2Gi,作者此前一次实验就是在正好这个上限上被 OOMKilled(退出码 137)丢掉了全部指标历史,他们跑的是 10Gi。
第三个坑是空区陷阱:一个没有 Pod 的可用区根本不产生时间序列,于是用 max() 减 min() 这类朴素查询会在最需要数据的时刻悄悄低估 skew——当那个区被清空时,朴素查询读出 0,而真实 skew 是 500。绕法是给每个有节点但没有匹配 Pod 的区补一个 0:把 sum by (zone) 的 Pod 计数结果,与 count by (zone) 的节点标签结果乘 0 再做 or 合并。
同一节还有第四个坑:不要在 CronJob 模式下画 descheduler_pods_evicted_total 指标。CronJob 模式下 Pod 每次运行只存活几秒,短于一个抓取周期,计数器永远不会被采集,面板会永远显示 0;要从 Job 日志或事件里数驱逐,或者改用 Deployment 模式部署 descheduler 才能拿到实时指标。
第三步部署工作负载。三个 Deployment 的 pod spec 里,拓扑约束写成 maxSkew: 1、topologyKey 取 topology.kubernetes.io/zone、whenUnsatisfiable 取 ScheduleAnyway(这一行就是漂移的来源),labelSelector 用 app 标签匹配,并加上 matchLabelKeys 列表含 pod-template-hash 以实现按发布批次分布,这个字段需要 Kubernetes 1.27 及以上。每个 Deployment 另外配一个 CPU 50% 的 HPA、一个 maxUnavailable 为 10% 的 PodDisruptionBudget,以及一个用 wget 发随机 HTTP 突发的压测负载。
第四步模拟节点可用性缺口,实验用 kubectl drain 加标签选择器完成(drain 会先 cordon 再驱逐 Pod),理由是可移植且贴近实例丢失的效果,命令带上 --ignore-daemonsets、--delete-emptydir-data 与 --timeout=10m。drain 完之后 skew 应当大于 1,被挤走的 Pod 已经落到另外两个区里,按工作负载的分布是 web-frontend 250/250、web-api 150/150、web-orders 100/100,被排空的区为 0。然后 uncordon 该区,等 5 到 10 分钟并盯着看板:什么都不会变,恢复健康的区仍然是 0 个 Pod,因为 Kubernetes 不会为了修正软约束而搬动运行中的 Pod。
原文也提到更真实的替代工具 AWS Fault Injection Service(AWS FIS),仓库里有对应实验模板,但作者实测到一个限制:针对托管节点组,aws:ec2:stop-instances 无法把可用区按住配置的时长,因为 Auto Scaling 组在几分钟内就把被停的实例换掉了。要做可控窗口,cordon 与 drain 才是更可靠的仪器。
第五步装并运行 descheduler,Helm chart 固定 0.36.0 版本,安装时先 --set suspend=true 挂起,让每次手动触发的轮次可以被独立测量。作者给出的 descheduler-values.yaml 是一份完整文件而不是节选,要点有六处:schedule 写 */(2) * * * *(注释明确说这只是演示,生产要用 */15 或更长)、resources 请求 cpu 100m 与 memory 128Mi、上限 cpu 500m 与 memory 256Mi、serviceMonitor 打开、deschedulerPolicy 里 maxNoOfPodsToEvictPerNamespace 取 200;profiles 命名为 rebalance-topology,pluginConfig 里 RemovePodsViolatingTopologySpreadConstraint 的 args 把 constraints 列为 DoNotSchedule 与 ScheduleAnyway 两项,namespaces 只 include 演示命名空间,并打开 topologyBalanceNodeFit: true;DefaultEvictor 段设 evictSystemCriticalPods 为 false、evictLocalStoragePods 为 false、ignorePvcPods 为 true、nodeFit 为 true、priorityThreshold 取 10000;plugins 段在 balance 下启用同一个拓扑插件。
有一处部署细节值得记住:Helm chart 默认给 descheduler Pod 打上 priorityClassName: system-cluster-critical,所以在容量吃紧时这个 CronJob 的 Pod 也能排在低优先级工作负载前面被调度;chart 按惯例装进 kube-system。
手动跑一轮用 kubectl 在 kube-system 里从一个 cronjob 创建 Job 的方式完成,然后抓最后那个 descheduler Job 的日志并过滤 totalEvicted 与 「violate the pod's disruption budget」两个关键词来数本轮驱逐数。原生原文也给了按事件数驱逐的写法:在演示命名空间里按 reason 等于 RemovePodsViolatingTopologySpreadConstraint 过滤事件并按时间排序。
一个很容易误判为故障的细节:日志里会出现「Error evicting pod」与 「Cannot evict pod as it would violate the pod's disruption budget」。原文明确说那不是错误,而是 PDB 正在履行职责。所以单轮跑不完是预期的:PDB 限住了每次能驱逐多少,需要继续触发下一轮,直到 skew 消失。实验里用了两轮修正加一轮验证,才让每个工作负载的 skew 降到 1,期间 30 秒采样的 Pending 一直贴在 0。
生产环境不要手动一轮轮跑。取消挂起即可让 CronJob 按计划自己收敛,做法是给 kube-system 里的 descheduler CronJob 打一个把 spec.suspend 置为 false 的 patch。
收尾清理要按顺序做,避免持续计费:先删掉演示命名空间停掉工作负载,再卸载 descheduler 与 monitoring 两个 Helm release(Helm 不会删除命名空间和 PVC,需要单独删掉 monitoring 命名空间),然后删掉节点组——这是成本大头,用 aws eks delete-nodegroup 指定 ng-drift-1000,再等它删完;如果整个集群只是为这个演示建的,最后把集群也删掉。
七、长期运维:先窄后宽、PDB 先行,以及四条容易被忽略的规则
原文给的长期运维规则里,第一条是先窄后宽:从一个能容忍重启的命名空间开始,跑满 48 到 72 小时无异常后再扩大范围,仓库里给了 pilot 与 production 两套策略对应这个推进过程。
第二条是启用之前必须给范围内每一个工作负载都配上 PDB,这是让驱逐变安全的那个控制项;并且要注意百分比型 PDB 在小 Deployment 上会向上取整,所以副本数低于大约 10 个时要用绝对值。
第三条是 maxNoOfPodsToEvictPerNamespace 要主动定值:从命名空间 Pod 总数的 10 percent 到 20 percent 起步(实验对 1000 个 Pod 的命名空间取 200),再按收敛速度与 PDB 余量调;启动慢的工作负载要调更低。
第四条是节奏要与工作负载匹配:2 分钟的调度只是为了让演示时的收敛过程可见,生产上 15 分钟或更长更合适,因为每一轮都是一次驱逐机会。
接下来四条是作者从数据里反推出来的、容易搞反的规则。第一,要按负载而不是按时间来设闸:把 descheduler 排在闲时听起来安全,但闲时正是自动扩缩集群缩到 minReplicas 的时候,此时 skew 存在算术下限、百分比型 PDB 也最弱——实验里 HPAs 把集群缩到每个 Deployment 的 minReplicas 为 2 时,剩下六个 Pod 落在 2/4/0,一轮 descheduler 正确地判定两个 Deployment 已经均衡并跳过,却驱逐了 web-api 两个 Pod 里的一个,也就是让这个工作负载一半的量重启,只为把 skew 从 2 挪到 1,而这个结果永远到不了 0;而百分比型 PDB 之所以允许,是因为 Kubernetes 把 maxUnavailable: 10% 在 2 副本的 Deployment 上向上取整成 1。
第二,要看每工作负载的 skew,不能只看集群总量。在区被 cordon 的那一轮里,修正 web-frontend 反而让集群级 skew 暂时变大,同时让 frontend 自己的 skew 变小;descheduler 作用的对象是每个 Deployment 的 skew,集群总量可以朝反方向走并把你带偏。
第三,初期不要同时跑拓扑插件和 LowNodeUtilization:一个把 Pod 摊到多个区,另一个把 Pod 收拢到更少的节点上,两者叠加会来回震荡。先只上拓扑插件,等确认 PDB 覆盖完整、收敛稳定之后,再考虑加基于利用率的插件。
第四,计划内的容量排空或集群升级期间要挂起 CronJob,避免冲突的驱逐同时发生。
八、不该用的场景,原文给了明确清单
不要对处在分区分配中途的 Kafka 消费者做 descheduling;不要对接近检查点的批处理或机器学习训练任务做;不要对有意共置的工作负载做,例如发布订阅组、被 Amazon EBS 卷钉在某个可用区的 Pod、为了延迟而保持同区相邻的微服务对;低负载集群里 skew 本来就小,也不要动。
原文对低负载这一条给了可直接套用的算术:当 HPAs 把集群缩到每个 Deployment 的 minReplicas 为 2 时,两个副本分布在三个区,必然有一个区是空的,skew 的算术下限就是 1,任何一次驱逐都无法把 skew 做到 0。所以对小型 Deployment 要用绝对值 PDB,最好干脆把它们排除在 descheduling 之外。
原文还给了启用前的四个自问:工作负载是否无状态、可重启?在持续负载下观察到的 skew 是否占集群的显著比例(扩缩过程中的瞬时尖峰会自愈,实验的流动基线到过 skew 45 也没人事干预就恢复了,真正需要干预的是缺口之后的持续偏移)?这份偏移是否真的在韧性、跨区流量或 IP 压力上造成了损失(如果并没有,加监控就够了)?工作负载能否承受 PDB 控制的多次重启(不能的话就先收紧 PDB 并把敏感命名空间排除掉)?
另外值得单独记住的一组数字:仓库里那个 100 个 Pod、3 台节点的缩小版复现了全部发现——漂移到 50/50/0、2 小时 45 分钟不自愈、用 23 次驱逐完成修正(正好等于理论下限)、收敛到 33/34/33。也就是说,验证这套机制不需要 21 台 m5.2xlarge。
九、原文自述的局限与未覆盖的场景
第一条是可用性数字依赖镜像启动速度。0.5 percent、5 个 Pending Pod 这个结果建立在 hpa-example 镜像启动很快的前提上;要预热缓存、同步状态或重放日志的应用,同样驱逐量下 unready 窗口会更宽,必须下调驱逐上限、上调 PDB 预算。
第二条是集群密度会改变结果。同样的实验换成 3 节点集群,Pending 峰值按比例明显更大,因为更密的集群缺少放置替代 Pod 的余地;因此 0.5 percent 不能当成容量规划的一般常数。
第三条是干扰没有消失,只是被限流。PDB 限制并发中断数,但无法消除重启本身带来的冷缓存、连接重建与就绪探针延迟,这部分代价原文没有给出量化。
第四条是替代工具的限制。AWS FIS 的 aws:ec2:stop-instances 对托管节点组无法按住可用区到设定时长,因为 Auto Scaling 组几分钟内就替换了实例,所以更真实的故障注入方式在这条链路上反而不可控。
第五条是指标可观测性的缺口。CronJob 模式下 descheduler_pods_evicted_total 无法被采集,驱逐数只能从 Job 日志或事件里数,这会影响长期运维时的自动告警设计。
第六条是样本与技术栈单一。整套测量来自一个 EKS 1.36 加 descheduler 0.36.0 的环境,三个 Deployment 都是同一类无状态 Web 工作负载(hpa-example 镜像);原文没有覆盖有状态工作负载、没有覆盖自建 Kubernetes 集群或其他发行版,也没有给出把拓扑插件与利用率类插件同时开启后的实测数据。
第七条是成本只给了粗口径。节点组约每小时 8 美元是按需价格、21 台 m5.2xlarge 的数量级估算,原文没有给出整轮实验的总花费,也没有给出 descheduler 自身长期运行的资源开销实测。
十、该谁动手、具体怎么做、适用条件与回退
该动手的是三类角色。前置部署 Forward Deployed Engineer(FDE)在客户自有 Kubernetes 集群里做交付与 Day 2 运维;平台与 SRE 团队在管多可用区的高可用姿态;负责成本与容量的人要处理跨可用区流量费和子网 IP 压力这两项次级后果。
第一件具体的事是先测量再动手,而且测量方式必须绕开空区陷阱:给 kube-state-metrics 的 metric-labels-allowlist 补上 topology.kubernetes.io/zone,并且在按区计数时用 or 合并一个 count by (zone) 乘 0 的兜底序列。否则你会像原文那样,在最需要数据的时刻读到一个假的 0。
第二件是给范围内每个工作负载先配 PDB,再谈启用;副本数低于约 10 个的 Deployment 用绝对值而不用百分比,因为 maxUnavailable: 10% 在 2 副本上会被向上取整成 1,等于允许一半的量重启。
第三件是把驱逐预算定成显式数字:maxNoOfPodsToEvictPerNamespace 从命名空间 Pod 数的 10 percent 到 20 percent 起步,实验对 1000 个 Pod 取 200;同时打开 topologyBalanceNodeFit: true,让 descheduler 在没有更好落点时拒绝驱逐,这一步能在目标区不可调度时省下整轮的无效驱逐。
第四件是在插件参数里显式列出 ScheduleAnyway,否则 RemovePodsViolatingTopologySpreadConstraint 默认只对硬约束生效,而你线上跑的多半是软约束,改了等于没改。
第五件是第一步先从单命名空间试点:选一个能容忍重启的命名空间,跑满 48 到 72 小时,期间盯每工作负载的 skew、驱逐数与 PDB 余量三条线,并避开自动扩缩到 minReplicas 的低负载时段。
第六件是排期:生产用 15 分钟或更长的周期,并在计划内排空或集群升级期间挂起 CronJob。
第七件是把 descheduler 的收敛过程纳入交付验收:前置部署 FDE 在客户环境里交完这套配置之后,应当记录一次缺口演练前后的每工作负载 skew、每轮驱逐数与 Pending 峰值三个数字,作为 Day 2 运维的基线;前置部署 Forward Deployed Engineer(FDE)后续扩大命名空间范围时,用同一组数字判断收敛是否变慢。前置部署 FDE 若发现某轮驱逐数持续等于该命名空间的驱逐上限,说明 PDB 余量不足,应先放宽 PDB 而不是继续加轮次。
适用条件是三条:工作负载无状态且可重启;集群是软约束(ScheduleAnyway)主导、缺口之后存在持续偏移;范围内每个工作负载都已经有 PDB,且集群规模足够放下被驱逐 Pod 的替代者(3 节点级别的密集群应先扩容)。三条里有任何一条不成立,就先别启用,退回只加监控或改用硬约束 DoNotSchedule 并给 Pending Pod 配告警。
回退方式做在三层,都不需要改动被监控或被调度的工作负载本身。第一层最轻:把 CronJob 的 spec.suspend 置为 true,立即停止后续所有驱逐轮次,已完成的分布调整保留不动。第二层是缩小作用范围:把 namespaces 的 include 列表改回单个试点命名空间,或把某个插件从 plugins 段的 balance 列表里摘掉,让拓扑修正只作用于部分工作负载。第三层是整体摘除:helm uninstall 掉 descheduler,集群回到纯软约束的原始行为,分布恢复为「只在 Pod 流动时自愈」的状态——这个状态是安全的(原文的基线就是这样),只是会再次累积漂移。
最后一条退出路径是清理成本:不打算继续做这个实验时,先删演示命名空间,再卸载 descheduler 与 monitoring,然后删掉 ng-drift-1000 节点组——节点组是成本大头,约每小时 8 美元,删掉之后其余资源几乎不产生费用。
不适合这套方案的情形也要说清:如果集群是低负载、每个 Deployment 只有两三个副本,skew 的算术下限就是 1,descheduler 只会造成重启而拿不到任何均衡收益;如果有状态工作负载占主体、或工作负载被 EBS 卷钉在特定可用区,应当改用硬约束并接受 Pending,而不是引入驱逐。
原始来源:
- https://aws.amazon.com/blogs/containers/fix-pod-distribution-drift-in-amazon-eks-with-the-kubernetes-descheduler/
- https://github.com/aws-samples/sample-eks-descheduler-drift-demo
- https://github.com/kubernetes-sigs/descheduler/tree/master/charts/descheduler
- https://github.com/kubernetes-sigs/descheduler#removepodsviolatingtopologyspreadconstraint
- https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/
- https://docs.aws.amazon.com/eks/latest/best-practices/reliability.html
- https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
- https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights.html
评论(0)
- 暂无评论。