利用Shell Script实现自动化部署

本地工作流

本地将完成编译测试推送三项任务。

编译

首先删除旧的编译出的用于存放class文件和JAR包的目录,再使用项目路径下的gradlew进行build任务,生成JAR包。

1
2
rm -rf ${BUILD_DIR}
${LOCAL_PATH}gradlew build -p ${LOCAL_PATH}

检测编译后的JAR包,如果存在则证明本次编译成功,执行后续工作。

1
2
3
4
if [ -f ${BUILT_ARCH_DIR}${ARCH_RAW_NAME} ]
then
# ...
fi

在编译成功的条件下,将必要的外部配置文件(如application.properties)拷贝至JAR包所在目录下,构成本地运行服务器应用的条件。

1
2
# prepare configuration files
cp -f ${CONF_FILE} ${BUILT_ARCH_DIR}

查询占用服务器应用预设端口(如80、8080等)的进程,排除TIME_WAIT在该端口的进程(进程号输出-),如果存在LISTENING该端口的进程(进程号输出形如1234/java)则发送SIGTERM让其退出端口占用,并使其进入TIME_WAIT状态直至完全退出。

1
2
3
4
5
6
7
# kill the running process of server app
get_server_pid
read server_pid < /tmp/tempout
if [ ${server_pid//-} ]
then
sudo kill -s TERM ${server_pid//-}
fi

切换至JAR包所在目录并在后台启动服务器应用,逆向上述思路等待进程启动完成,如果一直处在等待状态则说明其启动失败。

1
2
3
4
5
6
7
8
9
# wait for the server app to complete starting
get_server_pid
read server_pid < /tmp/tempout
while [ ! ${server_pid//-} ]
do
get_server_pid
read server_pid < /tmp/tempout
sleep ${CHECK_PERIOD}
done

检测到应用成功启动则进入下一环节。

测试

请求用户输入用于测试的必要信息(如测试用账户的用户名、密码等),将其作为参数传递给测试脚本,待其退出后关闭服务器应用,观察退出码(exit code),如果某一项测试失败则退出整个工作流,否则关闭服务器应用并进入下一环节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
read -p 'username for admin account: ' username
stty -echo
read -p 'password for admin account: ' password
stty echo
echo
ruby -C ${TEST_SCRIPT_PATH} ${TEST_SCRIPT_FILE} ${username} ${password}
exit_code=$?
# kill server app process after testing
sudo kill -s TERM ${server_pid//-}
# trigger gather-push script if pass all tests
if [ ${exit_code} -eq 0 ]
then
bash ${UPLOAD_FILE}
fi

推送

本地存放有部署于server端的远程Git仓库的本地仓库(如果没有则脚本会自动clone一个),将JAR包和外部配置文件拷贝到工作区下,根据当前时间生成commit信息并执行push

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# create local repository if not exists
if [ ! -d ${LOCAL_ARCH_DIR} ];
then
git clone ${REMOTE_ARCH_DIR} ${LOCAL_ARCH_DIR}
fi
if [ -f ${BUILT_ARCH_DIR}${ARCH_RAW_NAME} ]
then
cp -f ${BUILT_ARCH_DIR}${ARCH_RAW_NAME} ${LOCAL_ARCH_DIR}${ARCH_NEW_NAME}
cp -f ${CONF_FILE} ${LOCAL_ARCH_DIR}
git --work-tree=${LOCAL_ARCH_DIR} --git-dir=${LOCAL_GIT_DIR} add .
current_time=`date +%y-%m-%d-%H-%M-%N`
commit_comment="AUTO-DEPLOYED-AT-${current_time}"
git --work-tree=${LOCAL_ARCH_DIR} --git-dir=${LOCAL_GIT_DIR} commit -m ${commit_comment}
git --work-tree=${LOCAL_ARCH_DIR} --git-dir=${LOCAL_GIT_DIR} push
fi

远程工作流

远程将完成拉取更新两项任务。

关于如何在服务器上创建Git仓库请参见Git on the Server

拉取

在远程仓库设置post-update钩子,使得在远程仓库完成更新后能触发该钩子并在server端的本地仓库将新JAR包和配置文件pull下来。

1
2
3
4
5
# constants
WATCHED_DIR='/root/management-sys-workspace/management-sys/'
GIT_DIR="${WATCHED_DIR}.git/"
git --git-dir=${GIT_DIR} --work-tree=${WATCHED_DIR} pull

更新

在server端长期运行一个看门狗,用于检测本地仓库JAR包(甚至是配置文件)的变动,一旦发现被监视文件的最后修改时间相比于记录的更晚,则执行服务器应用的启停工作。

首先需要确认运行在root下。

1
2
3
4
while [ $UID -eq 0 ]
do
# ...
done

从dump文件中恢复上次的工作状态。

1
2
3
4
5
6
7
# recover from dump file if possible
if [ -f ${DUMP_FILE} ]
then
read last_modified_time < ${DUMP_FILE}
else
last_modified_time=-1
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (( $last_modified_time < $this_modified_time ))
then
# find out which process the server is working on
current_running_pid=`netstat -anp | grep ${SERVER_PORT} | awk '{printf $7}' | cut -d / -f 1`
if [ ${current_running_pid//-} ]
then
# kill the running process if exists
# send SIGTERM to guarantee it exit correctly
kill -s TERM ${current_running_pid//-}
fi
# generate log file with name log-<archive-modified-time>-<start-time>
# restart new version server app in background
current_time=`date +%y%m%d%H%M%N`
cd ${REPOSITORY_DIR} && java -jar ${LATEST_JAR_FILE} > log-${this_modified_time}-${current_time} &
# generate a dump file for watch-dog's recovery
echo ${this_modified_time} > ${DUMP_FILE}
last_modified_time=${this_modified_time}
fi

通过睡眠来减少看门狗的检测频率。

1
sleep ${CHECK_PERIOD}