Spring Boot 项目 GitLab CI/CD 自动构建并推送到 Harbor 教程
创建一条稳定、可审计的流水线:提交即构建,分支/标签分别产出测试/生产镜像,缓存加速、凭据安全、日志清晰。下面给出一篇从零到一的完整教程,并提供可直接复制使用的 .gitlab-ci.yml。
前提与准备
- 环境组件:
- GitLab: 14+(CE/EE 或 GitLab.com)
- GitLab Runner: Docker executor,Runner 主机已安装 Docker 并允许作业访问宿主机 Docker(通过
DOCKER_HOST=unix:///var/run/docker.sock) - Harbor: 2.5+,已启用 HTTPS,创建了项目(如
ayy-server)
- 凭据与变量(GitLab 项目或组级 CI/CD Variables):
- HARBOR_USER: Harbor 用户或 Robot 账户(推荐机器人账号)
- HARBOR_PASS: 对应密码或 token(Masked、Protected)
- SSH_PRIVATE_KEY: 若需要后续 SSH 部署(可选,Masked)
- Runner 节点证书(如 Harbor 使用自签名证书):
- Harbor CA: 放到 Runner 节点
/etc/docker/certs.d/192.168.0.12:5080/ca.crt,并重启 Docker
- Harbor CA: 放到 Runner 节点
- Dockerfile(推荐多阶段构建 + 非 root 用户):
- 使用你已有的多阶段 Dockerfile(Maven 构建 + 精简 JRE 运行),并确保端口、配置目录、日志目录、环境变量设置合理
流水线设计与策略
- 分支/环境策略:
- main 分支: 产出测试镜像,标签为
test-${CI_COMMIT_SHORT_SHA}与test-latest - tag 发布: 产出生产镜像,标签为
vX.Y.Z与prod-latest(强制校验版本格式)
- main 分支: 产出测试镜像,标签为
- 构建缓存:
- 使用 BuildKit 内联缓存 + “缓存镜像”标签(
test-cache/prod-cache)提升二次构建速度
- 使用 BuildKit 内联缓存 + “缓存镜像”标签(
- 登录与推送:
- 每个作业在
before_script里登录 Harbor,推送成功后清理本地镜像,保持 Runner 干净
- 每个作业在
- 凭据安全:
- Harbor 凭据使用变量注入,不写入仓库;变量设为 Masked + Protected
- 可审计性:
- 构建阶段生成
build.env(dotenv artifacts),在后续阶段复用镜像标签与环境信息
- 构建阶段生成
完整 .gitlab-ci.yml(可复制使用)
stages:- build- pushvariables:DOCKER_HOST: unix:///var/run/docker.sockDOCKER_BUILDKIT: "1"REGISTRY: 192.168.0.12:5080PROJECT: ayy-serverIMAGE_NAME: ayy-server-java17GIT_LFS_SKIP_SMUDGE: "1"# ==================== 公共锚点 ====================
.docker-base: &docker-baseimage: docker:20.10.24before_script:# 验证 Docker 可用(使用宿主机 Docker)- echo "使用宿主机 Docker"- docker info- docker version# 登录 Harbor- echo "$HARBOR_PASS" | docker login -u "$HARBOR_USER" --password-stdin $REGISTRY.ssh-base: &ssh-baseimage: alpine:latestbefore_script:- apk add --no-cache openssh-client curl- eval $(ssh-agent -s)- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -- mkdir -p ~/.ssh- chmod 700 ~/.ssh# Maven 缓存(加速依赖下载)
cache:key: maven-cachepaths:- .m2/repository# ==================== 测试环境(main 分支) ====================
build-test:<<: *docker-basestage: buildscript:- export IMAGE_TAG="test-${CI_COMMIT_SHORT_SHA}"- export CACHE_IMAGE="$REGISTRY/$PROJECT/$IMAGE_NAME:test-cache"- echo "开始构建测试镜像 $IMAGE_TAG"# 尝试拉取缓存镜像- docker pull $CACHE_IMAGE || echo "缓存镜像不存在,将从头构建"# 构建镜像- |docker build \--progress=plain \--cache-from $CACHE_IMAGE \--build-arg BUILDKIT_INLINE_CACHE=1 \--build-arg BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \--build-arg GIT_COMMIT=$CI_COMMIT_SHORT_SHA \-t $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG \-t $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest \-t $CACHE_IMAGE \.- echo "✓ 镜像构建成功"- docker images | grep $IMAGE_NAME# 保存构建信息- echo "IMAGE_TAG=$IMAGE_TAG" > build.env- echo "ENVIRONMENT=test" >> build.envartifacts:reports:dotenv: build.envexpire_in: 1 dayonly:- maintags:- jdk17retry:max: 2when:- runner_system_failure- stuck_or_timeout_failurepush-test:<<: *docker-basestage: pushdependencies:- build-testscript:- echo "开始推送测试镜像 $IMAGE_TAG"- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:test-cache- echo "✓ 镜像推送成功"# 显示推送的镜像信息- echo "推送的镜像:"- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG"- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest"after_script:# 清理本地镜像- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG || true- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:test-latest || true- docker system prune -f || trueonly:- maintags:- jdk17retry:max: 2when:- runner_system_failure# ==================== 生产环境(tag 发布) ====================
build-prod:<<: *docker-basestage: buildscript:- export IMAGE_TAG="${CI_COMMIT_TAG}"- export CACHE_IMAGE="$REGISTRY/$PROJECT/$IMAGE_NAME:prod-cache"- echo "开始构建生产镜像 $IMAGE_TAG"# 验证标签格式 (v1.0.0)- |if ! echo "$IMAGE_TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; thenecho "❌ 错误: 标签格式必须为 vX.Y.Z (例如: v1.0.0)"echo "当前标签: $IMAGE_TAG"exit 1fi- echo "✓ 标签格式验证通过 $IMAGE_TAG"# 尝试拉取缓存镜像- docker pull $CACHE_IMAGE || echo "缓存镜像不存在,将从头构建"# 构建镜像- |docker build \--progress=plain \--cache-from $CACHE_IMAGE \--build-arg BUILDKIT_INLINE_CACHE=1 \--build-arg BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \--build-arg GIT_TAG=$CI_COMMIT_TAG \-t $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG \-t $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest \-t $CACHE_IMAGE \.- echo "✓ 镜像构建成功"- docker images | grep $IMAGE_NAME# 保存构建信息- echo "IMAGE_TAG=$IMAGE_TAG" > build.env- echo "ENVIRONMENT=production" >> build.envartifacts:reports:dotenv: build.envexpire_in: 30 daysonly:- tagstags:- jdk17retry:max: 2when:- runner_system_failure- stuck_or_timeout_failurepush-prod:<<: *docker-basestage: pushdependencies:- build-prodscript:- echo "开始推送生产镜像 $IMAGE_TAG"- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest- docker push $REGISTRY/$PROJECT/$IMAGE_NAME:prod-cache- echo "✓ 镜像推送成功"# 显示推送的镜像信息- echo "推送的镜像:"- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG"- echo " - $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest"after_script:# 清理本地镜像- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:$IMAGE_TAG || true- docker rmi $REGISTRY/$PROJECT/$IMAGE_NAME:prod-latest || true- docker system prune -f || trueonly:- tagstags:- jdk17retry:max: 2when:- runner_system_failure
关键点讲解与常见坑
- Docker 访问模式:
- 说明: 该流水线采用“使用宿主机 Docker”的模式(非 DIND 服务),通过
DOCKER_HOST=unix:///var/run/docker.sock直接访问 Runner 主机的 Docker。优点是不需要privileged,性能和兼容性好;缺点是需确保 Runner 主机安全与权限隔离。
- 说明: 该流水线采用“使用宿主机 Docker”的模式(非 DIND 服务),通过
- Harbor 登录与命名:
- 仓库路径:
$REGISTRY/$PROJECT/$IMAGE_NAME:<tag>(如192.168.0.12:5080/ayy-server/ayy-server-java17:test-latest) - 建议: 使用项目/应用分层命名;避免顶层
library;区分test-*与prod-*标签。
- 仓库路径:
- 构建缓存:
- 机制:
--build-arg BUILDKIT_INLINE_CACHE=1 + --cache-from <cache image>搭配指定的缓存镜像标签,提升二次构建速度。 - 前提: 第一次构建没有缓存是正常的;后续才会生效。
- 机制:
- 版本规范校验(生产):
- 规则:
vX.Y.Z(语义化版本);不符合直接失败,避免随意发布“生产”镜像。
- 规则:
- 证书与私有仓库:
- 自签名: Runner 主机需信任 Harbor CA;如果仍报
x509,确认端口与域名一致、证书链完整,必要时将证书也挂载到作业环境。
- 自签名: Runner 主机需信任 Harbor CA;如果仍报
- 清理策略:
- after_script: 清理构建镜像与 dangling 层,防止 Runner 主机长期磁盘膨胀。
与 Dockerfile 的配合要点
- 构建参数联动:
- 建议: 在 Dockerfile(构建阶段)里接受
BUILD_TIME,GIT_COMMIT/GIT_TAG,打入镜像元信息;便于审计与追溯。
- 建议: 在 Dockerfile(构建阶段)里接受
- 端口与配置:
- 暴露端口: 与应用实际端口一致(示例为
48087)。 - 配置目录:
/app/config支持外部挂载;流水线构建阶段不需携带敏感配置,部署时再注入。
- 暴露端口: 与应用实际端口一致(示例为
- 非 root 与权限:
- 用户: 使用非 root 用户运行;确保日志目录与配置目录权限正确(避免 logback 写入失败)。
下一步:部署联动与质量门禁(可选增强)
- 质量门禁:
- 漏洞扫描: 在 Harbor 开启 Trivy;推送后自动扫描,发现高危阻断发布。
- SBOM: 构建后生成 SBOM(如 Syft),存档以提升可追溯性。
- 部署联动:
- Kubernetes: 推送成功后,触发 Helm/Argo CD 更新;镜像标签使用
commit_sha实现幂等与回滚。 - VM/主机: 使用
ssh-base锚点执行远程拉取与滚动重启脚本(确保蓝绿或最小化停机)。
- Kubernetes: 推送成功后,触发 Helm/Argo CD 更新;镜像标签使用
