Jenkins+Docker+git实现多环境快速交付-compose优化升级

Jenkins+Docker+git多分支实现springboot项目多环境快速交付一文我们介绍了CI/CD交付springboot项目过程中的

环境校验发版/回滚/重启、操作校验等步骤,在实际应用过程中有几点思考:

构建前的运行参数定义

构建前我们只按规范定义了APP_NAME(项目名)、IMAGE_NAME(镜像名)、MONITOR_URL(健康检查URL),相关的JVM参数、端口映射等与实际运行的参数仍然需要在后续的容器运行时手动修改,增加了配置难度,因此我们考虑将其提取到环境变量统一设置,以降低出问题的概率。

繁琐的pull/start/stop/rm操作


在构建过程中需要在远程服务器上频繁的docker pull/start/stop/rm等操作来更新镜像,有没有更好的方式来简化这些操作,来让整个过程更加简洁。因此,我们使用容器编排工具docker-compose来更新镜像、管理容器。

以上两点是本文重点解决的问题,我们使用docker-compose+环境变量来实现下。

docker-compose配置

compose模板文件

<code>version: '3.7' services: helloworld: image: harbor.test.cn/${IMAGE_NAME}:${VERSION} container_name: ${CONTAINER_NAME} restart: always environment: - JAVA_OPTS=${JAVA_OPTS} ports: - $PORT volumes: - /App/java_app/${APP_NAME}/logs:/logs healthcheck: test: ["CMD", "curl", "-fs", "$MONITOR_URL"] interval: 8s timeout: 10s retries: 3/<code>

我们主要提取了以下几个变量:

镜像名:${IMAGE_NAME}

镜像tag:${VERSION},用于版本回滚

容器名:${CONTAINER_NAME}

JVM参数:${JAVA_OPTS}

端口映射:${PORT}

项目名:${APP_NAME}

健康检查URL:${MONITOR_URL}

其中:镜像tag是我们在回滚时需要输入的参数,其他都是构建前根据实际情况插入到全局环境变量中。

.env环境变量配置文件

docker-compose默认使用同级目录下的.env文件作为环境变量配置文件,借助此文件我们可以在构建前将jenkins中的全局环境变量写入此文件,以便docker-compose使用。

<code>#vim .env IMAGE_NAME=helloworld/helloworld CONTAINER_NAME=helloworld APP_NAME=helloworld MONITOR_URL=http://127.0.0.1:8080 PORT=9080:8080 JAVA_OPTS=-Xmx129m -Xms129m -Dspring.profiles.active=test VERSION=5f06985aa4ed91a417ecd9b02abddb6efdbfd1b5/<code>更新镜像并启动容器

每次构建前修改.env文件后,我们可以通过docker-compose来pull指定的镜像并启动容器了。

<code>docker-compose up -d --build/<code>

通过--build参数,在启动容器前,都会更新使用的镜像。

优化实现(加粗处是优化点)

jenkins新建自由风格的job,名称为docker-test-helloworld参数化构建

插入全局环境变量及设置Build Name

<code>APP_NAME=helloworld IMAGE_NAME=helloworld/helloworld MONITOR_URL="http://127.0.0.1:8080" JAVA_OPTS="-Xmx129m -Xms129m -Dspring.profiles.active=$(echo ${JOB_NAME}|awk -F'-' '{print $2}')" PORT=9080:8080/<code>

以上是插入的全局环境变量,整个项目我们只需在此处集中修改,以减少配置错误为目的。

Build-环境校验、操作校验

Build过程主要进行环境校验、操作校验操作,用于

(1)环境校验,判断git分支与当前job-test/prod是否一致,不一致则停止后续发版操作;

(2)操作校验

发版:git对应分支是否有更新,防止在没有更新时构建多次,导致应用多次重启;

主要利用jenkins内置变量:


GIT_PREVIOUS_SUCCESSFUL_COMMIT 上次构建成功后的git版本号

GIT_COMMIT 当前构建任务的git版本号

回滚:判断远程分支是否有与参数匹配的版本号,没有则说明不合法,停止回滚;

代码如下:

<code>#!/bin/bash CHECK_ENV(){ #判断git分支是否与项目匹配,避免环境与项目混用 ENV=`echo ${JOB_NAME}|awk -F'-' '{print $2}'` #测试分支develop,生产分支master BRANCH=${GIT_BRANCH} if [ $BRANCH = "origin/develop" ];then [ $ENV="test" ] && echo -e "\033[34m$ENV environment is in building \033[0m" || { echo -e "\033[31m git branch is $BRANCH, not match environment $ENV \033[0m" exit 1 } else echo -e "\033[31m git branch is $BRANCH, not match environment $ENV \033[0m" exit 1 fi } #环境校验 CHECK_ENV #操作校验 if [ "${deploy_env}" = "deploy" ];then echo -e "\033[34mstart ${deploy_env}\033[0m" echo ${GIT_PREVIOUS_SUCCESSFUL_COMMIT} echo ${GIT_COMMIT} [ "${GIT_PREVIOUS_SUCCESSFUL_COMMIT}" != "${GIT_COMMIT}" ] && echo -e "\033[34mstart maven package\033[0m" || { #版本未更新,停止发版 echo -e "\033[31mRepositories not update, stop ${deploy_env}\033[0m" exit 1 } /usr/local/maven/bin/mvn clean package docker:build -DdockerImageTags=${GIT_COMMIT} -Dmaven.test.skip=true -DpushImageTag [ $? -eq 0 ] && echo -e "\033[32mmaven package success\033[0m" || { echo -e "\033[31mmaven package fail\033[0m" exit 1 } elif [ "${deploy_env}" = "rollback" ];then echo -e "\033[34mstart ${deploy_env}\033[0m" echo ${GIT_PREVIOUS_SUCCESSFUL_COMMIT} echo ${GIT_COMMIT} #查看远程分支是否有此版本 git branch -r --contains $version [ $? -eq 0 ] && echo -e "\033[34mstart docker steps\033[0m" || { echo -e "\033[31mverison is wrong,please check version\033[0m" exit 1 } fi/<code>Build-远程服务器构建

通过“SSH Publishers”插件登录远程服务器执行docker相关操作

<code>#!/bin/bash IN_FACE=`/sbin/route -n |awk '{if($4~/UG/){print $8}}'|head -n 1` LOCAL_IP=`/sbin/ip addr show "${IN_FACE}" | grep -w 'inet' | awk '{print $2}'` CONTAINER_NAME=`echo ${IMAGE_NAME} | awk -F/ '{print $2}'` #ENV=`echo ${JOB_NAME}|awk -F'-' '{print $2}'` #删除老镜像 DEL_IMAGE() { echo -e "\033[34mrm image ${IMAGE_NAME}:$1\033[0m" sudo docker image rm harbor.test.cn/${IMAGE_NAME}:$1 --no-prune [ $? -eq 0 ] && echo -e "\033[32mrm ${IMAGE_NAME}:$1 succss \033[0m" || { echo -e "\033[31mrm ${IMAGE_NAME}:$1 fail \033[0m" exit 1 } } #健康检查 HEALTHCHECK() { timeout=180 echo -e "\033[34mhealth check\033[0m" for (( i=1;i<=$timeout;i++ )) do status=$(sudo docker inspect --format='{{json .State.Health}}' ${CONTAINER_NAME}|grep -Po '"Status[":]+\K[^"]+') echo $status if [ $status = 'healthy' ];then echo -e "\033[32m${LOCAL_IP} ${CONTAINER_NAME} status is ${status}\033[0m" DEL_IMAGE ${OLD_VERSION} exit 0 elif [ $status = 'starting' ];then sleep 23 else echo -e "\033[31m${LOCAL_IP} ${CONTAINER_NAME} status is ${status}\033[0m" exit 1 fi done } #定义docker-compose变量,注意第一步清空env,后续追加env INIT_VAR() { echo -e "\033[34minit docker-compose variable\033[0m" echo "IMAGE_NAME=${IMAGE_NAME}" > .env echo "CONTAINER_NAME=${CONTAINER_NAME}" >> .env echo "APP_NAME=${APP_NAME}" >> .env echo "ENV=${ENV}" >> .env echo "MONITOR_URL=${MONITOR_URL}" >> .env echo "PORT=${PORT}" >> .env echo "JAVA_OPTS=${JAVA_OPTS}" >> .env } #进入项目目录 cd /App/java_app_tmp/${APP_NAME} #提前读取env文件中的老版本号,用于删除老镜像 OLD_VERSION=$(grep VERSION .env|awk -F= '{print $2}') echo $OLD_VERSION case ${deploy_env} in deploy) echo -e "\033[34mstart ${deploy_env} steps\033[0m" INIT_VAR echo "VERSION=${GIT_COMMIT}" >> .env sudo docker-compose up -d --build HEALTHCHECK ;; rollback) echo -e "\033[34mstart ${deploy_env} steps\033[0m" INIT_VAR echo "VERSION=${version}" >> .env sudo docker-compose up -d --build HEALTHCHECK ;; restart) sudo docker-compose restart HEALTHCHECK ;; *) exit 1 ;; esac/<code>

通过docker-compose将之前的pull/stop/rm/start等一系列的docker操作全部用"docker-compose up -d --build"代替,大大简化了代码。

注意:每次构建前通过">"清除.env文件并重启添加,之所以保留便于我们排查问题。

Post-build Actions

<code>#删除jenkins slave服务上的虚悬镜像 echo -e "\033[34mrm old image on jenkins slave\033[0m" if [ $(docker image ls harbor.test.cn/${IMAGE_NAME} -q|wc -l) -ne 0 ];then docker image rm `docker image ls harbor.test.cn/${IMAGE_NAME} -q` -f --no-prune fi docker image prune -f/<code>

删除jenkins slave服务上新构建的镜像及虚悬镜像,保持slave上的环境纯净。


通过以上步骤我们只在第3、5步配合docker-compose做了优化,其他不变仍可参考Jenkins+Docker+git多分支实现springboot项目多环境快速交付。


总结

本文之所以是优化升级,因为在配置过程中提高可读性、集中配置、简化操作可以有效的减少出错的概率,另外在DevOps中交付的效率问题也是非常重要的一个环节。

最重要的是目前的Docker实践我们需要一步一个脚印的走过来,在这过程中要不断的思考、总结。


我是【木讷大叔爱运维】,欢迎关注,与你分享运维路上的点点滴滴。