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實踐我們需要一步一個腳印的走過來,在這過程中要不斷的思考、總結。
我是【木訥大叔愛運維】,歡迎關注,與你分享運維路上的點點滴滴。