总体设计
- 准备工作
- 编译、打包
- 构建镜像
- 推送镜像到 harbor
- (应用服务器)拉取镜像、启动容器
- (应用服务器)停止老版本容器、清理老版本容器和镜像
下面分部介绍
准备工作
在 gitlab 项目页面 settings->gi/cd -> variables 设置 harbor 登录名和密码参数里的变量
并且留意变量保护的选项,如果当前不在受保护的分支,把保护选项去掉
检查和处理 ssh key(见另一篇)
这里 ssh key 没有做保密处理,直接存在 runner 所在的服务器。目前想到的办法是 base64 后保存, 使用的时候在解码。
主要节点处理思路
- 通过 gitlab 变量保存 harbor 的登录名、密码
- 提前打一个基础镜像 c7_j8_app(centos7、java8、app 应用),镜像里配置好了 jdk、字体、时区等信息
- 在 runner 服务器推送镜像后,删除本地镜像,减少硬盘占用
- 在应用服务器部署采用手动触发
- 因为部署在同一个应用服务器,所以采用随机端口(有重复的可能,目前为止没有发生过)
app_stop_and_clear
阶段用于停止和清理上一次的服务。这里使用配置文件辅助保存上一次服务的信息
配置文件
我原本的配置文件有多个应用,多个应用的部署方式相似,这里只保留了 1 个应用
variables:
harbor_server: '192.168.128.3:9900'
app_server: '192.168.128.16'
project_name: 'app'
base_image_name: 'c7_j8_app'
key_path: '/home/gitlab-runner/keys/id_rsa.pem'
CURRENT_CONTAINER_NAME_APP: app-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
CURRENT_IMAGE_NAME_APP: app:$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
stages:
- build_and_push
- app_deploy_and_start
- app_stop_and_clear
build_and_push:
stage: build_and_push
script:
- echo "build_jar start"
- mvn clean package -Dmaven.test.skip=true
- echo "build_jar success"
- docker pull $harbor_server/$project_name/$base_image_name:latest
# app镜像
- docker build -t $harbor_server/$project_name/$CURRENT_IMAGE_NAME_APP app
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD $harbor_server
# 推送镜像
- docker push $harbor_server/$project_name/$CURRENT_IMAGE_NAME_APP
# push以后,删除本地镜像
- docker rmi $harbor_server/$project_name/$CURRENT_IMAGE_NAME_APP
- echo "push image finish"
only:
- master
tags:
- app_server
app_deploy_and_start:
stage: app_deploy_and_start
needs:
- build_and_push
script:
- echo "deploy job start"
- export SERVER_PORT=$(shuf -i 10000-25000 -n 1) # 自动生成一个2000-65000之间的随机端口
- export JOB_PORT=$(shuf -i 33000-34000 -n 1) # 自动生成一个2000-65000之间的随机端口
- echo "random SERVER_PORT is " $SERVER_PORT
- echo "random JOB_PORT is " $JOB_PORT
- |
ssh -i $key_path root@$app_server <<EOF
docker pull $harbor_server/$project_name/$CURRENT_IMAGE_NAME_APP
docker run -d \
--network=host \
--restart=always \
-p $SERVER_PORT:$SERVER_PORT \
-v /home/attachments:/home/attachments:rw \
-v /home/attachments_tmp:/home/attachments_tmp:rw \
-v /home/log/prd:/home/log/prd:rw \
--name=$CURRENT_CONTAINER_NAME_APP \
-e SPRING_PROFILES_ACTIVE=prd \
-e xxl.job.executor.port=$JOB_PORT \
-e xxl.job.executor.address=192.168.3.5 \
-e server.port=$SERVER_PORT \
$harbor_server/$project_name/$CURRENT_IMAGE_NAME_APP
EOF
when: manual
only:
- master
tags:
- app_server
app_stop_and_clear:
#停止和清理老版本容器、镜像
stage: app_stop_and_clear
needs:
- app_deploy_and_start
before_script:
- |
if [ -f ../previous_commit_app.env ]; then
source ../previous_commit_app.env
echo "load previous_commit_app.env data"
else
echo "file previous_commit_app.env not fond"
fi
script:
- echo "stop_and_clear start"
- echo $PREVIOUS_CONTAINER_NAME_APP
- echo $harbor_server/$project_name/$PREVIOUS_IMAGE_NAME_APP
# 停止容器、删除容器、删除镜像,先判断容器存在再删除,避免报错
- |
ssh -i $key_path root@$app_server <<EOF
if docker ps -a --format "{{.Names}}" | grep -q "^$PREVIOUS_CONTAINER_NAME_APP$"; then
echo "Container $PREVIOUS_CONTAINER_NAME_APP exists"
docker stop $PREVIOUS_CONTAINER_NAME_APP
docker rm $PREVIOUS_CONTAINER_NAME_APP
docker rmi $harbor_server/$project_name/$PREVIOUS_IMAGE_NAME_APP
else
echo "Container $PREVIOUS_CONTAINER_NAME_APP does not exist"
fi
EOF
# 在../previous_commit_app.env保存当前的标记,用于下次提交时关停上一次的服务
- echo "PREVIOUS_CONTAINER_NAME_APP=$CURRENT_CONTAINER_NAME_APP" > ../previous_commit_app.env
- echo "PREVIOUS_IMAGE_NAME_APP=$CURRENT_IMAGE_NAME_APP" >> ../previous_commit_app.env
- echo "app_stop_and_clear finish"
when: manual
only:
- master
tags:
- app_server