搞 CI/CD 的人,十有八九都绕不开 GitLab 和 Jenkins 这对组合。GitLab 管代码仓库、管分支、管 Merge Request,Jenkins 负责拉代码、跑构建、跑测试、出产物。两者各司其职,但真正麻烦的是把它们“缝”在一起。今天我把实际配置 Jenkins 和 GitLab 集成的完整过程拆开讲一遍,从触发器怎么选、凭据怎么配,到踩过的坑和排查思路,尽量给到可以直接照着抄的程度。
如果你是刚接触持续集成的开发者,或是在已有 GitLab 体系的团队里想把 Jenkins 接进去,这篇文章基本能覆盖你前几次集成会遇到的大部分问题。内容偏实操,不会堆太多概念,同时每个关键步骤我会解释为什么这样做,方便你后面自己调整。
1. 集成设计与思路拆解
1.1 为什么是 Jenkins + GitLab,而不是只用 GitLab CI
很多团队问过:GitLab 自己就有 CI/CD,为什么还要再引一个 Jenkins?
实话实说,如果项目是从零开始,团队又愿意用 YAML 写流水线,GitLab CI 完全能胜任,而且集成成本最低。但现实情况往往是:团队里已经有大量 Jenkins Job,插件体系早就搭好了,比如对接企业内部的发布平台、调用特定的测试工具、做各种自定义的构建后动作;这时候硬要把所有东西迁到 GitLab CI,成本远高于把两者打通。另一种典型场景是复杂流水线——多分支并行构建、跨项目联动、大量参数化任务,Jenkins 的插件生态和任务管理能力依然更顺手。
所以我的建议很简单:不要讨论谁取代谁,而是把 GitLab 当作代码源和事件源,把 Jenkins 当作执行引擎。GitLab 负责“什么时候触发”,Jenkins 负责“触发之后做什么”,这就是集成的核心思想。
1.2 触发方式选型:Webhook 优先,轮询留作兜底
Jenkins 获取 GitLab 代码变更的方式有两种:轮询和 Webhook。
轮询是老办法,Jenkins 每隔一段时间去 GitLab 查一次“代码变没变”。比如配置H/2 * * * *,每两分钟检查一次。优点是配置简单,对网络要求低;缺点是会有延迟,而且会产生大量无效请求,代码量大了之后两个服务都被拖着。
Webhook 是 GitLab 主动把“代码有变更”这个事件推给 Jenkins,达到的是毫秒级触发。思路就是:开发者git push,GitLab 收到后,向 Jenkins 的特定地址发一个 HTTP POST 请求,Jenkins 收到请求后开始构建。
二选一的话,毫无疑问是Webhook。但选型要看你的部署环境:
- 如果 Jenkins 和 GitLab 在同一个内网,Webhook 放通即可,很轻松。
- 如果 Jenkins 在公网、GitLab 在自己机房里,需要保证双向网络可达,Webhook 地址要是 Jenkins 能被 GitLab 访问到的地址。
- 如果两边网络通不了,只能用轮询。
还有一点容易踩坑:轮询和 Webhook 不要同时开。不然推一次代码可能触发两次构建,看着没什么,实际上会浪费构建资源。我之前帮某团队排查过“为什么 push 一次 Jenkins 跑了两遍”的问题,最后发现就是两个开关都开着。
1.3 集成后的整体工作流程
先看一个最简单的流程:
- 开发者在 GitLab 上推送代码到指定分支(比如
main)。 - GitLab 识别到这个 push 事件,请求 Jenkins 上配置好的 Webhook 地址。
- Jenkins 收到请求后,找到对应的 Job,按配置从 GitLab 克隆代码。
- Jenkins 执行构建脚本(编译、测试、打包等)。
- 构建结束,结果展示在 Jenkins 页面上,也可以通知回 GitLab。
整个链路里有两个关键点:触发器要能准确捕获“哪个分支的变更”,以及“让 Jenkins 拉取代码时不卡在认证上”。这两个痛点,会在下面的实操环节里具体展开。
2. 环境准备与前置配置
2.1 版本选型与硬件建议
先说版本。Jenkins 建议直接用官方 LTS 版本,别追求 latest,插件兼容性大坑。GitLab 侧变量也不少,社区版和商业版在 Webhook 功能上没本质区别,已有 GitLab 项目直接接着用就行。
我整理了一个基础清单:
| 组件 | 建议版本/要求 | 备注 |
|---|---|---|
| Jenkins | 当前 LTS 版本 | 比如 2.4xx 系列 |
| GitLab | 你的现有版本即可 | 太老的 GitLab 注意 API 差异 |
| GitLab Plugin 插件 | 与 Jenkins 版本兼容的版本 | 插件名是 GitLab Plugin |
| Git Plugin | 随 Jenkins 自带 | 用于拉取 Git 代码 |
| JDK | Jenkins 要求对应版本 | 比如 Jenkins 需要 JDK 11 或 17(看版本) |
| 构建机资源 | 至少 4C8G | 跑大型项目建议更高 |
硬件这点要专门提醒:很多人把 Jenkins 装在 2C4G 的小机器上,前端项目还好,如果构建 Java/大型 C++ 项目,内存一上去,Jenkins 直接卡死,Webhook 请求都处理不过来。最好先确认构建任务的资源需求再定配置,别省。
2.2 Jenkins 侧的前期准备
如果是新装的 Jenkins,初始化过程会要求创建管理员账号,这些常规操作就不啰嗦了——但插件安装一定要确认。
进入系统管理 → 插件管理 → 可选插件,搜索安装这几个:
Git Plugin:让 Jenkins 的 Job 能从 Git 仓库拉代码GitLab Plugin:处理 GitLab 的 Webhook 请求,也提供了日志输出到 GitLab 的功能Pipeline:如果你准备用流水线方式写构建步骤(强烈建议),这个必须装Credentials Plugin:一般随 Jenkins 自带,用于管理凭据
这里有个操作习惯问题:我见过很多人在插件安装时无脑“一键安装全部”,结果装了一堆用不到的插件,系统变慢,权限配置也复杂。按需安装就好。
装完插件后,建议顺手去系统管理 → 全局安全配置看一下CSRF 防护设置。新版 Jenkins 默认开得很严,如果 GitLab Webhook 触发时被 CSRF 拦截,会出现“403 无权限”的情况。正常配置不会遇到这个问题,但一旦遇到,要能想到去这里排查,而不要急着把 CSRF 全局关掉——那是很危险的做法。
2.3 GitLab 侧的项目准备
GitLab 这边要确认三件事:
- 项目存在,并且你有至少 Maintainer 级别权限。不是所有人都能配置 Webhook。
- 分支模型尽量简单清晰。集成初期别玩太花的分支策略,一般就是
main触发正式构建,dev/feature/*触发测试构建。 - 为 Jenkins 准备一个独立的账号,别用你的个人账号。这个账号用来做两件事:clone 仓库、通过 API 操作 GitLab。
独立账号这个点很多人忽略。如果哪天某人离职,他的个人账号被禁用,Jenkins 的拉取凭据也跟着失效,构建全部失败,这就尴尬了。团队里最好有一个类似jenkins的机器人账号,单独走一套凭据权限。
3. 核心集成实操
3.1 在 Jenkins 中创建 Job 并配置 Git 仓库
先走最基础的自由风格项目,把拉代码这条链路打通,再谈触发器。
在 Jenkins 首页点击新建任务,输入名称,选自由风格项目。
源码管理选择Git,填仓库地址:
git@gitlab.example.com:group/project.git这里推荐用 SSH 地址,不要用 HTTPS。原因是 SSH key 的权限更好控制,也不会遇到 GitLab 对 HTTPS 密码/Token 频繁过期的问题。
填完地址后,下面会出现Credentials下拉框,点击添加:
- 类型选择
SSH Username with private key - Username 随便填个有意义的标识,比如
jenkins - Private Key 填入 Jenkins 机器上用
ssh-keygen生成的私钥内容
生成 key 可以直接在 Jenkins 服务器上操作:
ssh-keygen -t ed25519 -C "jenkins@example.com" -f /var/lib/jenkins/.ssh/id_ed25519公钥加到哪里?两处都要注意:
- 加在 GitLab 的
jenkins机器人账号下的SSH Keys里,这是用来 clone 项目的。 - 如果你的 GitLab 开启了分支保护,确保这个账号在项目里至少有
Reporter或Developer权限,能读代码就行,别给 Maintainer。
首次配置完可以手动执行一次 Build,确认控制台输出的git clone是成功的。这一步务必先过——因为后面 Webhook 触发后,你肯定不希望第一次构建就卡在“无法连接仓库”这种基础问题上。
3.2 配置触发器:Webhook 与轮询二选一
Job 配置页往下拉,在构建触发器区域能看到 GitLab Plugin 提供的选项,大致是Build when a change is pushed to GitLab。勾选后,页面会给出一个 GitLab Webhook URL,比如:
http://jenkins.example.com/project/project-name同时页面会要求填写一个Secret token。这个 token 是你自定义的一段随机字符串,相当于一个密钥,之后要原样填到 GitLab 的 Webhook 配置里,用来做请求校验。
生成方式建议直接用 Jenkins 自带的“Generate”按钮,或者用系统命令生成:
openssl rand -hex 20拿到这两样东西后,去 GitLab 项目页:
设置 → Webhooks → 新建 Webhook
- URL 填 Jenkins 给的 Webhook 地址。
- Secret token 填刚生成的那串。
- 触发选项里选
Push events,一般就够用。如果想监听标签推送,可以勾上Tag push events。 - 分支过滤可以填具体分支名称,比如
main,这是很多团队容易漏掉的地方——如果不加过滤,GitLab 会把所有分支的 push 都推给 Jenkins,Jenkins 就会频繁触发。
创建成功后,页面底部可以点测试,选“推送事件”,GitLab 会立即发一个测试请求给 Jenkins。如果配置正确,Jenkins 那边会开始一个新的构建。
再强调一遍:Webhook 和轮询不要同时启用。如果选了 Webhook,就把轮询表达式留空,或者干脆别理会那个英文注释。
3.3 GitLab Access Token 的用途与配置
除了 Webhook,集成过程还经常需要 Jenkins 调用 GitLab API。举个例子:构建完成后,你想把 Jenkins 的构建状态通过 API 回写到 GitLab 的某个 commit 上,这样 MR 页面就能看到“这个提交到底构建过没有、结果是什么”。
这就是 GitLab Plugin 中GitLab API access的用途。操作路径:
系统管理 → 系统配置 → GitLab
Connection name:随便填,比如gitlabGitLab host URL:填你的 GitLab 地址,比如http://gitlab.example.comCredentials:添加一个GitLab Personal Access Token
Token 怎么生成?在 GitLab 右上角头像 →设置 → 访问令牌:
- 名字随便写
- 过期时间建议设置短一点,比如 90 天,到期记得轮换
- 权限范围(Scopes)不要全选,一般
api就够了
这里要强调:权限最小化。很多问题都是 Token 权限过大导致的,比如 Jenkins 不小心通过 API 改了仓库里的内容。集成只需要构建和回写状态,甚至只给read_repository+api就足够了。具体看你的需求,但能少给就少给。
3.4 用 Pipeline 写构建步骤(推荐)
自由风格 Job 只能填“构建命令”,复杂一点就得靠脚本或者多个构建步骤,管理起来很痛苦。现在主流做法是直接上声明式 Pipeline,用Jenkinsfile把整个构建流程写进代码仓库里,好处是流程和代码一起版本化。
贴一个最简单的 Java + Maven 项目的Jenkinsfile示例:
pipeline { agent any environment { // 直接用 GitLab 触发的分支信息 BRANCH_NAME = "${env.GIT_BRANCH ?: 'main'}" } stages { stage('拉取代码') { steps { checkout scm echo "当前分支: ${BRANCH_NAME}" } } stage('执行测试') { steps { sh 'mvn test' } } stage('打包') { steps { sh 'mvn package -DskipTests' } } } post { success { echo '构建成功' } failure { echo '构建失败,请查看日志' } } }新建 Job 的时候,项目类型选流水线,然后在流水线 → 定义里选Pipeline script from SCM,给它指定 Git 仓库地址、分支和Jenkinsfile路径。这样每次代码变更,会先从仓库拉Jenkinsfile,再按里面的定义执行各阶段。
写 Jenkinsfile 有几个实用心得:
- 不要在 Pipeline 里写死任何密码或 token,全部用
credentials()引用 Jenkins 凭据。 post块里别只写success/failure,建议把超时、中断也写上(aborted),便于排查。- 如果构建内容多,每个
stage加上timeout,防止任务挂死把构建节点占满。
3.5 完整触发链路验证
配置完成后,做一次完整的链路验证:
- 先在本地改一行代码,push 到 GitLab 的
main分支。 - 回到 GitLab 项目页的
Webhooks设置,查看最近一次“推送事件”的记录,状态是否为success。 - 打开 Jenkins,看到对应的 Job 有没有自动开始构建。点进去看控制台输出,拉代码、执行测试、打包这些阶段是否顺利。
- 构建结束后,刷新 GitLab 的提交记录页面,看看这个 commit 有没有显示对应的构建状态图标。
如果第四步没出现状态图标,多半是 GitLab API 配置有问题,或者是提交人权限没有公共访问,别慌,后面排查章节会讲。
4. 从触发到反馈的完整闭环
4.1 构建状态回写 GitLab
前面提过,集成不只是“GitLab 到 Jenkins”的单向触发,另一条方向是“Jenkins 回写状态”。项目多人协作时,MR 页面上一颗绿色的 ✔、一颗红色的 ✘,比群里喊“谁帮我看看构建挂了”直观得多。
GitLab Plugin 装好后,在系统配置里填了 API Token,巧妙的事情就发生了:Jenkins 在构建结束时会自动把状态写回 GitLab 对应的 commit。这个过程默认开启,一般不需要额外配置。
唯一要留意的:回写状态是基于“当前构建的 commit”来定位的。如果你的流水线里有打 tag、有 reset 操作,commit 和实际源码不一致,回写就可能错位。这种场景比较高级,集成初期基本不会碰到,但要有这个意识。
4.2 构建结果通知机制
构建结束后,团队成员要能第一时间知道结果。两种常见的做法:
- 邮件通知:Jenkins 自带邮件功能,配置 SMTP 后,在自由风格 Job 的“构建后操作”里选
构建后发送邮件,填收件人列表。 - 群机器人通知:企业内部常用的钉钉、企微、飞书都提供了自定义机器人 Webhook。在 Jenkins 里安装对应的插件,或者在 Pipeline 里用
sh直接 curl 群机器人地址,把构建结果 JSON 发过去。
实现方式千差万别,但有一个通用原则:把失败通知及时发给对应提交人,成功通知发给构建负责人就行,别全员轰炸。否则一个月后,所有人都会把 Jenkins 通知当垃圾信息处理掉。
4.3 参数化构建:手动触发也要好用
不是所有构建都希望由 GitLab 自动触发。很多时候,测试人员想手动选一个分支、选一个环境、构建一个特定版本。
做法很简单:在 Job 配置里勾选参数化构建过程,添加字符串参数或选择参数。比如添加一个名为BRANCH的字符串参数,默认值填main。在源码管理里,分支填*/${BRANCH}。
Webhook 自动触发时,会使用默认值;手动触发时可以选择。这套机制用熟了之后,你会发现团队内部分支管理、版本挑选都方便了很多——但注意,参数越多,构建配置就越复杂,尽量保证参数语义明确,随手写个必填项就很好。
4.4 并发与资源控制
当团队人数多、提交频繁时,同一个 Job 可能同时被触发多次。如果不断创建并发构建,构建机器资源会被挤爆。
处理方法:
- 在 Job 配置里勾选
不允许并发构建,这样前一次构建没结束,新触发的会一直排队。 - 如果用的是流水线,可以在
agent块上指定资源标签,把不同项目隔离到不同构建节点。 - 给每个任务添加超时时间。
这块是上线之后才会体会到的重要配置,我建议从一开始就设定好“单个 Job 同时只允许一个构建”之类的基础策略,后续再根据项目需要放开并发数。
5. 常见问题与排查实录
5.1 Webhook 到达了但 Jenkins 没反应
这是出现频率最高的一个问题:GitLab Webhook 状态是“成功”,但 Jenkins 没有任何动作。
排查路线:
- 去 Jenkins 的系统日志里搜一下有没有相关报错,路径在
系统管理 → 系统日志。 - 检查 GitLab Webhook 的 URL 是不是内网解析不到的域名或 IP。很多团队联调时,写了
localhost,GitLab 怎么可能通到 Jenkins 的 localhost? - 确认 Job 勾选了对应的触发器选项。听起来像废话,但真有人只填了 Webhook URL,没勾选触发器。
- 检查是否同时配了轮询和 Webhook(我不止一次踩过这坑)。
- 检查 GitLab Webhook 里有没有配分支过滤,如果过滤规则写漏了,也不会触发。
如果以上都正常,可以尝试在 GitLab Webhook 配置页面点“测试”,观察能不能触发。如果手动测试能触发但 push 不行,多半是事件类型没选对。
5.2 Git clone 时报权限错误
构建日志里看到类似Permission denied (publickey),或者Could not read from remote repository,几乎都是 SSH key 的问题:
- 私钥没加到 Jenkins 的凭据里,或加错了凭据类型。
- 公钥没加到 GitLab 机器人账号的 SSH Keys 里。
- 机器人账号没有项目的读取权限。
- 克隆地址写成了 SSH 但 Jenkins 机器网络连不上 GitLab 的 SSH 端口(默认 22)。
最直接的验证方法,在 Jenkins 机器上手动试试:
sudo -u jenkins ssh -T git@gitlab.example.com能通过就说明 SSH 链路没问题,问题在 Jenkins 凭据配置上。
5.3 插件版本不兼容
GitLab 升级到新版本后,某些老版本的 GitLab Plugin 可能解析 Webhook 请求失败。常见表现是 GitLab 测试时返回 500 或报错。
解决办法:升级 GitLab Plugin 插件。如果升级后出现兼容性问题,可以看一下 Jenkins LTS 版本是否需要一并升级。这里不推荐盲目追新,但 GitLab API 的变动确实比较频繁,生产环境里插件保持在中等版本梯队即可。
5.4 CSRF / API 403
GitLab Webhook 请求发往 Jenkins,如果返回 403,大概率是 Jenkins 的 CSRF 防护拦截了。凡是外部系统调用 Jenkins API 或 Webhook 端口,都需要确认调用 URL 带有正确的认证信息,比如apiToken参数。用 GitLab Plugin 配置的 Secret Token 就是认证信息,只要保证两侧填的 token 一致,一般能绕开;如果依然 403,去全局安全里检查“允许匿名读取”等选项,但千万别为省事关掉 CSRF。
5.5 构建记录过多导致磁盘爆炸
触发链路畅通后,另一个问题会浮现:每次 push 都触发构建,构建记录和产物占满磁盘。Jenkins 默认不自动清理历史记录。
建议在 Job 配置里设置:
- 保留构建的最大数量,比如只保留 10 次。
- 保留构建的日期范围,比如 7 天。
- 同时定期清理
workspace里的旧产物,或在构建脚本里设清理逻辑。
这条应该和集成配置一并做好,不然一个月后 Jenkins 机器磁盘告警,运维就要半夜爬起来了。
6. 个人实操总结与一点经验
整套流程配置下来,我的最大感受是:Jenkins 和 GitLab 的集成本身不复杂,真正消耗精力的是两边各种隐蔽的细节——网络、凭据、权限、版本、CSRF、磁盘。越是基础的东西,越容易在联调时被忽略。
如果让我给刚接触这套体系的团队一点建议,我会说:
- 第一,先把 Git clone 这条路走通,再配触发器和流水线。顺序反了,排查问题的范围会成倍扩大。
- 第二,用 Pipeline as Code 的方式去定义构建流程,而不是在 Jenkins 页面点来点去,这样流程可审查、可回滚、可复用。
- 第三,给 GitLab 机器人账号和 Jenkins 凭据设好最小权限,并且定期检查。权限管控不是安全团队的专属任务,运维和开发同样要养成习惯。
- 第四,把 Webhook 和触发器的选择固化下来,能上 Webhook 就不要轮询,拖垮效率,也容易产生大量无效历史记录。
- 第五,明确一点:集成的最终目标不是“能跑通”,而是“团队每天都愿意依赖它”。所以结果通知、状态回写、产物归档这些后续环节,一定要花时间做好,否则第二天大家就会觉得构建系统不好用。
最后补充一个小技巧:每次排查完问题,顺手把根因和修复步骤记录到项目的 README 里。很多坑,第二次遇到的概率远大于第一次回忆起来的概率。有这套文档,新同事上手也快得多。