DevOps实践指南

  • Part 3 第一步 :流动的技术实践
  • 11. 应用和实践持续集成
  • 11.1 小批量开发与大批量合并
  • 11.2 应用基于主干的开发实践
  • 11.3 小结
  • 12. 自动化和低风险发布
  • 12.1 自动化部署流程
  • 12.1.1 应用自动化的自助式部署
  • 12.1.2 在部署流水线中集成代码部署
  • 12.2 将部署与发布解耦
  • 12.2.1 基于环境的发布模式
  • 12.2.2 基于应用的发布模式更安全
  • 12.3 持续交付和持续部署实践的调查
  • 12.4 小结
  • 13. 降低发布风险的架构
  • 13.1 能提高生产力、可测试性和安全性的架构
  • 13.2 架构原型:单体架构与微服务
  • 13.3 安全地演进企业架构
  • 13.4 小结


<- 接前文 ->

Part 3 第一步 :流动的技术实践

第三部分的目标是创建必要的技术实践和架构,从而使开发到运维的工作能够稳定地快速流动,并确保不会造成生产环境的混乱或客户服务的中断。这意味着需要降低在生产环境中部署和发布变更的风险。这一点可以通过一套被称为持续交付的技术实践来实现。

11. 应用和实践持续集成

分支在版本控制系统中的主要作用是,让开发人员可以并行地工作在软件系统的各个组成部分,同时避免开发人员提交的代码对主干(trunk,有时候也被称为 master 或 mainline)的稳定性造成影响,或者引入错误。

然而,开发人员在自己的分支上独自工作的时间越长,就越难将变更并入主干。事实上,当分支个数和每个分支上的变更数同时增加时,合并难度会骤增。

集成问题会导致大量的返工,包括不得不通过手动合并解决变更冲突,以及多名开发人员共同解决导致自动化测试或手动测试失败的合并问题。因为在传统的开发模式里,代码集成工作通常发生在项目的末期,所以在集成工作消耗过多时间时,我们不得不为了按时发布而偷工减料。

这会导致另一个恶性循环:既然合并代码如此痛苦,那么大家索性就减少合并次数,而这会使未来的合并工作更加令人痛苦。持续集成旨在通过将合并融入日常工作来解决这个问题。

11.1 小批量开发与大批量合并

每当提交到版本控制系统的代码变更导致部署流水线失败时,我们就会群策群力地解决问题,力求尽快将部署流水线恢复到绿色状态。然而,如果开发人员长时间工作在自己的分支(也称为“特性分支”)上,只是偶尔将代码合并到主干,那么他们的每一次合并都会为主干引入大批量的变更,这会造成严重的问题。

尽管分支策略有很多种,但是可以分为以下两类

  • 提高个人生产力:所有人都在自己的分支上工作。每个人都独立地工作,并且不能干扰其他人;然而,代码合并将是一场噩梦。协作将变得相当困难,每个人都不得不谨小慎微地合并代码,即便是完成系统里最小的部分也是如此
  • 提高团队生产力:所有人都在同一个区域里工作。并没有分支,只有一条很长、不可被中断的主干;也没有规则,因此代码的提交过程很简单。但是,任何一次提交都有可能破坏整个项目,同时导致项目中断。

Ward Cunningham 开发了世界上第一个维基系统,也创造了“技术债务”一词。他在描述技术债务时说,如果不能主动地重构代码库,它就会慢慢地变得难以修改和维护,新特性的增加速度也会因此而下降。持续集成和基于主干的开发实践,其主要目的就是解决这些问题,从而在提高个人生产力的基础之上提高团队生产力

11.2 应用基于主干的开发实践

解决大批量合并问题的对策是,应用持续集成和基于主干的开发实践,让每个开发人员每天都至少向主干提交一次代码。这样做能够将代码提交量降低为开发团队每日的工作量。开发人员提交得越频繁,每次的提交量就越小,他们离理想的单件流状态也就越近。

频繁地向主干提交代码,意味着可以针对整个软件系统执行所有的自动化测试,并且在应用或接口的某个部分出现问题时,及时收到告警信息。由于合并问题能被及时发现,因此也能被及时解决。

我们甚至可以对部署流水线进行这样的配置:拒绝接受任何使系统偏离可部署状态的提交(例如代码变更或环境变更)。这种方式被称为门控提交,即部署流水线要确认所提交的变更能成功合并和正常构建,并且在合并到主干之前就已经通过了所有的自动化测试。如果测试失败,则开发人员将收到通知,这样就可以在不影响价值流中的其他人的情况下自己解决问题。

应用这些实践后,我们再来修订“完成”的定义(黑体文字为新增内容):“在每个迭代周期结束时,已经在类生产环境中集成和测试了可工作和可交付的代码;这些代码通过一键式流程在主干上创建,并已通过自动化测试。

11.3 小结

基于主干的开发方式可能是本书中最具争议的实践。许多工程师都认为它行不通,他们更喜欢在自己的分支上工作,不必与其他开发人员协作。然而,Puppet Labs 的《2015 年 DevOps 现状报告》表明,基于主干的开发方式能带来更高的生产力、更好的稳定性,甚至更高的工作满意度和更低的职业倦怠率。

虽然在开始时很难说服开发人员,但是一旦他们认识到显著的优势,就会彻底改变。持续集成实践为下一步实现低风险的自动化部署流程铺平了道路。

12. 自动化和低风险发布

本章旨在通过减小生产环境部署的阻力,使运维团队或开发团队能频繁、轻松地进行部署。我们通过扩展部署流水线来实现这一点。

与仅将代码持续集成到类生产环境中不同,我们将能够按需地(即一键式发布)或自动化地(即在构建和测试成功以后,直接进行自动化部署)将已通过自动化测试和验证流程的任何构建版本发布到生产环境中。

12.1 自动化部署流程

当完整记录目前的部署流程以后,下一步的目标便是尽可能地简化和自动化手动步骤,举例如下:

  • 将代码打包成便于部署的格式;
  • 创建预配置的虚拟机镜像或容器;
  • 将中间件的部署和配置自动化;
  • 将安装包或者文件复制到生产服务器;
  • 重启服务器、应用或者服务;
  • 基于模板生成配置文件;
  • 通过执行自动化冒烟测试,确保系统能正常运行,并且配置正确;
  • 运行各种测试程序;
  • 将数据库迁移工作脚本化和自动化。

大多数具有持续集成和测试功能的工具,也有扩展部署流水线的能力。通常在生产验收测试执行完之后,这些工具可以将验证过的构建版本发布到生产环境中(这样的工具包括 Jenkins Build Pipeline插件、ThoughtWorks的GoCD和Snap CI、Microsoft Visual Studio Team Services,以及Pivotal Concourse)

部署流水线有如下需求

  • 用相同的方式处理所有环境的部署:通过对所有环境(例如开发环境、测试环境和生产环境)采用相同的部署机制,可以提高生产环境部署的成功率,因为它已经在流水线中被成功地部署过很多次了。
  • 对部署执行冒烟测试:在部署过程中,应该测试依赖的所有系统(例如数据库、消息总线和外部服务)是否能正常访问,并通过单次测试看看系统是否能正常工作。如果以上任何一个测试失败,那么部署就是失败的。
  • 维持环境的一致性:上述步骤创建了一步搭建环境的流程,使得开发环境、测试环境和生产环境有了共同的搭建机制。必须持续保证这些环境的搭建方式是一致的
12.1.1 应用自动化的自助式部署

为了更好地促进工作,需要一个可以由开发人员或运维人员来执行的代码发布流程,并且在理想情况下,应该不需要任何手动操作或工作交接。这个流程的步骤如下。

  • 构建:部署流水线必须基于版本控制系统构建可部署到任何环境(包括生产环境)的软件包。
  • 测试:任何人都应该能够在他们的工作站上或测试系统中运行任何一个自动化测试套件。
  • 部署:任何人都应该能够将这些软件包部署到具有访问权限的任何环境,通过执行(已提交到版本控制系统中的)脚本来完成部署。

以上实践有助于成功地执行部署,谁来执行并不重要。

12.1.2 在部署流水线中集成代码部署

如果代码部署过程是自动化的,就能将其变成部署流水线的一部分。因此,自动化部署必须具备如下能力:

  • 保证在持续集成阶段构建的软件包可以部署到生产环境中;
  • 使生产环境的就绪情况一目了然;
  • 为能在生产环境中部署的任何代码,建立一键式和自助式的发布机制;
  • 自动记录审计和合规管理所需的相关内容,包括在哪台机器上运行了命令,运行了什么命令,是谁授权的,以及结果如何;
  • 通过冒烟测试验证系统正常工作,并且数据库连接字符串等配置正确;
  • 为开发人员快速提供反馈,使他们能够尽快了解部署结果(例如部署是否成功,应用是否能在生产环境中正常运行,等等)

我们的目标是实现快速部署——不用等待数小时之后才知道部署是否成功,也不用在修复代码上耗费数小时。运用 Docker 等容器技术,可以在几秒钟或几分钟内完成很复杂的应用部署。

DevOps实践指南pdf_生产环境


通过构建上述能力,能够实现一键式代码部署,通过部署流水线将代码和环境变更一起安全、快速地发布到生产环境中

12.2 将部署与发布解耦

在实践中,人们通常交替使用“部署”和“发布”这两个词。然而,它们其实是不同的动作,并且有着截然不同的目标。

  • 部署是指在特定的环境中安装指定版本的软件(例如,将代码部署到集成测试环境中或生产环境中)。具体地说,部署可能与某个特性的发布相关,也可能无关。
  • 发布是指把一个特性(或者一组特性)提供给所有客户或者一部分客户(例如,向 5%的客户群开放特性)。代码和环境架构要能够满足这种要求:特性发布不需要变更应用的代码。

换句话说,如果我们混淆了部署和发布,就很难界定到底由谁来对结果负责。解耦这两个活动,可以提升开发人员和运维人员快速且频繁部署的能力,同时使产品负责人承担成功发布的责任(即确保构建和发布特性所花的时间是有价值的)。

如果部署周期过长,就会限制向市场频繁地发布新特性的能力。然而,假如能够做到按需部署,那么何时向客户发布新特性,就成了业务和市场决策,而不再是技术决策。通常使用的发布模式有以下两种

  • 基于环境的发布模式:在两个或更多的环境中部署系统,但实际上只有一个环境处理客户流量(例如,通过配置负载均衡器切换流量)。将新的代码部署到非生产环境中,然后再把生产流量切换到这个环境。这种模式非常强大,因为一般只需要对应用做很少的改变,或者几乎不用改变。这种模式包括蓝绿部署、金丝雀发布和集群免疫系统。我们随后将讨论这些模式。
  • 基于应用的发布模式:对应用进行修改,从而通过细微的配置变更,选择性地发布或开放应用特性。例如,可以通过特性开关逐渐地开放新特性——先开放给开发团队,再开放给所有内部员工,然后开放给 1%的客户;或者在确认特性完全符合设计后,直接发布给全体客户。这就是所谓的黑启动技术——在生产环境里将所有特性都部署完毕,并在发布前用生产环境的流量做测试。例如,在发布前的几周里,用生产环境的流量来测试新特性,以便在正式发布之前发现和解决所有问题。
12.2.1 基于环境的发布模式

解耦部署和发布将极大地改变我们的工作方式。我们不再需要为了降低对客户可能造成的负面影响而在三更半夜或周末做部署。相反,我们可以在正常的工作时段里进行部署。运维人员终于能像其他人一样正常下班了。

基于环境的发布模式,这种发布模式不需要更改应用的代码。我们使用多套环境来部署,但实际上只有一套环境处理客户流量。这种方式可以显著地降低生产环境发布的风险,并缩短部署时间。

  1. 蓝绿部署模式
    蓝绿部署是 3 种模式中最简单的一种。在这种模式下,我们有两个生产环境:蓝环境和绿环境。在任一时刻,只有其中的一个环境处理客户流量。在图 12-5 中,处理客户流量的是绿环境。
  2. DevOps实践指南pdf_运维_02

  3. 在发布新版本的服务时,先把它部署到非在线环境,以便在不影响用户体验的情况下执行测试。在确信一切都正常以后,再把客户流量切换到蓝环境,用这种方式来交付新版本。之后,蓝环境就变成了生产环境,绿环境则变为预生产环境。通过把客户流量再重定向回绿环境,还可以实现回滚。
    蓝绿部署模式比较简单,也非常易于在已有的系统中实现。它有很多好处,例如能使团队在正常的工作时段内执行部署工作,并在非高峰时段里轻松地实施版本切换(如变更路由配置或符号链接)。仅这些就能使部署团队的工作境遇得到巨大的改善。
  4. 处理数据库变更
    当应用的两个版本依赖同一个数据库时,就会出现问题。如果部署操作需要更改数据库模式,或者添加、修改或删除表或列,那么数据库将无法同时支持应用的两个版本。一般通过下面两种方法来解决这个问题。
  • 创建两个数据库(即蓝数据库和绿数据库):应用的每个版本——蓝色(旧版本)和绿色(新版本)——都有自己的数据库。在发布期间,将蓝数据库设置为只读模式,然后执行备份,再恢复到绿数据库,最后将流量切换到绿环境。这种模式的问题是,如果需要回滚到蓝色版本,就必须先手动地把事务数据从绿数据库迁移回蓝数据库,否则可能丢失这些数据。
  • 将数据库变更与应用变更解耦:与支持两个版本的数据库不同,通过执行以下两项操作,将数据库的变更发布和应用的变更发布解耦:首先,只对数据库进行增量式变更,不更改已有的数据库对象;其次,应用逻辑对生产环境里的数据库版本不做假设。这与我们对数据库一贯的思维方式有很大差异,这样做就避免了产生重复数据。
  1. 金丝雀发布模式和集群免疫系统发布模式
    蓝绿部署模式实现起来比较简单,而且可以显著地提高软件发布的安全性。它有一些变体,能通过自动化进一步提高安全性和缩短部署时间,但同时可能引入复杂性。

金丝雀发布这个术语来自于煤矿工人把笼养的金丝雀带入矿井的传统。矿工通过金丝雀来了解矿井中一氧化碳的浓度。如果一氧化碳的浓度过高,金丝雀就会中毒,从而使矿工知道应该立刻撤离。

  1. 金丝雀发布模式下,我们会监控软件在每个环境中的运行情况。一旦出现问题,就回滚;否则就在下一个环境中进行部署。
  • A1 组:仅向内部员工提供服务的生产环境服务器。
  • A2 组:仅向一小部分客户提供服务的生产环境服务器,在软件达到某些验收标准后部署(自动化部署或手动部署均可)。
  • A3 组:其余的生产环境服务器,软件在 A2 组中达到某些验收标准后再部署。

集群免疫系统发布模式扩展了金丝雀发布模式,将生产环境的监控系统和发布流程联系起来,并在面向用户的生产系统的性能超出预定范围时(如新用户的转化率低于 15%~20%),自动回滚代码。这种保护措施有两个明显优势:首先,避免了通过自动化测试难以发现的缺陷,例如使某些关键的页面元素不可见的页面变更(如 CSS 代码变更);其次,减少了排查和解决变更造成的性能下降问题所需的时间。

12.2.2 基于应用的发布模式更安全

上一小节介绍了基于环境的发布模式。它的特点是,通过使用多个环境并在其间切换流量,实现部署与发布的解耦。这是完全能在基础设施层面上实现的

本小节将介绍基于应用的发布模式,通过代码来更灵活、更安全地向客户发布新特性(通常是逐一发布特性)。因为基于应用的发布模式是在应用的代码里实现的,所以需要开发团队的参与

  1. 实现特性开关
    基于应用的发布模式主要是通过特性开关来实现的。特性开关机制使我们能在不进行生产环境代码部署的情况下,选择性地启用和禁用特性。通过特性开关,可以将应用的特性向某些特定用户(例如内部员工和某些客户群)开放。
    特性开关的实现机制通常是用条件语句封装应用逻辑或用户界面元素,并根据保存在某处的配置信息启用或禁用某个特性。可以使用简单的应用配置文件(例如 JSON 或 XML 格式的配置文件)存储配置信息,也可以通过服务目录来配置,甚至可以专门设计用于管理特性开关的Web 服务
    特性开关还具有如下优势
  • 轻松地回滚
  • 缓解性能压力
  • 采用面向服务架构提高恢复能力
  • 特性开关将代码部署与特性发布解耦
  1. 实现黑启动
    特性开关实现的效果是,将特性部署到生产环境中,但暂时使其不可用。它使黑启动技术成为可能——先把所有特性都部署到生产环境中,然后对客户不可见的特性执行测试。对于大规模或高风险的变更来说,黑启动过程往往持续数周,从而保证在正式发布之前使用类生产负载安全地进行测试。
    假设我们使用黑启动技术发布了一个有很大潜在风险的新特性,如新的搜索功能、账户创建流程或数据库查询。在将所有代码都部署到生产环境中之后,禁用新特性,然后通过修改用户会话代码调用新函数,不向用户显示调用结果,而仅记录或丢弃测试结果。
    通过这种方式,我们再也不用等到大规模的发布以后才能验证客户对产品的满意度。相反,在宣布进行重大发布时,我们已经完成了对业务假设的验证,并且在真实的客户中进行了无数次的改良实验,这些措施有助于提高产品和客户需求的匹配度

12.3 持续交付和持续部署实践的调查

持续交付和持续部署的新定义如下

  • 持续交付是指,所有开发人员都在主干上进行小批量工作,或者在短时间存在的特性分支上工作,并且定期向主干合并,同时始终让主干保持可发布状态,并能做到在正常的工作时段里按需进行一键式发布。开发人员在引入任何回归错误时(包括缺陷、性能问题、安全问题、可用性问题等),都能快速得到反馈。一旦发现这类问题,就立即加以解决,从而保持主干始终处于可部署状态。持续交付可以自动把已验证的代码发布到企业自己的存储库。
  • 持续部署是指,在持续交付的基础上,由开发人员或运维人员自助式地定期向生产环境部署优质的构建版本,这通常意味着每天每人至少做一次生产环境部署,甚至每当开发人员提交代码变更时,就触发一次自动化部署。

持续交付是持续部署的前提条件,就像持续集成是持续交付的前提条件一样。持续部署更适用于交付线上的 Web 服务,而持续交付适用于几乎任何对质量、交付速度和结果的可预测性有要求的低风险部署和发布场景,包括嵌入式系统、商用现货产品和移动应用。

12.4 小结

发布和部署不一定是高风险、状况百出的工作,也不一定需要几十个甚至几百个工程师加班加点地完成。相反,它们可以成为日常工作的一部分。将发布和部署融入日常工作,能够把部署时间从几个月缩短到几分钟,使组织能够快速地向客户交付价值,同时避免意外事故和服务中断。此外,开发人员和运维人员的紧密合作,能使运维工作变得人性化。

13. 降低发布风险的架构

本章将介绍可以逆转上述恶性循环的措施,同时回顾一些主要的架构原型,探究有助于提高开发生产力、可测试性、可部署性和安全性的架构特性,以及相关的架构迁移策略,以便从任何现有架构安全地迁移至能更好地实现组织目标的架构

13.1 能提高生产力、可测试性和安全性的架构

紧耦合架构不仅会降低生产力,还会影响安全变更的能力。接口定义清晰的松耦合架构则与之相反,它优化了模块间的依赖关系,提高了生产力和安全性,让小型且高产的“双比萨”团队可以执行小的变更,并能安全和独立地进行部署。因为每个服务都有一个定义明确的 API,所以更容易测试,团队之间的服务等级协议条款也更容易确定。

Google Cloud Datastor是世界上最大的一个 NoSQL 服务,但其支持团队只有大约 8 个人,这主要是因为它是构建在一层层可靠的基础服务之上的。面向服务的架构能让小型团队各自负责更小、更简单的开发任务,并且每个团队都可以独立、快速和安全地进行部署。

DevOps实践指南pdf_运维_03

13.2 架构原型:单体架构与微服务

单体架构的本质并不坏。事实上,在产品生命周期的早期阶段,单体架构通常是最佳的选择。

适用于创业公司的单体架构(例如,需要为新特性快速创建原型,或者公司的战略目标可能出现重大改变)完全不同于拥有数百个开发团队的公司所采用的架构,后者的每一个团队都要能够独立地向客户交付价值。通过采用与时俱进的演进式架构,能够确保组织当下的需求得到满足。

DevOps实践指南pdf_数据库_04

13.3 安全地演进企业架构

如果确信已有的架构过于紧耦合,那么可以在其基础上安全地解耦部分功能。通过这种方式,负责这些功能的开发团队能够独立且安全地进行开发、测试和部署,同时减少了架构的熵。

如前所述,绞杀者应用模式涉及用 API 封装已有功能,并按照新架构实现新的功能,仅在必要时调用旧系统。在绞杀者应用模式下,所有服务都通过版本化 API 访问,也称为版本化服务不可变服务

版本化 API 能够在不影响调用者的情况下变更服务,这降低了系统的耦合度。如果需要修改参数,就创建一个新的 API 版本,并将依赖该服务的团队迁移至新版本。如果允许新的应用与其他任何服务发生紧耦合(例如直接连接到另一个服务的数据库),那么我们将无法实现重构架构的目标

通过不断地从已有的紧耦合系统中解耦功能,工作被逐渐转移到一个安全且充满活力的生态系统中,这使开发人员的生产力大大提高,同时已有的应用功能逐渐萎缩。当所有业务功能都迁移至新架构之后,旧应用甚至可能完全消失。

绞杀者应用这一术语。这源于他在澳大利亚旅行时由当地藤类绞杀植物得到的启发。他写道:“它们的种子落在无花果树的顶部,然后藤蔓逐渐沿树干向下生长,最后在土壤中生根。多年以后,藤蔓形成奇妙和美丽的形状,但同时绞杀了其宿主树。”

通过创建绞杀者应用,可以避免运用新架构或新技术复制已有功能。现有系统本身的特点使得业务流程变得过于复杂,因此复制现有流程不可取(通过研究用户,往往能够重新设计业务流程,用更简单的流程来实现业务目标)。

与其他任何转型一样,我们要力求速战速决,并在迭代中持续交付价值。前期分析有助于识别出最小的突破口,让新架构有效地帮助我们实现业务目标。

13.4 小结

在很大程度上,服务赖以生存的架构决定了代码的测试和部署方式。这一点已经在 Puppet Labs 的《2015 年 DevOps 现状报告》中得到了验证。该报告显示,架构是影响工程师生产力的首要因素,它还决定了是否能快速和安全地实施变更。

因为我们通常受制于追求不同方向的组织目标和长期存在的传统架构,所以必须安全地进行架构演进。本章介绍的案例描述了绞杀者应用模式等技术,这些技术可以帮助我们逐步地推进架构转型,从而跟上组织需求的变化