关于代码自动化发布工具的思考

2019/08/30 CICD

2019-08-30-关于代码自动化发布工具的思考

实力践行了敏捷开发的套路,常规发版,频率逐渐增加,发布测试,整理繁琐的发布包,代码版本升级,反复的工作确实让人心烦,那么,做一套简单而又实用的代码发布工具吧。

1、前言,选型

​ 用什么工具能够让我们舒服的做到代码自动发布,我们有几个前提,1、简单 2、扩展性强 3、可视化。

​ 简单,shell脚本最简单,但是,扩展性不强,毕竟从一个JAVA开发者的眼中,shell脚本比JAVA程序难多了,这块捂个嘴,可视化就更满足不了了。

思来想去,突然想到了给部门做的一个CICD平台,也是纯用开源工具实现的,主导的是Jenkins,这么一想,貌似Jenkins可以满足我所有的需要,简单,Jenkins的支持groovy语法,groovy和JAVA属于兄弟语言,都是自家人,干起活来比较从容,扩展性,高级语言扩展性都是杠杠的,可视化,Jenkins的BlueOcean看起来还不错。

​ 那就这么定了,选型Jenkins,加上各种代码发布的工具,拼凑成一整套解决思路。

2、发布过程

对于发布过程,我大概采集和思考了一下,大概可以分为三个大步骤,

  1. 发布前测验
  2. 发布包整理
  3. 代码版本迭代

每一个大步骤中有很多的小步骤,整体下来应该如下

  1. 发布前测验
    1. 代码拉取测验
    2. 代码编译测验
    3. sonar检查报告
    4. 生成包测验
    5. 生成镜像测验
    6. 生成预览环境
    7. 对预览环境进行接口自动化测验(已经有啦)
    8. 预览环境删除(可以保留,扩展一些人工测验)
  2. 发布包整理步骤
    1. 当前分支的快照版本去掉
    2. 获取升级,或者全新安装的脚本
    3. 生成我们需要的各式各样的包
    4. 拉取相关的发布包文档资料
    5. 对所有的文档进行合并
    6. 生成制品供下载
    7. 制品拷贝到发布商店(可以使用minio搭建)
  3. 代码发布
    1. 当前分支打tag(release分支,tag默认是发布版本)
    2. release合并到master和develop分支
    3. develop开发主分支升级版本号
  4. 邮件通知,发布包发布结果

整个一套下来,需求点还是不少,好在之前CICD工具基础上,提供了很多解决方案。

3、搞定代码发布,只需要一套平台

​ 这个标题有点彪,很多人都说平台很大的,我要有这个平台我啥不能干,咱们这说的这个平台其实就是一个工具的集合,当然还有工具周边的环境支持,不搞玄机了,讲讲这个平台到底是个啥?

​ 这个平台其实是用来CICD的,CICD包括的部分大概有以下几个点

  1. 代码仓库(gitlab和git工具)
  2. 代码编译(jdk,maven,gradle,npm,yarn)
  3. 代码检查(sonar)
  4. 部署支持(docker ,kubernetes,helm)
  5. 存储支持(nfs,minio)
  6. 接口测试支持(jmeter)
  7. 结果展示服务(nginx)
  8. 步骤可视化支持(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的基础上,利用了它的高扩展性,集成了很多的工具和周边环境,搞出了这么一套小平台。

Search

    欢迎添加我的个人微信号

    个人微信哦

    Table of Contents