传统的服务运维对自动化的要求不需要太高,但使用微服务后服务的数量会急剧上升,一个成熟的微服务可能有几十到几百个组件,每个组件做HA,最终可能有成百上千个实例,同时还要考虑开发、测试、UAT/预发、生产、灰度等环境,人工部署将是灾难性的。
所以微服务带来的服务数量提升及快速响应要求直接导致了运维体系的升级,成功的微服务架构必须有一套成熟的持续交付CD流程。
如上图是一个比较完整的持续交付示例,发布的环境有多个,常规的我们会有开发、测试、UAT(用户验收)/预发、生产等,不同环境会有不同的流程。
一般而言发布到开发环境的流程是开发人员提交代码到代码仓库(如GitLab),持续集成服务(如Jenkins、GitLab CI)通过监控获取到代码Push事件进而拉取最新代码进行编译,完成之后可以连接代码质检平台(如Sonarqube)完成质量评估,在质量达到一定要求后再向后执行到打包与发布。这一环境的发布要求准实时以供开发与程序员自测所需。
而发布到测试环境的流程多为在一个功能点或迭代开发完成后由开发人员提交转测单给测试,测试人员手工触发CD流程,持续集成服务拉取最新代码后编译打包并发布,然后再执行自动化测试,这一过程可以由持续集成服务触发也可以由更专业的测试系统完成,我们实际情况中有很多环节还是无法实现测试自动化(或实现成本很高),这时由人工介入测试,测试完成后撰写并发布测试报告。
在测试人员测试完成后就可以发布到UAT(用户验收)/预发由产品或业务人员验收,这一流程将要测试通过的包去掉SNAPSHOT依赖版本后重新打包并部署到UAT(用户验收)/预发环境即可。
在产品或业务人员验收通过后才会发布到生产环境,运维人员在收到产品负责人确认后手工触发。这一流程的关键点在于根据不同的发布策略执行上线操作,如果是破坏性地发布,即无法做到向后兼容(如有数据库字段的修改)则需要执行的核心流程如下:
- 将验收通过的包上传到所影响服务的各个实例所在节点
- 下线所影响的服务,发布维护公告,切断对应服务所有实例的对外链路(内部链路依然畅通,用于验证),关闭所有这些实例,防止定时器、MQ等作业的影响
- 备份旧版本服务
- 启动部分实例,此时运行的是新版本的服务
- 在内部网络中验证服务是否正常
- 正常后启动其它实例
- 恢复对外链路,撤销维护公告
- 产品负责人发送上线成功邮件
- 开发负责人创建当前发布版本的Tag结束此版本开发
如果可以平滑升级则可采用如下流程:
- 根据策略将验收通过的包上传到受影响服务的部分实例所在的节点
- 关闭这部分实例对外的链路并关闭这些实例
- 在这些节点上备份旧版本服务
- 在这些节点上启动实例,此时运行的是新版本的服务
- 在内部网络中验证服务是否正常
- 正常后根据策略重复1-6,直到所有实例都已替换成新版本
- 产品负责人发送上线成功邮件
- 开发负责人创建当前发布版本的Tag结束此版本开发
上面的两类流程比较典型,被大部分项目采用,在后面章节我们会谈K8S、Service Mesh,通过这些工具及架构可以适度地简化运维的操作。
这里要强调的是生产服务的包必须是验收通过的包,有一些项目中生产服务需要重新打包,浪费时间不说更有可能导致生产与验收内容的不一致,比如在开发不规范的情况下可能发布还用的是SNAPSHOT(或是引用的直接/间接依赖是SNAPSHOT),在验收通过后生产打包前就存在代码被更新的可能,而且CI工具打包操作本身也有可能出错。所以让生产服务使用验收通过的包是非常有必要的,但它有一个前提是配置独立,不同环境的代码要求相同但配置肯定不会相同,部分项目喜欢将配置混入包中(比如Java开发人员喜欢在Maven中通过不同的Profile加载不同的配置信息,这些配置信息会打到Jar包中)导致无法重用,所以这也是我们前文提到使用统一配置中心的必要性,让包内包含不同环境(Profile)下配置获取的方式,不同环境的实际配置存放在配置中心,通过运维启动脚本指定当前执行的环境以实现不同配置的加载。
上面这些基本涵盖了自动部署的大部分流程,当然在有些情况下我们还需要灰度发布或要求有A/B环境,更有甚者需要有金丝雀通道,这时自动化部署的流程相较而言没有大的区别,但对服务的监控、各环境的准备提出了更高的要求。比如灰度与A/B环境中流程怎么划分、如有数据存储差异时环境升级时数据怎么同步这些都要有统一的考虑。
微服务下服务运行节点多又要求可以支持快速伸缩,在服务峰值时可以自动增加节点,在低谷时又可以回收部分节点,这对运维也是个很大的挑战,好在Docker、K8S等技术的兴起为微服务提供了很好的运维支撑,它可以很方便地根据服务负载实现节点自动伸缩,下文会有针对性的讨论。
下一节:服务监控是保证生产环境稳定最基础也是最重要的措施,微服务下尤是。如果没有成熟的一套监控方案那微服务化注定会失败!