距离今年9月份在两天内两次宕机仅间隔3个月。12月22日,全球知名的开源托管服务GitHub意外遭遇了历史上最严重的一次宕机事件。GitHub官方确认了本次宕机是在例行的软件升级过程中发生的,本文将带领大家回顾一下宕机事故。
背景
周六上午我们按计划要做一次例行维护,以确保软件在聚合交换机上的顺利升级。这次升级是在网络供应商建议下进行的,目的是为了解决早些时候发生的宕机事故。在此之前,我们已经在类似设备上做过多次测试都没有出现任何意外,我们对本次升级也充满了信心。然而,即便准备充分,软件升级仍然总是伴随着风险,为此,我们预留了维护窗口期和技术支持人员提供电话服务,以防意外事件的发生。
问题出在哪里?
在GitHub的网络中,每一台接入交换机,也就是每一台服务器都连接着一对聚合交换机。这些聚合交换机成对安装并通过一种叫做MLAG的功能来伪装成一个单线路由,因为link aggregation,spanning tree和其他layer 2协议希望有一个单独的主设备。这使得我们能够在一个聚合交换机上进行升级,而不会影响其他合作伙伴交换机或其他接入连接的交换机。值得一提的是,我们之前已多次成功运行过(MLAG)功能。
我们打算每次升级一台聚合交换机,这一过程被称作在服务中软件升级(in-service software upgrade)。即首先上传新的软件到一台交换机,配置交换机后,在新版本上重新启动,并发出reload命令。余下的交换机探测到不能连接到它,就将开始一个故障转移过程接管原来由MLAG共同管理的资源。
在开始升级后,我们遇到了一些预料外的故障,这些故障引起了20-30分钟的网络不稳定,随后我们尝试在维护窗口期处理这些问题。我们为此关闭了一半的聚合交换机和接入交换机的链路,这样做能够缓和一些问题,然后我们继续与网络供应商协作,试图找到让网络不稳定的深层原因。这仍然没有起到作用,受累于冗余的制度,我们只能操作一半的上路链接,我们的流量是比较低的,这在当时并没有导致真正的问题。在太平洋时间11:00,根据我们以往的经验,如果我们对于这个新版本问题还不能有一个好的解决办法,我们打算回滚本次软件升级,并且在13:00 PST的时候开始回滚升级,恢复充分冗余的状态。
在太平洋时间12:15分,我们的网络供应商开始从交换机上收集***的取证信息,以便他们尝试找出本次事故的原因。绝大多数的信息收集是隔离开进行的,包括收集日志文件和检索交换机各部分的硬件状态。作为***一个步骤,他们尝试收集一个运行在交换机上的代理的状态。这涉及到停止的过程和促使它以某种方式写入状态,并在稍后进行分析。于是我们断开了接入交换机之间的链路,让它们彼此不相互受影响。这与我们之前以MLAG模式重启交换机类似,而在此前多次测试中都是没有出过意外的。
这时候,事情开始变得糟糕起来。一个部署在交换机上的代理被终止后,(成对部署的)另一个节点将等待 5 秒钟窗口期判断前者是否会恢复。如果它无法收到***个节点的响应,却看到两者之间链路处于活跃状态,它会默认对方处于运行但状态不同步的情况。在这种情况下,它不能安全地接管与另一个路由器共同管理的资源,因此它会默认回到link aggregation, spanning tree和其他两层协议下的独立交换机运行状态。
通常情况下,这并不是一个问题,因为该交换机在它的对端失效之前还会查看这条链路的状态。当链路失效时,交换机会等待2秒看看链路是否恢复。如果链路没有恢复,交换机会假设对端完全失效的同时接管MLAG资源。这一类接管并不会触发任何第二层(链路层)的变化。
当***个交换机上的代理被终止时,这一对路由器之间的链接并未被中断,因为代理无法操作硬件去中断链接。只有当代理程序重新启动后才可能发送命令操作底层 硬件。当时间非常不巧,且路由器还需要更多额外时间由代理程序为分析而记录运行状态时,这一对路由器之间的链接保持了足够长时间的活跃状态,最终使得对端 路由器发现了在活跃线路上心跳消息的缺失,因此进行了后面这种有着更强破坏性的故障转移操作。
这个过程带来了网络内部的巨大波动,因为所有的聚合链路要重新建立、spanning-tree协议中要求的***选举过程必须完成,而且网络中的所有链路都必须重新进行spanning-tree的收敛过程。这一切直接导致了接入层交换机的流量被阻塞了1.5分钟。
文件服务器影响
我们的文件服务器是由一些采用高可靠性保障软件Pacemaker, Heartbeat 和 DRBD管理的主/备服务节点对组成,其中DRBD软件可以将主节点上的磁盘数据变更同步到备节点,而Heartbeat和Pacemaker一起协同工作,管理主节点的服务进程和故障恢复。
通过DRBD,确定数据卷是否单独挂载在簇中的一个结点是非常重要的。DRBD保证数据挂载在一个链接的两端节点上并且保护接收端是只读的。再说明一点,我们使用STONITH(Shoot The Other Node In The Head)进程在等待失败或者过期之前去关闭活动的节点。我们想确保我们没有进入“精神分裂”(也就是数据被同时写入两个节点)的状态,因为这样可能导致无法恢复的数据错误。
当网络冻结后,很多为应对冗余而刻意部署在不同机架上的文件服务器,超出了他们的heartbeat集群系统的响应限时,而导致它们需要控制文件服务器资源。它们向合作节点发出STONITH命令,试图去控制(文件服务器)资源,然而,由于受累于网络,这些命令中的相当部分并没有传达出去。当网络恢复、节点之间的消息被送达之后,许多对服务器都处于这样的状态:两台服务器都认为自己应该接管共享的资源。这个竞争状态导致两台服务器互相停止了对端(利用前述STONITH进程)。我们有多对文件服务器最终都处于全部停止的状态。
当我们发现了这个问题后,我们立即采取了以下措施:
- 将GitHub.com网站置为维护模式
- 协调整个运维团队开始进行恢复
- 降级切换到上一个软件版本
- 制定恢复服务的计划
- 严密监视网络30分钟,以确保是否稳定复苏
恢复
如前述被停止的成对节点在恢复时,重新激活鼓掌之前已被激活运行的节点尤为重要,因为它拥有对于当前文件系统应该所处的正确状态的***信息。在大多数情况下,当结对的文件服务器在检查集中式日志数据的过程中出现问题的时候,来确定哪个节点是活跃节点是一件容易的事。然而在某些情形下,依据日志数据尚不能得出***的判断,我们只好在不开动文件服务器资源的情况下启动双节点中的一个,检查它的本地日志文件,以此来判断哪个节点才是主节点。
这种恢复是一个非常耗时的过程,我们决定在恢复每个文件服务器之前保持(GitHub.com运行在)维护模式,直到最终恢复每一个文件服务器对。由于问题广泛存在,这个过程花了五个多小时才完成。我们不得不对整个GitHub文件存储基础设施中的绝大比例部分进行重新启动,验证它们是否工作正常,并确保所有的对节点正确复制。这个过程中并没有发生其他的意外,我们在太平洋时间20:23重新上线。
从宕机事故中获得的五点重要反省
- 我们将和网络供应商紧密合作来鉴别和搞清楚导致MLAG功能失效以致故障转移无法如愿进行的原因,按设计的规划,我们的网络供应商将重新审查个别延迟时间的状况,(通过增加路由器超时设置)使路由器有更多时间去检查判断链路超时,以防止此类事件在此发生。
- 我们已经推迟了所有针对聚合网络的软件升级事宜,直到我们在测试环境(Staging)建立起与生产环境功能完全一致的副本用于测试。这项工作已经展开,在此同时,我们会继续密切留意在此前报告中提到的MAC地址学习的问题,并在必要时适用相应的解决方法。
- 从今往后,我们会在我们实施任何网络变更前,让文件服务器的高可靠性保障软件进入维护模式,不管所作的网络变更在交换层面是多么简单。这个措施可以使服务器继续工作,而不会因为变更操作导致服务器采取任何自动的故障恢复行为。
- 文件服务器节点之间的通信依赖于(其它的)网络基础设施是一个长久以来已知的问题。我们正在与主机提供商积极协调、寻找解决这个问题的方法。
- 我们引入了新的人员对我们的高可用性配置环境配置进行重新评估以确保故障迁移行为是合适的。