Featured image of post GitLab CI 实战:GitLab EE 部署、Pipeline 接入 checkstyle 与 Java 项目自动化部署

GitLab CI 实战:GitLab EE 部署、Pipeline 接入 checkstyle 与 Java 项目自动化部署

一份"自托管 GitLab + 跑 CI + Java 项目自动部署"完整路径——从 GitLab EE 镜像部署、root/普通用户管理、SSH 免密、Webhook 触发 Jenkins、到集成 checkstyle 做代码规范检查,全部走 docker 实战视角。

GitLab 不只是一个"私有 GitHub"——从 2015 年起,GitLab 把 CI/CD 做成"开箱即用"的能力。GitLab CI 在很多公司里其实是比 Jenkins 更省心的选择:不用单独搭 CI 服务,仓库一推代码,.gitlab-ci.yml 一写,pipeline 自动跑。

自托管 GitLab EE 镜像Pipeline 跑 Java 构建、**代码规范检查(checkstyle)**这三件事串起来,有很多实战细节。这篇文章按"先把 GitLab 跑起来 → 接 Jenkins 做自动部署 → 集成 checkstyle"的顺序梳理——和之前 0.4 批次的"CI 工具对比"完全不同,本文只聚焦 GitLab CI + Java 这条具体路径

阅读对象:需要从零搭一套自托管 GitLab,或正在用 GitLab 但想加 checkstyle / 自动化部署的开发者、运维
覆盖范围:GitLab EE 镜像部署、首次配置 / 创建用户 / SSH 免密、GitLab → Jenkins Webhook、Java 项目 Maven 构建、SSH 传包 + 远端 docker-compose restart、checkstyle 集成、.gitlab-ci.yml 完整模板

一、GitLab EE 镜像部署

1.1 选镜像

GitLab 的 Docker 镜像有三个变体:

镜像用途
gitlab/gitlab-ce:latest社区版(CE),免费,但功能受限(无 SAML、无审计、无 Geo 镜像等)
gitlab/gitlab-ee:latest企业版(EE),有 30 天试用(License 文件未上传时仍可使用)
gitlab/gitlab-ee:<version>指定版本的 EE 镜像

When to use:中小团队(< 50 人)用 CE 足够;对审计、合并审批、Geo 镜像、SCIM 等有要求选 EE。镜像体积大(约 2 GB),首次启动慢——容器内要执行 reconfigure 脚本。

1.2 docker-compose 部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# docker-compose.yml
version: '3'
services:
  gitlab:
    image: 'gitlab/gitlab-ee:13.12.9-ee.0'
    container_name: gitlab
    restart: always
    privileged: true
    ports:
      - '8000:80'        # HTTP
      - '4433:443'       # HTTPS
      - '2222:22'        # SSH(宿主机 22 容易被占用,用 2222)
    volumes:
      - '/home/docker/gitlab/nginx.conf:/etc/gitlab'
      - '/home/docker/gitlab/logs:/var/log/gitlab'
      - '/home/docker/gitlab/data:/var/opt/gitlab'
      - '/etc/localtime:/etc/localtime:ro'
1
docker-compose up -d

坑 1:GitLab 启动慢是因为内部有 reconfigure(生成 nginx / sshd / postgresql 配置)、migrations(数据库迁移)。首次启动 5-10 分钟是正常的,用 docker logs -f gitlab | grep "Reconfigured" 跟踪进度。

1.3 首次访问

浏览器访问 http://<HOST>:8000/,首次进入会要求设置 root 密码。

坑 2:默认情况下 root 账号没有密码(启动日志里也没输出),必须通过 Web UI 第一次设置。设置后才能用 root 登录。

坑 3:HTTP 端口 80 → 宿主机 8000、SSH 端口 22 → 宿主机 2222 是常用的"避让"做法——宿主机 22 经常被 SSH 服务占用。

二、初始化项目仓库

2.1 关闭注册 + 创建用户

关闭公开注册Admin Area → Settings → General → Sign-up restrictions → 关闭 Sign-up enabled

创建用户Admin Area → Users → New User

2.2 创建第一个项目

  1. 用 admin 登录
  2. 项目 → New Project(或某用户的 Projects → New Project)
  3. 创建一个空项目 gutai/test
  4. 第一次提交时,项目 → Settings → Repository → Protected branches:受保护分支默认锁了 master先解锁才能 push

2.3 SSH 免密推送(管理员操作)

生成 SSH 密钥(本地 Git Bash):

1
2
3
4
ssh-keygen -t rsa -C '<YOUR_EMAIL>'
# 一直回车
# 生成位置: C:\Users\<USERNAME>\.ssh\id_rsa
#  公钥: id_rsa.pub

配置 Git 全局

1
2
git config --global user.name "<YOUR_NAME>"
git config --global user.email "<YOUR_EMAIL>"

把 id_rsa.pub 加到 GitLabUser Settings → SSH Keys → 粘贴 id_rsa.pub 内容

推送本地代码

1
2
3
4
5
6
7
git init
git add .
git commit -m "init"
git remote add origin ssh://git@<HOST>:2222/gutai/test.git
git push -u origin master
# 第一次 push 会被 Protected branches 拒绝, 先在网页上解锁 master
git push -f origin master

坑 4:用 SSH 推送时,git@<HOST>:2222 的端口要带——ssh://git@<HOST>:2222/gutai/test.git 形式。如果用 HTTP 推送(http://<HOST>:8000/...),首次需要输入账号密码。

三、Java 项目自动化部署

3.1 整体流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
开发者 push 代码
GitLab 触发 Webhook
Jenkins 收到 Webhook
Jenkins 拉代码 → mvn clean package
SSH 传 jar 到目标服务器
目标服务器 docker-compose restart

3.2 Jenkins 侧:Maven 任务 + Publish Over SSH

Jenkins 插件

  • git / gitlab
  • Maven Integration
  • Publish Over SSH

Jenkins 系统配置系统管理 → 系统配置 → Publish Over SSH

1
2
3
4
5
6
7
新增一个 SSH Server
  name: production-server
  Hostname: <INTERNAL_HOST>
  username: root
  use password auth
  password: <REDACTED>
  Remote directory: /home

3.3 任务配置

新建任务 → 构建一个 Maven 项目

源码管理

1
2
3
Repository URL: http://<GITLAB>:8000/gutai/test.git
Credentials:  <刚加的 root/密码>
Branches: */master

构建触发器

1
2
3
4
Build when a change is pushed to GitLab
  → 点 "高级" → Generate Secret token
  → 复制生成的 token
  → 粘贴到 GitLab 项目的 Webhook

Build

1
2
Root POM: jeecg-boot/pom.xml
Goals and options: clean install -P prod -Dmaven.test.skip=true

构建后操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Send build artifacts over SSH
  → SSH Server: production-server
  → Transfers:
       Source files: jeecg-boot/jeecg-boot-module-system/target/jeecg-boot-module-system-2.4.2.jar
       Remove prefix: jeecg-boot/jeecg-boot-module-system/target
       Remote directory: /home/project/gutai/backend
       Exec command:
         rm -f /home/project/gutai/backend/jeecg-boot-module-system-2.4.2.jar
         mv /home/jeecg-boot/jeecg-boot-module-system/target/jeecg-boot-module-system-2.4.2.jar /home/project/gutai/backend
         rm -rf /home/jeecg-boot
         cd /home
         docker-compose restart backend

丢弃旧的构建

1
2
保持构建最大个数: 10
保持构建天数: 7

坑 5Source files 路径要和 Maven 实际产出的 jar 路径完全一致——Jenkins 不会"模糊匹配",路径写错就直接报"找不到文件"。

3.4 GitLab 侧:配 Webhook

GitLab 项目 → Settings → Webhooks

1
2
3
4
URL: http://<JENKINS>/project/<JOB_NAME>
Trigger: Push events
  (可选) SSL verification: Enable SSL verification
Secret token: <Jenkins 生成的 token>

点击 “Add Webhook” → “Test” → “Push events”

如果 200 OK 说明配置成功;如果 403 / 404 检查 Jenkins Job 的"构建触发器"是否勾选正确。

坑 6:GitLab 11+ Webhook 默认不允许内网地址——内网 IP 直接 403 “Url is blocked: requests to the local network are not allowed”。解决:Admin Area → Settings → Network → Outbound requests → 勾选 “Allow requests to the local network from hooks and services”

3.5 提交触发验证

1
2
3
4
5
6
# 本地代码修改
echo "// test" >> README.md
git add .
git commit -m "test webhook"
git push origin master
# 30 秒内, Jenkins 应该有新构建被触发

四、集成 checkstyle:代码规范检查

为什么需要 checkstyle?mvn package 只编译过、mvn test 只跑单元测试——它们不检查命名规范、import 顺序、注释规范、代码风格。checkstyle 才是"代码可读性"的守门员

4.1 checkstyle 的两种集成方式

方式 A:Maven 本地跑(开发期)

pom.xml 加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-checkstyle-plugin</artifactId>
      <version>3.3.0</version>
      <configuration>
        <configLocation>checkstyle.xml</configLocation>
        <includeTestSourceDirectory>true</includeTestSourceDirectory>
        <consoleOutput>true</consoleOutput>
        <failsOnError>true</failsOnError>
        <linkXRef>false</linkXRef>
      </configuration>
      <executions>
        <execution>
          <id>validate</id>
          <phase>validate</phase>
          <goals>
            <goal>check</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

坑 7:默认 failsOnError=true —— 任意一行代码违反规范,mvn validate 就失败。建议先设 false 看报告,确认规则合适后再打开

方式 B:CI 流水线跑(提交期)

GitLab CI 的 .gitlab-ci.yml 加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
stages:
  - check
  - build
  - deploy

checkstyle:
  stage: check
  image: maven:3.9-eclipse-temurin-17
  script:
    - mvn checkstyle:check -Dmaven.test.skip=true
  allow_failure: true        # 失败不阻塞后续 stage
  artifacts:
    when: always
    reports:
      junit: target/checkstyle-result.xml

坑 8allow_failure: true 让 checkstyle 失败不阻塞——但生产项目应该改为 false,强制规范。

4.2 自定义 checkstyle 规则

checkstyle.xml 放在项目根目录(或 config/checkstyle/ 下):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
  "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
  "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
  <property name="charset" value="UTF-8"/>
  <property name="severity" value="warning"/>
  <property name="fileExtensions" value="java, properties, xml"/>

  <module name="TreeWalker">
    <!-- 命名规范 -->
    <module name="PackageName"/>
    <module name="TypeName"/>
    <module name="MethodName"/>
    <module name="LocalVariableName"/>
    <module name="MemberName"/>
    <module name="ParameterName"/>
    <module name="ConstantName"/>

    <!-- import 规范 -->
    <module name="AvoidStarImport"/>
    <module name="UnusedImports"/>
    <module name="ImportOrder">
      <property name="groups" value="java,javax,org,com,*"/>
      <property name="sortStaticImportsAlphabetically" value="true"/>
    </module>

    <!-- 代码风格 -->
    <module name="LeftCurly"/>
    <module name="RightCurly"/>
    <module name="WhitespaceAround"/>
    <module name="ArrayTypeStyle"/>
    <module name="MagicNumber"/>
    <module name="NeedBraces"/>
    <module name="EmptyBlock"/>
    <module name="MethodLength">
      <property name="max" value="200"/>
    </module>
    <module name="ParameterNumber">
      <property name="max" value="8"/>
    </module>
  </module>
</module>

4.3 进阶:把 checkstyle 接到 GitLab Merge Request

真实场景:开发提交 MR(Merge Request),CI 跑 checkstyle,有违规就阻止合并

.gitlab-ci.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
include:
  - template: Security/Secret-Detection.gitlab-ci.yml

stages:
  - validate
  - build
  - deploy

validate:
  stage: validate
  image: maven:3.9-eclipse-temurin-17
  script:
    - mvn checkstyle:check
  rules:
    - if: $CI_MERGE_REQUEST_ID
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

GitLab 设置 MR 必跑 pipeline项目 → Settings → Merge requests → Merge checks → 勾选 "Pipelines must succeed" → 保存

这样配置后,MR 不跑过 checkstyle 就不能合并

五、.gitlab-ci.yml 完整模板(Java + Docker)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
image: maven:3.9-eclipse-temurin-17

variables:
  MAVEN_OPTS: "-Xms1g -Xmx2g -Dmaven.repo.local=$CI_PROJECT_DIR/.m2"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
  DOCKER_REGISTRY: "<private-registry>"
  DOCKER_IMAGE: "$DOCKER_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHORT_SHA"

stages:
  - test
  - build
  - push
  - deploy

# 单元测试
test:
  stage: test
  script:
    - mvn $MAVEN_CLI_OPTS test
  artifacts:
    when: always
    reports:
      junit:
        - "**/target/surefire-reports/TEST-*.xml"
      coverage_report:
        coverage_format: cobertura
        path: target/site/cobertura/coverage.xml
  coverage: '/Line coverage:\s*(\d+.\d+)\%/'

# 打 jar
build:
  stage: build
  script:
    - mvn $MAVEN_CLI_OPTS clean package -DskipTests
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 day
  only:
    - main
    - develop
    - /^release\/.*$/

# 构建并推送 Docker 镜像
docker-build:
  stage: push
  image: docker:20.10
  services:
    - docker:20.10-dind
  before_script:
    - echo "$DOCKER_REGISTRY_PASSWORD" | docker login -u "$DOCKER_REGISTRY_USER" --password-stdin $DOCKER_REGISTRY
  script:
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  only:
    - main
    - /^release\/.*$/

# 部署到 staging
deploy-staging:
  stage: deploy
  image: alpine:3.18
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - ssh-keyscan -H $STAGING_HOST >> ~/.ssh/known_hosts
  script:
    - ssh $STAGING_USER@$STAGING_HOST "docker pull $DOCKER_IMAGE && docker-compose -f /home/project/docker-compose.yaml up -d --no-deps backend"
  environment:
    name: staging
    url: http://$STAGING_HOST
  only:
    - develop

坑 9DOCKER_REGISTRY_PASSWORD 走的是 GitLab CI/CD Variables项目 → Settings → CI/CD → Variables),不要写在 .gitlab-ci.yml。建议加 masked: true + protected: true

六、GitLab CI vs Jenkins:何时选哪个

维度GitLab CIJenkins
学习曲线低(一个 .yml 文件)中(需配插件、节点、凭据)
插件生态中(GitLab 官方 + 社区)极强(1800+ 插件)
多语言支持强(每个 stage 独立 image)
大规模 Runner 管理较弱强(K8s plugin 动态 Agent)
流水线即代码原生支持支持(Jenkinsfile
适用场景代码托管 + CI 一体化已有 Jenkins 集群、复杂编排

真实情况:很多公司的混合架构是 GitLab 托管代码 + Jenkins 跑 CD——本文讲的"GitLab 触发 Jenkins"就是这个模式。如果你的项目全在 GitLab 里、部署也不复杂,直接 GitLab CI 一步到位;如果部署涉及多套环境(dev/test/staging/prod)+ 复杂审批,建议 GitLab CI 跑构建 + Jenkins 跑发布。

七、常见排错

7.1 GitLab Webhook 403 “Url is blocked”

症状:GitLab → 项目 → Webhooks → Test → Hook execution failed: URL is blocked

解决Admin Area → Settings → Network → Outbound requests → 勾选 "Allow requests to the local network from hooks and services"

7.2 Jenkins 收到 Webhook 但 403

症状:GitLab Webhook 测试 200 OK,但 Jenkins Job 没触发

解决:检查 Jenkins 的"构建触发器"里 GitLab webhook URLSecret token 与 GitLab 是否完全一致。

7.3 mvn package OOM

症状:Jenkins Slave OOMKilled

解决:在 MAVEN_OPTS-Xmx2g;或换 K8s 动态 Agent(资源独立)。

7.4 checkstyle 输出乱码

症状:checkstyle 报告里中文乱码

解决:在 pom.xml 加:

1
2
3
4
5
6
7
<plugin>
  <artifactId>maven-checkstyle-plugin</artifactId>
  <configuration>
    <inputEncoding>UTF-8</inputEncoding>
    <outputEncoding>UTF-8</outputEncoding>
  </configuration>
</plugin>

7.5 GitLab Runner 启动慢 / 卡

GitLab Runner 是 GitLab CI 的执行器。自托管 Runner 推荐装在 K8s 上,配合 kubernetes executor

1
2
3
4
5
# 在 K8s 上跑 GitLab Runner(Helm)
helm repo add gitlab https://charts.gitlab.io
helm install gitlab-runner gitlab/gitlab-runner \
  --set runnerRegistrationToken=<REGISTRATION_TOKEN> \
  --set rbac.create=true

八、写在最后

GitLab CI 的核心价值是 “一个 YAML 走完整条流水线”——不需要在 UI 上点几百次"保存",所有配置都在 .gitlab-ci.yml 里。代码规范、单元测试、构建、推送镜像、部署——一条流水线串起来。

下一步建议:

  • Auto DevOps:GitLab 自带的"零配置"流水线(开箱即用)
  • GitLab Container Registry:仓库一开,镜像就推到 GitLab 自带 Registry
  • GitLab Geo:多地域镜像仓库(EE 特性)
  • 多 Runner 节点:用 K8s executor 弹性伸缩 Runner

参考资料

使用 Hugo 构建
主题 StackJimmy 设计