本文主要译自两份材料:“https://dougseven.com/2014/04/17/knightmare-a-devops-cautionary-tale/”以及“DevOps in Finance”,并经过译者整理和分析总结。
译者:冬哥
前言
背景
可能出错的地方就一定会出错?的确!
“在部署过程中,相关技术人员忘记把新代码拷贝到这八台服务器其中的一台上。骑士资本也没有安排另外的技术人员对部署过程进行复查,所以没有人意识到第八台机器上的 Power Peg 代码并没有被删除,新的 RLP 代码也没有被添加。对于复查,骑士资本并没有相关的书面流程。 —— 美国证监会SEC文件 | 发布编号 70694 | 2013-10-16”
来自「僵尸」代码的攻击
45分钟的地狱
想象一下,当一个失去追踪计数功能的“智能型”高频交易软件系统,疯狂的、高速地、毫无限制的向市场发出订单,是什么情况?!
9:30,市场开放,立即有人意识到出了问题。 9:31,一分钟后,华尔街大部分人都感觉到大事不妙了,股票市场中某些个股涌现出大量不符合常规交易量的订单。 9:32,又过了一分钟,人们发现交易仍然没有停止——就高速交易系统而言,交易根本停不下来。为什么没人尝试停止出问题的系统呢?事后发现,这个系统根本没有切断开关kill-switch。 在 45 分钟之内,骑士资本执行了超过日均交易额 50% 的订单,导致部分股票市值上升超过 10%,带来的连锁反应是其他股票价格暴跌。
更糟的是,早在上午的 8:01(这时 SMARS 在进行开市前交易),骑士资本的系统就自动发送了有关问题的邮件。这些标记为 SMARS 的邮件提及 Power Peg 功能出现了问题。从 8:01 到 9:30,共有97 个骑士资本人员的邮箱收到了这封邮件。然而,这些电子邮件并没有设计为系统警报,因此没有人立即查看它们。天呐。
在这灾难性的 45 分钟里,骑士资本尝试了几种对策来终止错误的交易。由于这个系统没有切断开关(也没有相关情况的文档说明),因此他们只能在每分钟交易 800 万股的线上环境中诊断问题。然而他们无法确定是什么原因导致了错误的订单,因此他们做出的反应是卸载掉已经部署到几台服务器上的新代码。
换句话说,他们删除了工作代码并留下了损坏的代码。这导致情况进一步恶化,除了第八台未被正确部署的服务器,另外七台服务器中的父订单也触发了 Power Peg 功能。最后,他们终于想办法终止了交易系统,然而已经过去了 45 分钟。
在开市后的 45 分钟内,骑士资本接收并处理了 212 个父订单,SMARS 发出了数百万个子订单,累计对 154 支股票进行了 400 万次交易,交易量超过3.97亿股。
在内行人看来,骑士资本建立了 80 支个股 35 亿美元的净多头仓位和 74 支个股 31 亿 5000 万美元的净空头仓位。用非专业人士的话来解释,骑士资本在 45 分钟内亏损了4.6亿美元。
请记住,骑士资本仅有3.65亿美元。仅仅 45 分钟内,骑士资本从美国股市最大的交易商和纽交所以及纳斯达克的大庄家变得一文不值。破产后,骑士资本有 48 小时的时间筹集资金弥补损失(他们设法从大约六名投资者那里获得了4亿美元的投资)。
骑士资本Knight Capital Group最终于2012年12月被Getco LLC收购,合并后的公司现在称为KCG Holdings。
从DevOps的角度复盘骑士资本事件
手动部署多台生产系统服务器 没有对部署过程进行复查的机制 非工作代码长期遗留在系统中 缺乏对代码Flag标签的管理 系统没有设置切断开关kill-switch 标记为 SMARS 的邮件并没有设计为系统警报,因此没有人立即查看它们。 没有机制来确定是什么原因导致了错误的订单。
运维工程师遵循手工流程来部署更改,但是错过了在其中的一台服务器上部署程序,不幸的是也没有人注意到该错误。 这一问题正是自动化配置管理以及自动化部署旨在防止的问题。经过审核的、自动化的,并且经过良好测试的部署流水线,通过发布后检查和冒烟测试(包括在所有服务器上查找版本不匹配)来检查部署是否成功,可以避免此问题。 软件发布过程应该是可靠且可重复的,部署应该是自动化且可重复的,这个过程应该尽量排除人为因素的干扰。假如骑士资本采用的是自动部署系统——配置、部署和测试完全自动化,这场Knightmare(骑士悲剧)可能就不会发生了。
将代码更改风险最小化的一种方法是将变更保护在功能开关后面,以便运维人员可以通过打开或关闭标志来在运行时控制系统的行为。 骑士资本在修改SMARS的过程中重新加进了一个Flag,这个Flag与之前使用PowerPeg的Flag完全相同。如果该Flag在运行新的SMARS软件系统的过程中被设置成“是”,则执行与RLP相关的新软件模块。 新代码是基于每个订单消息中的Flag而不是运行时开关值执行的,这意味着运维人员没有简单的方法可以立即停止代码。 这也凸显了使用条件开关和代码分支来控制系统运行时行为的风险。对于新功能,骑士资本的开发人员选择重新设置一个标记,然而该标记在旧版本的代码中具有完全不同的含义。并且由于上述部署错误,这些旧代码仍在其中一台服务器上运行,这意味着它被意外触发了,其结果是无法预测且令人困惑。 使用条件逻辑来“分支代码”可以在运行时控制系统的不同行为,但这也使代码更难理解,更难更改和更难测试。代码中保留的逻辑条件和功能开关的时间越长,随着时间的推移添加的开关越多,这种问题就越严重。过不了多久,就没人会知道打开某些Flag或Flag的组合会发生什么,这就是骑士资本发生的事情。 代码中的功能开关和分支是一种危险的技术债务,团队需要纪律严明的管理代码,确保一旦条件逻辑和Flag不再需要时就将其删除。 对于代码也是一样,需要定期的对代码进行清理和审视。9年未使用的代码依然存留,这就像你家里的清洁死角一样。
高频交易系统曾自动向公司内部共计97名员工发送了包含“(PowerPeg disabled)”信息的电子邮件,却没有引起任何收到信息人员的警惕。 DevOps中的另一个重要实践是确保开发人员随时待命,并随时可以帮助他们进行任何更改。如果开发人员在清晨看到“ Power Peg”警报,他们应该认识到出了问题,并能够在股市开放前阻止事态的发生。
始终为故障做好准备并拥有成熟的事件响应能力极为重要。包括知道何时回滚代码,并且知道代码是否起作用,定义明确的问题升级机制以便于在问题失控之前迅速关闭设备。 “无法确定是什么原因导致了错误的订单,因此他们做出的反应是卸载掉已经部署到几台服务器上的新代码。换句话说,他们删除了工作代码并留下了损坏的代码。这导致情况进一步恶化,除了第八台未被正确部署的服务器,另外七台服务器中的父订单也触发了 Power Peg 功能。” 骑士资本的团队花了过长时间来做出关键的(错误)决定,到系统真正关闭时,公司实际上也已经倒闭了。