GitHub 宕机近 8 小时!一次 sidecar 配置失误,如何放倒全球最大代码平台

8 月 17 日晚,无数开发者像往常一样打开 GitHub,准备提 PR、跑流水线、找代码。结果等来的,不是页面,是超时、报错、转不完的圈。

Issues 打不开,PR 提不上,Actions 卡住不动,连 Copilot 都开始罢工。微博上有人调侃:"今天全世界的程序员都在摸鱼。" 但真相,比摸鱼严重得多。

这不是一次普通抖动,而是一场持续 7 小时 47 分钟的全球性故障。Web 和 API 的错误率峰值达到 20%,归档和源码下载的错误率更是冲到了 50%。每两次下载,就失败一次。

一个 sidecar 的配置失误,怎么就放倒了全球最大代码平台?让我们一块一块拆解这次事故的多米诺骨牌。

故障持续时长 Web/API 错误率峰值 下载错误率峰值 受影响核心服务
7h47m ~20% ~50% 8+

第一块骨牌:一个"看不见"的伸缩盲区

一切的起点,是 Central US 数据中心。

当天流量达到了一个新峰值,承载流量的 Istio sidecar pod 首先撑不住了,并发连接数打满了上限。按理说,系统应该自动扩容。但问题恰恰出在这里:

自动伸缩策略(HPA)只监控了宿主服务的 CPU 和内存,没有把 sidecar 的并发和容量纳入判断。

于是出现了荒诞的一幕:宿主服务一切正常,伸缩系统认为"不用扩容",可实际吞吐的 sidecar 已经累趴下了。瓶颈在 sidecar,决策却只看宿主。

第二块骨牌:4 个 HAProxy,扛起了所有

sidecar 撑不住,请求开始堆积、超时、失败。压力顺着链路传导,最后砸到了 4 个 HAProxy 负载均衡节点上,它们的流限制(flow limits)被全部耗尽。

HAProxy 是网关认证的必经之路。它一倒,认证路径直接退化,Issues、PR、Actions、Copilot 全都跟着遭殃。

到这一步,故障已经从局部过载演变成了全局认证危机。一个点的问题,扩散成了一张网的问题。

第三块骨牌:乐观重试,成了火上浇油

到这里,按理说该往回收了。但分布式系统里,还藏着一个隐形放大器:重试。

当请求失败时,客户端和网关会"乐观地"重试。这本是好事,可系统已经过载时,每一次重试,都是往超载的负载均衡器上再压一块砖。

越失败,越重试;越重试,越过载。一个完美的死亡螺旋。

GitHub 的工程师后来透露了处理方式:暂停过载节点的 HAProxy 后,大部分流量立刻恢复;再用一个 PR 临时降低网关的重试逻辑,才稳住了局面。

第四块骨牌:VS Code 的一个 bug,把流量放大了 10 倍

前三块骨牌,撑死了算教科书级故障。真正让这次事件延长了几个小时的,是第四块,一个客户端 bug。

Copilot Token Service 的正常流量是 7,000-9,000 RPS。但在故障期间,这个数字飙到了 70,000-100,000 RPS。

原因在于 VS Code 客户端里藏着一个潜在的重试 bug。一次令牌操作失败,会触发大量额外请求,进入重试循环。

GitHub 有海量的 VS Code 用户。一个 bug 乘以百万用户,就是一场重试风暴。即使服务端开始恢复,客户端的重试风暴也能把服务端再次打垮。工程师们又是降低网关认证重试,又是在负载均衡器层面用 403 阻断令牌请求,再逐站放流量,Copilot 才在 21:02 彻底缓过来。

最扎心的地方在于:服务端做了所有正确的事,一个客户端 bug 就能让恢复多花 3 个小时。

插曲:恢复期的"趁火打劫"

还有一个细节值得注意:在恢复过程中,codeload 端点还遭遇了多轮爬虫攻击。

系统越脆弱,越要提防额外负载。这提醒我们,故障恢复预案里,不能只写"怎么修",还得写"怎么挡住外面的洪水"。

GitHub 官方的五条整改

GitHub 在故障报告末尾列了五项后续行动,每一条都对应着这次事故的一个环节:

  1. 修正自动伸缩策略,纳入 sidecar 的并发和容量指标
  2. 审计 Istio 的请求、并发、伸缩限制,覆盖所有受影响服务
  3. 审查网关和客户端的重试限制与退避行为
  4. 修复 VS Code 的重试行为,消除流量放大器
  5. 改善负载均衡器容量监控和区域故障转移保障

留给你我的五个自检问题

这次故障,最大的启示是:现代云原生系统的故障,很少是单一原因造成的。一个配置疏忽、一个重试缺陷、一个客户端 bug,单独看都不算大事,叠加在一起,就是一场全球性事故。

所以,不妨拿这五个问题,给自己系统做个体检:

  1. 你的 HPA 策略,是否覆盖了 sidecar 的并发和容量?还是只看了 CPU 和内存?
  2. 你的重试策略,有没有断路器、指数退避、次数上限?
  3. 你的客户端收到 5xx 错误时,是立即重试,还是退避等待?
  4. 你的系统具备区域间的自动故障转移吗?还是全靠人工?
  5. 你的恢复预案里,有没有"恢复期限流 + 拦截恶意流量"这一条?

分布式系统的韧性,不由最强的环节决定,而由最弱的环节决定。

那个最弱的环节,可能藏在一个 sidecar 的配置文件里,也可能藏在一个 IDE 插件的重试循环里。

GitHub 用 8 小时,替我们验证了这些道理。剩下的问题是:我们能不能在自己的系统里,在故障发生之前,就把这些坑填上?

💬 你的系统,现在最脆弱的环节在哪里?是伸缩策略、重试逻辑,还是客户端?欢迎在评论区聊聊,说不定你踩过的坑,就是别人正要踩的坑。

本文参考 GitHub Status 官方故障报告