资讯动态

K8s Service 一直 Pending?SLB 配额超限引发发布事故的排查与修复全记录

发布时间:2026/9/17 12:02:33 来源:尧图企业网站定制
凌晨一点半发布群里突然炸了锅。阿里云K8s集群里的新服务已经部署上去Pod全部Running但访问一直报错服务的EXTERNAL-IP状态一直停在pending。我第一反应是Ingress配置写错了或者安全组把端口挡了查了一圈都没发现问题。直到我打开Service的事件日志看到那条反复出现的QuotaExceeded才意识到这次发布事故的根源根本不在应用层而在阿里云SLB的实例配额上。这篇内容的受众应该是所有在用阿里云ACK、或者自建K8s但对接了云负载均衡的同学。尤其是那些每天要发版、一创建LoadBalancer类型Service就会让云厂商自动创建SLB的团队建议把这篇看完。因为这种事故不会天天发生但只要发生一次就足够让一个发布窗口彻底瘫痪。我会从事故现象、底层原理、排查过程、恢复手段再到预防机制把整个链条完整拆开来讲。1. 事故现场一次看着与应用毫无关系的发布1.1 现象比想象中更隐蔽先说说当时业务侧的呈现。我们上线的是订单查询服务的v2版本变更点包括数据库字段调整和一个新对外查询端口。按照惯例应用先滚动更新Pod起来之后再通过新增的LoadBalancer类型Service对外暴露新端口。整个发布流水线在“创建新Service”这一步卡住了后续的流量切换步骤跟着全部失败。应用日志、Pod事件、节点网络查下来全部正常。新Pod的Ready状态也是正常的但Service一直拿不到公网入口。如果只是Service pending按理说旧版本还能继续承载流量但那天发布流水线里把Ingress路由联动也放进去了先创建新Service、再更新路由、然后逐步切流量。新Service卡住之后后续步骤全部失败最终导致部分请求出现了五分钟左右的黑屏现象。这个表面现象非常容易误导人。当时群里已经有人开始怀疑镜像拉取失败有人去看应用启动参数还有人问是不是新端口没加安全组规则。但实际情况是所有应用层问题排查完都没有任何异常。这就是这一类事故最阴险的地方它把症状展示在网络和应用层根因却埋在云资源的配额里。1.2 第一波排查从Pod到Service的探路过程我当时的第一个动作是拉出Pod列表确认副本数量是否正常。从状态上看新版本的副本数已经满足期望值Readiness探针通过CPU和内存都在合理水位。这说明应用本身没有问题。接着看Service。执行kubectl get svc新服务的EXTERNAL-IP一栏果然显示pending。这时候我还没把问题往SLB上想第一反应是Service的类型或Annotations写错了于是执行了kubectl describe svc order-query-v2结果在Events区域刷出了一堆错误信息核心内容大概是Failed to ensure load balancer: QuotaExceededcloud-controller-manager 持续重试创建SLB监听配置始终没能挂到SLB上看到QuotaExceeded这个关键词排查方向才彻底拉回云资源层。简单来说K8s里的LoadBalancer类型Service并不是凭空出现一个公网IP它背后是由云厂商的Controller组件去调用云API创建负载均衡实例的。一旦云上某个配额被打满Service就会一直pendingPod再健康也没有用。这里也提醒各位遇到“Pod正常、Service pending”的现象第一步应该是看Service事件而不是去查Ingress、查网络策略、查安全组。事件信息往往会直接告诉你真正的故障源省掉后面一整轮无头苍蝇式的排查。2. SLB与K8s的联动为什么发布会碰配额2.1 LoadBalancer类型Service背后的CCM机制要理解这次事故得先把K8s和SLB之间的“契约关系”讲清楚。很多刚开始用ACK的同学在集群里创建一个LoadBalancer类型的Service看到公网IP正常分配就觉得这是K8s自带的能力。实际上这个动作背后隐藏着一条完整的云底层链路。在K8s中Service的type有三种常见取值ClusterIP、NodePort、LoadBalancer。ClusterIP只在集群内部可达NodePort会在每台Node上暴露端口而LoadBalancer则是把服务暴露到公网由云厂商提供负载均衡入口。当我们创建一个typeLoadBalancer的Service时K8s自身的控制面并不会去“变出”一个公网IP而是把请求交给云厂商提供的Cloud Controller Manager一般简称CCM。CCM是K8s中的一个控制器专门负责把K8s API里的LoadBalancer类型Service与云厂商的负载均衡SLB实例一一对应起来。它的工作逻辑大致是这样的用户创建Service后CCM调用阿里云OpenAPI尝试创建或复用SLB实例然后把Pod IP、端口、监听信息等配置同步到SLB上最终把SLB的公网地址回填到Service的status.loadBalancer字段。CCM会在Service存在且类型为LoadBalancer的整个生命周期里持续协调一旦发现配置漂移或者云上资源被手工删除就会自动重建或修正。这也是为什么SLB配额超限时Service的EXTERNAL-IP会一直pending。SLB实例本身没有创建出来CCM拿不到公网地址只能按照退避策略反复重试。重试期间K8s不会主动告诉你“配额不够了”只会通过Event把云API返回的错误原样带出来。如果不熟悉这套链路很容易误以为问题出在应用或网络配置上。2.2 阿里云SLB的配额体系与默认边界阿里云SLB的配额不是一个笼统的“能创建多少个VIP”而是分了好几层实例数配额、监听数配额、后端服务器数配额等。其中平时最容易撞上的就是实例数配额。每个阿里云账号在每个地域都有SLB实例数的默认配额。这个数值不是固定不变的不同账号、不同地域会有些许差异常见的是每个地域几十个。更麻烦的是SLB实例被删除后配额并不会瞬间回到可用状态回收有一定的时间延迟所以“明明删了资源可配额还是不够”的情况也时有发生。除了实例数配额监听数配额同样值得关注。一个SLB实例默认可以挂载的监听器数量是有限制的比如有些账号默认每个实例最多挂几十个监听。如果某个微服务的Service端口特别多或者发布脚本在同一个SLB上反复新增监听配置新增监听时一样可能碰到QuotaExceeded。这次事故里我们撞到的就是实例数配额。当时集群里LoadBalancer类型的Service已经不少加上历史部署遗留下来的未清理SLB实例数逼近上限。新服务一创建配额直接被占满CCM拿到报错后只能不断重试最终表现为公网入口迟迟无法分配。我后来在阿里云配额中心看了一眼明细支持调整的配额项很多不只是实例数还包括访问控制策略组、证书、扩展域名等。大家有空可以打开自己账号的配额中心把负载均衡相关的配额项从头到尾扫一遍提前知道边界在哪总比出事后再查要好。3. 定位根因从事件日志到配额中心3.1 kubectl事件里的关键线索定位到配额问题靠的不是玄学而是Event信息。我再次强调遇到“Pod正常、Service pending”这类诡异现象第一选择就是看Service的事件而不是一头扎进Ingress或者网络策略里。我们当时执行的几条关键命令是kubectl describe svc service-name kubectl get events --sort-by.metadata.creationTimestamp | tail -50 kubectl -n kube-system get pods | grep ccm kubectl -n kube-system logs ccm-pod-name --tail1000第一条命令能看到与Service直接相关的事件第二条命令能拿到集群里最近发生的事件列表第三条和第四条则是定位到CCM组件的日志确认它反复创建SLB失败的具体报错。如果你在日志里看到QuotaExceeded或者类似LoadBalancer quota limit reached的描述几乎可以断定是配额问题。这里有一个经验值得分享CCM的重试机制带有时间退避一开始可能几秒重试一次后面会拉长到几十秒甚至几分钟。所以即使配额问题已经解除也需要等一段时间才能看到Service恢复。不要误以为K8s卡死了它只是在按自己的节奏重试。补充一个小技巧查看与某个Service相关的全部事件可以用字段选择器过滤kubectl get events --field-selector involvedObject.nameservice名这样输出内容会干净很多不会被集群里其他资源的事件刷屏。我们在后续复盘时就是用这条命令把事故时间线里的所有事件一次性拉出来做分析的。3.2 配额中心的确认与配额提升确认SLB实例配额是否占满最直观的入口是阿里云配额中心。登录控制台之后在配额中心里找到负载均衡SLB可以看到实例数、监听数、后端服务器数等项目的当前用量和剩余量。我们当时一打开页面就看到实例数配额已经用满剩余为0。再回头对照集群里的Service列表发现确实有相当数量的Service挂在LoadBalancer类型上其中有一部分是几个月前发布后就没有再管过的旧服务它们各自占用了一个SLB实例。加上一些在控制台里手工创建的SLB整体配额就被蚕食干净了。配额不够时最常规的解决办法是在配额中心提交配额提升申请。申请时需要填写期望配额和申请理由比如“生产环境微服务数量增长需要更高的SLB实例数”一般几个小时内就能审批通过。如果特别紧急可以通过工单或者商务渠道加速。这里也说一个细节配额中心显示的“用量”和“剩余量”与K8s集群里实际看到的资源数量可能存在短暂不一致。因为CCM创建和释放SLB都不是实时的中间有一段时间差。所以不要看到控制台显示剩余几个就觉得安全最好留出20%到30%的缓冲空间尤其是在有大批量发布计划的时候。3.3 为什么这个问题之前一直没暴露按理说配额快满的时候应该有人提前发现。但现实情况是大家的注意力都集中在业务指标上很少有人会点开配额中心看数字。再加上平时发布新服务SLB的消耗是缓慢增长的一次增加一个两个根本感觉不到压力。等到某个节点集中上线多个服务或者某个服务从NodePort改成LoadBalancer一下就撞上了最后的余额线。另一个容易被忽略的点是K8s自动创建的SLB在Service被删除时理论上会被CCM回收但如果你在阿里云控制台手动解绑过或者对SLB做过手工调整CCM的回收逻辑就可能失灵甚至完全不回收。这些SLB会一直残留在账号下悄无声息地占用配额。时间一长它们就成了配额黑洞。这次事故之后我专门养成了一个习惯每个月至少看一次集群里LoadBalancer类型Service的列表对照云控制台的SLB实例列表找出那些“K8s里已经不存在但SLB还活着”的孤儿资源统一清理。这个动作看起来不起眼却能在关键时候救你一命。4. 紧急恢复与彻底修复把服务抢回来4.1 快速止血从释放闲置SLB开始事故处理的第一步永远是止血然后才是追责和优化。我们当天的处理顺序大致可以拆成三个阶段。最要紧的是让新服务尽快拿到公网IP。我第一时间打开SLB控制台翻看整体实例列表发现有五六个SLB是几个月前老应用遗留下来的对应的Service早就删掉了但SLB实例还挂在账号下面。这些“孤儿”资源是最快的配额来源。确认这些SLB没有承载任何流量之后我迅速把它们释放掉。SLB释放后配额不会立刻变回可用官方文档说存在一定的回收延迟但实际操作中几分钟到十几分钟基本就能恢复。在这个时间窗口里CCM依然在按退避节奏重试等配额一恢复它便会自动把SLB创建出来并完成监听配置Service的EXTERNAL-IP随之正常分配。这里要特别提醒删除SLB之前务必先确认它没有承载生产流量尤其是那些通过控制台手工创建的SLB它们的监听和后端服务器配置可能都是手动维护的误删会导致线上服务批量502。我们的做法是先看监控里的流量曲线确认近7天几乎没有请求再考虑释放。宁可多花十分钟确认也不要图快酿成二次事故。4.2 临时提额与资源清理双管齐下逃过一劫之后光靠释放旧资源是不够的。我同步在配额中心提交了SLB实例数的提升申请把默认配额调到了一个更充裕的额度。配额申请通过之后即使后续再有新的微服务上线也不会轻易触顶。同时我还把集群里未使用的LoadBalancer类型Service统一清理了一遍把遗留在控制台里的孤儿SLB全部释放。这些动作看起来繁琐但都是为了避免同样的事情在两周后再发生一次。如果你们也遇到类似场景一定要记住一个顺序先释放确定无用的SLB把眼前的发布救回来然后再提额避免提额审核期间服务继续不可用。如果提额申请时间不确定甚至可以考虑把部分非核心服务临时改为NodePort方式访问先让业务恢复再慢慢把SLB配额补回来。4.3 长期治理让SLB配额管理不再成为盲区恢复只是开始。接下来我们做了三类事情比较值得参考。第一类增加配额监控。通过OpenAPI定期拉取SLB实例数、监听数的使用量写入监控系统在用量达到配额80%的时候触发告警。如果团队使用的云监控本身支持阈值告警也可以直接在配额中心或者云监控里配置。这一步成本极低收益却很高。第二类规范Ingress和Service的暴露方式。以前团队习惯每个服务都单独暴露一个LoadBalancer现在统一收敛到Ingress网关由少量SLB统一承载流量从架构层面减少SLB实例的“边际消耗”。核心服务如果确实需要独占入口再单独申请LoadBalancer但必须走审批流程避免随意创建。第三类定期巡检。我们每月做一次存量资源盘点把不用的Service、Ingress、SLB全部收走并输出一份清单给业务方确认。刚开始做的时候清理出来的废弃资源数量相当惊人连续清理了两三个月之后资源环境才变得干净有序。5. 发布前检查与事故复盘清单5.1 把配额余量纳入发布预检到了这个阶段事故本身已经处理完但真正值得沉淀的是防止再犯的机制。我们现在在上线脚本里加入了配额余量检查发布前先通过OpenAPI查询SLB实例数和监听数的当前使用量如果接近配额上限发布流水线会直接暂停并告警而不是等到SLB创建失败后才知道。这个预检看起来只是多了一步实际只增加十几秒耗时却能把一次“发布事故”变成“发布前的一个提醒”。对于频繁发版的团队这种前置检查的价值非常大。我们后来还做了扩展发布脚本不仅检查SLB配额还会检查VPC剩余IP数、EIP数量、安全组规则数等云资源边界把所有可能导致发布卡住的隐性限制全部纳入预检。5.2 发布流程中的经典自查项经历这次事故之后我们总结了一套发布前的自查项可以供参考新服务是否使用了LoadBalancer类型Service如果是确认对应地域的SLB实例配额是否充足旧服务是否存在同名Service但其SLB已经在控制台被手动删除或修改过这种情况容易导致CCM协调逻辑异常发布脚本是否会触发新的Ingress Controller部署如果会检查对应网关SLB的监听配额发布过程中是否依赖Service的EXTERNAL-IP如果SLB pending后续步骤是否会阻塞有没有超时和熔断机制健康检查是否依赖外部SLB如果SLB没就绪业务自身是否需要降级开关这些自查项看起来基础但绝大多数团队在发布前并不会逐条执行。我们把这些内容固化成发布Checklist每次上线由负责人勾选确认可以减少很多低级的运维事故。5.3 从事故中沉淀的教训这次事故说到底是一场典型的“隐性资源边界”问题。应用层、网络层、镜像层全都正常唯独云资源的配额被打满了。这在传统运维时代几乎不存在因为每台机器、每个SLB都是独立采购和独立配置的。但K8s的最大特点是自动化联动服务创建、SLB分配、监听配置全部自动发生这固然带来了效率也让资源配额变成了新的“单点瓶颈”。我个人的体会是K8s环境里的发布事故有相当比例不是代码问题而是云底层资源联动问题。配额、白名单、安全组、子网IP耗尽、EIP数量不足每一项都可能成为发布链路里的隐藏地雷。把这些资源边界提前纳入监控和预检比任何形式的“发布演练”都实在。最后再分享一个小技巧查看异常Service时除了看当前事件还可以追加field-selector过滤参数只筛选出与目标Service相关的事件排查速度会快很多。希望这篇复盘能帮到正在使用阿里云ACK或类似托管K8s产品的同学。以后再看到EXTERNAL-IP始终pending的情况先别急着查应用打开事件日志看一眼答案可能就写在里面。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价