2019-08-30-关于代码自动化发布工具的思考
实力践行了敏捷开发的套路,常规发版,频率逐渐增加,发布测试,整理繁琐的发布包,代码版本升级,反复的工作确实让人心烦,那么,做一套简单而又实用的代码发布工具吧。
1、前言,选型
用什么工具能够让我们舒服的做到代码自动发布,我们有几个前提,1、简单 2、扩展性强 3、可视化。
简单,shell脚本最简单,但是,扩展性不强,毕竟从一个JAVA开发者的眼中,shell脚本比JAVA程序难多了,这块捂个嘴,可视化就更满足不了了。
思来想去,突然想到了给部门做的一个CICD平台,也是纯用开源工具实现的,主导的是Jenkins,这么一想,貌似Jenkins可以满足我所有的需要,简单,Jenkins的支持groovy语法,groovy和JAVA属于兄弟语言,都是自家人,干起活来比较从容,扩展性,高级语言扩展性都是杠杠的,可视化,Jenkins的BlueOcean看起来还不错。
那就这么定了,选型Jenkins,加上各种代码发布的工具,拼凑成一整套解决思路。
2、发布过程
对于发布过程,我大概采集和思考了一下,大概可以分为三个大步骤,
- 发布前测验
- 发布包整理
- 代码版本迭代
每一个大步骤中有很多的小步骤,整体下来应该如下
- 发布前测验
- 代码拉取测验
- 代码编译测验
- sonar检查报告
- 生成包测验
- 生成镜像测验
- 生成预览环境
- 对预览环境进行接口自动化测验(已经有啦)
- 预览环境删除(可以保留,扩展一些人工测验)
- 发布包整理步骤
- 当前分支的快照版本去掉
- 获取升级,或者全新安装的脚本
- 生成我们需要的各式各样的包
- 拉取相关的发布包文档资料
- 对所有的文档进行合并
- 生成制品供下载
- 制品拷贝到发布商店(可以使用minio搭建)
- 代码发布
- 当前分支打tag(release分支,tag默认是发布版本)
- release合并到master和develop分支
- develop开发主分支升级版本号
- 邮件通知,发布包发布结果
整个一套下来,需求点还是不少,好在之前CICD工具基础上,提供了很多解决方案。
3、搞定代码发布,只需要一套平台
这个标题有点彪,很多人都说平台很大的,我要有这个平台我啥不能干,咱们这说的这个平台其实就是一个工具的集合,当然还有工具周边的环境支持,不搞玄机了,讲讲这个平台到底是个啥?
这个平台其实是用来CICD的,CICD包括的部分大概有以下几个点
- 代码仓库(gitlab和git工具)
- 代码编译(jdk,maven,gradle,npm,yarn)
- 代码检查(sonar)
- 部署支持(docker ,kubernetes,helm)
- 存储支持(nfs,minio)
- 接口测试支持(jmeter)
- 结果展示服务(nginx)
- 步骤可视化支持(Jenkins集群)
看看对于上诉需求是否满足,其实不用看了,我已经验证过了,需求完全满足,这边再捂个嘴。
看看Jenkinsfile完成这个工具的源码,没办法,就是这么直接和开源。
pipeline {
agent any
parameters {
// 项目名称
string(name: 'project_name',defaultValue: '', description: '填写要发布的项目名称');
// 是否是测试发布
booleanParam(name: 'is_deploy',defaultValue: 'false', description: '是否发布,默认值是false');
// 项目信息
string(name: 'git_address',defaultValue: '', description: '选择要发布的项目git地址');
string(name: 'release_branch', defaultValue: 'release/*', description: '选择要发布的分支');
string(name: 'credential_id', defaultValue: 'haoxiaofei', description: '选择要使用的凭证id');
// 检查信息
string(name: 'compile_pom_path', defaultValue: 'server', description: '填写要执行编译的pom路径');
string(name: 'front_path', defaultValue: 'web', description: '填写前端打包路径');
string(name: 'front_code_path', defaultValue: 'server/src/main/resources/static', description: '前端代码放置路径');
string(name: 'sonar_pom_path', defaultValue: 'server', description: '填写要执行sonar检查的pom路径');
string(name: 'package_pom_path', defaultValue: 'server', description: '填写要生成包的pom路径');
string(name: 'docker_path', defaultValue: 'server/docker', description: '填写要生成镜像的docker路径');
string(name: 'helm_path', defaultValue: 'charts', description: '填写要生成helm应用的charts路径');
booleanParam(name: 'is_delete_envirment', defaultValue: 'true', description: '是否要删除预览环境,默认值是true');
//关于持久化 可以做一些约定 所有持久化目录 全部按照charts_name +1 +2 来进行 jenkins中判断有多少个 就去nfs上创建多少个
string(name: 'pv_nums', defaultValue: '1', description: '持久化目录个数');
// 关于代码发布
booleanParam(name: 'is_merge_to_develop', defaultValue: 'false', description: '是否合并到develop分支');
string(name: 'next_version', defaultValue: '', description: 'develop分支下一个版本号');
// 整理发布包的事情
string(name: 'docs_server', defaultValue: '', description: '发布包所在的git远端库');
string(name: 'jar_path', defaultValue: 'deploy_package/tap-portal/01_安装包/01_windows/tap_portal/ deploy_package/tap-portal/01_安装包/02_linux/tap_portal/', description: '在tap-docs库中部署文档路径');
}
environment {
def project_imagename = "registry.thunisoft.com:5000/tap/";
def branch_hash = "${params.release_branch}".hashCode();
def pv_path = "logserver/tap/${params.project_name}/${params.release_branch}";
def nfs_path = "/home/nfs/tap/${params.project_name}/${params.release_branch}";
def image_name = "${project_imagename}${params.project_name}/${release_branch}:latest";
def server_ip = "";
def server_address = "";
}
stages {
stage('1、分支测验') {
steps{
echo "1.1、拉取程序"
git branch: "${params.release_branch}", credentialsId: "${params.credential_id}", url: "${params.git_address}"
echo "1.2、代码编译"
dir("${params.compile_pom_path}"){
// sh 'mvn clean compile'
}
echo "1.3、sonar检查"
dir("${params.sonar_pom_path}"){
// sh 'mvn clean compile sonar:sonar'
}
echo "1.4、是否加压库检查"
echo "1.5、生成包检查"
dir("${params.front_path}/${params.project_name}"){
sh "yarn && yarn build"
}
sh "cp -r ${params.front_path}/${params.project_name}/dist/ ${params.front_code_path}/"
dir("${params.package_pom_path}"){
sh 'mvn package -Dmaven.test.skip=true'
}
// 拷贝程序包到dockerfile同级路径下 打出镜像
sh "cp -r ${package_pom_path}/target/*.jar ${docker_path}/"
echo "1.6、测验生成镜像"
sh 'docker build -t ${image_name} ${docker_path} && docker push ${image_name} && docker rmi ${image_name}'
// 因为现在所有的预览环境是通过helm的方式生成的 ,所以在生成预览环境之间,先打出helm包。
echo "1.7、测验生成helm应用"
dir("${params.helm_path}/${params.project_name}"){
sh 'helm install --dry-run --debug . && helm package .'
}
//生成预览环境 这边要做的事情比较多
// 1.持久化目录还是使用的pv pvc是否要改成storageclass,如果不改就需要对多个pv pvc进行赋值 并且动态的添加路径
// 2.对分支取hash完成部分的chartvalue赋值问题
// 3.部署完成之后判断应用是否部署成功 需要增加部署判断
echo "1.8、生成预览环境"
script{
if(sh(script: "mc find ${pv_path}", returnStatus: true) != 0){
sh " touch package && mc cp package ${pv_path}/package"
}
}
dir("${params.helm_path}/${params.project_name}"){
sh "helm upgrade ${params.project_name}.${params.release_branch} ${params.project_name}-*.tgz --set image=${image_name} --set chartName=ptl-${branch_hash} --set pv.pvpath=${nfs_path} --force -i --recreate-pods"
}
// 等待部署完成
sh "kubectl rollout status -w deployment/ptl-${branch_hash}"
//输出环境地址
script{
server_address = sh(script: "kubectl get -o template service/ptl-${branch_hash} --template=''",returnStdout: true);
}
echo "请访问服务地址--> http://${server_address}:8080"
echo "1.9、接口自动化测试,对接项目的jmeter测试脚本"
echo "1.10、删除预览环境"
script{
if("${params.is_delete_envirment}" == true){
sh "helm del --purge ${params.project_name}.${params.release_branch}"
}
}
}
}
stage('2、发布包整理') {
steps{
echo "2.1、更改版本号为正式版"
dir("${params.compile_pom_path}"){
sh "mvn versions:set -DremoveSnapshot=true"
}
echo "2.2、smd生成全量和升级脚本"
// 集成smd工具 完成脚本输出
echo "2.3、生成需要的程序安装包"
dir("${params.compile_pom_path}"){
sh "mvn clean package -Dmaven.test.skip=true"
}
echo "2.4、获取部署文档"
sh "mkdir -p docs && mkdir -p deploy_package"
dir("docs"){
//从git上获取发布包结构
git branch: "develop", credentialsId: "${params.credential_id}", url: "${params.docs_server}"
}
sh "cp -r docs/docs/tap-portal/ deploy_package"
sh "echo ${params.jar_path} | xargs -n 1 cp -v ${params.package_pom_path}/target/${params.project_name}-*.jar"
// sh "cp -r ${params.package_pom_path}/target/${params.project_name}-*.jar deploy_package/${params.jar_path}/"
echo "2.5、整理发布包并上传到minio"
// sh "zip -r deploy_package.zip deploy_package/"
// sh "zip -r deploy_package.zip server/"
dir("deploy_package"){
sh "tar -zcf ${params.project_name}-deployPackage.tar.gz ${params.project_name}/"
}
echo "2.6、生成制品供下载"
archiveArtifacts "deploy_package/${params.project_name}-deployPackage.tar.gz"
}
}
stage('3、代码发布') {
when {
expression {
"${params.is_deploy}" == true
}
}
steps {
echo "3.1、打标签"
// sh "";
echo "3.2、合并到主分支"
// sh "";
echo "3.3、develop自动更新到下一个开发版本"
// sh "";
echo "3.4、删除release分支"
// sh "";
}
}
}
post{
always{
cleanWs();
echo "发送邮件"
}
}
}
4、总结一下
上述很多步骤被我删除了,也就是说,上诉Jenkinsfile其实可以运行,但是大概只能满足需求的百分之七十的功能。
回归话题,Jenkins真的是一个很好用的工具,不仅仅是传统给它的定义,打包,发布,它其实可以做更多的事情,比如像今天的这个需求点,实现的很好,还可以定制,只需要修改相关模板的执行步骤即可。
咱们说的这个Jenkins不仅仅是单纯的一个Jenkins,而是在Jenkins的基础上,利用了它的高扩展性,集成了很多的工具和周边环境,搞出了这么一套小平台。