引用Redhat对CI、CD的解释:【CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。】
https://www.redhat.com/zh/topics/devops/what-is-ci-cd
基于微服务的系统设计,通常使用注册中心来管理服务,实现相互之间的寻址。如nacos、eureka、consul等。考虑到持续集成中,从开发调试、集成测试到生产运营环境要尽量做到各个阶段的一致性,有必要保持交付件的一致性。
什么是交付件?简单来说,包含了程序包和依赖的数据(配置文件,应用数据,环境变量等)。你可能会问为什么不是代码?有些团队就是把代码当做交付件,在开发、测试、运维团队间传递。类比于一个整装销售的硬件产品,在生产阶段、物流阶段、使用阶段,其物流与使用阶段的形态是一致的。当然软件产品为了保障可回溯到具体代码,需要在测试阶段对此再次验证,代码将作为交付件在开发、测试之间传递。
类比下:CI CD CD
怎么保持一致性? 有团队仅仅保持代码的一致性,统一从指定的版本库拉取,分别在开发、测试、生产环境构建生成程序包,但明显是不够的;如果在各个阶段都需要独立构建程序包,手工修改配置文件,持续的交付就会出现困境。团队之间会经常出现“我以为”的增量提交变动造成的问题,不可遏制。进而失控。理想地说,我们应该保障程序包和依赖数据的一致性;但一般不可能,所以我们应该确保程序包的一致性,每一次程序的变更要对依赖的数据进行充分的描述,并尽可能的确保其中的配置文件可分离出来以指向不同的环境,并将这部分一致化。其依赖的其余数据,应该明确责任人,确保CD过程中,职责清晰,要求的前后向兼容能获得充分的解释。
依赖的数据分层
上面定义依赖的数据为配置文件,应用数据,环境变量等,无论形态如何,大体可分为如下几类。
- 节点信息 :IP与端口、用户与密码对、域名与库、表、队列名、文件路径与环境变量等
- 负载信息 : 灰度策略、分级服务、日志级别、线程数、内存数等
- 业务信息 : 参数格式、存在性、版本与检查性数据常量等
- 会话信息 : 初始不存在,程序包运行后存取的信息,会影响下次的运行路径
除了会话信息之外,我们可以使用nacos统一配置。按照这里的分层可以让运维、测试、开发工程师协作各自负责其所应该承担的部分职能。
Nacos的能力
nacos提供了名字空间与分组来区分配置文件,名字空间可以用来分离不同的产品,分组可用来分离同一个产品下的不同模块。
名字空间
分组
结合配置的优先级
Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。
- A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置
- B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置
- C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置
当三种方式共同使用时,他们的一个优先级关系是:A < B < C
一般而言,开发、测试、生产环境的差异主要是【1 节点信息】,【2 负载信息】,【4 会话信息】。通常我们会确保其模式的一致性,即程序包访问他们的时候,确保访问方式的一致性。【3 业务信息】应该在所有环境中保持一致。
应该分别在开发、测试、生产环境部署nacos,并分别为他们配置相同的域名。这样在代码的bootstrap.yaml里面就可以使用相同的配置,例如下面:
spring:
application:
name: va-restful-crud-svr
cloud:
nacos:
config:
server-addr: www.zctx.online:80
file-extension: yaml
group: zctx
namespace: zctx
shared-configs:
- data-id: nodeinfo.yaml
group: prod
refresh: true
extension-configs:
- data-id: loadinfo.yaml
group: prod
refresh: true
- data-id: businfo.yaml
group: prod
refresh: true
discovery:
server-addr: www.zctx.online:80
group: zctx
namespace: zctx
从开发、测试到生产环境
时常看到团队开发通过后、测试不通过、测试通过后生产不通过。是在是团队协同中缺乏一定的共识基础。
高效的变更流程,必要且有效的检查点设定,才是优秀的CI/CD。推荐的流程如下图所示,可简单将研发过程分为设计(确定部署方案)、开发(编码+调试)、测试(构建+测试)与运维(发布+验证)四个环节,并结合实际版本变更的三种情形(部署变更、节点扩容、常规迭代)明确每次变更的内容。
部署变更
此情形,包括:新的产品设计、网络拓扑变更等会改变部署图的场景都归于此类。此时一般需要在设计环节做一些工作,确定部署图,并对业务、负载、节点配置做清晰定义。而后开发、测试、运维节点直接引用业务配置、并基于各自环境的不同,分别参考定义各自环境的负载与节点配置。
节点扩容
这里仅仅只是单个实例的扩容,不影响用方的寻址;例如CPU、内存、磁盘、带宽的扩容,也包括集群节点的扩容,例如消息队列的集群、负载均衡器下节点的增加,这些都归于此类。此时只需要运维来修改,要改动的也仅仅是生产环境节点与负载配置信息。当然,如果测试环境、开发环境要做类似的事情,需要变更的也仅仅是开发、测试环境关于节点与负载配置信息的调整。
常规迭代
此情形为不调整部署结构的版本变更,有开发人员提交代码,和可能的业务配置,测试与生产环境直接应用业务配置,并在测试环境构建程序包,生产直接引用测试的程序包。
高效流程
让应该负责的人负责,而不是让大家迷茫共担。软件的开发过程,应该依照上述的变换指定流程,确保高效。这是一个高效的实现,供参考:
- 运维团队:负责管理资源(包括服务器、网络、一些中间件等),那么开发、测试、生产环境的基础设施应该归属他们完成。管理好开发、测试、生产环境的节点与负载配置信息。
- 测试团队:负责确保代码与程序包的对应关系,并确保与需求规格匹配,设计用例,执行并给出测试报告。建立需求规格、代码、用例、报告、程序包、配置的关联关系,并确保可回溯。
- 开发团队:负责代码的编写、业务配置的设计。日常迭代中程序包通过测试后,开发团队负责发布。
检查点
测试、运维是两个检查点,也是最必要的和有效的。测试需要确保关联,运维需要确保使用与测试一致的交付件。
关于nacos的一个实践过的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>online.zctx.wechat</groupId>
<artifactId>restful-crud-svr</artifactId>
<version>1.0-SNAPSHOT</version>
<name>va-restful-crud-svr</name>
<description>restful-crud-svr of va for zctx.</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 本地调试要注释掉
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
-->
</exclusions>
</dependency>
<!-- 部署到tomcat时增加的配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.0</version>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>online.zctx.wechat</groupId>
<artifactId>restful-crud-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
<!--
<version>1.1.22</version>
-->
</dependency>
</dependencies>
<build>
<finalName>restful-crud-svr</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>