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