背景
近期团队其他成员们在开发一个新项目,项目仓库初始化时还缺少 CD(持续部署)的内容,于是老师将这个 issue 分配给了我。
简述 CD
CD 全称为 Continuous Deployment ,翻译成中文是持续部署。说得再通俗一些就是自动打包发布。
我们的项目从开发环境到服务器上的生产环境之间,还有一个打包部署的步骤。 所以 gitlab 给我们提供了 CD 功能,方便我们自定义执行我们的自动打包和部署。
通过查阅官方文档可以得知,要配置 CD ,我们需要在项目根目录下创建 .gitlab-ci.yml 文件,并按它的语法编写脚本。
在脚本中我们可以设置执行时机、开放端口,甚至还可以通过捕获标签加以判断后再执行不同的操作等等。
更多相关内容请见官方文档。
CD 的思想
在自己上手之前先是参考了其他项目的 CD ,进行了一些总结。
首先 CD 分为两个部分:build 和 deploy。
然后,配置自动打包的时机:当我们在 gitlab 网站上新建 Tag 时。所以,在要执行的部分( build 和 deploy )就应该有
rules:
- if: $CI_COMMIT_TAG != null
新建 Tag 之后,服务器就会执行我们的脚本。见下图。
build配置
angular-build
angular-build:
tags:
- docker
stage: build
artifacts:
paths:
- dist
cache:
- key:
files:
- web/package-lock.json
paths:
- web/node_modules
image: registry.cn-beijing.aliyuncs.com/mengyunzhi/node-chrome:18.16.0
before_script:
- cd web
script:
- env
- npm install -dd
- npm run build
- mv dist/* ../dist
rules:
- if: $CI_COMMIT_TAG != null
spring-build
spring-build:
tags:
- docker
stage: build
artifacts:
paths:
- app.jar
cache:
- key:
files:
- api/pom.xml
paths:
- /root/.m2/repository
image: registry.cn-beijing.aliyuncs.com/mengyunzhi/maven:3.6-openjdk-8-node
before_script:
- cd api
script:
- env
- node -v
- npm -v
- mvn -v
- mvn package -Dmaven.test.skip=true
- mv target/*.jar ../app.jar
rules:
- if: $CI_COMMIT_TAG != null
deploy配置
variables
variables:
# 工程名称唯一
PROJECT_NAME: "${CI_PROJECT_TITLE}"
DB_NAME: "xxx"
DB_USER: "xxx"
DB_PASSWORD: "xxx"
DB_ROOT_PASSWORD: "xxx"
HTTP_EXPORT_PORT: xxx
DB_EXPORT_PORT: xxx
deploy
deploy:
tags:
- debian-cd-pro
stage: deploy-cd
dependencies:
- spring-build
- angular-build
script:
- ls -a -l
- mkdir -p /home/app/${PROJECT_NAME}
- mkdir -p /home/app/${PROJECT_NAME}/db
- mkdir -p /home/app/${PROJECT_NAME}/backups
- rm -f /home/app/${PROJECT_NAME}/nginx.conf
- rm -f /home/app/${PROJECT_NAME}/app.jar
- rm -f /home/app/${PROJECT_NAME}/application.yml
- rm -rf /home/app/${PROJECT_NAME}/web
- cp app.jar /home/app/${PROJECT_NAME}/app.jar
- cp -r dist /home/app/${PROJECT_NAME}/web
- sed -i "s/api-server/${PROJECT_NAME}-api/g" cd/nginx.conf
- |
sed "s/\$DB_PASSWORD/$DB_PASSWORD/g" cd/application.yml | sed "s/\$PROJECT_NAME/$PROJECT_NAME/g" | \
sed "s/\$DB_NAME/$DB_NAME/g" | sed "s/\$DB_USER/$DB_USER/g" > /home/app/${PROJECT_NAME}/application.yml
- cat cd/nginx.conf
- cp cd/nginx.conf /home/app/${PROJECT_NAME}/nginx.conf
# - ls
# - cd /home/app/${PROJECT_NAME}
# - ls
- |
if [ "$(docker ps -q -f name=${PROJECT_NAME}-api)" ]; then
docker stop ${PROJECT_NAME}-api
fi
if [ "$(docker ps -q -f name=${PROJECT_NAME}-nginx)" ]; then
docker stop ${PROJECT_NAME}-nginx
fi
if [ "$(docker ps -q -f name=${PROJECT_NAME}-db)" ]; then
docker stop ${PROJECT_NAME}-db
fi
- |
if [[ $CI_COMMIT_TAG == *"#resetapi"* ]]; then
if [ "$(docker ps -a -q -f name=${PROJECT_NAME}-api)" ]; then
echo 'remove api container' && docker rm ${PROJECT_NAME}-api
fi
fi
- |
if [[ $CI_COMMIT_TAG == *"#backdb"* ]]; then
echo 'backup db'
NOW=$(date +"%Y-%m-%d-%H-%M-%S")
tar czf /home/app/${PROJECT_NAME}/backups/dbbackup-$NOW.tar.gz /home/app/${PROJECT_NAME}/db
fi
- |
if [[ $CI_COMMIT_TAG == *"#resetdb"* ]]; then
if [[ $CI_COMMIT_TAG != *"#backdb"* ]]; then
echo 'backup db'
NOW=$(date +"%Y-%m-%d-%H-%M-%S")
tar czf /home/app/${PROJECT_NAME}/backups/dbbackup-$NOW.tar.gz /home/app/${PROJECT_NAME}/db
fi
if [ "$(docker ps -a -q -f name=${PROJECT_NAME}-db)" ]; then
echo 'remove db container' && docker rm ${PROJECT_NAME}-db && rm -rf /home/app/${PROJECT_NAME}/db
fi
fi
- |
if [[ $CI_COMMIT_TAG == *"#resetnginx"* || $CI_COMMIT_TAG == *"#resetweb"* ]]; then
if [ "$(docker ps -a -q -f name=${PROJECT_NAME}-nginx)" ]; then
echo 'remove nginx container' && docker rm ${PROJECT_NAME}-nginx
fi
fi
- |
docker network inspect ${PROJECT_NAME}-network >/dev/null 2>&1 || \
docker network create --driver bridge ${PROJECT_NAME}-network
- |
if [ "$(docker ps -a -q -f name=${PROJECT_NAME}-nginx)" ];
then
echo "${PROJECT_NAME}-nginx exist"
else
echo "create ${PROJECT_NAME}-nginx"
docker create -p ${HTTP_EXPORT_PORT}:80 --cpus=1 --memory=1G --name=${PROJECT_NAME}-nginx \
--network=${PROJECT_NAME}-network \
-v /home/app/${PROJECT_NAME}/web:/usr/share/nginx/html:ro \
-v /home/app/${PROJECT_NAME}/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx
fi
- |
if [ "$(docker ps -a -q -f name=${PROJECT_NAME}-db)" ];
then
echo "${PROJECT_NAME}-db exist"
else
echo "create ${PROJECT_NAME}-db"
docker create --cpus=1 --memory=2G --name=${PROJECT_NAME}-db \
-p ${DB_EXPORT_PORT}:3306 \
--network=${PROJECT_NAME}-network \
-e MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} -e MYSQL_DATABASE=${DB_NAME} \
-e MYSQL_USER=${DB_USER} -e MYSQL_PASSWORD=${DB_PASSWORD} \
-v /home/app/${PROJECT_NAME}/db:/var/lib/mysql \
mysql:5.7 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci
fi
- |
if [ "$(docker ps -a -q -f name=${PROJECT_NAME}-api)" ];
then
echo "${PROJECT_NAME}-api exist"
else
echo "create ${PROJECT_NAME}-api"
docker create --cpus=2 --memory=4G --name=${PROJECT_NAME}-api \
--network=${PROJECT_NAME}-network \
--workdir=/opt/app \
-v /home/app/${PROJECT_NAME}:/opt/app \
adoptopenjdk/openjdk8:jre8u282-b08-debian \
java -jar app.jar \
--spring.config.location=/opt/app/
fi
- docker start ${PROJECT_NAME}-db
- sleep 1m
- docker start ${PROJECT_NAME}-api
- docker start ${PROJECT_NAME}-nginx
- sleep 1m
- docker ps
# - docker network inspect ${PROJECT_NAME}-network
# - docker logs ${PROJECT_NAME}-api
# - docker logs ${PROJECT_NAME}-nginx
rules:
- if: $CI_COMMIT_TAG != null
实现过程中遇到的问题
打包后机器人提示打包成功,这意味着服务器在跑脚本时并没有出错。但是访问对应网址却无法访问到资源。
查看日志发现,缺少 api 容器(项目名称为 dentistry)
但是日志显示 nginx 容器是启动的,这样的话就算缺少api也不应该访问不到资源。
当时在这个节点上停留了很久,最后找学长帮忙。
学长用老师的权限登录服务器后再次查看容器,发现只有一个 db 容器, 而 nginx 容器并没有被成功启动。
之后我又加入一些语句用于查看日志,得到如下结果。
先前打印的日志是在,容器启动后立刻打印的。而这次等待一分钟后再打印的结果可以看到,nignx 容器确实没有启动成功。
猜测或许是执行了几秒钟后发现存在问题,然后自动关闭了。
打印 nginx 容器的日志。
在 nignx 配置文件里找不到 dentistry-api。这是当然的,从上述截图可以看出 api 容器也没有启动成功。
打印 api 容器的日志。
no main manifest attribute, in app.jar 直接翻译为中文意思是 app.jar 中没有主要清单属性。
经过谷歌之后,才知道这个报错实际上说的是找不到主类入口(java 从主类开始启动)。
于是我就去谷歌 spring boot 在打包时如何配置主类入口。
最后发现在 spring boot 项目的 pom.xml 中是有配置主类入口的,只是被忽略了。(下图中88行 <skip></skip>
配置项表示是否跳过)
将88行注释后,再推送,新建 Tag,这次打包后所有容器都启动成功了。
访问对应地址自然也能够访问到我们的项目了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。