接着上一篇处理jenkins的问题,Jenkins调整好以后(主要包括docker和maven),准备把之前的一个.net项目通过jenkins发布。

项目里面分5个子项目,server/receiver/push/attachment/web,之前用shell脚本做过一次编译封装,目前欠缺的主要是代码的下载、触发构建和部署。

Jenkins构建简介

Jenkins的执行过程叫pipeline,一个pipeline基本组成单元是stage,这个stage是一个逻辑的概念,当然可以把所有操作放在一个stage里,但是配置好不同的stage,比如下载,编译,部署等,在Jenkins构建界面上,可以看到具体项目的执行步骤,更友好一些。

除了页面配置,还需要Jenkinsfile控制pipeline的过程。这个文件使用Groovy语言写的,分为声明式和脚本式两种语法。我这里用的是声明式的,其实脚本式的可能功能更强大。

脚本的基本结构:

pipeline {
      agent any
      environment {
            ......
      }

      stages {
            stage('1.') {
                  steps {
                        //执行动作
                        ......
                  }
            }
            stage('2.') {
                  steps {
                        //执行动作
                        ......
                  }
            }
            ......
      }
}

pipeline是固定结构,agent代表运行这个构建的agent的要求,这里可以指定标签,也可以指定跑一个docker镜像等等。

之前尝试跑一个maven镜像,但是镜像里没有docker命令,没有办法在里面打包docker镜像(也可以二次打包maven镜像,但是实在有点麻烦),最终还是直接用了宿主的maven和docker。

steps里执行的动作可以参考jenkins的官方教程,但是实在不怎么友好,我的大多数问题还是通过搜索引擎得到的。。。

设计

  • 拉取代码:通过界面配置git地址和分支,我这里只用了master分支,实际是可以指定不同分支的
  • 构建:替换真正的编译脚本build.sh中的环境变量,在编译脚本中会根据Dockerfile构建并推送镜像
  • 部署:根据不同的环境,推送到相应的环境,然后通过docker-compoes更新部署镜像文件
  • 发送通知:成功通知或失败通知

配置

界面配置

配置一个Jenkins项目,配置pipeline为从git拉取代码后,执行项目的Jenkinsfile文件完成后续的构建和部署。

这里我配置了3个参数

  • BUILD_TYPE:决定构建哪个子项目,如果传0那么就构建所有子项目
  • BUILD_VER:这里我是用来控制发布到生产还是测试环境的
  • DEFAULT_RECIPIENTS:这其实是一个系统变量,但是可以被程序覆写,代表邮件接收人

脚本配置

整体脚本放在全文最后。

Jenkinsfile的environment

可以通过以下方法根据环境变量,设置另一个环境变量的值。

DEPLOY_CONFIG = """${sh(
                returnStdout: true,
                script: 'if [ "${BUILD_ENV}" = "正式环境" ] ; then echo "ALIYUN_FORMAL"; else echo "ALIYUN_TEST"; fi'
                ).trim()}"""

这里碰到过两个问题

  • if里不支持正则匹配运算符~=,因为我的Jenkins跑在alpine里,默认的ash不支持。
  • 参数通过env.xxx或者params.xxx获取不到,这点比较奇怪,但是通过直接使用变量名却可以访问。
执行脚本命令

可以通过sh执行shell命令,比如我用到了一些sed替换环境变量。这里调用的实际就是jenkins安装环境里的shell处理的,我的shell用的是alpine的ash,所以比bash少一些功能,这也是中间碰到过的问题。

//这是编译中一个替换编译脚本环境变量的命令
sh "sed -i 's=JENKINS_VERSION_NO=${DOCKER_REGISTRY_IMAGE_TAG}=g' ${BUILD_SCRIPT}"
部署

这里用的是Publish Over SSH插件,需要先在Jenkins的配置文件里,创建一个SSH配置,其中包括了主机信息、用户、密码等。

sshPublisher(
                   continueOnError: false, failOnError: true,
                   publishers: [
                    sshPublisherDesc(
                     configName: "${DEPLOY_CONFIG}",
                     verbose: true,
                     transfers: [
                      sshTransfer(
                       sourceFiles: "${DEPLOY_SCRIPT}",
                       remoteDirectory: "${DEPLOY_REMOTE_PATH}",
                       execCommand: "cd ${DEPLOY_REMOTE_PATH} && /bin/bash ${DEPLOY_SCRIPT} && rm ${DEPLOY_SCRIPT}"
                      )
                     ])
                   ])

${DEPLOY_SCRIPT}是我的部署脚本,里面会触发docker重新下载镜像。
exeCommand参数是脚本上传完成以后执行的命令,我这里切换到脚本目录,运行了脚本。

发送通知邮件

这个网上教程还是挺好找的,用Email Extension Plugin插件,同样需要在Jenkins web界面配置邮件基本信息。

我用的qq邮箱,需要先去开启SMTP以及获得授权码。

然后通过模板发送邮件就可以了。

后记

主要是在语法上碰到了一些问题,Jenkins相关的文档不是很好看,需要结合很多网上例子,才能达到自己的目的。

完整脚本

pipeline {
    agent any
	
    environment {
        //modify for need
        DEBUG_JENKINS = 0
        DEPLOY_CONFIG = """${sh(
                returnStdout: true,
                script: 'if [ "${BUILD_ENV}" = "正式环境" ] ; then echo "ALIYUN_PRD"; else echo "ALIYUN_TEST"; fi'
                ).trim()}"""
        DEPLOY_REMOTE_PATH = """${sh(
                returnStdout: true,
                script: 'if [ "${BUILD_ENV}" = "正式环境" ] ; then echo "swarm-deploy/formal"; else echo "swarm-deploy/test"; fi'
                ).trim()}"""

        DOCKER_REGISTRY_HOST = 'registry.cn-beijing.aliyuncs.com'
        DOCKER_REGISTRY_HOST_VPC = 'registry-vpc.cn-beijing.aliyuncs.com'
        DOCKER_REGISTRY_REPO = "${DOCKER_REGISTRY_HOST}/trt-iot"
        DOCKER_REGISTRY_REPO_VPC = "${DOCKER_REGISTRY_HOST_VPC}/trt-iot"
        DOCKER_REGISTRY = credentials('DOCKER_REGISTRY_ALIYUN_TEST')

        BUILD_SCRIPT = 'build.sh'

        DEPLOY_FLAG = '_BUILD_FLAG'
        DEPLOY_IMAGE_PREFIX = '_IMAGE_PREFIX'
        DEPLOY_IMAGE_NAME = '_IMAGE_NAME'

        DEPLOY_SCRIPT = 'deploy.sh'

        VERSION_NO  = """${sh(
                returnStdout: true,
                script: 'version=`echo "${GIT_BRANCH}"|cut -d / -f 2`;echo "${version}_`date +%y%m%d`"'
                ).trim()}"""
        DOCKER_REGISTRY_IMAGE_TAG = "${VERSION_NO}_${BUILD_ID}"
    }

    stages {
        stage('Prepare') {
            steps {
                echo "Copy script files..."
                sh "cp Tools/Docker/Build/Dockerfile.* ./"
                sh "cp Jenkins/*.sh ./"
                echo "Done!"
            }
        }
        stage('Build') {
            steps {
                echo "Building TYPE ${params.BUILD_TYPE} from BRANCH ${GIT_BRANCH}.."
                sh "printenv"
                //replace build script variables
                sh "sed -i 's=JENKINS_VERSION_NO=${DOCKER_REGISTRY_IMAGE_TAG}=g' ${BUILD_SCRIPT}"
                sh "sed -i 's=JENKINS_IMAGE_HUB_PREFIX=${DOCKER_REGISTRY_REPO}=g' ${BUILD_SCRIPT}"
                sh "if [ ${DEBUG_JENKINS} = 1 ] ; then cat ${BUILD_SCRIPT} ; fi"
                //login in to custom docker registry because docker push will be triggered inside build script
                sh "docker login -u ${DOCKER_REGISTRY_USR} -p ${DOCKER_REGISTRY_PSW} https://${DOCKER_REGISTRY_HOST}"
                //execute build script
                sh "/bin/bash ${BUILD_SCRIPT} ${params.BUILD_TYPE}"
            }
        }
        stage('Pre-Deploy') {
            stages{
                stage('server') {
                    when {
                        anyOf {
                            equals expected: "0", actual: params.BUILD_TYPE
                            equals expected: "1", actual: params.BUILD_TYPE
                        }
                    }
                    steps {
                        sh "sed -i 's=SERVER${DEPLOY_FLAG}=1=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=SERVER${DEPLOY_IMAGE_PREFIX}=${DOCKER_REGISTRY_REPO_VPC}/server=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=SERVER${DEPLOY_IMAGE_NAME}=${DOCKER_REGISTRY_REPO_VPC}/server:${DOCKER_REGISTRY_IMAGE_TAG}=g' ${DEPLOY_SCRIPT}"
                    }
                }
                stage('receiver') {
                    when {
                        anyOf {
                            equals expected: "0", actual: params.BUILD_TYPE
                            equals expected: "2", actual: params.BUILD_TYPE
                        }
                    }
                    steps {
                        sh "sed -i 's=RECEIVER${DEPLOY_FLAG}=1=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=RECEIVER${DEPLOY_IMAGE_PREFIX}=${DOCKER_REGISTRY_REPO_VPC}/receiver=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=RECEIVER${DEPLOY_IMAGE_NAME}=${DOCKER_REGISTRY_REPO_VPC}/receiver:${DOCKER_REGISTRY_IMAGE_TAG}=g' ${DEPLOY_SCRIPT}"
                    }                    
                }
                stage('push') {
                    when {
                        anyOf {
                            equals expected: "0", actual: params.BUILD_TYPE
                            equals expected: "3", actual: params.BUILD_TYPE
                        }
                    }
                    steps {
                        sh "sed -i 's=PUSH${DEPLOY_FLAG}=1=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=PUSH${DEPLOY_IMAGE_PREFIX}=${DOCKER_REGISTRY_REPO_VPC}/push=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=PUSH${DEPLOY_IMAGE_NAME}=${DOCKER_REGISTRY_REPO_VPC}/push:${DOCKER_REGISTRY_IMAGE_TAG}=g' ${DEPLOY_SCRIPT}"
                    }                    
                }
                stage('attachment') {
                    when {
                        anyOf {
                            equals expected: "0", actual: params.BUILD_TYPE
                            equals expected: "4", actual: params.BUILD_TYPE
                        }
                    }
                    steps {
                        sh "sed -i 's=ATTACHMENT${DEPLOY_FLAG}=1=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=ATTACHMENT${DEPLOY_IMAGE_PREFIX}=${DOCKER_REGISTRY_REPO_VPC}/attachment=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=ATTACHMENT${DEPLOY_IMAGE_NAME}=${DOCKER_REGISTRY_REPO_VPC}/attachment:${DOCKER_REGISTRY_IMAGE_TAG}=g' ${DEPLOY_SCRIPT}"
                    }                    
                }
                stage('web') {
                    when {
                        anyOf {
                            equals expected: "0", actual: params.BUILD_TYPE
                            equals expected: "5", actual: params.BUILD_TYPE
                        }
                    }
                    steps {
                        sh "sed -i 's=WEB${DEPLOY_FLAG}=1=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=WEB${DEPLOY_IMAGE_PREFIX}=${DOCKER_REGISTRY_REPO_VPC}/web=g' ${DEPLOY_SCRIPT}"
                        sh "sed -i 's=WEB${DEPLOY_IMAGE_NAME}=${DOCKER_REGISTRY_REPO_VPC}/web:${DOCKER_REGISTRY_IMAGE_TAG}=g' ${DEPLOY_SCRIPT}"
                    }                    
                }
                stage('test') {
                    when {
                        equals expected: "1", actual: DEBUG_JENKINS
                    }
                    steps {
                        sh "cat ${DEPLOY_SCRIPT}"
                    }
                }
            }
        }
        stage('Deploy') {
            steps {
                echo 'DEPLOYING (${DEPLOY_CONFIG})......'
                  sshPublisher(
                   continueOnError: false, failOnError: true,
                   publishers: [
                    sshPublisherDesc(
                     configName: "${DEPLOY_CONFIG}",
                     verbose: true,
                     transfers: [
                      sshTransfer(
                       sourceFiles: "${DEPLOY_SCRIPT}",
                       remoteDirectory: "${DEPLOY_REMOTE_PATH}",
                       execCommand: "cd ${DEPLOY_REMOTE_PATH} && /bin/bash ${DEPLOY_SCRIPT} && rm ${DEPLOY_SCRIPT}"
                      )
                     ])
                   ])
                echo 'DONE......'
            }
       }
    }
    post {
        success {
            emailext (
                subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建正常",
                body: """
                详情:<br/><hr/>
                  <span style='color:green'>SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'</span><br/><hr/>
                  项目名称:$JOB_NAME<br/><hr/>
                  构建编号:$BUILD_NUMBER<br/><hr/>
                  构建环境:$BUILD_ENV - $BUILD_TYPE<br/><hr/>
                  构建日志地址:<a href="${BUILD_URL}console">${BUILD_URL}console</a><br/><hr/>
                  构建地址:<a href="$BUILD_URL">$BUILD_URL</a><br/><hr/>
                  <b>本邮件是程序自动下发的,请勿回复!</b><br/><hr/>
                """,
                to: "${DEFAULT_RECIPIENTS}"
             )
        }
        failure {
            emailext (
                subject: "'${env.JOB_NAME} [${env.BUILD_NUMBER}]' 构建失败",
                body: """
                详情:<br/><hr/>
                  <span style='color:red'>FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'</span><br/><hr/>
                  项目名称:$JOB_NAME<br/><hr/>
                  构建编号:$BUILD_NUMBER<br/><hr/>
                  构建环境:$BUILD_ENV - $BUILD_TYPE<br/><hr/>
                  构建日志地址:<a href="${BUILD_URL}console">${BUILD_URL}console</a><br/><hr/>
                  构建地址:<a href="$BUILD_URL">$BUILD_URL</a><br/><hr/>
                  <b>本邮件是程序自动下发的,请勿回复!</b><br/><hr/>
                """,
                to: "${DEFAULT_RECIPIENTS}"
             )
        }
    }
}