第五部分 第三步:持续学习与实验的技术实践
第19章 将学习融入日常工作 180
前言
他们有一个特别的设计目标,那就是即使 AWS 的整个可用区域都发生了故障,就像这次美国东部区的事故,也要确保 Netflix 的服务能够持续运行。要达到这一点就需要系统架构是松耦合的,每个组件都有特别敏感的超时设计,从而保证出现故障的组件不会拖垮整个系统。作为替代,Netflix 每个功能和组件都设计为具有完全降级的能力。例如,当流量剧增造成了 CPU 使用率暴涨的时候,就不向用户显示个性化的电影推荐列表,而是只显示已经缓存的静态内容,从而减少计算资源的需求。
称为“捣乱猴”。它会不断地随机删除生产服务器,来模拟 AWS 环境故障。他们这样做是希望所有的“工程团队习惯于在常规的故障水平上工作”,使得服务能够“在没有任何人工干预的情况下,自动恢复正常”。“捣乱猴”不断地将故障注入到预生产和生产环境中,从而实现了运维上的恢复性目标。
通过在正常工作时间里不断地探测和解决这些问题,同时创造出了组织学习成果
“捣乱猴”只是将学习融入日常工作中的一个例子。这个故事还展示了学习型组织是如何思考故障、事故和错误的——将其视为学习的机遇,而不是惩罚的机会。
19.1 建立公正和学习的文化 181
由于我们所建立的复杂系统中存在着不可避免的设计问题而导致的,那么就不应该对造成故障的人进行“点名、责备和羞辱”。我们的目标应该总是最大限度地抓住组织学习的机会,目标是从学习的角度看待错误、报错、失误、过失等。
当工程师犯错误时,如果可以有安全感地给出详细信息,那么他们不仅愿意对事情负责,而且会热情地帮助其他人避免同样的错误发生。这就创造了组织学习。
有两个有效的实践有助于创造公正的学习型文化:一是不指责的事后分析;二是在生产环境中引入受控的人为故障,用于创造机会针对复杂系统中不可避免的问题进行练习。
19.2 举行不指责的事后分析会议 182
为了有助于建立公正文化,当事故和重大事件发生时(例如,部署失败,影响到客户的生产事件),应该在问题解决以后进行不指责的事后分析。
在不指责的事后分析会议上,我们会做以下事情:
构建时间表,从多个角度收集关于故障的所有细节,保证不会惩罚犯错误的人;
通过让所有工程师详细说明自己如何导致了故障,使他们能够提高安全性;
允许并鼓励那些犯错误的人成为教育他人以后不会犯同样错误的专家;
营造一个自由决策的空间,让人们决定是否采取行动,并且把对所做决定的评判放在事后;
制定预防类似事故的对策,并确保记录这些对策、目标日期和负责人,以便进行跟踪。
为了获得足够的理解,以下利益干系人需要出席会议:
参与相关问题决策的人;
识别问题的人;
响应问题的人;
诊断出问题的人;
受问题影响的人;
任何有兴趣参加会议的人。
找一个受过训练的、和事故无关的人来组织并引导会议很
有帮助,特别是在召开头几次事后分析会议时,在会议和决议的过程中,应该明确禁止使用“原来应该”或“原本可以”等词语,因为它们是反事实的陈述,
会议必须预留足够的时间,开展头脑风暴和决定应对措施。一旦确定了对策,就必须排定工作的优先次序,指定负责人和实施时间表。
19.3 尽可能广泛地公开事后分析会议结果 184
开完不指责的事后分析会议以后,应该广泛地公开会议记录和所有相关文档资料,在理想情况下,公开的信息应该放在一个集中的位置,方便整个组织里的所有人访问,从过去的事故中学习。
广泛地公开这些事后分析文档并鼓励组织中的其他人阅读,能增进组织学习。
19.4 降低事故容忍度,寻找更弱的故障信号 185
在复杂系统中工作时,放大微弱的故障信号对于防范灾难性故障是至关重要的。2003 年,哥伦比亚号航天飞机于执行任务的第 16 天,在重新进入地球大气层时爆炸了。我们现在知道,在它起飞时,一块绝缘泡沫击穿外部燃料箱。
在哥伦比亚号返航前,一些中级 NASA 工程师就已经报告了这个事件,但是他们的意见并没有得到重视。泡沫问题并不是什么新鲜事。在以前的发射中,泡沫漂移曾经损坏过飞船,但从未导致重大事故。NASA 将这次事件定性为维护问题,并没有采取任何行动。直到最后事故发生,一切都晚了。
19.5 重新定义失败,鼓励评估风险 186
19.6 在生产环境注入故障来恢复和学习 186
本节描述在系统中演练和注入故障的过程,以确保正确地设计和构建系统,进而让故障以特定和受控的方式发生。我们通过定期(甚至持续不断地)执行测试来确保系统正常失败。
可恢复性要求我们首先定义故障模式,然后进行测试,以确保这些故障模式是按照设计运行的。一种做法是,在生产环境中注入故障,并且演练大规模故障。这样才能自信系统在事故发生时能够自我恢复,在理想情况下,甚至不会影响到客户。
19.7 创建故障演练日 187
演练日的目标是帮助团队模拟和演练事故,使其具备实战能力。首先,计划一个灾难性事件,例如模拟整个数据中心在未来的某个时间点遭到破坏。然后,给团队准备的时间来消除所有的单点故障,并创建必要的监控程序和故障切换程序等。
团队在演练日定义并执行各种演习。例如,进行数据库故障转移或通过中断重要的网络连接,在既定的流程中暴露问题。
通过在演练日逐步创建更具恢复力的服务和更高程度的确定性,我们能在发生意外事件时恢复正常运行,同时创造更多的学习机会和更具可恢复性的组织。
我们可以实践、建立所需的操作手册。演练日的另一个输出是,人们确实知道了应该给谁打电话、应该与谁交谈。
19.8 小结 189
组织唯一的可持续竞争优势就是比对手更快的学习能力。
第20章 将局部经验转化为全局改进 190
20.1 使用聊天室和聊天机器人自动积累组织知识 190
20.2 软件中便于重用的自动化、标准化流程 192
20.3 创建全组织共享的单一源代码库 192
我们保存在共享源代码库里的不仅是源代码,还有包含其他学习经验和知识的工件:
程序库、基础设施和环境(Chef 的配方文件、Puppet 类文件等)的配置标准;
部署工具;
测试标准和工具,包括安全方面;
部署流水线工具;
监测和分析工具;
教程和标准。
20.4 运用自动化测试记录和交流实践来传播知识 194
采用测试驱动开发(TDD)实践,即在编写代码之前编写自动化测试,好处是测试几乎全部自动化。这个原则将测试套件变成活跃、保持更新的系统规范。任何想知道如何使用系统的工程师都可以查看测试套件,找到系统 API 的可行使用示例。
20.5 通过确定非功能性需求来设计运维 194
下面是应该具备的非功能性需求示例:
对各种应用和环境进行充分的遥测;
准确跟踪依赖关系的能力;
具有弹性并能正常降级的服务;
各版本之间具有向前和向后的兼容性;
归档数据来管理生产数据集的能力;
轻松搜索和理解各种服务日志信息的能力;
通过多个服务跟踪用户请求的能力;
使用功能开关或其他方法实现简便、集中式的运行时配置。
20.6 把可重用的运维用户故事纳入开发 195
20.7 确保技术选型有助于实现组织目标 195
例如只有一个团队掌握了关键服务的专业技术,而且只有该团队才能进行问题的变更或修复,这就形成了瓶颈。换句话说,我们可能已经优化了团队生产力,但却在无意中阻碍了组织目标的实现。
为了确保深入研究某些特定的技术,我们希望运维团队参与生产环境的技术选型,
无模式数据库的所有优势都被它们引发的运维问题抵消了。这些问题涉及日志记录、图形绘制、监控、生产遥测、备份和恢复等,还有开发人员通常不需要关心的大量问题。最终的结果是我们放弃了 MongoDB,
20.8 小结 197
第21章 预留组织学习和改进的时间 198
21.1 偿还技术债务的制度化惯例 199
一个最简单的方法就是,安排和进行为期几天或几周的改善闪电战,让团队里的每个人(或整个组织)自行组织来解决关心的问题——不允许进行任何功能性工作。可以着眼于代码、环境、架构、工具等的一个问题点。这些团队经常由开发、运维和信息安全工程师组成,横跨整个价值流。
些闪电战期间的目标不是简单地为了测试新技术而进行实验和创新,而是改进日常工作,如找出日常工作中的变通方案。虽然实验也会带来一定的改进,但是改善闪电战的重点是解决日常工作中遇到的具体问题。
每隔几个月就举行一次黑客马拉松,每个人都为自己
的新想法设计原型。最后,整个团队聚在一起,检阅所有已经完成的工作。我们许多最成功的产品都来自于黑客马拉松,包括 Timeline、聊天、视频、移动开发框架和一些最重要的基础设施,如 HipHop 编译器。”,将 Facebook 的所有生产服务从解释型的 PHP 程序文件转换为了编译型的 C++二进制文件。HipHop 使 Facebook 的平台能够处理比原生 PHP 程序高 6 倍的生产负载。
通过定期举办改善闪电战和黑客周,价值流中的所有人都自豪地以主人翁精神来进行创新,不断地将改进整合到系统中,进一步提高了安全性、可靠性和学识。
21.2 让所有人教学相长 200
为同伴安排的每周一次的学习时间。在两个小时的学习时间里,每个同伴都既要自己学习,又要教别人。主题都是他们想要学习的内容,有些关于技术,有些关于新软件开发或流程改进方法,有些甚至关于如何更好地进行职业管理。
21.3 在DevOps会议中分享经验 201
21.4 传播实践的内部顾问和教练 203
谷歌有一个 20%创新时间政策,让开发人员可以拿出每周的一天时间花在主要责任范围之外但与谷歌相关的项目上。有些志同道合的工程师自发组成小组,集中利用这 20%的时间专注于改善闪电战。
21.5 小结 204
本章描述了如何建立一系列惯例,来帮助强化终身学习以及重视在日常工作中改进日常工作的文化。具体实现方法是:预留偿还技术债务的时间;创建论坛,使大家能够在组织内部和外部互相学习和指导;通过辅导、咨询,或者仅仅设置一段面谈时间,让专家能够为内部团队提供帮助。
21.6 第五部分总结 204
第六部分 集成信息安全、变更管理和合规性的技术实践
第22章 将信息安全融入每个人的日常工作 207
22.1 将安全集成到开发迭代的演示中 207
22.2 将安全集成到缺陷跟踪和事后分析会议中 208
开发和运维团队的问题跟踪系统来管理所有已知的安全问题,以确保安全工作的可视性,以及能够将它与其他工作放到一起来安排优先级。
我们将所有的安全问题都纳入到了 JIRA 系统里。这是一个所有工程师日常使用的系统,将问题标记为 P1 和 P2,
每当出现安全问题的时候,我们都会召开事后分析会议,因为它可以更好地教育工程师如何防止问题复发,也是将安全知识传递给工程团队的绝佳机制。
22.3 将预防性安全控制集成到共享源代码库及共享服务中 208
要把任何有助于确保应用程序和环境安全性的机制和工具都添加到共享源代码库中。我们将添加用来满足特定信息安全目标的受安全保护的程序库,例如身份验证和加密库及服务。
我们可以向开发和运维团队提供安全培训,帮他们评审项目产品,以确保安全目标的正确实施,特别是在团队第一次使用这些工具的时候。
22.4 将安全集成到部署流水线中 209
将在这一步尽可能地自动化信息安全测试。每当开发或
运维人员代码提交时,甚至在软件项目的最早期阶段,就可以在部署流水线中与其他所有测试一起运行安全测试。
目标是为开发和运维人员提供快速反馈,以便在他们提交有安全隐患的变更时,及时发出通知。这样就可以快速地检测和修复安全问题,
22.5 保证应用程序的安全性 210
部署流水线中持续运行这些测试。我们期望包含以下内容作为测试的一部分。
静态分析:将检查程序代码所有可能的运行时行为,并查找编码缺陷、后门和潜在的恶意代码
动态分析:动态测试监视诸如系统内存、功能行为、响应时间和系统整体性能等项目。
依赖组件扫描:会清点二进制文件和可执行文件依赖的所有包和库,并确保这些依赖组件(我们通常无法控制)没有漏洞或恶意二进制文件。
源代码完整性和代码签名:所有开发人员都应该有自己的 PGP 密钥,向版本控制系统中提交的一切都应该签名
我们应该定义设计模式,帮助开发人员编写防止滥用的代码,例如为服务设置速率限制,将按下的提交按钮变为无法点击的状态。OWASP 发布了大量有用的指导,包括如下内容:
如何存储密码;
如何处理忘记的密码;
如何处理日志记录;
如何防止跨站点脚本(XSS)漏洞。
22.6 确保软件供应链的安全 214
在选择软件时,我们检测软件项目是否依赖有已知漏洞的组件或库,并帮助开发人员慎重地选择要使用的组件
22.7 确保环境的安全 215
利用自动化测试的方式来保证已正确应用了所有必要的设置,包括安全加固配置、数据库安全设置、密钥长度等。此外,我们将使用测试来扫描环境中的已知漏洞
22.8 将信息安全集成到生产环境遥测中 216
我们部署必要的监控、日志记录和告警系统,过将安全遥测集成到开发、QA 以及运维使用的工具中,价值流中的每个人都可以看到应用程序和环境在恶意威胁入侵中的表现,包括:攻击者不断尝试利用漏洞,获得未经授权的访问,植入后门,执行欺诈,拒绝服务等破坏活动。
22.9 在应用程序中建立安全遥测系统 217
为了进行检测,必须在应用程序中创建相关的遥测系统。
这样的例子包括:
成功和不成功的用户登录;
用户密码重置;
用户电子邮件地址重置;
用户信用卡更改。
例如,暴力登录是企图获取非法访问权限的早期迹象,因此可以显示失败和成功登录次数的比率。当然,我们应该对这些重要事件建立告警策略,以确保能够快速检测和纠正问题。
22.10 在环境中建立安全遥测系统 217
在环境中创建全面的遥测系统,特别是对于运行在非受控基础设施上(例如,托管环境、云端)的组件。需要对某些事件做监控和告警,包括:
操作系统的变更(例如,生产环境中、构建基础设施中);
安全组的变更;
配置的变更(例如,OSSEC、Puppet、Chef、Tripwire);
云基础设施变更(例如,VPC、安全组、用户和权限);
XSS 尝试(即“跨站点脚本攻击”);
SQLi 尝试(即“SQL 注入攻击”);
Web 服务器错误(例如,4××和 5××错误)。
我们还要确认已正确配置了日志记录,以便将所有遥测信息发送到正确的地方。在监测攻击时,除了记录事件,还可以选择拦截访问,
没有为了实现这些目标组建单独的反欺诈或信息安全部门,而是将这些责任融入到了 DevOps 价值流中。创建了安全相关的遥测系统,将其与每个 Etsy 工程师日常关注的、面向开发和运维的监控指标显示在一起,包括如下内容。
生产程序的异常终止(例如,段错误、内核转储等):“需要特别关注的是,为什么某些进程在整个生产环境中持续发生的内核转储全都是被来自同一个 IP 地
址的流量反复触发的。同样需要关注的还有那些 HTTP 报错信息‘500 内部服务器错误’。这些指标表明一个漏洞正在被利用,有人想非法访问我们的系统,并且需要紧急给应用打补丁。”
数据库语法错误:“我们一直在代码中寻找数据库语法错误——这些错误要么会遭到 SQL 注入攻击,要么是正在进行的攻击。因此,我们不能忍受代码里有数据库语法错误,它仍然是用于危害系统的主要攻击向量。”
SQL 注入攻击的迹象:“有一个简单得荒谬的测试——我们只需要对用户输入字段设置关键字为 UNION ALL 的告警,因为它几乎总是表明 SQL 注入攻击。我们还添加了单元测试,以确保这种不受控制的用户输入类型永远不能进入数据库查询。”
22.11 保护部署流水线 219
为了保护持续构建、集成或部署流水线,缓解风险的措施还可能包括:
加固持续构建和集成服务器,并确保可以用自动化的方式重建它们,就像构建面向客户的生产服务基础架构一样,防止持续构建和集成服务器受到破坏;
审查任何提交到版本控制系统的变更——可以在提交时进行结对编程,也可以在提交和主干合并之间设置代码评审流程——从而防止持续集成服务器运行不受控代码(例如,单元测试可能包含允许或触发未授权访问的恶意代码);
检测包含可疑 API 调用的测试代码(例如,访问文件系统或网络的单元测试)是何时签入到代码库中的,然后可以立即隔离并触发代码审查;
确保每个 CI 流程都运行在自己的隔离容器或虚拟机里;
确保 CI 系统使用的版本控制凭据是只读的。
22.12 小结 219
第23章 保护部署流水线 220
23.1 将安全和合规性集成到变更批准流程中 220
23.2 将大量低风险变更重新归类为标准变更 221
23.3 如何处理常规变更 222
23.4 减少对职责分离的依赖 224
职责分离会减慢和减少工程师在工作中获得反馈,可能对上述要求造成阻碍。这妨碍了工程师对工作质量承担全部责任,降低了企业创建组织学习的能力。
在可能的情况下,应避免使用职责分离作为控制手段。我们应该选择结对编程、持续检查代码签入和代码审查等,它们能为工作质量提供必要的保障。