在之前的一个技术大会上,我发表了一个与 DevOps、配置即代码和持续交付相关的演讲。我用本文的故事说明了完全自动化和可重复的部署对于 DevOps 和持续交付来说是多么的重要。自从那次大会之后,有一些人希望我能够通过博客来分享接下来这个故事。需要说明的是:这个故事是真实的,它真的发生过。
故事背景
骑士资本集团(Knight Capital Group)是美国一家全球性金融服务公司,从事市场营销、电子执行、机构销售和电子交易等方面的业务。2012 年的时候,骑士还是美国最大的股票交易商,在纽交所和纳斯达克的市场份额分别为 17% 左右。骑士电子交易部门 (ETG)的日均交易量超过 33 亿笔,日均交易额超过 210 亿美元。
截止 2012 年 7 月 31 日,骑士集团还拥有约 3.65 亿美元的现金和现金等价物。
纽约证券交易所计划在 2012 年 8 月 1 日推出一个新的零售流动性计划(该计划旨在通过像骑士这样的零售经纪人为散户投资者提供更好的定价服务)。为了准备这次活动,骑士升级了他们的自动化高速算法路由器,这个路由器将订单发送到交易市场去执行,也就是所谓的 SMARS。SMARS 的一个核心功能是接收来自骑士交易平台其他组件的订单 (“父”订单),然后将一个或多个“子”订单发送到交易中心。换句话说,SMARS 会从交易平台收到大订单,并将其分成多个较小的订单,以便找到与股票交易量匹配的买家 / 卖家。父订单越大,生成的子订单就越多。
对 SMARS 的更新是为了替换被称作“Power Peg”的老旧代码,后者在骑士集团已经 8 年没有使用过了。(为什么已经废弃 8 年的代码仍然被保留在代码库中?这是一个谜,但这不是重点)。更新后的代码使用了一个旧标志,可用来激活 Power Peg 功能。代码经过了全面的测试,证明可以可靠地运行。
一切好像都没什么问题,黑天鹅事件却在不经意间来临了,并最终在 45 分钟内击垮了这家公司。
究竟哪里出了问题?
在 2012 年 7 月 27 日至 2012 年 7 月 31 日期间,骑士每天会手动将新版本部署到八台服务器上。这就是美国证券交易委员会文件中所说的手动部署过程(顺便说一句,如果美国证券交易委员会文件提到了部署相关东西,可能意味着出现了严重的错误)。
在事发后的 2013 年 10 月 16 日美国证券交易委员会第 70694 号文件中,我们才了解到当时发生了什么:
“然而,在部署新代码期间,骑士的一名技术人员没有将新代码复制到所有的 SMARS 计算机服务器上,而是漏掉了其中的一台。没有其他技术人员负责评审这个部署过程,也没有人知道 Power Peg 代码并没有从第八台服务器上删除,第八台服务器也没有部署新代码。骑士没有正式的书面程序要求进行这样的评审。”
2012 年 8 月 1 日美国东部时间上午 9 点 30 分,股市开盘,骑士开始代表客户处理新零售流动性计划订单。部署了新代码的七台 SMARS 服务器开始处理这些订单,但发送到第八台服务器的命令触发了使用旧代码的标志,激活了 Power Peg 功能。
来自僵死代码的进攻
我们有必要了解一下 Power Peg 代码是干什么用的。在执行子订单时,它会根据父订单计算购买 / 出售的股票数量。一旦父订单执行完成,Power Peg 会指示系统停止路由子订单。也就是说,Power Peg 会跟踪子订单,并在父订单完成后停止发送它们。早在 2005 年,骑士就将这个累计跟踪功能移到了代码执行的较早阶段(因此从 Power Peg 中删除了计数跟踪功能)。
当第八台服务器上的 Power Peg 功能被激活后,它开始路由子订单,但却没有根据父订单的情况跟踪股票数量,这有点像一个死循环。
地狱 45 分钟
想象一下,如果你有一个系统,它会向市场自动快速发送订单,但不跟踪是否已经执行了足够多的订单,会发生什么?是的,后果很严重。
上午 9 点 30 分股市开盘,人们很快就知道出了问题。到上午 9 点 31 分,华尔街有很多人都清楚地看到一些严重的事情正在发生。股市上充斥着一些非正常交易量的股票订单。上午 9 点 32 分,华尔街的人开始寻思着为什么问题没有得到解决?为什么没有人使出必杀技来解决这个问题?事实是,根本就没有所谓的必杀技。在头 45 分钟里,骑士的订单交易量占比超过 50%,将某些股价推高了 10%,而其他一些股价因错误交易而下跌。
更糟糕的是,骑士的系统在当天早些时候就开始自动发送电子邮件了——早上 8 点 01 分。电子邮件提到了 SMARS,其中有一个错误是“Power Peg 已禁用”。在上午 8 点 01 分至 9 点 30 分之间,有 97 封邮件被发送给骑士的员工。当然,这些邮件不算是系统告警,所以没有人去查看它们。
在这地狱般的 45 分钟内,骑士尝试了几种应对措施,试图阻止错误的交易。但并没有什么必杀技可用(也没有关于如何应对这类情况的正式程序),所以他们只能试着在每分钟处理 800 万股交易的实时交易环境中诊断问题。由于他们无法确定是什么原因导致了错误的发生,所以打算将服务器上新部署的代码回滚。换句话说,他们移除了可以运行的代码,留下了坏代码。这样做只会将问题放大,导致更多的父订单在所有服务器上都激活了 Power Peg 功能。最终,在经过了 45 分钟的交易后,他们停止了系统。
在开盘后的 45 分钟内,Power Peg 代码收到并处理了 212 个父订单。SMARS 向交易市场发送了数以百万计的子订单,总共有 400 万笔交易,涉及 154 支股票,总量 3.97 亿股。用外行的话说,骑士在 45 分钟内就亏损了 4.6 亿美元。但骑士只有 3.65 亿美元的现金和现金等价物。在 45 分钟内,骑士从美国最大的股票交易商和纽约证券交易所及纳斯达克的主要做市商变成了一家破产企业。他们有 48 小时的时间来筹集弥补损失所需的资金(他们也的确成功地从大约 6 个投资者那里获得了 4 亿美元的投资)。但是,骑士最终被 Getco LLC(2012 年 12 月)收购,合并后的公司叫作 KCG Holdings。
学到的教训
所有的开发和运营团队都应该引以为戒。开发出好的软件,经过全面测试还远远不够,还需要确保它们被正确地交付给市场,让客户获得软件交付的价值(这样就不会让你的公司破产)。在这次事件中,部署 SMARS 的工程师并不是唯一的罪魁祸首——骑士的开发部署流程并没有考虑到他们所面临的风险。此外,他们的流程(甚至是没有流程)天生就容易出错。不管在任何时候,只要部署过程依赖人工阅读和遵循指令,那就是将自己暴露在风险之中。人是会犯错误的。错误可能出在指令上、指令的解释上或指令的执行上。
部署应该是自动化且可重复的,并且尽可能避免潜在的人为错误。如果骑士的部署系统是自动化的——包括配置、部署和测试——那么这次事件就可以避免。
以下是适用于持续交付的两个原则:
软件发布应该是一个可重复的、可靠的过程。
尽可能实现自动化。
你还见过哪些因为研发、部署流程问题而引起重大故障的 IT 事件?可以在评论区说说。
英文原文
Knightmare: A DevOps Cautionary Tale
评论 3 条评论