本文要点
- 持续部署对业务、质量、速度和满意度有着重要的影响。
- 任何巨大的更改都开始于增量小步骤。
- 任何完全持续部署得以应用之前,都需要一些测量到位,其中最重要的就是测试。
- 持续部署是一个革命性过程,从“足够好的事情”开始,并一路改进。
- 手工脚本和任务是易于出错的(浪费的)和消耗时间的,因此应瞄准于实现整个流水线的自动化。
你可能会提出疑问:“这又是一篇持续部署的文章吗?”如果要让我回答“是”或“不是”。那么我可以回答“是”,因为该文的确是介绍持续部署(CD,Continuous Deployment)的;我也可以回答“不是”,因为文中将介绍我们在 MyHeritage 所实现的一种独特的 CD 过程,其中涵盖了测试、所有开发人员工作于同一分支(即主线 Trunk)上,并且聊天机器人也构成了部署流水线的一部分,还有更多的内容。
引言
多年前,MyHeritage 的研发部门同时在多个分支上工作,其中部分是长期存活的分支。一旦某个特性接近于准备好的阶段,代码就会归并到主线上。但是这样的归并的确是一种梦魇般的经历,其中存在很多些冲突,并且要找出存在的问题,需耗费大量珍贵的研发时间。
我们在归并上所面对的麻烦远非如此。由于我们每周都要做部署,其中一些部署需要归并来自于不同分支的组件,这些组件在此之前从未一起工作过。它们被规划在一个“超大”的代码发行版(具有多个软件包)中发布。这样的服务包的发布过程是一个非常繁琐、易于出错并且令人感到沮丧的过程。
在与此类部署方式多次较劲后,我决定应启动向持续部署工作方式的转移。
预备知识:测试等级、特性标识、统计信息和日志
对于一家服务于 9 千万用户、具有 27 亿条 DNA 树结构概要、77 亿条历史记录和 TB 级敏感 DNA 数据的企业,要使企业转换到持续部署方式,必须要以增量小步伐完成。我们必须要确保工作方式的更改不会影响到站点的稳定性。
我们迈出的第一步是对新编写的后端和前端代码都添加单元测试。。但是单独这样做是不够的,因为依然无法覆盖已有的数百万行代码。我们决定要解决这些遗留代码,采取的方法是通过对重要组件编写集成测试和端到端测试(使用 Cucumber )实现更广泛的覆盖。这些测试并没有单元测试那样有效,也更为缓慢。但是这些测试对于使用较少的开发工作实现更好的覆盖上具有很大的优势。很快,我们就达到了对代码块合理覆盖的程度。
(点击放大图像)
图1 应用CD 中的一个关键因素,是在多层次上实现测试。
我们已经形成对部分代码外部行为的控制能力,这主要是通过特性标识将特性暴露给用户实现的。该功能使用了基本的键存储值(出于高性能的考虑,采用了由Memcache 支持的MySQL 数据库),并可以控制出于测试目的以及真正对用户的特性暴露。为追踪更改的情况,对这些控制的全部更改都存储在一个审计表中。我们已经启动了金丝雀发布(通过暴露新代码给少部分用户而实现)、对生产环境的监控。在一切运行良好的情况下,我们会逐步增加特性暴露。
(点击放大图像)
图2 特性标识系统和所触发的电子邮件通知。
与此同时,我们确保每个特性随监控生产环境中特性行为的统计信息和传感器一并发布(例如成功或失败次数、响应时间等)。我们瞄准采用秒为单位的最小可能采样时间,特别是可以监控生产环境中严重问题的关键传感器。联合详细的日志信息,我们能在生产环境中发生问题时进行快速的分析。
随着统计信息和日志消息数量的增加,我们采用了一些有助于我们了解生产环境状态的工具,例如 NewRelic 和 ELK Stack。我们也开发了一些内部工具,简化了对日志信息和日志层级更改的理解和追踪(即如何从一个稳定状态发展到有问题的状态)。这些工具每日会对所有负责监控并修复自身责任范围内问题的开发人员发送两次报告。我们还开发了一个类似的工具,对我们所有的统计信息进行扫描,并报告其中所发现的任何异常。
(点击放大图像)
图 3 状态及日志自动扫描的电子邮件报告。
新手入门:实验开展持续集成
一旦所有的部署到位,我们就准备好开始“实验”。实验通过一小组开发人员以持续部署的方式构建一个新特性,即频繁的提交,不存在分支,并且由开发人员负责将代码发布到生产环境。
从发布至生产环境是一个手工过程,其中使用了发布服务包中所使用的同一脚本。这并非是一种更新生产环境的理想方式,但是我们的实验确实非常成功,并且有更多的敏捷团队逐渐地转换到这种工作方式。很快大家就抛弃了“打包服务”的方式。
我们已经认识到以持续部署方式工作的好处:
- 高速:不再需要在归并上耗费时间。
- 高质量:发布到生产环境的代码片段更小,其中具有更少的软件缺陷,并在发生失败时易于追踪(恢复更快)。
- 更好的编码:开发人员开始设计更为模块化的代码,这样的代码可以分别独自发布。同时提供了更好的可测试性。
- 更高的满意度:研发部门喜欢这样的工作方式,我们的用户也喜欢更为频繁的更新。
一路改进:流水线的自动化
不断寻求提高的方法,是我们研发的价值观之一。很显然,下一步就是要改进手工发布代码到生产环境这一过程。
为此我们建立了一个敏捷团队,团队目标就是自动化 CD 流水线。我们想要避免耗费时间去手工运行脚本,这样的脚本必须由开发人员在特定时间手工调用。这种解决方案无法扩展,并且很浪费时间。
在完成一次 Sprint 之后,我们处于一种更好的状态。我们所具有的解决方案使得开发人员可以从 IDE 提交代码并触发构建,最终形成可供九千万用户使用的软件发布。
我们在后台开发了一个 ** Jenkins 工作流 **。该工作流在对主线的任何提交(即我们的单一分支)之后调用。作为工作流的组成部分,我们执行如下的步骤(其中一些是并行开展的):
- 解析提交的消息(结构化的):在启动构建后,更新相关的人员。
- 单元测试和集成测试。
- 在 JS 极简化后,构建包括主线快照的 RPM。
- 对于不从外部世界获取流量的生产服务器,将 RPM 上传到一个金丝雀服务器。
- 在该金丝雀服务器上运行端到端的测试(用于主用户流程)。
- 如果一切进展正常,将 RPM 上传到一个代码库服务器,允许在每个生产机器上安装的 MCollective 代理获取并安装新的 RPM。
- 在过程的最后,以通知电子邮件的形式发送生产环境的更新情况。
(点击放大图像)
图 4 由许多易于监控的步骤所组成的 CD 流程图。
大家对这一“提交等同于部署”新过程的满意度非常高。它使得开发人员能在一个完全自动化的过程中,在 25 分钟内就将自身的代码从提交推送到生产环境。
更进一步的改进
我们在此期间学到了很多,并就如何进一步的改进收集了大量的反馈意见。其中包括:
- 与 Slack 对话机器人的集成:以指定会话通道报告构建进度,通知相关人员构建的进度。
- 在任何生产环境更新后,激活自动“日志扫描”任务。如果日志中存在大量的错误消息,就在会话通道中进行报告。
- 改进发布时间:在部署 RPM 到我们的大型服务器农场的过程中,我们遇上了速度慢的问题。因此我们添加了一个 HTTP 反向代理,实现了 RPM 的并发上传。
- 为增加并行度,我们将测试分为多个组,进一步缩短了发布时间。
- 我们通过从代码库移除资产,并借助于一个专用于此的 Jenkins 流程,减少了 RPM 的大小。
- 我们在 Jenkins 中添加了一个特殊的“回滚”任务,允许对生产环境做更快的恢复或是紧急更新。
- 对于构建信息以及特性标识的修改情况,我们对它们做索引并集成到 ElasticSearch 中,用于 Grafana/Graphite 的标注。这使得它们与其它度量一并可见,易于实现关联分析。
- 所有相关的更新以每日摘要电子邮件形式发送,使得公司中的所有利益相关者同步掌握最新的更新情况。
(点击放大图像)
图5 在CD Slack 通道中提供的典型CD 进展情况报告。
(点击放大图像)
图6 在构建失败时给出的详细信息。
结论
持续部署是改进我们的质量、速度和满意度的一个关键因素。企业在转换到持续部署方式工作前,需要实现一些里程碑工作。一旦企业已经着手做更改,这将使企业的研发部门乃至整个企业受益匪浅。
谨以本文献给那些我们研发部门中那些以持续集成方式工作的先行者,以及整个研发和DevOps 部门。
本文作者简介
Ran Levy是 MyHeritage 的研发副总裁,近六年来一直工作于 MyHeritage。他具有 20 年的业界工作经验,曾在复杂大规模系统中历任开发人员、架构师和管理人员。Ran 对敏捷和高效过程情有独钟,一直领导着公司向持续部署转换的过程。
评论