《微服务设计》第 6 章 部署
- 2019 年 10 月 8 日
- 笔记
第 6 章 部署
- 在众多相互依赖的微服务中,部署却是完全不同的情况。如果部署的方法不合适,那么其带来的复杂程度会让你很痛苦
6.1 持续集成简介
- CI(Continuous Integration,持续集成)已经出现很多年了,但还是值得花点时间来好好复习一下它的基本用法,因为在微服务之间的映射、构建及代码库版本管理等方面,存在很多不同的选择
- CI 能够保证新提交的代码与已有代码进行集成,从而让所有人保持同步。CI 服务器会检测到代码已提交并签出,然后花些时间来验证代码是否通过编译以及测试能否通过
你真的在做CI吗
- 如果没有持续集成,向微服务架构进行转型就会非常痛苦
- 即便如此,很多宣称自己在做 CI 的团队并没有真正在做。他们认为使用了 CI 工具就算是采用了 CI 这个实践,事实上,只有工具是远远不够的
- 用来测试别人是否真正理解 CI 的三个问题
- 你是否每天签入代码到主线?
- 你是否有一组测试来验证修改?
- 当构建失败后,团队是否把修复CI当作第一优先级的事情来做?
6.2 把持续集成映射到微服务
- 如果从最简单的做法开始,我们可以先把所有东西放在一起。现在我们有一个巨大的代码库,其中包括所有的代码,并且只有一个构建。然后产生多个构建物,所有这些都在同一个构建中完成

- 一般来讲,我们绝对应该避免这个模式,但在项目初期是个例外
- 我比较喜欢的方法是,每个微服务都有自己的 CI,这样就可以在将该微服务部署到生产环境之前做一个快速的验证

6.3 构建流水线和持续交付
- 将构建分解成为多个阶段,从而得到我们熟知的构建流水线。在第一个阶段运行快速测试,在第二个阶段运行耗时测试
- CD(Continuous Delivery,持续交付)基于上述的这些概念,并在此之上有所发展。CD 能够检查每次提交是否达到了部署到生产环境的要求,并持续地把这些信息反馈给我们,它会把每次提交当成候选发布版本来对待
- 我们可以看到一个熟悉的示例流水线

不可避免的例外
- 在最开始的阶段,把所有服务都放在一个单独的构建中,可以减轻跨服务修改所带来的代价
- 当服务的 API 稳定之后,就可以开始把它们移动到各自的构建中。如果几周(或者几个月)之后,你的服务边界还是不够稳定,那么再把它们合并回单块服务中(当然还可以在边界内部保持模块性),然后花些时间去了解领域。这也是我们 SnapCI 团队的经验
6.4 平台特定的构建物
- 但是从微服务部署的角度来看,在有些技术栈中只有构建物本身是不够的。所以为了部署和启动这些构建物,需要安装和配置一些其他软件,然后再启动这些构建物。类似于 Puppet 和 Chef 这样的自动化配置管理工具,就可以很好地解决这个问题
6.5 操作系统构建物
- 有一种方法可以避免多种技术栈下的构建物所带来的问题,那就是使用操作系统支持的构建物
- 使用 OS 特定构建物的好处是,在做部署时不需要考虑底层使用的是什么技术。只需要简单使用内置的工具就可以完成软件的安装。这些操作系统工具也可以进行软件的卸载及查询,甚至还可以把 CI 生成的构建物推送到软件包仓库中。其缺点是,刚开始编写构建脚本的过程可能会比较困难
6.6 定制化镜像
- 使用类似 Puppet、Chef 及 Ansible 这些自动化配置管理工具的一个问题是,需要花费大量时间在机器上运行这些脚本。比如,可能需要使用 collectd 来收集操作系统的状态,使用 logstash 来做日志的聚合,还可能需要安装 nagios 来做监控(
- 一种减少启动时间的方法是创建一个虚拟机镜像,其中包含一些常用的依赖,

- 这个方法也有一些缺点。首先,构建镜像会花费大量的时间;其次,产生的镜像可能会很大
6.6.1 将镜像作为构建物
- 我们可以更进一步,把服务本身也包含在镜像中,这样就把镜像变成了构建物。现在
- 然后把精力放在镜像创建和部署的自动化上即可。这个简洁的方法有助于我们实现另一个部署概念:不可变服务器
6.6.2 不可变服务器
- 但是如果部署完成后,有人登录到机器上修改了一些东西呢?这就会导致机器上的实际配置和源代码管理中的配置不再一致,这个问题叫作配置漂移
- 为了避免这个问题,可以禁止对任何运行的服务器做手动修改。相反,无论修改多幺小,都需要经过构建流水线来创建新的机器
6.7 环境
- 当软件在 CD 流水线的不同阶段之间移动时,它也会被部署到不同的环境中。这些环境之间的不同可能会引起一些问题
6.8 服务配置
- 服务需要一些配置。理想情况下,这些配置的工作量应该很小,而且仅仅局限于环境间的不同之处,比如用来连接数据库的用户名和密码。应该最小化环境间配置的差异
- 一个在应对大量微服务时比较流行的方法是,使用专用系统来提供配置
6.9 服务与主机之间的映射
- 我倾向于使用“主机”(host)这个词来做通用的隔离单元,也就是能够运行服务的一个操作系统
6.9.1 单主机多服务
- 在每个主机上部署多个服务是很有吸引力的

- 首先,从主机管理的角度来看它更简单;其次是关于成本。但这个模型也有一些挑战。首先,它会使监控变得更加困难;这个模型对团队的自治性也不利;这个方法会限制部署构建物的选择;会增加对单个服务进行扩展的复杂性
6.9.2 应用程序容器
- 如果你对基于 IIS 的 .NET 应用程序部署,或者基于 servlet 容器的 Java 应用程序部署比较熟悉的话,那么应该非常了解把不同的服务放在同一个容器中,再把容器放置到单台主机上的模式

- 这种设置也可以节省语言运行时的开销。比如,在一个 Java servlet 容器中部署五个 Java 服务的话,只需要启动一个 JVM 即可。
- 我还是认为这种应用程序容器的做法存在很多问题,所以在使用时需要非常谨慎
- 它会不可避免地限制技术栈的选择
- 还会限制自动化和系统管理技术的选择
- 会影响服务的可伸缩性
- 其中的很多容器启动时间也特别长,这会影响开发人员的反馈周期
6.9.3 每个主机一个服务
- 这种模型避免了单主机多服务的问题,并简化了监控和错误恢复。这种方式也可以减少潜在的单点故障

- 但主机数量的增加也可能是个问题。管理更多的服务器,运行更多不同的主机也会引入很多的隐式代价。尽管存在这些问题,但我仍然认为在使用微服务架构时这是比较好的模型
6.9.4 平台即服务
- 当使用 PaaS(Platform-as-a-Service,平台即服务)时,你工作的抽象层次要比在单个主机上工作时的高。大多数这样的平台依赖于特定技术的构建物
6.10 自动化
- 想要游刃有余地应对复杂的微服务架构,自动化是必经之路
- 关于自动化好处的两个案例研究
6.11 从物理机到虚拟机
6.11.1 传统的虚拟化技术
- 虚拟化技术允许我们把一台物理机分成多台独立的主机,每台主机可以运行不同的东西
6.11.2 Vagrant
- Vagrant 是一个很有用的部署平台,通常在开发和测试环境时使用,而非生产环境。Vagrant 可以在你的笔记本上创建一个虚拟的云。它的底层使用的是标准的虚拟化系统(通常是 VirtualBox,但也可以使用其他平台)。你可以使用文本文件来定义一系列虚拟机,并且可以在其中定义网络配置及镜像等信息
- 但它的缺点是,开发机上会有很多额外的资源消耗。如果一个服务占用一台虚拟机,你可能就很难在本地机器上搭建起整个系统
6.11.3 Linux容器
- Linux 用户可以使用另外一种虚拟化的替代方案。相比使用 hypervisor 隔离和控制虚拟主机的方法来说,Linux 容器可以创建一个隔离的进程空间,进而在这个空间中运行其他的进程

6.11.4 Docker
- Docker 是构建在轻量级容器之上的平台。它帮你处理了大多数与容器管理相关的事情。你可以在 Docker 中创建和部署应用,这些基于容器的应用与 VM 世界中的镜像很类似。Docker 也能管理容器的配置,并帮你处理一些网络问题,甚至还提供了自己的 registry 概念,允许你存储 Docker 应用程序的版本
- 另一个基于 Docker 的有趣的工具是 Deis(http://deis.io/),它试图在 Docker 之上,提供一个类似于 Heroku 那样的 PaaS
6.12 一个部署接口
- 使用统一接口来部署给定的服务都是一个很关键的实践。在很多场景下,都有触发部署的需求,从本地开发测试到生产环境部署。这些不同环境的部署机制应该尽量相似,我可不想因为部署流程不一致,导致一些只能在生产环境才能发现的问题
6.13 小结
- 首先,专注于保持服务能够独立于其他服务进行部署的能力,无论采用什么技术,请确保它能够提供这个能力。我倾向于一个服务一个代码库,对于每个微服务一个 CI 这件事情,我不仅仅是倾向,并且非常坚持,因为只有这样才能实现独立部署
- 无论你采用什么技术,自动化的文化对一切管理来说都非常重要
书
- 《持续交付》
工具
- Chef
- Puppet
- Ansible