最近拖延症犯了,一个文章标题写了一个月才写了个标题... 不高兴.png

前言

自从入坑Drone CI/DI以来,我极力推荐在小微项目上使用Drone来完成自动构建,主要是轻量化,安装配置方便 吐舌.png ,
只需写一个docker compose文件即可完成Drone的安装配置,
只需写一个.drone.yml即可完成接入,极为方便;

Drone更深一层探究

经过我的不懈努力,经主管同意,最终也在公司项目上使用上了Drone来逐步替代Jenkins进行小项目微服务的自动构建部署 太开心.png

但是实际使用中发现一个问题,即SpringBoot工程,通常是一个主工程下包含多个微服务子工程,使用drone不太好控制其中某一个工程的自动构建部署,总不能每次都重新构建整个服务,然后重启所有工程吧,这样效率也太低了 不高兴.png

//多工程目录结构,本文主要演示SpringBoot多工程项目自动构建部署
demoParant
├── common   //公共工程
├── api      //API工程
├── user     //用户工程
└── back     //后台工程

能想到的最简单实现的方式就是来通过不同的分支来触发不同的构建任务,这样理论上可行,但是实际操作会产生一堆分支,显得极为不整洁,并且正常开发也是开发一个分支,测试一个分支,生产一个分支,太多了操作起来也不方便 黑线.png

所以最理想的方式也就是在同一个分支下,通过某种方式来触发不同的构建任务;

Drone的启发

在Drone CI下有一个默认功能,即在Commit log中输入[CI SKIP]即可跳过本次自动构建,于是我就想能否通过Commit log来控制本次部署具体哪个子工程,这样下来我只要在输入commit log的时候输入需要构建的工程,即可完成对应工程的自动构建部署,并且不影响同项目下的其他工程;

初步设想的原理就是在drone执行部署命令时,通过自定义脚本完成工程部署,并将commit log作为参数传入脚本,在脚本中判断commit log中是否指定某些工程的运行的参数,如果不指定则默认运行所有工程 乖.png

例如:我提交commit日志update Admin.java; add admin management interface; [CI API] [CI BACK];
这样一来,经过drone自动构建后,只重新部署了API工程和后台工程;

具体实现

具体实现可参考如下脚本:

Drone自动构建脚本参考

# drone 自动构建
name: test-project autodeploy
kind: pipeline
type: docker
# drone 构建步骤
steps:
  # 1. 进行 maven 打包
  - name: maven compile
    pull: if-not-exists
    image: maven:ibmjava-alpine
    volumes:
      # 本机准备挂载到宿主机的 maven构建缓存
      - name: cache
        path: /root/.m2
    commands:
      # 开始打包maven工程 跳过测试步骤
      - mvn clean install -Dmaven.test.skip=true
      - cp api/target/*.jar ./
      - cp user/target/*.jar ./
      - cp back/target/*.jar ./
  # 2. 将打包后的jar包部署到指定服务器
  - name: jar deploy
    pull: if-not-exists
    image: appleboy/drone-scp
    settings:
      host:
        from_secret: test_host
      username:
        from_secret: test_username
      password:
        from_secret: test_password
      port:
        from_secret: test_port
      target: /home/drone/data/${DRONE_REPO_NAME}
      source: ./*.jar
    when:
      branch:
        - master
  #3. 使用ssh访问测试服务器进行服务部署
  - name: test ssh-start
    pull: if-not-exists
    image: appleboy/drone-ssh
    settings:
      host:
        from_secret: test_host
      username:
        from_secret: test_username
      password:
        from_secret: test_password
      port:
        from_secret:test_port
      script:
        # 运行部署脚本
        - cd /data/testProjrct
        # 这一步是运行部署脚本,并将Comment日志作为参数传入给脚本,部署命令一定要放到drone的最后一条命令,这样在脚本中抛出异常退出后,drone可以捕捉到异常退出,将该次构建标记为构建失败
        - ./drone.sh ${DRONE_COMMIT_MESSAGE}
        -
    when:
      branch:
        - master
# 挂载的主机卷,映射到宿主机对应的目录,对应name为steps.volumes.name
volumes:
  # maven构建缓存
  - name: cache
    host:
      # path: /tmp/cache/.m2
      path: /opt/maven
# drone执行触发分支
trigger:
  branch:
    - master
  event:
    - push

Jar运行部署脚本参考
只做了最简单的实现,脚本有很多可以优化的空间,在这里不多加阐述了

#!/bin/bash
echo "开始运行Demo服务..."
PARAM=$1;
echo -e "Commit log: "$PARAM;
#----------------------- 基本参数配置 start -----------------------
# JAVA安装目录
JAVA_PATH="/opt/jdk1.8.0_191/bin/java";
# 运行环境配置
JAVA_RUN_ENV="test";
# drone部署的jar包所在路径
DRONE_JAR_PATH="/home/drone/data/demoParant";
# API工程
APP_API_NAME="apiService-0.0.1-SNAPSHOT.jar"
APP_API_PATH="/home/demoPatent/apiService"
APP_API_PING_URL="http://127.0.0.1:18091/api/open/ping"
APP_API_RUN=
# 用户端工程
APP_USER_NAME="userService-0.0.1-SNAPSHOT.jar"
APP_USER_PATH="/home/demoPatent/userService"
APP_USER_RUN=
APP_USER_PING_URL="http://127.0.0.1:18071/user/open/ping"
# 后台服务工程
APP_BACK_NAME="backService-0.0.1-SNAPSHOT.jar"
APP_BACK_PATH="/home/demoPatent/backService"
APP_BACK_RUN=
APP_BACK_PING_URL="http://127.0.0.1:18081/back/open/ping"

# 记录是否有启动失败的服务
APP_START_FAIL=
APP_START_RESULT=
#----------------------- 基本参数配置 end -----------------------

echo "程序运行环境:$JAVA_RUN_ENV"
#----------------------- 定义启动函数 start-----------------------
startAPP() {
    APP_NAME=$1;
    APP_PATH=$2;
    PING_URL=$3;
    # echo "APP_NAME:$APP_NAME"
    # echo "APP_PATH:$APP_PATH"
    # echo "PING_URL:$APP_NAME"
    # 部署新jar包到程序运行目录
    if [ -f "$DRONE_JAR_PATH/$APP_NAME" ];then
        echo "Move the new jar package to the deployment directory ..."
        mv -f $DRONE_JAR_PATH/$APP_NAME $APP_PATH/$APP_NAME
        else
        echo "The drone original jar file not exist, skip move."
    fi

    # 获取程序PID
    getPid() {
        APP_PID=`ps -ef | grep -v grep | grep $APP_NAME | awk '{print $2}'`
    }
    getPid

    # 启动前检查应用是否启动,如果已经启动则先停止再重新启动
    while [ ${APP_PID} ]
    do
        echo -e "App ${APP_NAME} is still RUNNING! PID:$APP_PID";
        echo "stop ${APP_NAME} ...";
        kill -9 ${APP_PID};
        sleep 2;
        getPid
    done

    # 运行jar包
    echo "start ${APP_NAME} ...."
    cd ${APP_PATH}
    nohup ${JAVA_PATH} -server -Xms256m -Xmx512m  -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=768m -Xss256k -jar ./${APP_NAME} --spring.profiles.active=$JAVA_RUN_ENV > /dev/null & 2>&1 &

    # 请求测试接口,判断服务是否正常启动
    getHttpCode () {
        http_code=`curl -Is -m 10 -w %{http_code} -o /dev/null $PING_URL`;
    }

    # 检查服务启动状态
    sleep 2s;
    count=0;
    # 获取接口状态码
    getHttpCode
    while [ $http_code -ne 200 ]
    do
        if (($count == 8));then
            echo "${APP_NAME}: $(expr $count \* 5)秒内未启动,请检查!";
            msg="start ${APP_NAME} failed!\n";
            echo -e $msg;
            APP_START_RESULT=$APP_START_RESULT$msg;
            APP_START_FAIL="YES";
            return;
        fi
        count=$(($count+1));
        echo "${APP_NAME} waiting to start ...";
        # sleep 1s;
        sleep 5s;
        # 获取接口状态码
        getHttpCode
        echo "http_code --> $http_code";
    done

    getPid
    msg="start ${APP_NAME} success! PID=$APP_PID\n";
    echo -e $msg;
    APP_START_RESULT=$APP_START_RESULT$msg;
}
#----------------------- 函数定义结束 end -----------------------

#----------------------- commit log 判断 start-----------------------
# 根据commit日志参数判断启动哪个工程
if [[ $PARAM == *'[CI API]'* ]]; then
    echo -e "Commit参数指定启动API工程...\n";
    APP_API_RUN="true"
fi

if [[ $PARAM == *'[CI USER]'* ]]; then
    echo -e "Commit参数指定启动用户端工程...\n";
    APP_USER_RUN="true"
fi

if [[ $PARAM == *'[CI BACK]'* ]]; then
    echo -e "Commit参数指定启动BACK工程...\n";
    APP_BACK_RUN="true"
fi

if [[ -z "$APP_API_RUN" ]] && [[ -z "$APP_USER_RUN" ]] && [[ -z "$APP_BACK_RUN" ]]; then
    echo -e "Commit未指定参数,准备启动所有工程...\n";
    APP_API_RUN="true"
    APP_USER_RUN="true"
    APP_BACK_RUN="true"
fi
#----------------------- commit log 判断结束 -----------------------

# 启动运营端服务
if [ $APP_API_RUN ]; then
    echo "Run api service ..."
    startAPP $APP_API_NAME $APP_API_PATH $APP_API_PING_URL
fi
# 启动用户端服务
if [ $APP_USER_RUN ]; then
    echo "Run user service"
    startAPP $APP_USER_NAME $APP_USER_PATH $APP_USER_PING_URL
fi
# 启动商家端服务
if [ $APP_BACK_RUN ]; then
    echo "Run back service"
    startAPP $APP_BACK_NAME $APP_BACK_PATH $APP_BACK_PING_URL
fi


# 删除drone构建缓存
rm -rf $DRONE_JAR_PATH/*.jar

# 打印运行结果
echo "------------ service start status ------------"
echo -e $APP_START_RESULT
echo "----------------------------------------------"

# 如果有启动失败的应用,则退出状态码为1,用于drone标记构建失败
if [ $APP_START_FAIL ]; then
    exit 1;
fi

以上脚本在实际生产环境已经稳定运行几个月了,经过如上配置可以很方便的去控制多工程项目下某一个工程的自动构建部署,只需在Commit日志中指定构建参数即可,在实际开发中无疑是解放了双手,能让人更加专注于业务代码实现上(摸鱼) 滑稽.png

Demo演示

最后附上我一个测试环境工程的Drone自动构建日志的Demo演示
Commit log未指定参数,默认自动构建并部署所有工程:
QQ截图20210812134905.jpg

Commit log指定参数,只自动构建并部署指定工程:
QQ截图20210812200148.jpg