车端构建系统
# 01.车端版本
# 0、项目介绍
# 项目背景
- 项目名称: 车端构建系统
- 项目描述:
- 本项目旨在解决车端Bundle包的打包构建问题,确保在车端环境中生成高效且稳定的Bundle包
- 系统支持多项目的关联管理以及树形结构的版本控制,确保各个代码仓库能够统一管理和打包
# 主要技术栈:
# 职责与贡献
- 项目管理与模块管理
- 实现功能:
- 设计并实现了版本管理系统,使一个版本能够关联多个项目,每个项目对应一个独立的代码仓库
- 开发了模块管理功能,支持对项目中的各个模块进行细粒度的操作和配置管理
- 技术细节:
- 使用Golang编写了项目管理和模块管理的核心逻辑,实现了高效的版本控制和模块配置
- 运用Python脚本自动化处理配置和日志管理,实现操作可追溯性
- 实现功能:
- Bundle管理
- 实现功能:
- 构建了自动化Bundle生成系统,根据指定的版本和模块生成对应的Bundle包,并实现了稳定制品的标记功能
- 技术细节:
- 使用Golang编写构建和打包流程的核心组件,确保生成的Bundle符合系统稳定性要求
- 利用Python编写辅助脚本,自动化处理生成过程中所需的依赖管理和环境配置
- 实现功能:
- Bundle对比
- 实现功能:
- 开发了Bundle对比工具,支持对不同版本或模块之间的Bundle进行快速对比,帮助团队识别和解决构建中的差异和不一致性
- 技术细节:
- 使用Golang编写对比工具,优化对比算法,提升对比速度和准确性
- 实现功能:
- 自动发版
- 实现功能:
- 集成自动化发版流程,简化了版本发布操作,确保系统能够快速、精准地进行版本迭代
- 技术细节:
- 通过Golang和Python编写自动化流水线脚本,整合了CI/CD工具,实现了从构建到发布的全流程自动化
- 实现功能:
- 任务管理
- 实现功能:
- 实现了多种任务类型管理功能,包括提测任务、Daily任务、伴生任务、灰度任务和多MR合入任务,满足不同开发和发布场景的需求
- 技术细节:
- 使用Golang实现任务管理的核心逻辑,确保任务的调度和执行高效稳定
- 通过Python实现任务日志的收集和分析,辅助开发团队进行任务追踪和故障排查
- 实现功能:
- 配置管理
- 实现功能:
- 开发了系统配置管理模块,支持测试能力配置、操作日志和配置日志的记录与管理,增强了系统的可配置性和可维护性
- 技术细节:
- 使用Python实现配置管理模块,通过灵活的配置选项适配不同的测试和部署环境
- 实现功能:
# 正式构建管理
- 构建模式:
- 系统支持人为触发的正式构建,采用
PEEK_BUILD
模式,由mazu
触发,通过mr_trigger
任务标签进行管理 - 此次构建针对模块
locolsim
进行操作,确保模块的构建过程符合标准化要求
- 系统支持人为触发的正式构建,采用
- 技术实现:
- 使用Golang和Python结合实现构建触发与管理的功能,通过自动化脚本集成CI/CD管道,实现了正式构建的可控性和稳定性
# 项目成果
- 提升了车端Bundle包的构建效率和质量,减少了发布过程中的人为错误,确保了构建制品的稳定性和一致性
- 项目上线后显著降低了多项目、多版本管理的复杂度,提高了团队的开发和交付效率
# 1、介绍
项目背景:
- 车端构建系统的核心目标是解决车端Bundle包的打包构建问题
- 通过该系统,能够高效地生成符合要求的Bundle包,确保各个版本的稳定性和一致性
系统特性:
多项目关联: 一个版本中可以关联多个项目,每个项目对应一个独立的代码仓库
树形结构版本管理: 最终的版本构建为树形结构,虚拟根节点为
algorithm_all_app
,其下包含各个子项目和模块
# 2、核心功能模块说明
项目管理与模块管理
项目管理: 提供版本管理功能,一个版本可以关联多个项目,确保每个项目的代码仓库都能在版本中被统一管理和打包
模块管理: 系统支持对各个模块进行管理,便于用户进行模块级别的操作和配置
Bundle管理
Bundle生成: 系统会依据指定的版本和模块生成对应的Bundle包
Bundle稳定性标记: 对于
正式的构建
,系统会将构建产出的模块和制品标记为稳定制品
,确保该版本的稳定性
Bundle对比
- 提供对不同版本或不同模块之间的Bundle进行对比,快速识别出差异,确保版本的一致性和可靠性
自动发版
- 系统支持自动发版流程,简化了发布操作,确保各个版本的发布能够快速、精准地进行
任务管理
提测任务: 支持在开发流程中进行提测任务的管理,确保代码在合适的阶段被测试
Daily任务: 系统支持每日任务的执行,确保日常构建的有效性
伴生任务: 支持与主任务同步进行的次要任务,以保证完整的任务流
灰度任务: 系统提供灰度任务管理,便于版本的逐步推广和测试
多MR合入任务: 系统支持将多个Merge Request(MR)合入到同一个版本或模块中进行构建
配置管理
测试能力配置: 系统允许配置不同的测试能力,确保在不同场景下能够执行合适的测试
操作&配置日志: 提供详细的操作日志和配置变更日志,方便用户追踪和审计系统操作
# 3、正式构建说明
- 触发条件: 系统支持人为触发的正式构建操作,触发者为
mazu
- 构建模式: 正式构建采用
PEEK_BUILD
模式 - 任务标签: 任务标签为
mr_trigger
,标识此次任务为由MR触发的构建任务 - 触发模块: 构建将针对模块
locolsim
进行操作和处理
# 08.版本克隆
- 通过发布系统『创建版本』时,新版本及其依赖配置得通过纯手工的方式一个个添加,耗费人力成本比较高,且易出错
- 可以基于已存在的版本配置clone一份新的配置,再根据需要对部分配置进行人工的update

# 09.逆序增量构建
# 1、逆序构建介绍
当新增、删除、修改依赖时,对应更新redis内容
此处需要存在一个异步的事件驱动器来保证用户更改依赖关系后,redis能实时进行依赖同步
注意:当查找X模块的Parents时,若redis中不存在,需要到数据库中查找,最终需要将结果更新到redis中
获取待构建的模块列表 S {J, H, I, E, F, D, A}
找到J的parents: {H,I}
迭代来找模块,最终到A
在构建模块列表S的过程中,因为遍历了J->A的完整逆向链路,所以也就同时生成了J->A的DAG图,如下
{
"J:10": ["H:8", "I:9"],
"H:8": ["E:5"],
"I:9": ["E:5", "F:6"],
"E:5": ["D:4"],
"D:4": ["A:1"],
}
2
3
4
5
6
7
从A开始正向全量构建
若当前模块在列表S中,表示该模块需要重新编译,遍历其依赖模块(比如A模块依赖B、C、D)
若当前模块不在列表S中,取当前模块最新编译产物即可
遍历至无需重新编译模块或叶子节点时,开始编译父模块

- 特点
- 每次都动态生成待构建的模块列表S,触发全量构建后,只有模块在待构建的列表S中时才会触发该模块的重新构建
- 复用了当前正向构建流程,并且可以将列表S内所有的构建任务归并到一个task中
# 2、处理流程
假设模块H发生了变更,需要逆向构建与其相关的各个模块,最后打包一个新的A
处理流程如下
编译H模块,得到产物H1
找到H模块的parents: {E}
触发E模块的编译(只编译一层,依赖G\I取最新编译产物,H模块取H1,不会递归编译所有依赖项),得到产物E1
找到E模块的parents: {D}
触发D模块的编译,依赖F取最新编译产物,依赖E取E1,得到产物D1
逆向迭代至A模块,得到产物A1即为最终产物
存在的问题
- H、F节点同时向上触发构建时,E节点无法同时获取H\F节点的最新构建记录,需要有一个外部的构建控制逻辑来指导
# 3、旧流程问题

# 10.车端构建系统梳理
# 1、版本管理
- 一个版本中关联了多个项目

- 版本管理项目
- 虚拟根节点:algorithm_all_app

# 2、配置关联关系
- 一个版本中关联了多个项目,每个项目都是一个代码仓库
- 最终一个版本是一个树形结构,根节点是虚拟根节点:algorithm_all_app
- 触发人为 mazu 的是一次
正式的构建
(正式构建产生的 模块和制品就会标记为一个稳定制品
)- 构建模式:PEEK_BUILD 任务标签:mr_trigger 触发模块:locolsim
- 模块中定义了依赖的子模块
# 3、prepare阶段
- arg_app 模块运行时为例
- arg_app在prepare阶段触发自己依赖的子节点运行,子节点又触发自己依赖的子节点,直到没有依赖开始执行
- 父节点在prepare阶段会一直监听自己子节点的完成,拿到子节点的制品url,更新到自己的“构建配置快照”中
# 4、compile阶段
- 在compile阶段会比较prepare阶段中依赖的子模块上报的制品版本是否有变化
- 如果所有子模块制品都没有变化就不编译,否则使用子模块最新版本重新编译(类似于golang项目中升级某一个包)
- 每个子模块
产物名称是唯一的
,每次新的构建会产生一个新的版本号
- compile阶段会
触发GitLab的pipeline
,pipeline中定义了执行的cli命令行 - cli中将制品上报到制品仓库,并将执行状态上报到平台(通知上报到nfs中)
- 判断是否有构建必要(delta_build: ture)
- 比如在 arg_app这个模块,在AD150这个版本中,从数据库获取数据找到最新的制品
- 第一:判断构建的
- 最新的制品与依赖子模块上报的运行时制品是否一致
# 5、.gitlab-ci.yml
stages:
- build
- publish
- deploy
build:
stage: build
image:
name: adas-img.nioint.com/aa-devops/golang:1-14-ssh-build
script:
- git config --global user.email "${CI_USERNAME}@nio.com"
- git config --global user.name "${CI_USERNAME}"
- go env -w GOPROXY="https://goproxy.cn"
- make
artifacts:
paths:
- ./bin/release-system
only:
- develop
- release
- /^patch_.+/
- /^hotfix_.+/
- /^feature_.+/
tags:
- gitlab-runner-aip
publish:
stage: publish
image:
name: adas-img.nioint.com/aa-devops/kaniko-executor:no-error-check
entrypoint: [""]
script:
- /build.sh ./Dockerfile $CI_SERVICE $CI_PROJECT $CI_COMMIT_REF_NAME tencent-dev
dependencies:
- build
only:
- develop
- release
- /^patch_.+/
- /^hotfix_.+/
- /^feature_.+/
tags:
- gitlab-runner-aip
deploy:
stage: deploy
image:
name: adas-img.nioint.com/aa-devops/deploy-util:latest
script:
- CURRENT_LOCATION=`pwd`
- python3 /deploy-utils/deploycli.py deploy -env tencent-dev -project $CI_PROJECT -service $CI_SERVICE -branch $CI_COMMIT_REF_NAME -deployname aip-release-system -sourcecodepath $CURRENT_LOCATION
only:
- develop
tags:
- gitlab-runner-aip
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 11.创建版本
https://nio.feishu.cn/wiki/D7i8wEQAniXXn2k2mP3cHHGknPh
# 1、创建版本
版本是一个完整的编译,发布的整体集合
- 点击创建版本

- 填写版本信息
- 版本类型说明:
- 主线/T线版本为公开版本,可支持多人写作可见
- 个人版本为私人版本,只有自己可见
- 版本类型说明:

# 2、添加模块
- 一个模块对应gitlab中的一个repo
- 所以需要自行在代码仓库中先创建好 repo,并且在repo中引入gitlab-ci

备注:
Git Trigger Token在对应的repo中可获取到
在对应的repo-》设置-》CICD-》流水线触发器 中创建一个触发器

- 流水线触发器所对应的Token 即为需要填写在表单中的 Gitlab Trigger Token

# 3、模块与版本关联
- 模块可以被关联到不同的版本中,并赋予不同的配置参数
- 1)选择目标版本

- 2)“编辑”添加模块

- 检索刚才关联的模块

- 4)进入模块当前版本详情页
- 因为该模块新被加入到版本下,会显示需要对该模块进行初始化

- 5)初始化模块版本关系
- 点击初始化按钮,在基础版本中,选择"develop" 版本,并点击“初始化”按钮完成配置

# 4、建立模块之间的依赖关系
- 在同一个版本中的模块,可以建立上下游的依赖构建发布关系
- 1)点击“项目构建依赖关系图”
- 已经完成多个模块的添加,本步骤完成之后,可建立模块与模块之间的依赖关系图

- 2)在父模块中配置依赖子模块
- 进入到依赖关系的父模块(点击模块,进入到详情页面),并点击“修改构建定义&配置”

- 3)在弹出表单中切换到“构建定义”页,并编辑如下内容

- 4)说明: 如果A依赖B,并且B的模块ID为 x的话, 则在A的配置中,增加如下标红的内容
dependency_list:
- dependency_release_id: "xxxx"
artifact_list: []
如果想添加多个下游的依赖,可增加多行
dependency_list:
- dependency_release_id: "xxxx"
artifact_list: []
- dependency_release_id: "xxxx"
artifact_list: []
2
3
4
5
6
7
8
9
10
11
- 5)关系图展示
- 备注:建议模块的更加复杂的依赖关系,可以重复上述步骤,然后在项目模块依赖关系图中查看建立的依赖关系

# 12.Gitlab CICD
# 1、术语解释
DevOps
- (Development和Operations的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合
CICD:持续集成/持续发布,主要以自动化流水线的方式
Gitlab-CI gitlab CI是gitlab内嵌的一套支持持续集成的工具链
Harbor
- Harbor是由VMware公司开源的企业级的Docker Registry管理项目,相比docker官方拥有更丰富的权限权利和完善的架构设计,适用大规模docker集群部署提供仓库服务
Kaniko
- kaniko 是 Google 开源的一个工具,旨在帮助开发人员从容器或 Kubernetes 集群内的 Dockerfile 构建容器镜像
- 其好处是在构建过程中不需要依赖root用户权限,保证了构建阶段的安全性
Kustomize
- kustomize 是 sig-cli 的一个子项目
- 它的设计目的是给 kubernetes 的用户提供一种可以重复使用配置的声明式应用管理
- 从而在配置工作中用户只需要管理和维护 kubernetes 的原>生 API 对象,而不需要使用复杂的模版
Consul
- consul是HashiCorp公司推出的一款工具,主要用于实现分布式系统的服务发现与配置
Docker Promote
- 将docker image通过重新打tag并上传到不同的harbor project下,完成容器镜像在不同project之间的流转,实现了容器管理的逐级管理
# 2、代码目录组织规范
- 代码根目录下必须定义.gitlab-ci.yml 用户启动 gitlab-ci pipeline
- 代码根目录下必须定义Dockerfile文件用于构建容器镜像
- 代码根目录下必须定义deployment目录用于存放kubernetes object的定义文件以及kustomization.yaml
- ( kustomizatoin.yaml用于在部署过程中对kubernetes object定义文件进行渲染)
- README 文件用于表述项目的基本内容 (建议提供)
├── Dockerfile
├── README
├── VERSION #需要填写当前的版本信息 (例如 v1.0.1 )
├── deployment #基础部署描述文件
│ └── base
│ ├── deployment.yaml
│ ├── ingress.yaml
│ ├── kustomization.yaml #kustomization模版渲染描述文件
│ └── service.yaml
2
3
4
5
6
7
8
9
- kustomization.yaml文件式例
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- ingress.yaml
- service.yaml
2
3
4
5
6
Kubernetes 命名空间规范
在K8S环境中通过namespace将各个研发阶段的微服务进行隔离
namespace根据研发阶段名定义,并通过添加后缀的方式区分不同的环境(后缀为 -dev / -qa / -prod)
# 3、CICD配置中心
CICD配置中心提供中间状态的热数据的记录组件,用户存储当前部署的最新状态的能力
本设计中采用consul单节点的方式,由于系统只是状态记录节点,不需要考虑对其稳定性做过多考虑,只需要保证其中数据能够持久化
CICD配置主要是利用了consul的key/value存储的特性,该特性可以直接通过cur命令访问创建,修改,删除,遍历数据目录
无需第三方类库的依赖,使得整个流水线更加的轻量化
通过CICD配置中心可提供动态数据,在Consul中的路径组织结构如下
cicd
|---> projects
|---> <project_name>
|---><env> (dev/qa/stage/prod)
|---> <service>
|--->latest --> value (jsonformat)
|--->current --> value (jsonformat)
|--->pre --> value
|--->deploy_patch
|---> kustomization
|---> xxxxxx
|--->status
......
|--->scripts
|---> deploy_sh
|--->kubeconfig
|--->kubeconfig_qa
|--->kubeconfig_prod
......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 4、CICD流程
# 0)开发阶段流水线
开发阶段流水线将依赖于gitlab-runner实现代码提交到主分支之后的构建,打包
对于开发流水线对接的是开发自测环境,对于环境的稳定性要求较低,因此直接在gitlab-ci中集成部署步骤
目前项目使用gitlab作为代码托管工具, Gitlab-CI 原生具有了强大的集成功能因此在开发阶段的整个CI/CD将通过 Gitlab-CI完成
对于任何一个项目均需要定义 .gitlab-ci.yml 并在文件中定义三个基本的阶段
stages:
- build
- publish
- deploy
2
3
4
# 1)Build 阶段
- 主要使用容器化构建,通过定义特定语言的构建容器和脚本,完成代码的构建
- 例如下面的例子是在容器镜像中构建go语言代码
build:
stage: build
image:
name: golang:1.13.1
script:
- go build -o main main.go
artifacts:
paths:
- main
tags:
- <gitlab-runer-tag>
only:
- <branch_name>
2
3
4
5
6
7
8
9
10
11
12
13
# 2)Publish阶段
- 该阶段主要完成Docker镜像的构建,使用kaniko工具完成
- 该工具相比Docker In Docker的方式的好处是不需要为容器申请privilage的权限,保证了构建过程的安全
publish:
stage: publish
image:
name: adas-img.nioint.com/aa-devops/kaniko-executor:debug-v1.3.0-curl
entrypoint: [""]
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile ./Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$TIME_STAMP
- curl -X PUT -d "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$TIME_STAMP" http://10.115.8.55:8500/v1/kv/cicd/projects/$CI_PROJECT/dev/$CI_SERVICE/latest
dependencies:
- build
only:
- <target_branch>
tags:
- <gitlab-runner>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
注意:上面例子中在“scirpts"阶段主要做了三件是事情
将预先定义在project环境变量中的docker registor信息写入构建容器的配置中
通过执行容器内置的kaniko/executor命令,并传入目标路径,构建文件地址,容器仓库地址,容器镜像名称,完成容器的构建和上传
通过curl命令完成构新构建容器的注册(注册在远端CMDB中)