书接【Bug周刊】的gitlab-ci
构建部分,我们已经对一个 maven
项目进行了CI构建,实现每次提交代码后自动打包为 jar
包,并在docker in docker
的镜像中 build
为 docker 镜像。避免跳转麻烦,把上文的构建内容放到了基础部分。
需要对一个maven
项目进行自动化构建,要求每次提交都会触发构建,减少运维的工作量,将构建好的jar包打包成docker镜像并推送至私有的镜像仓库。
详情如下:
1、自定义开发的common
模块并不完善,也没有上传至私有的nexus
仓库,需要打包的功能模块依赖于common
2、项目依赖的部分jar
包需要从私有的nexus
仓库下载,需要配置对应的仓库地址
3、构建时间的优化、提升
1、在代码仓库中增加 .m2/settings.xml
文件,配置对应的私有nexus仓库地址、阿里云或者腾讯云的nexus地址提升下载速度
2、增加 localReposity
配置,告诉maven在找不到对应jar
包时,从本地读取,完成common
模块的引入。由于common模块是独立开发的,故和其他模块的pom父类并不一致,各个模块也有不同的配置,在原项目根目录下并没有pom文件,所以不能通过构建根pom文件的方式完成项目的打包。
3、引入cache
variables:
RELEASE_TAG: "1.0.0"
MAVEN_CLI_OPTS: "../.m2/settings.xml --batch-mode -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
cache:
key: ${CI_COMMIT_REF_SLUG} # cache键值对 减少mvn下载jar包的时间 key指向当前项目分支
paths:
- ~/.m2/repository/ # 缓存地址 镜像根目录下的 .m2/repository/ 文件夹
- target/
# CI构建两步
stages:
- package
- release
# 在maven镜像中构建jar包
package:
image: maven:3.6.1-jdk-8-alpine
stage: package
only:
- master # 触发构建的分支
tags:
- docker
script:
- cd test-common # 进入common模块打包
- mvn -s $MAVEN_CLI_OPTS -e package install # 打包 并将打包后生成的jar下载至 镜像根目录.m2/repository/路径下(本地仓库)
- cd ../test-app # 切出common 打包其他模块
- mvn -s $MAVEN_CLI_OPTS -e package
artifacts: # 构建好的jar文件上传 并设置过期时间
paths:
- test-app/target/test-app-application-exec.jar
expire_in: 1 hours
release: # docker in docker 在docker中构建jar为docker镜像
image: docker:20-dind
stage: release
only:
- master
tags:
- docker-slim
before_script:
- docker login --username=username -p $REPOS_PASSWORD test.com # 配置私有镜像仓库的账号 密码 地址
script:
- df -h
- docker build -t test/test-app:$RELEASE_TAG . # docker镜像标签
- docker push test.com/test-image/test-app:$RELEASE_TAG # 推送
目前的业务需求是,在原maven项目的基础上,根目录增加了同级的模块,需要分模块构建,并且每次提交代码只对产生变更的模块进行打包。
文件夹树如下:
|---.m2
|---gateway
|---moudle
|---hr
|---manage
|---adminstrive
|---.gitlab-ci.yml
|---dockerfile
|---startup.sh
1、对变更模块进行判断,需要使用 git diff
命令
单纯的maven3.6版本的镜像没有git,同时也未安装对应的命令行工具,如apt、apk、yum等,因此无法在 before_script
阶段安装 git
工具曲线救国。只能更换原来的镜像。
2、分模块构建,需要使用通用的 dockerfile
,即在gitlab-ci.yml
中对构建模块名进行判断,将此作为变量传入到dockerfile中。
docker build 命令提供了 --build-arg
的参数可以将变量传入dockerfile中。
3、明确CI文件 script
中的命令与 linux
终端命令细微的区别,避免出现标点的错误。
少年要不要来回试试,没有这些; \ \n &&
符号,可能会寸步难行。
4、选择正确的镜像,满足打包和构建的使用要求。
如果在 dind
镜像中没有git命令对模块判断进行 build
,不妨试试判断上一步,对产生变更的模块进行打包,是否有产物传给这一步骤,判断文件是否存在,比再安装一遍git省事多了。
5、纠正基础中的缓存地址。
.gitlab-ci.yml
variables:
# 将打包文件的路径作为变量 简化后续代码长度
RELEASE_TAG: "0.0.1"
JAR_HR: "module/module-hr/target/module-hr-exec.jar"
JAR_MANAGE: "module/module-manage/target/module-manage-exec.jar"
# .m2文件夹在代码的根目录 模块在 下两层 比如 moudle/hr moudle/manage 所以需要跳出两次
MAVEN_CLI_OPTS: "../../.m2/settings.xml --batch-mode -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
#cache:
# key: ${CI_COMMIT_REF_SLUG}
# paths:
# 这是第一次的缓存配置 路径错误 因为已经指明了maven 安装jar包的地址
# 为 maven.repo.local=$CI_PROJECT_DIR/.m2/repository
# - ~/.m2/repository/
# - target/
# - /usr/share/maven
# - /root/.m2/repository
cache:
key: m2-repo
paths:
# 这两个地址是等效的 都指向服务器的 /builds/username/projectName/.m2/repository
- .m2/repository/
- $CI_PROJECT_DIR/.m2/repository
stages:
- package
- release
package:
# 单纯maven不行 我用java带maven很合理吧 java镜像有apt-get 这很河狸吧
image: labelinsight/java-maven:3.6-jdk-8
stage: package
only:
- dev
tags:
- docker
before_script:
- apt-get install -y git
script:
# git diff 命令判断模块是否发生变更 并判断是否发生在对应的 hr manage 模块下
- if [[ -n $(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '^module/module-hr/') ]]; then
cd module/module-hr;
mvn -s $MAVEN_CLI_OPTS -e package;
cd ../../;
fi;
- if [[ -n $(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '^module/module-manage/') ]]; then
cd module/module-manage;
mvn -s $MAVEN_CLI_OPTS -e package;
cd ../../;
fi;
artifacts:
paths:
- module/module-hr/target/module-hr-exec.jar
- module/module-manage/target/module-manage-exec.jar
expire_in: 1 hours
release:
image: docker:20-dind
stage: release
only:
- dev
tags:
- docker-slim
before_script:
# - apk add git 判断文件是否存在 不用装git了
# $REPOS_PASSWORD 为管理员提前设置好的系统变量
- docker login --username=username -p $REPOS_PASSWORD docker.repos.cscec81.com:4433
script:
- df -h
# 打标签 --build-arg 传变量 变量名为 BUILD_JAR_NAME 对应值是开始在 variables部分 设定好的
# 推送至 私有的docker镜像仓库
- if [[ -f "module/module-hr/target/module-hr-exec.jar" ]]; then
docker build -t test/test-hr:$RELEASE_TAG --build-arg BUILD_JAR_NAME=$JAR_HR .;
docker push repos.test.com/test-image/test-hr:$RELEASE_TAG;
fi;
- if [[ -f "module/module-manage/target/module-manage-exec.jar" ]]; then
docker build -t test/test-manage:$RELEASE_TAG --build-arg BUILD_JAR_NAME=$JAR_MANAGE .;
docker push repos.test.com/test-image/test-manage:$RELEASE_TAG;
fi;
# 如果有更多模块 按照 加变量 -> 加git diff -> 加产物 -> 加判断推送 的流程,ctrl c v 就行了
dockerfile
#FROM openjdk:8-jre
FROM openjdk:8-jdk
# docker build 时传入的变量
ARG BUILD_JAR_NAME
# 配置JVM参数
ENV BASE_DIR="/app" \
JAVA_HOME="/usr/local/openjdk-8/" \
JAVA="/usr/local/openjdk-8/bin/java" \
JVM_XMS="8g" \
JVM_XMX="8g" \
JVM_XMN="3g" \
JVM_MS="128m" \
JVM_MMS="320m" \
TZ="Asia/Shanghai" \
BUILD_JAR_NAME=$BUILD_JAR_NAME
WORKDIR $BASE_DIR
# 测试环境配置 部署时注释掉 从rancher配置
# 如果使用 请换成你自己的ip 和 密码
ENV MYSQL_HOST=127.0.0.1 \
MYSQL_PORT=3306 \
MYSQL_SERVICE_DB_NAME=root \
MYSQL_SERVICE_PASSWORD=123456
# 时区配置 打印该模块路径 及 文件名
RUN rm -f /etc/localtime \
&& ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& echo "${BUILD_JAR_NAME}" \
&& JAR_FILE_NAME=$(basename "${BUILD_JAR_NAME}") \
&& echo "The jar file name is: ${JAR_FILE_NAME}"
# 注意 RUN命令的运行结果变量JAR_FILE_NAME 是局部变量 到下一层读取的话是 null
# 所以我直接将 docker build 传入的 BUILD_JAR_NAME 变量作为环境变量,在启动脚本 startup.sh 中处理了
# 拷贝jar包 及 脚本
COPY $BUILD_JAR_NAME ./startup.sh $BASE_DIR/
RUN chmod +x startup.sh
ENTRYPOINT ["sh","startup.sh"]
#!/bin/sh
# 打印启动的文件名
JAR_FILE_NAME=$(basename "${BUILD_JAR_NAME}")
echo "> JAR_FILE_NAME: ${JAR_FILE_NAME}"
JAVA_OPT="${JAVA_OPT} -server -Xms${JVM_XMS} -Xmx${JVM_XMX} -Xmn${JVM_XMN} -XX:MetaspaceSize=${JVM_MS} -XX:MaxMetaspaceSize=${JVM_MMS}"
# debug 模式下参数配置(传输、端口号、调试服务器、不在 JVM 启动时暂停,而是等待调试器连接后再开始执行。)
if [ "${MODE_DEBUG}" = "y" ]; then
JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=12345,server=y,suspend=n"
fi
# 异常处理机制 及 禁用大页面
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/logs/java_heapdump.hprof"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
# 垃圾回收 日志 轮换文件大小限制
mkdir -p "${BASE_DIR}/logs" && touch "${BASE_DIR}/logs/gc.log"
JAVA_OPT="${JAVA_OPT} -Xloggc:${BASE_DIR}/logs/gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"
# 配置文件编码 Redis等ip和密码 启动jar包
JAVA_OPT="${JAVA_OPT} -Dfile.encoding=utf-8"
#JAVA_OPT="${JAVA_OPT} -DMYSQL_HOST=${MYSQL_HOST} -DMYSQL_PORT=${MYSQL_PORT} -DMYSQL_SERVICE_DB_NAME=${MYSQL_SERVICE_DB_NAME} -DMYSQL_SERVICE_PASSWORD=${MYSQL_SERVICE_PASSWORD}"
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/${JAR_FILE_NAME}"
echo "This server is starting, you can docker logs your container"
echo ${JAVA_OPT}
exec $JAVA ${JAVA_OPT}