一些持续交付的实践经验

分析团队的问题

我是2020年3月份加入该部门。刚加入时发现问题还挺多。而这些问题在行业里都很典型,比如:

  • 分支管理不统一:虽说大部分人还是在master上开发,但是还有部分人自己拉feature分支开发。
  • 没有统一的制品打包:Docker镜像的打包基本都是在开发人员的电脑上进行的。
  • 对制品仓库的push权限没有管理:每个人都有push权限,而且使用的是同一个账号。
  • 没有版本管理:在交给测试人员测试(俗称提测)时,开发在本地打包后,push上制品库的包的版本号为uat20200302(UAT是测试环境的简称)。测试通过后,再使用此版本号,部署到生产环境。结果就是你会看到生产环境运行的包的版本:uat20200302,是不是很奇怪?
  • 没有监控:这也是很多团队的通病了。
  • 没有单元测试:开发人员有在main方法写单元测试的,有写出来的测试是无法自动化的。
  • 开发团队没有自己的自动化测试。
  • 多个应用部署在同一台机器。
  • 手工部署:每次部署都是人工登录到服务器执行部署。
  • 数据库没有版本化。
  • 没有代码审查。

问题还有很多,就不一一列举。在笔者看来以上问题的最终表现都是软件系统的质量低下。笔者希望通过实践持续交付以提升软件系统的质量。

但是问题是该如何实践呢?笔者认为只要掌握了它的基本原则,剩下的就是根据实际情况结合基本原则来解决问题了。

持续交付的原则

幸福的家庭都是相似的,不幸的家庭各有各的不幸——《安娜卡列尼娜》

从《持续交付》书中,基本原则有:

  1. 为软件的发布创建一个可重复且可靠的过程
  2. 将几乎所有的事情自动化
  3. 把所有的东西都纳入版本控制
  4. 提前并频繁地做让你感到痛苦的事
  5. 内建质量
  6. “DONE”意味着“已发布”
  7. 交付过程是每个成员的责任
  8. 持续改进

我们可以这么理解这些原则:基于所有东西都要进行版本化的原则,所有的东西都要代码化。

因为代码化以后就可以放到类似Git这类版本化工具中。而代码化以后的东西就可以很容易地实现自动化。在实现自动化以后就可以为软件的发布创建一个可重复且可靠的过程。

实现自动化的过程是需要每一位成员持续参与的,因为交付过程是每个人责任。

“DONE意味着已发布”是团队每个成员都要达成的共识。达成共识后,才能更好的参与持续改进。在持续改进过程中,我们的软件系统就获得了内建质量。

笔者认为,在持续交付中,代码化与版本化是基础。

实践持续交付

在理解原则后,我们就可以开始实践了。可是该如何下手呢?笔者通常遵循以下指导思想:

  1. 先CI,后CD。
  2. 无监控,无安心觉。
  3. 先配置项版本化,后标准化,最后才有自动化。

根据指导思想,再结合团队的实际情况,笔者做出以下计划:

  1. 打包自动化。
  2. 实现基础监控(机器级别监控、中间件监控)。
  3. 实现所有的配置版本化。
  4. 实现自动化部署应用。
  5. 实现应用监控。
  6. 实现数据库版本化。
  7. 实现业务监控。
  8. 研发数据收集
  9. ….

由于团队原来已经有日志收集机制,所以,暂且不需要实现。以上步骤只代表一个优先级。如果团队人力充足,可以同时一起做。

虽说有了计划,但是团队不具备相应的能力,什么计划都白搭。

1. 打包自动化

之所以使用“打包”这个听起来不怎么“高端”的词,而不是使用“构建”。是因为“构建”这个词,太容易引起歧义。而且打包这个词很形象,就是把源代码编译后,链接,最后打包成一个可执行包。当然不同的编程语言,打包过程可能不同。

因为大多数团队都没有写自动化测试的习惯(我们团队也不例外),让他们写自动化测试,他们只会觉得自己的工作量增加了。所以,我在团队中导入持续交付实践时,一开始就不要求自动化测试。团队意识的转变需要很长的过程。这是使用“打包”的第二个原因:它不包括自动化测试。

要实现自动化打包,其实并不难。基本步骤就是:

  1. 搭建制品库:Nexus。
  2. 搭建自动化服务:Jenkins。
  3. 在Jenkins中创建pipeline任务。
  4. 在业务代码仓库中加入Jenkinsfile,将打包逻辑写到Jenkinsfile中。

所谓打包逻辑就是你在本地开发时,利用IDE或命令将源代码编译成可执行文件的过程。打包自动化的过程,就是将你在本地执行的打包过程“搬”到自动化系统上执行,再加上一些优化。

在这个阶段中,我们需要实现:

  1. 统一制品库:收回所有人上传制品的账号。只能由Jenkins打包上传。
  2. 统一版本号:比如使用格式年-月-日-commitId-构建号来定义所有的后端应用。注意,不管使用哪种方式,你必须很容易的根据版本号找回相应的源码。
  3. 统一分支管理:使用主干开发,分支发布的模式。我们根据团队情况有稍微做了一些改变。发布并没有切分支出来,而在发布后发现某版本有Bug,我们就会从该版本的代码切一个分支出来改,打包,部署。最后再将该分支的commit cherry pick回master分支。

2. 实现基础监控

没有监控,在我们这个行业太常见了。所以,在我加入团队后,发现几乎没有任何监控,也就没有什么好惊讶的了。所以,在解决打包问题之后 ,紧接着就是给所有的机器加上监控。至少机器的CPU、硬盘、内存等要监控起来。

上一阶段,我们已经把Jenkins搭建起来,所以,Prometheus就开始自动化部署了。

使用Prometheus的原因很多,但是关键是它的配置是代码化的,非常容易版本化。持续交付的原则:将几乎所有的事情自动化、把所有的东西都纳入版本控制。像Zabbix,使用需要使用界面进行操作的,就被我排除了。

在这个阶段中,我们需要实现:

  1. 尽量不要动老机器的配置。要做的就是在该机器上安装node-exporter方便监控。
  2. 新机器使用Hashcorp Packer进行标准化操作系统 。新机器统一使用新的标准化的操作系统 。
  3. 使用Prometheus监控所有的机器。同时,需要对告警进行分级。我们根据紧急程度,将告警发往不同的钉钉群。
  4. 中间件的监控,找到相应的exporter即可。

3. 实现所有的配置项版本化

“配置”是一个非常容易产生分歧的词。它可以是一个名词,也可以是一个动词。作为名词,我们把它称为配置项,以区别于动词。另一个使用“配置项”的原因是,当人们讨论配置时,大脑里通常是propertiesyaml等这类文件格式,又或者是像consulctripcorp/apollo等这类分布式配置中心。而笔者所说的配置项是独立于任何文件格式及其被使用的方式的。

笔者认为这样理解“配置”更合理:

Screenshot from 2020-09-16 21-25-20.png

虽然配置项从概念上是独立的,但是它总归要被人增加、删除、修改吧?笔者使用的是Ansible的配置管理方式管理团队所有的配置项。目录结构如下:

├── dev  --> dev环境。一个环境一个git仓库。
│   ├── group_vars 			--> 组配置
│   │   ├── all
│   │   │   └── global.yaml --> 全局配置
│   │   ├── nginx.yaml      	--> nginx组配置
│   │   └── springboot.yaml 	--> springboot应用组配置
│   ├── hosts               --> 定义机器IP与应用组之间的关系
│   └── host_vars			--> 主机级配置
│       ├── 192.168.52.10   --> 192.168.52.10的配置
│       └── 192.168.52.11	--> 192.168.52.11的配置

通常我们认为这是针对使用Ansible进行传统部署时的管理方式。但是我们的实践结果证明,它也适用于K8s的应用部署。

在这个阶段中,我们需要实现:

  1. 不同环境的配置项放在不同的Git仓库。
  2. 将敏感配置项进行加密处理,使用ansible-vault。
  3. 与现有的配置中心集成,即将配置项部署到配置中心。比如,使用Nacos时,部署配置时,通过Nacos的API将配置发布到Nacos中。

4. 实现自动化部署应用

实际上,我们的基础监控、配置项版本化、自动化部署是同时进行的。因为搭建基础监控,也需要配置项版本化和自动化。

自动化部署又分成两种:传统部署和K8s应用部署。初期团队还没有迁移至K8s,所以,初期还是得使用Ansible部署。

这个阶段,我们需要实现:

  1. 标准化所有的应用的部署逻辑,争取所有的应用使用同一套部署代码,只要修改几个配置就能部署不同的应用。
  2. 实现部署清单。所谓部署清单就是包含了所有应用的版本的列表。部署流水线会读取部署清单,并进行部署。这点有点抽象,后面会讲。
  3. 对所有应用进行自动化部署的策略:部署到新机器,采取一台机器只部署一个应用的原则。使用这种策略逐渐淘汰旧机器。
  4. 搭建堡垒机。之前因为是人工部署,开发从自己的办公电脑SSH到远程服务进行部署。现在自动化部署了,需要将SSH登录机器的权限收回。

部署清单其实就是一个Yaml文件,如下:

deployments:
- name: foo
  version: 2020-09-02-75c23d40-1
  deploy: true
- name: config
  version: 2020-08-02-15q6hd41-111
  deploy: true
- name: prometheus
  version: 2020-09-02-75c23d40-1
  deploy: true
.... 其他需要部署应用的版本列表

我们在Jenkins pipeline中读取此文件,然后根据文件内容执行相应的部署脚本。因为我们已经把应用的部署方式标准化了,所以,真正部署时,就只需要将部署清单中的name和version等配置值以参数的方式传给标准的部署方式就好。

为什么要这样做呢?

  1. 团队中的任何人,只要有版本号,通过修改文件中的版本,就可以实现部署。以实现“交付过程是每个成员的责任”。
  2. 在一个地方就能看到软件系统当前的所有的组件的版本,一目了然。
  3. 与具体的流水线技术解耦。即使不通过Jenkins pipeline,通过其它pipeline方式也可以实现。

5. 实现应用监控

实现应用监控,有三种方式:日志、指标、链路跟踪。通过指标的方式的成本是最低的。因为通过日志,需要搭建EFK;通过链路跟踪,需要对应用进行改动。所以,我选择了使用Prometheus监控各个应用。再者,前面我们已经搭建好了Prometheus。

我们后端基本是Java应用。所以,只需要在Java工程中加入Micrometer的依赖,接着加入配置就可以。不需要对业务代码做任何的修改。

这个阶段,我们需要实现:

  1. 为所有的应用加入Micrometer监控依赖。
  2. 加入告警规则。

6. 实现数据库版本化

实现数据库版化,是一项需要非常谨慎实施的工作。我们使用的是Flyway实现的版本化。注意,网上不少文章写的是将Flyway集成到Java应用中实现的。这种方式不适合工程化。更工程化的方式是,单独创建一个数据库版本化的Git仓库,然后通过执行Flyway的命令行工具进行版本化数据库。

需要注意的是,生产环境的DB与测试环境的数据量没有可比性。在测试环境能直接运行的SQL,放在生产环境执行可能会发现事故。所以,数据库版本化,我们也只是在测试环境做。

对于数据库版本化,我们是这样实现的:

  1. 创建一个Git仓库,仓库的目录按照Flyway的目录结构要求创建。
  2. 团队成员根据Flyway的命名方式提交自己要执行sql文件。
  3. Git的push触发Jenkins pipeline执行flyway命令,以进行数据库版本化。
  4. 生产环境还是由人工执行。

Flyway目录结构:

├── config
│   ├── flyway.conf
│   └── flyway.template.conf
├── Jenkinsfile.groovy
├── README.md
└── sql
    └── V1__Create_person_table.sql
    └── V2__Add_person_column.sql

7. 实现业务监控

软件开发的目的是为了实现业务价值。如何进行业务监控其实应该是每个软件功能开发前就要思考了。可惜,我们做软件行业的人做着做着就忘记了初心,业务监控往往等功能上线后才想到。

实现业务监控可通过日志数据取得。这个阶段需要实现:

  1. 标准化日志,方便收集日志。
  2. 实现日志处理逻辑。

8. 研发数据收集与分析

在我们实现自动化、版本化一切后,收集研发数据就变得可行了。目前行业里还没有好的开源的研发数据模型。我们也还在摸索中。

迁移到K8s

我们一开始也是传统部署。而传统的部署方式要实现像K8s那样多的功能(比如:自动化扩缩容、滚动更新等),非常的困难。所以,也决定迁移到K8s。

迁移的思路是先迁移无状态的应用上K8s,然后再逐渐迁移其它应用。而pipeline的具体做法:

  1. 实现新打包方式

    1. 应用Docker化。将Docker镜像放至制品库。
    2. 使用Helm3管理应用的k8s yaml部署文件。在业务代码仓库中加入相应的chart包的代码。 所以,一个应用将会有3种制品:Jar,Docker镜像,Chart包。每次部署它们的版本号都应该是确定的,可查的。
  2. 实现新的部署方式。优化部署清单,让其既可以支持传统部署,又可以支持K8s部署方式。 因为我们之前实现了配置项是与具体的使用方式无关的,所以,切换到K8s部署时,我们的配置项的管理方式不需要变。这样就节约了很多成本。

关于度量

“你如果无法度量它,就无法管理它”。——彼得.德鲁克

我相信一定会有人问,经过这一整套实践,你们团队的软件质量提升了多少?

因为在本文开发,我们也讲了,希望持续交付的实践能提高我们的软件系统的质量。

在讨论软件质量之前,我们都应该先讨论软件质量的定义。在整个行业中,笔者认同温伯格在其《质量.软件.管理》的第一卷中,给出的定义:

质量就是对某个(些)人而言的价值。 需求并非是最终的目的,而只是达到目的的手段——我们的目的在于,要为某个(某些)人提供相应的价值。

基于此定义。如果我们没有对用户价值的定义,那么也就无法定义软件质量。不同类型的软件,用户价值具体表现不同,我们不在此文展开。

当实现业务监控,我们才能了解用户价值。因为每一次的交付之后的,监控数据就会告诉我们,这次交付,对于某个(些)人到底有没有价值。基于此,我们谈软件质量才是更合适的。

回到我们的问题。我们团队的软件质量提升了多少?如果使用政治套话,肯定是提升的,你看,持续交付所有的实践,我们都实践了;如果从实际出发,没有业务监控,我们无法回答软件质量提升了多少。我们依然在路上。

后记

最后,我对于持续交付的实践,个人总结如下:

难的不是背诵持续交付的原则和定义,而是能发现团队问题,并根据团队的情况、能力制定持续交付改进计划的能力。所以,以上经验并不适合所有的团队,只是提供一种实践持续交付的一个案例,以供大家参考。


Last modified on 2020-09-09