本文介绍如何使用开源软件快速搭建微服务持续交付系统。本文假设的环境是Linux操作系统,使用的软件包括Git、Jenkins、Salt、ZooKeeper、Apache等。在开始之前,先简单介绍一下持续交付和微服务的概念,让大家更好的理解这篇文章的精髓。什么是持续交付?先举个物流的例子。现在各大电商企业都非常重视物流的自动化。在实现运输、装卸、包装、分拣、识别等过程中设备设施自动化的同时,我们更在研究无人机、自动驾驶汽车的配送,实现物流的全自动化。对于软件开发,从开发人员签入代码到代码仓库,再到代码构建、部署、测试、发布,我们可以形象地把这个过程称为“软件物流”。现实世界中的物流已经实现了相当程度的自动化。“软件物流”也应如此,实现从开发人员签入代码(客户订单)到生产系统上线(上门)的自动化。说到这里,我们可以给持续交付一个“非专业”的定义,就是“软件物流”的自动化。图1.持续交付流水线图1摘自《持续交付:发布可靠软件的系统方法》,展示了持续交付的具体内容。本文重点介绍如何实现微服务的持续交付流程,因此会忽略整个流程的一些细节(如代码分析、单元测试等)。那么什么是微服务呢?微服务的概念最早由MartinFowler和JamesLewis于2014年提出。微服务架构风格是一种使用一组小型服务开发单个应用程序的方式。每个服务运行在自己的进程中,并使用轻量级的机制进行通信,通常是HTTPAPI,这些服务是基于业务能力构建的,并且可以通过自动化部署机制独立部署,这些服务是用不同的编程语言编写的,和不同的数据存储技术,并将集中管理保持在最低限度。目前微服务的主流实现方式有两种:RESTfulAPI和消息队列。图2RESTful微服务图3消息队列微服务图2和图3是两种典型微服务架构的简化图。当然,真实的系统会复杂很多,比如微服务聚合、多级缓存、注册中心等。相对于单体应用,微服务有明显的优势:它解决了单体应用的复杂性问题,并且单个微服务易于开发、理解和维护。每个微服务都可以由独立的团队开发,开发语言可以自由选择。每个微服务都可以独立部署,系统可以快速演进。每个微服务都可以独立扩展,大大提高了系统的扩展性和资源利用率。但是,当一个单体应用被拆分成几十个甚至上百个微服务时,由于服务数量的增加以及微服务支持多种编程语言的特点,软件的构建、部署、测试、监控等都会带来一个整体新的挑战。本文将讨论如何通过持续交付来降低微服务构建和部署的复杂性。微服务的持续交付:一种统一的方法由于微服务的性质,微服务的持续交付可能比单一应用程序的持续交付更复杂。本节列出了我们为降低微服务持续交付的复杂性而遵循的一些原则:统一方法。这里有两个层面的意思。首先是流程的统一。很多公司非常重视运维自动化,但是在开发测试阶段并没有采用自动化的方式。随着DevOPS运动的兴起,大家逐渐意识到在开发测试阶段有必要采用与生产环境相同的交付方式,这样当系统部署到生产环境时,这个交付过程已经过测试多次,出错的概率大大降低。第二层含义与微服务有关。每个微服务可能用不同的语言实现,比如Java、Python、C++、Golang、纯前端(JavaScript)。对于不同语言实现的微服务,我们需要使用统一的交付方式。在版本控制系统中,每个微服务都应该对应一个独立的仓库。以Git为例,在一个Project下,每一个微服务对应一个独立的Repository。这样每个微服务在持续构建的过程中可以独立的签入代码,互不影响。在设计持续交付系统时,需要考虑软件交付的全自动化,虽然在现实中会有提交测试、审核生产变更等人工环节。但在理想情况下,开发人员签入代码后,可以自动触发多个环境的构建、部署和测试。支持单个微服务的升级升级,需要一个持续交付系统,每个可部署单元(微服务)都有独立的版本号。程序与配置分开。支持一组程序(可执行包+配置文件包)的多次部署,这里强调一组程序,意思是开发者签入代码后,构建系统只生成一个程序(可执行包+配置文件)包裹))。无论是部署到开发环境、测试环境,还是生产环境,我们都需要使用同一套程序,而不是将每个环境单独打包。我们知道Java的war包会要求在其中包含配置文件,这会导致不同的环境需要不同的war包,这就违背了我们说的原则,后面会讨论如何处理这个问题。部署应用时,不要依赖外部网络资源。我们将部署过程分为两个阶段:环境准备阶段和应用程序部署阶段。环境准备包括操作系统、JDK或其他语言运行时系统级依赖的安装。由于IaaS的相对成熟,我们把这个阶段分开。应用程序的部署需要定制,这也是本文的一部分。部署应用时,要求所有资源都从内网获取,保证应用部署过程快速、稳定、可重复。快速构建微服务持续交付:持续构建下面我们结合一个虚构的项目来介绍一下持续交付的实现细节。假设我们有一个项目BetaCat,它由ms1,ms2...msN,n个微服务组成。下面我们重点介绍ms1微服务是如何实现持续交付的,其他微服务可以类推。本节讨论如何实现持续构建,下一节讨论持续部署。图4Jenkins进程仓库代码流程如图4所示,开发人员将代码签入Git仓库后,Jenkins会自动构建并上传包到Repo服务器。图5配置文件示例作为统一方法的一部分,我们在每个微服务仓库上创建了一个CI目录,用于打包配置文件。在CI目录下,只放需要参数化的配置文件,执行脚本等。并且会严格按照原系统的目录结构,如图5所示,我们需要start.sh、stop.sh和service(init用于Linux启动和停止微服务)。图5配置文件的参数化内容,参数部分被“{{”和“}}”包围,持续部署时会根据传入的参数替换成具体的值。我们也定义了持续构建的统一输出,每个微服务使用tgz打包格式。微服务ms1持续构建输出文件示例如下:ms1-1.0.7.tgz(可执行包)ms1_config-1.0.7。tgz(配置文件包)要求可执行包中包含所有依赖库(系统lib库除外)。不同编程语言对微服务构建工具没有强制要求,由Jenkins统一调用。C/C++推荐使用CMake,Java一般使用Maven,Python直接打包。配置文件包直接从之前git仓库的CI目录下打包。图6Bundle示例同时,为了在部署时不指定每个微服务的版本号,我们引入了bundle的概念,如图6所示。任何一个微服务构建完成后,bundle和sha512验证文件都会生成并上传到回购服务器。最后我们看一下持续交付上传到RepoServer的目录结构:图7目录结构这样持续构建的工作就完成了,接下来需要持续部署。快速构建微服务持续交付:持续部署在开始讨论持续部署之前,先描述一下软件运行注入配置的三个时间点:图8配置注入的三个时间点和打包时间点,典型的Javawar包会打包配置文件放在一起。在部署点,我们使用专门的部署工具在部署时更新配置文件,这也是我们采用的方法;在运行时点,程序运行时通过环境变量或者注册中心/配置中心获取配置信息,比如用Docker部署微服务就需要考虑这种方式来获取所需的配置信息。图9使用Salt进行部署图9显示了我们使用salt在不同的环境中进行部署。由于我们支持用户只输入bundle的版本信息就可以实现部署,这就需要部署系统在持续部署时能够自动获取各个微服务的版本号。为此,我们对salt/foreman做了一个小改动,修改返回的pillar格式包含每个微服务的版本。同时下载对应的配置文件包并解压到saltmaster对应的目录下,并关闭saltmaster的file_list缓存:fileserver_list_cache_time:0。图10foremanweb界面和Salt格式图10左侧在工头web界面显示我们设置的参数,右侧显示通过saltpillar.items获取的格式。可以看到每个微服务的版本号信息。下面我们按照部署三部曲(安装、配置注入、服务运行)来介绍部署规则文件(saltstate、sls文件)的准备:1.betacat_ms1.slsPart1:Install这部分,勾选并创建安装目录,下载需要的可执行包解压到正确位置,直接从RepoServer获取可执行包,通过sha512校验文件完整性。2.betacat_ms1.sls第二部分:配置注入配置注入部分读取配置文件包,通过saltmaster转换后发送到目标机。设计的核心在这里用红色框标出。传入salt的file.recurse和之前持续部署创建的配置包,传入所有的配置项。无需为多个配置文件单独编写部署逻辑,即可完全参数化。3.第三部分betacat_ms1.sls:该部分运行服务,保证微服务正在运行,必要时重启。这里需要指出的是,在整个sls文件中,针对不同微服务的元参数只有三个:项目名(BeatCat)、微服务名(ms1)和sig(ms1,微服务进程字符串的唯一标识)).那么我们就可以通过简单的脚本自动生成sls文件,无需手动编写。大大降低持续部署的开发和维护成本。快速构建微服务持续交付:全自动化为了支持持续交付过程的全自动化,我们引入了ZooKeeper,如图14所示。图14引入ZooKeeper后的流程代码签入Git后,构建为触发,Jenkins会将打包好的包上传到RepositoryServer,并更新ZooKeeper当前最新的包版本信息。检测到ZooKeeper最新包版本信息变化后,会触发saltstack的部署命令,将最新的程序部署到各个环境中。部署完成后,会更新目标机器在ZooKeeper上的部署版本信息。在ZooKeeper上监听到目标机器部署版本信息的变化后,会触发一组或多组自动化测试脚本的运行。自动化测试通过后,会更新ZooKeeper上的包版本测试信息。测试通过的包可以自动上传到生产环境的repo服务器,更新生产环境ZooKeeper的包版本信息。在生产环境中,检测到ZooKeeper的包版本信息发生变化后,会触发生产环境的部署。生产环境部署完成后,目标机器在ZooKeeper上的部署版本信息会更新。
