news 2026/9/17 12:02:20

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s Service 一直 Pending?SLB 配额超限引发发布事故的排查与修复全记录

凌晨一点半,发布群里突然炸了锅。阿里云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: QuotaExceeded
  • cloud-controller-manager 持续重试创建SLB
  • 监听配置始终没能挂到SLB上

看到QuotaExceeded这个关键词,排查方向才彻底拉回云资源层。简单来说,K8s里的LoadBalancer类型Service,并不是凭空出现一个公网IP,它背后是由云厂商的Controller组件去调用云API创建负载均衡实例的。一旦云上某个配额被打满,Service就会一直pending,Pod再健康也没有用。

这里也提醒各位,遇到“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则是把服务暴露到公网,由云厂商提供负载均衡入口。

当我们创建一个type=LoadBalancer的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> --tail=1000

第一条命令能看到与Service直接相关的事件,第二条命令能拿到集群里最近发生的事件列表,第三条和第四条则是定位到CCM组件的日志,确认它反复创建SLB失败的具体报错。如果你在日志里看到QuotaExceeded,或者类似LoadBalancer quota limit reached的描述,几乎可以断定是配额问题。

这里有一个经验值得分享:CCM的重试机制带有时间退避,一开始可能几秒重试一次,后面会拉长到几十秒甚至几分钟。所以即使配额问题已经解除,也需要等一段时间才能看到Service恢复。不要误以为K8s卡死了,它只是在按自己的节奏重试。

补充一个小技巧:查看与某个Service相关的全部事件,可以用字段选择器过滤:

kubectl get events --field-selector involvedObject.name=<service名>

这样输出内容会干净很多,不会被集群里其他资源的事件刷屏。我们在后续复盘时,就是用这条命令把事故时间线里的所有事件一次性拉出来做分析的。

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的情况,先别急着查应用,打开事件日志看一眼,答案可能就写在里面。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 12:00:08

Windows 11安装SQL Server 2016报错0x851A001A的排查与解决方案

如果你最近刚好要在 Windows 11 上装 SQL Server 2016&#xff0c;而且安装进度条走到“数据库引擎恢复句柄”这一步时突然弹出一个错误窗口&#xff0c;错误代码 0x851A001A&#xff0c;那我太懂你现在的心情了。我第一次碰到这个错误时也愣了半天&#xff1a;这个错误既不像是…

作者头像 李华
网站建设 2026/9/17 11:59:06

数据库表关系设计:一对多、一对一、多对多的实现与取舍

做数据库设计这些年&#xff0c;我最深的一个体会是&#xff1a;大部分业务系统的烂摊子&#xff0c;根源不在 SQL 写得多差&#xff0c;而在表关系从一开始就没理清楚。一对多、一对一、多对多&#xff0c;这六个字几乎能概括日常开发里九成以上的数据模型问题。尤其是刚入行的…

作者头像 李华