通过Jenins实现应用程序部署
接着上一篇处理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}"
)
}
}
}