Jenkins 能做的事情太多了,如果要一一讲解肯定讲不完,因此此系列文章只以多分支流水线任务(Pipeline)为例。

我们要实现的是,能够通过 Jenkins 的 pipeline,拉取代码,编译,发布 docker 镜像,部署 docker 服务器。

我们使用 git 作为代码版本管理工具,使用 nexus3 私服作为jar包、docker 镜像的仓库。Pipeline 中,主要是使用 Jenkinsfile 来实现主要的逻辑。

代码将分为 master、dev、test、prod 四个分支,其中 master 分支主要开发使用,dev 为部署到开发环境服务器的代码分支,test 为部署到测试环境服务器的代码分支,prod 为部署到正式环境的代码分支。

Jenkinsfile 是 Jenkins 2.x 核心特性 Pipeline 的脚本,由Groovy语言实现。Jenkinsfile一般是放在项目根目录,随项目一起受源代码管理软件控制。

代码中存在 Jenkinsfile-dev、Jenkinsfile-test、Jenkinsfile-pord 三个文件,依次在开发、测试、正式三个环境中会使用到。

关于 Jenkinsfile 的具体语法规则,不在本文的讲解范围之内,需要读者自行去查看资料,官方文档可参考:https://jenkins.io/zh/doc/book/pipeline/jenkinsfile/

在开始之前,我们尚有一些准备工作要做。

1. 创建一个全局的 git 访问凭证。

Jenkins实践(二)------ Jenkinsfile_Jenkins

然后在打开的页面中填写访问 git 的账号密码,并且填写一个 ID,记住这个ID,后面在 Jenkisnfile-prod 中要用。本文中定义ID为 config-user。

Jenkins实践(二)------ Jenkinsfile_docker_02


首先讲解下各个环境的Jenkinsfile脚本的主要思路:

Jenkins 脚本中,统一设置 dev 环境拉取 master 分支最新的代码,test 环境拉取 dev 分支最新的代码,prod 环境依旧拉取 prod 分支最新的代码。

Jenkinsfile-dev 脚本逻辑:因为 dev 环境获取的是master分支最新的代码, 因此会先执行 git check out dev , 然后 git merge master,在本地切出 dev 分支,并且将 master 代码合并入 dev 分支,然后执行编译操作,编译通过之后,执行 git push origin dev 将合并后的本地 dev 分支代码推送到远端 dev 分支,最后进行 docker 打包发布的操作。

pipeline {
agent {
docker {
image 'maven:3.6.0-jdk-8'
args '-v /root/.m2:/root/.m2'
}
}

environment {
DOCKER_REPOSITORY_HOST = 'wx.ankoninc.com.cn'
ACTIVED_PROFILE = 'dev'
NETWORK = 'servicenet'
GIT_REPOSITORY = 'wx.ankoninc.com.cn/gitbucket/git/cloud/cloud-app-service.git'
}
stages {

stage('merge code') {
steps {
sh '''git checkout dev
git merge master'''
}
}

stage('Maven install') {
steps {
sh 'mvn install'
}
}

stage('Sonar') {
steps {
sh 'mvn sonar:sonar'
}
}

stage('push code to branch dev') {
steps {
withCredentials([usernamePassword(credentialsId: 'config-user', usernameVariable: 'username', passwordVariable: 'password')]){
sh "git push https://$username:$password@${GIT_REPOSITORY}"
}
}
}

stage('Deploy cloud-app-service-server') {
steps {
script {
env.WORKSPACE_PATH = "./cloud-app-service-server"
def pom = readMavenPom file: "${WORKSPACE_PATH}/pom.xml"
env.PROJECT_NAME = pom.artifactId
env.JAR_FILE_PATH = "./target/${PROJECT_NAME}-${pom.version}.jar"
env.PORT = pom.properties.ankonAppPort
env.IMAGE = "${DOCKER_REPOSITORY_HOST}/${PROJECT_NAME}:${pom.version}-${ACTIVED_PROFILE}-${BUILD_TIMESTAMP}"
}
sh "docker build --build-arg JAR_FILE_PATH=${JAR_FILE_PATH} --build-arg PORT=${PORT} -t ${IMAGE} ${WORKSPACE_PATH}"
sh "docker service rm ${PROJECT_NAME} || true"
sh "docker push ${IMAGE}"
sh "docker service create --name ${PROJECT_NAME} --limit-memory 500M --with-registry-auth --replicas 1 --publish published=${PORT},target=${PORT} --network ${NETWORK} ${IMAGE} --spring.profiles.active=${ACTIVED_PROFILE}"
}
}
}
}

Jenkinsfile-test 脚本逻辑:因为 test 环境获取的是 dev 分支最新的代码, 因此会先执行 git check out test , 然后 git merge dev,在本地切出 test 分支,并且将 dev 代码合并入 test 分支,然后执行编译操作,编译通过之后,执行 git push origin test 将合并后的本地 test 分支代码推送到远端 test 分支,最后进行 docker 打包发布的操作。

pipeline {
agent {
docker {
image 'maven:3.6.0-jdk-8'
args '-v /root/.m2:/root/.m2'
}
}

environment {
DOCKER_REPOSITORY_HOST = 'wx.ankoninc.com.cn'
ACTIVED_PROFILE = 'test'
NETWORK = 'servicenet'
GIT_REPOSITORY = 'wx.ankoninc.com.cn/gitbucket/git/cloud/cloud-app-service.git'
}
stages {
stage('merge code') {
steps {
sh '''git checkout test
git merge dev'''
}
}

stage('Maven install') {
steps {
sh 'mvn install'

}
}

stage('Sonar') {
steps {
sh 'mvn sonar:sonar'

}
}

stage('push code to branch test') {
steps {
withCredentials([usernamePassword(credentialsId: 'config-user', usernameVariable: 'username', passwordVariable: 'password')]){
sh "git push https://$username:$password@${GIT_REPOSITORY}"
}
}
}

stage('Deploy cloud-app-service-server') {
steps {
script {
env.WORKSPACE_PATH = "./cloud-app-service-server"
def pom = readMavenPom file: "${WORKSPACE_PATH}/pom.xml"
env.PROJECT_NAME = pom.artifactId
env.JAR_FILE_PATH = "./target/${PROJECT_NAME}-${pom.version}.jar"
env.PORT = pom.properties.ankonAppPort
env.IMAGE = "${DOCKER_REPOSITORY_HOST}/${PROJECT_NAME}:${pom.version}-${ACTIVED_PROFILE}-${BUILD_TIMESTAMP}"
}
sh "docker build --build-arg JAR_FILE_PATH=${JAR_FILE_PATH} --build-arg PORT=${PORT} -t ${IMAGE} ${WORKSPACE_PATH}"
sh "docker service rm ${PROJECT_NAME} || true"
sh "docker push ${IMAGE}"
sh "docker service create --name ${PROJECT_NAME} --limit-memory 500M --with-registry-auth --replicas 1 --publish published=${PORT},target=${PORT} --network ${NETWORK} ${IMAGE} --spring.profiles.active=${ACTIVED_PROFILE}"
}
}
}
}

Jenkinsfile-prod 脚本逻辑:prod 环境获取的依旧是 prod 分支最新的代码。Jenkinsfile-prod 脚本获取 prod 分支最新代码之后,读取 pom 文件中的项目配置,主要获取项目名、版本号,然后从 nexus 的 docker 仓库中获取当前项目、当前版本对应的 test 环境打包的最后那个镜像,将其重新切出一个 XXXX-release 的镜像之后,推送这个 release 镜像到 nexus 仓库中。最后使用这个 release 的镜像进行 docker 打包发布的操作。

#!groovy

import groovy.json.JsonSlurperClassic;

pipeline {
agent any

environment {
DOCKER_REPOSITORY_HOST = 'wx.ankoninc.com.cn'
ACTIVED_PROFILE = 'prod'
NETWORK = 'servicenet'
}
stages {

stage('Deploy cloud-app-service-server') {
when {
branch "${ACTIVED_PROFILE}"
}

steps {
script {
env.WORKSPACE_PATH = "./cloud-app-service-server"
def pom = readMavenPom file: "${WORKSPACE_PATH}/pom.xml"
env.PROJECT_NAME = pom.artifactId
env.PORT = pom.properties.ankonAppPort
env.IMAGE = "${DOCKER_REPOSITORY_HOST}/${PROJECT_NAME}:${pom.version}-release"
env.POM_VERSION = "${pom.version}"

env.LAST_TEST_TAG = getTagFromDockerHub("${PROJECT_NAME}", "${POM_VERSION}")
echo "get ${PROJECT_NAME} last test image tag ${LAST_TEST_TAG}"
env.LAST_TEST_IMAGE_WITH_TAG = "${DOCKER_REPOSITORY_HOST}/${PROJECT_NAME}:${LAST_TEST_TAG}"
}

echo "pull image ${LAST_TEST_IMAGE_WITH_TAG}"
sh "docker pull ${LAST_TEST_IMAGE_WITH_TAG}"

echo "tag image ${LAST_TEST_IMAGE_WITH_TAG} to ${IMAGE}"
sh "docker tag ${LAST_TEST_IMAGE_WITH_TAG} ${IMAGE}"

echo "push image tag ${IMAGE}"
sh "docker push ${IMAGE}"

echo "depoy docker service for ${LAST_TEST_IMAGE_WITH_TAG}"
sh "docker service rm ${PROJ<!-- ECT_NAME} || true"
sh "docker service create --name ${PROJECT_NAME} --limit-memory 500M --with-registry-auth --replicas 1 --publish published=${PORT},target=${PORT} --network ${NETWORK} ${IMAGE} --spring.profiles.active=${ACTIVED_PROFILE}"
}
}
}
}

def getTagFromDockerHub(imgName, version) {
echo "find image ${imgName} all tags"
def url = new URL("https://wx.ankoninc.com.cn/nexus/repository/local-docker/v2/${imgName}/tags/list")
def jsonResponse = url.getText()
def parsedJSON = parseJSON(jsonResponse)
def testTags = []
def tagPattern = "${version}-test-"
for (result in parsedJSON.tags) {
if (result.indexOf(tagPattern) != -1) {
testTags.add(result)
}
}

if (testTags.size() > 0) {
return testTags[testTags.size() - 1]
} else {
return "";
}
}

def parseJSON(json) {
return new groovy.json.JsonSlurperClassic().parseText(json)
}

在下一篇文章中,将会讲解使用 jenkins 创建多分支流水线任务(pipeline),那时将会用到本文中的三个 jenkinsfile,来执行编辑构建发布工作。