值得注意的是,我们上周有两个大的事故,而且很多用户受到影响(再次抱歉)。第一个事故持续将近一周时间,只影响了我们的预付费产品-ie,Monzo Alpha和Beta卡。第二个事故在周五上午持续影响1.5个小时,这次不仅影响了我们的预付费产品,而且还影响了我们的现金账户。这篇文章主要介绍的是后者。
通过我去年发布的这些博客(https://monzo.com/blog/2016/09/19/building-a-modern-bank-backend/),你可以更深入的了解下我们整体的后台架构设计,但是更重要的是要了解下我们技术栈中下面几个组件扮演的角色,这样才能更深度的了解这篇文章。
- Kubernetes是我们所有基础架构的管理部署系统。Monzo的后端是上百个微服务、已经打包的Docker容器。Kubernetes是这些容器的管理者,确保他们能够正常运行在我们的AWS节点。
- etcd是一个分布式数据库,存储有关部署哪些服务,它们在哪里运行以及它们处于什么状态的信息,这些数据提供给Kubernetes。Kubernetes需要稳定的连接到etcd才能正常的工作。如果etcd停止运行,我们所有的服务虽然继续可以运行,但是他们无法升级或者缩容、扩容等。
- linkrd是我们用来管理后端服务通讯连接的一个软件。在像我们的系统中,每秒钟发生数以千计的网络连接,linkrd扮演了这些连接任务的路由和负载的角色。为了知道路由从何处发起,他还依赖于能够收到Kubernetes更新服务位于何处。
两周前:平台研发团队对我们的etcd集群做了变更,升级了一个新的版本,对集群做了扩容。以前,这个集群只包含了三个节点(每一个zone中一个节点),这次我们提升为9个节点(每个zone中三个节点分布)。因为etcd的依赖能够达到分布式的Quorum ,这意味着在这个设置我们可以容忍同时失去整个zone再加上另一个zone中的单个node。
这次是按计划升级,并没有设计安排任何停机时间。我们可以确认这个集群是正确的,但是很重要的是这里触发了另一个系统的错误。
一天前:我们的一个团队对于目前的账户开发了一个新的功能,在生产环境部署了一个新的接口,但是注意到他正在经历的问题。作为防范措施,他们将服务缩减为没有运行副本,但Kubernetes services仍然存在。
工程师部署变更服务需要处理当前的支付账户。 这种做法并非罕见,我们的工程师也经常做这样的事情:为了尽量减少变化的风险,我们以更小的粒度,更频繁的节奏,使用一个可重复的,明确定义的过程来发布这些功能。 但是当服务部署完成时,所有对它的请求都开始失败。 此时开始我们站点当前的账户开始支付失败。 而与此同时,预付卡不受影响,因为它不使用失败的的服务。
我们回滚了发布的应用。这也是发布失败标准的操作流程,当接口被改变,他们应该通过回滚操作确保向前兼容。然而,这种情况下即使是回滚操作,错误依旧存在,支付仍然不能成功。
我们立即宣布内部故障。团队成员开始召集,以确定问题的影响,并开始调试。
工程师确定linkerd似乎处于不健康的状态,并试图使用一个内部工具来识别出现问题的单个节点并重新启动它们。如前所述,linkerd是一个我们用来管理后端服务之间通信的系统。 要知道发送特定请求的位置,需要从请求中获取一个逻辑名称,如service.foo,并将其转换为IP地址/端口。 在这种情况下,linkerd没有收到Kubernetes关于新的pods13运行在网络上的更新。 因此,它试图将请求路由到不再对应于正在运行的进程的IP地址。
我们认为,最好的方法是重启后端的所有linkerd实例,其中有几百个,假设它们都遇到同样的问题。 当我们遇到问题时,许多工程师正试图通过激活旨在提供备份的内部流程来最小化客户对付款或接收银行转帐的影响。 这意味着尽管持续不稳定,大多数客户仍然能够成功使用他们的卡。
替换linkerd无法启动,因为运行在我们每个节点上的Kubelet无法从Kubernetes apiservers检索适当的配置。 此时,我们怀疑Kubernetes或etcd有其他问题,并重新启动三个apiserver进程。 完成后,替换linkerd实例将能够成功启动。
所有linkerd pod都重新启动,但每秒处理数千个请求的服务现在没有收到任何流量。 此时,客户完全无法刷新其Monzo应用程序中的Feed或余额,而我们的内部COps(“客户操作”)工具停止工作。 现在这个问题已经升级为全面的平台停机,没有任何服务能够满足要求。 正如您可以想像的,几乎所有的自动警报都已经触发。
我们注意到,linkerd在尝试解析来自Kubernetes apiserver的服务发现响应时正在记录NullPointerException(http://t.cn/Rl086mW)。 我们发现这是我们正在运行的Kubernetes和Linkerd版本之间的不兼容,特别是没有解析空服务。因为linkerd更新版本已经在我们的暂存环境中测试了几个星期,其中包含了对不兼容性的修复,平台团队的工程师开始部署新版本的linkerd以试图向前滚动。
在检查代码更改之后,工程师意识到他们可以通过删除不包含endpoints的Kubernetes服务(即前面提到的服务作为预防措施缩小到0个副本)来防止解析错误。他们删除违规服务和链接器 能够成功加载服务发现信息。 此时,平台恢复正常,流量开始在服务之间正常转移,付款开始重新开始工作。 事件结束!
分布式系统中的大规模失败可能是很难理解的,善意的人为行为有时会使问题复杂化,就像这里所发生的那样。 当这样的事情发生时,我们希望尽可能地从事件中学习,以确保它不会重新出现。 我们已经确定了我们将在短期内采取的几个步骤:
- 修复Kubernetes中的错误,可以在集群重新配置后触发超时。
- 推出修复解析错误的新版本的linkerd。
- 为受影响的组件创建更好的健康检查,仪表板和警报,以更清晰地显示有关错误的信号,并防止出现人为错误。
- 改进我们的程序,以确保我们尽可能清楚,快速地在内部和外部传达中断。
我想向大家保证,我们非常认真地对待这件事。 这是我们历史上发生的最严重的技术事件之一,我们的目标是经营一家客户可以依赖的银行。 真的很抱歉,我们知道我们让您失望。 我希望这个事件分析能清楚地说明发生了什么,以及我们正在做什么来确保未来不会再发生。 我会确保我们发布类似的事情来处理这种严重的事件:如果我是一个客户,我想知道。而且我个人觉得这个帖子很有趣,可以作为对生产系统的深入了解。 如果您有任何问题,请告诉我。
评论