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: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 创建第一个项目
- 用 admin 登录
- 项目 → New Project(或某用户的 Projects → New Project)
- 创建一个空项目
gutai/test - 第一次提交时,项目 → 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 加到 GitLab:User 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 / gitlabMaven IntegrationPublish 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
|
坑 5:Source 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
|
坑 8:allow_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
|
坑 9:DOCKER_REGISTRY_PASSWORD 走的是 GitLab CI/CD Variables(项目 → Settings → CI/CD → Variables),不要写在 .gitlab-ci.yml 里。建议加 masked: true + protected: true。
六、GitLab CI vs Jenkins:何时选哪个
| 维度 | GitLab CI | Jenkins |
|---|
| 学习曲线 | 低(一个 .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 URL 和 Secret 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
参考资料