自动构建/部署规范
自动构建/部署规范
本文主要是提供基于Jenkins的软件自动构建/部署相关的规则、约束、最佳实践,为软件研发流程的自动化提供指导和建议。
相关工具的更详细的使用和操作方法请参考:
1 工作流程
2 基础工具准备
软件研发涉及到项目管理、编码、测试、部署、文档等内容,借助相应的软件研发工具可以帮助研发团队更高效的完成工作。要完成自动构建/部署等流程,需要先准备以下工具:
- 代码仓库:Gitlab
- 内部仓库:Nexus
- 项目管理:禅道
- 镜像仓库:Harbor
- 代码扫描:SonarQube
- 知识库:Wiki
- 统一认证:LDAP
以上工具的安装、配置、使用请参考:软件研发基础工具。研发团队根据实际场景和需求可以选择使用具体哪些工具。
3 Git代码管理规范
3.1 分支管理
master 主分支
对应线上(正式环境)的代码,版本上线由测试人员确认,通知开发人员将对应上线版本合并至master分支并打tag,准备部署或更新至生产环境。
dev-版本号 开发分支
- 基于开发分支进行功能开发、Bug修复等,命名建议:dev-版本号。
- 开发版本部署到开发环境进行集成、测试。
test-版本号 开发测试分支
当开发在开发环境测试完成以后,合并到内部测试分支:test-版本号,将功能移交给内部测试人员进行测试,内部测试人员部署到内部测试环境进行测试。
release-test-版本号 生产测试分支
当内部测试完成后,合并到生产测试分支:release-test-版本号,将分支提交到测试组,由测试组负责部署到生产测试环境进行测试。
3.2 TAG管理
release-版本号 发布TAG
当测试组完成生产测试全部工作后,对生产测试分支进行tag操作,并根据产品发布计划,基于tag进行生产发布。
4 Jenkins运维规范
4.1 版本约束
软件的不同版本,或者插件的不同版本,在使用起来都有可能带来不可预知的影响,因此需要统一整理,固定下来,不允许轻易变更。
软件版本:
- JDK:1.8.0_231
- Tomcat:8
- Maven:3.8.5
- Jenkins:2.414.3
插件:一些插件非常重要,在某些版本中未必好用,比如用户权限控制插件,因此验证了当前好用的版本之后及时保存,以备扩容的时候可以用。
- LDAP:711.vb_d1a_491714dc
- Role-based Authorization Strategy:689.v731678c3e0eb_
- Git:5.2.0
- Git Parameter:0.9.19
注意
插件的安装或升级在运维团队需严格管控,管理员安装插件时,务必与组内同步,先做评估,经过验证,再行安装或升级。因为一些插件的升级可能带来未知问题与影响。
4.2 Jenkins管理规范
主机配置
- 配置:在越大越好的前提下,最好不低于8C16G,建议16C32G,注意优化Tomcat。
- 存储:建议100G系统盘+1T数据盘。盘的规格越高越好。最低SSD,其中数据盘可酌情加,数据盘一定要用所能提供的最高规格的盘,这是保障Jenkins使用体验的最核心。
- 其他:其他配置按照一般主机配置初始化即可。
扩容
Jenkins新增slave不要单独从头部署,需要和原slave保持一致:
- 如果使用容器作为slave,应从原slave主机打镜像,然后进行新增扩容,只需要调整agent脚本启动即可注册,agent通过docker维护。
- 如果直接使用服务器,那么需要确保所有配置和已有slave保持一致
JENKINS_HOME
需要注意,JENKINS_HOME应该统一配置,例如:
$ tail -n1 /etc/profile
export JENKINS_HOME=/data/jenkins_home
严禁非此标准的情况。
WORKSPACE
需要注意,我们的 WORKSPACE 统一在:
$ grep workspaceDir config.xml
<workspaceDir>${JENKINS_HOME}/workspace/${ITEM_FULL_NAME}</workspaceDir>
严禁非此标准的情况。
编译缓存目录
因为根目录磁盘并不大,所以要修改缓存目录到数据目录中,缓存目录统一存放在/data/.cache 目录之下,下一层目录以语言栈为标识。
如下列举了常见语言编译过程中缓存目录的设置方法。
maven
vim /usr/local/maven/conf/settings.xml
# 修改
<localRepository>/data/.cache/java/.m2/repository</localRepository>
node
$ npm config get cache
/root/.npm
# 设置 npm 全局包下载路径
$ npm config set prefix "/data/.cache/node/node_global"
# 设置 npm 缓存路径
$ npm config set cache "/data/.cache/node/node_cache"
pip
$ cat ~/.config/pip/pip.conf
[global]
cache-dir = /data/.cache/pip
go
export GOPATH=/data/go
export GOCACHE=/data/.cache/go-build
备份
- 定时备份到其他服务器
- 通过git统一管理和备份
- 使用thinBackup插件进行备份
4.3 Jenkins使用约定
构建过程中,我们会依赖一些公共组件,或者共享库,这些内容都应该使用统一的目录或者仓库,从而便于统一维护与管理。在说具体约定之前,首先说一个前置约定,亦即所有第三方需要统一固定的目录,例如都应该在 /data/.jenkins/other之下。
版本目录
要实现回滚,有赖于本地版本目录,此目录我们统一约定在:
$ ls /jenkins_sync/version/
剧本目录
剧本是项目在发布过程中使用的ansible剧本等内容。
关于命名,我们约定如下规范:
- 命名规范:ansible-{语言栈}
关于使用,我们约定如下规范:
- 我们约定一切剧本的更改都需要同步到git仓库中,可以创建一个自动推送job,提交之后自动将此项目同步到Jenkins主机上来。
- 正式启用之后,严禁直接修改master提交,必须经过其他分支测试验证过之后才能合并到master,因为这是核心,牵一发而动全身。
- 每次运行构建的时候,都会有一个stage单独拉取playbook,剧本的暂存目录为:${WORKSPACE}/ansible_tmp。
共享库
共享库是为我们将项目模板提取出来之后提供的一种高效率方案。
关于命名,我们约定如下规范:
- 命名规范:尽量做到见名知意。
关于使用,我们约定如下规范:
- 项目非特殊不得绕开共享库单独配置pipeline,部分项目采用流水线外置到仓库中,并通过目录进行分类,这种策略虽然将jenkinsfile版本控制了,但是并不利于以后模板化的方向,因此不推荐过度使用这种方案。
- 正式启用之后,严禁直接修改master提交,必须经过其他分支测试验证过之后才能合并到master,因为这是核心,牵一发而动全身。如有变更,最好做到二人check。
- 现在已经将共享库的内容接入到了ci测试,普通的语法错误都能够检测出来,从而无法合并到主分支。
- 共享库作为抽象出来的构建模板,不能够毫无节制地添加模板,而应该更多去思考如何在原有共享库中做到更优的兼容,过多地模板对维护来说,将会是灾难。
- 共享库模板中的内容固然可以进行再次声明函数进行抽离,但是我并不建议这么做,过多地调用性抽离,会让一个模板的流程逻辑复杂化,反而会给后期的应用以及维护带来更多的认知成本,这是不划算的。
风格约定
我们约定统一的pipeline风格为声明式
,声明式是固定语法,接近原生shell的使用方式,对运维也更加友好。事实上这不仅仅是风格统一的问题,还可以做到研究成果共享。
项目命名约定
项目命名规范,决定了权限配置的便利以及可用。项目命名原则上与gitlab仓库中项目命名保持一致,然后在前边添加环境作为区隔,如果项目名为admin,那么不同环境的命名应该为:
- dev-admin
- test-admin
- prod-admin
如果一个项目仓库将会部署多个子项目,那么命名风格应该保持队形:test-admin-(a/b/c)
,以此类推。
授权方面
建议人员通过对接openLDAP、Gitlab等进行同步。
授权统一使用openLDAP用户分组绑定Jenkins权限角色的方式进行,不通过个人绑定角色。发布权限类似如下分类:
- 开发环境发布权限。
- 测试环境发布权限。
- 灰度环境发布权限。
- 正式环境发布权限。
一个业务通常分不同角色对应各个环境的应用,同理也要在openLDAP创建对应分组,将分组绑定到角色上,而后的维护则在运维平台上只需要将人员加入到分组即可。
4.4 Jenkinsfile编写约定
变量
准确简洁的变量名是可读性及易维护性的重要保障,针对脚本的变量名应当至少遵循以下规范:
- 自定义环境变量命名约定:统一采用大写单词与下划线拼接方式命名,自定义变量必须要有备注。
- 变量名必须有实际含义, 避免英文下划线以外的特殊字符。
- 避免变量名过短(数据或对象内容复杂而变量名无法推断其含义)与过长(变量名接近甚至超过半行造成视觉阅读困难)。
- 变量名尽量避开 groovy 语言自身关键字,避开环境变量。
- 无特殊需求,避免修改流水线默认生成的环境变量。
- 不允许出现后置流程引用未落位的变量,否则容易造成不熟悉者的维护障碍。
- 在固定的拼接处理中, 应尽量减少定义语句使用的嵌套层数,例如:
// 套娃式定义
def pathA = "path1"
def paraZ = "${pathA}/path2"
def paraB = "${pathZ}/path3"
def paraY = "${pathB}/path4"
def paraX = "${pathY}/path5"
// 展开嵌套, 具有更好的可读性
def paraX = "path1/path2/path3/path4/path5"
stage与标签命名
stage定义了每个节点的任务内容,应该使用简洁清晰的文字对该阶段任务进行注释。名字不能超过7个字,失败原因也要做到简单清晰。
镜像风格定义
如果使用容器,那么容器镜像的命名规则如下:
server_name commit date build_id
hub.hos.com/multienv/hos-back-admin:16c525_20201013114449_10
在实际使用中变量定义为:
BASE_IMAGE_NAME = "hub.hos.com/hos/${SERVICE_NAME}"
env.IMAGE_NAME = sh(script: "echo ${BASE_IMAGE_NAME}:" + "${COMMIT_ID}_" + "`date '+%Y%m%d%H%M%S'`" + "_${BUILD_ID}", returnStdout: true).trim() //构建版本号
建议尽可能的利用变量来组建项目镜像的tag,这在不影响构建稳定的情况尽可能地提供出有利于我们快速定位的信息,这将在后续问题排查中受益。
4.5 Jenkins维护规范
- 插件的安装或升级需严格管控,管理员安装插件时,务必与组内同步,先做评估,经过测验,再行安装或升级。因为一些插件的升级可能带来未知问题与影响。
- 流水线尽量使用一种语法,比如声明式,这样以后大家各自研究的成果可以直接共享,便于一起迭代前进。
- 尽可能将单个流水线的主逻辑放在更少的地方,这样对于后期的维护以及变更绝对是更加高效且省力的,不要一个流水线七零八落调用了五六个地方,非常不利于快速预览与定位。当然,一些非重要的公共逻辑,可以放在约定好的固定地方统一调用,比如通知脚本,回滚用的库,共享库等。
- 构建过程中所用工具,一定要版本统一,不给自己留坑,比如mvn,go,node等。
- 根据标准化的流水线,需要输出对研发的接入标准,让所有的研发项目/框架都来适配我们运维的标准,严禁接入非标的项目。
- 运维自己的流水线迭代更新也需要走正规的开发流程,调试环境和正式使用环境分开,代码合并的时候也需要互相review。
- master节点不允许运行任务,只作为调度节点。
- 不允许变更虚拟机的环境,不允许随意在虚拟机安装软件,如果构建过程有环境依赖,则必须将环境依赖注入到容器中,通过docker进行构建。