GitLab Jenkins Webhook
GitLab + Jenkins Webhook 配置指南
适用环境:GitLab CE + Jenkins(已安装
gitlab-plugin)核心结论:Jenkins 的
/project/和/job/是两套完全独立的触发机制,绝对不能混用。
一、原理说明
1.1 整体流程
开发者 push 代码
↓
GitLab 触发 Webhook(POST 请求)
↓
Jenkins GitLab Plugin 接收请求(/project/JOB_NAME)
↓
Jenkins 根据分支过滤决定是否触发构建
↓
Pipeline 执行(拉取代码 → 运行任务 → 通知)1.2 两种触发机制
Jenkins 提供两种完全独立的远程触发方式。它们的 URL 路径、认证方式、处理逻辑完全不同,绝对不能混用。
1.2.1 机制一:GitLab Plugin 触发(用于 GitLab 仓库)
由 gitlab-plugin 插件提供,专门为 GitLab 设计。
Webhook URL 格式:
https://JENKINS_URL/project/JOB_NAME认证方式:Secret Token,通过 HTTP Header 传递。
GitLab 发送 webhook 时,在请求头中带上 X-Gitlab-Token: <token>,
Jenkins GitLab Plugin 收到请求后,对比 Job 配置中的 secretToken 字段进行验证。
完整请求流程:
GitLab Jenkins
| |
| POST /project/JOB_NAME |
| Headers: |
| X-Gitlab-Event: Push Hook |
| X-Gitlab-Token: <secret> |
| Body: { ref: "refs/heads/xxx", ... } |
| ─────────────────────────────────────────> |
| |
| GitLab Plugin 拦截 /project/ 请求 |
| ↓ 校验 X-Gitlab-Token |
| ↓ 解析 Body 中的分支名 |
| ↓ 匹配 includeBranchesSpec 过滤 |
| ↓ 通过 → 触发构建 |
| |
| 201 Created |
| <───────────────────────────────────────── |特点:
- 能解析 GitLab 的 push / merge request payload,获取分支名、提交信息、用户等
- 支持分支过滤(只有匹配的分支才触发构建)
- 构建描述自动显示 "Started by GitLab push by <用户>"
- 不需要 Jenkins 用户认证(插件自行处理鉴权)
Jenkins 配置位置:Job → 配置 → 构建触发器 → "Build when a change is pushed to GitLab"
Jenkins config.xml 中的对应节点:
<com.dabsquared.gitlabjenkins.GitLabPushTrigger>
<triggerOnPush>true</triggerOnPush>
<branchFilterType>NameBasedFilter</branchFilterType>
<includeBranchesSpec>master</includeBranchesSpec>
<secretToken>your-secret-token</secretToken> <!-- 认证密钥 -->
</com.dabsquared.gitlabjenkins.GitLabPushTrigger>1.2.2 机制二:Remote Build Trigger(Jenkins 内置)
Jenkins 自带的远程构建触发功能,不依赖任何插件,适用于任意外部系统通过 HTTP 触发构建。
Webhook URL 格式:
https://JENKINS_URL/job/JOB_NAME/build?token=TOKEN认证方式:Auth Token,直接拼在 URL 的 query 参数中。
Jenkins 收到请求后,对比 Job 配置中的 authToken 字段进行验证。
完整请求流程:
任意系统 Jenkins
| |
| POST /job/JOB_NAME/build?token=<auth> |
| (无特殊 Header,无 Body 或自定义 Body) |
| ─────────────────────────────────────────> |
| |
| Jenkins 核心引擎处理 /job/ 请求 |
| ↓ 校验 URL 中的 token 参数 |
| ↓ 通过 → 直接触发构建 |
| ↓ 不解析任何 Body 内容 |
| |
| 201 Created |
| <───────────────────────────────────────── |特点:
- 不解析请求体,不知道是谁推送的、推送了什么分支
- 不支持分支过滤(收到请求就触发)
- 构建描述显示 "Started by remote host ..."
- 适用于 cron、脚本、其他 CI 系统等非 GitLab 场景
Jenkins 配置位置:Job → 配置 → 构建触发器 → "触发远程构建"
Jenkins config.xml 中的对应节点:
<authToken>your-auth-token</authToken> <!-- 注意:和 secretToken 是完全不同的字段 -->1.2.3 两种机制对比
| GitLab Plugin | Remote Build Trigger | |
|---|---|---|
| URL 路径 | /project/JOB_NAME | /job/JOB_NAME/build |
| Token 传递 | HTTP Header | URL 参数 (?token=) |
| Jenkins 配置字段 | <secretToken> | <authToken> |
| 是否解析 Payload | 是(分支、用户、提交) | 否 |
| 分支过滤 | 支持 | 不支持 |
| 触发来源显示 | Started by GitLab push | Started by remote host |
| 依赖插件 | gitlab-plugin | 无(内置) |
| 推荐场景 | GitLab webhook | 脚本 / cron / 其他系统 |
1.3 常见陷阱
这两个陷阱是 GitLab + Jenkins 集成中最容易踩且最难排查的,本质都源自对两种机制的混淆。
1.3.1 陷阱 A:混用 /project/ 与 /build?token=
最常见的错误写法:
https://JENKINS_URL/project/JOB_NAME/build?token=xxx
^^^^^^^^^ ^^^^^^^^^^^^^^^^
GitLab Plugin Remote Build Trigger
的路径 的参数Jenkins 收到这种请求时的处理逻辑:
- 看到
/project/前缀 → 交给 GitLab Plugin 处理 - GitLab Plugin 期望的路径是
/project/JOB_NAME,但实际收到/project/JOB_NAME/build - Plugin 找不到名为
JOB_NAME/build的 Job → 把请求当作普通 HTTP 请求转发 - 转发后 Jenkins 核心尝试以匿名用户身份访问
/project/JOB_NAME/build - 匿名用户没有 Build 权限 → 返回 403: anonymous is missing the Job/Build permission
典型表现:webhook 配置长期存在,但从未成功触发构建,构建记录里只有手动触发。
排查口诀:看到 /project/ 后面跟了 /build 或 ?token=,一定是错的。
1.3.2 陷阱 B:Declarative Pipeline triggers{} 会清空 secretToken
这是最隐蔽的坑。表现为 webhook 首次成功,后续全部 403。
原理:Declarative Pipeline 引擎在每次构建成功后,会按 triggers {} 块的声明重写 Job 的触发器配置。如果 triggers 块没有指定 secretToken,重写时就会把 secretToken 清空为 null。
首次构建前: secretToken = "abc123"(通过 UI/API 设置)
↓ 构建成功
Pipeline 引擎: 按 triggers{} 声明重写触发器
↓ triggers{} 里没有 secretToken
secretToken = null
↓ 下次 webhook
GitLab 发送 X-Gitlab-Token: abc123 → Jenkins 比对 null → 认证失败 → 403正确写法:在 triggers 块中显式指定 secretToken:
triggers {
gitlab(
triggerOnPush: true,
branchFilterType: 'NameBasedFilter',
includeBranchesSpec: 'master',
secretToken: 'your-secret-token' // 必须加这一行
)
}排查口诀:看到 403 且构建历史显示"只有第一次是 webhook 触发,后面全是手动触发",立刻检查 Pipeline triggers 块有没有写 secretToken。
1.4 该用哪种(速查)
从 GitLab 仓库触发 → 用 GitLab Plugin(机制一)
Webhook URL: https://JENKINS_URL/project/JOB_NAME
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/project/ 路径,末尾不加 /build,不加 ?token=从脚本 / cron / 其他系统触发 → 用 Remote Build Trigger(机制二)
触发 URL: https://JENKINS_URL/job/JOB_NAME/build?token=TOKEN
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/job/ 路径,带 /build?token=注意:Job 名称区分大小写。
二、配置步骤
2.1 Jenkins 端配置
方式 A:通过 Jenkins UI(推荐)
- 打开 Jenkins Job → 配置
- 构建触发器 区域,勾选 Build when a change is pushed to GitLab
- 记录页面显示的 GitLab webhook URL(格式为
/project/JOB_NAME) - 点击 高级 → Secret token → Generate,生成 Secret Token 并记录
- 设置 分支过滤:
- 选择
Filter branches by name - Include 填写需要触发的分支名(如
master、main、demo)
- 选择
- 保存
方式 B:通过 API
# 1. 下载当前配置
curl -s -u "用户名:API_TOKEN" \
"https://JENKINS_URL/job/JOB_NAME/config.xml" \
> config.xml
# 2. 在 <com.dabsquared.gitlabjenkins.GitLabPushTrigger> 中添加:
# <secretToken>你的密钥</secretToken>
#
# 确保 <triggerOnPush>true</triggerOnPush>
# 确保 <includeBranchesSpec> 填写了正确的分支名
# 3. 重要:删除 <actions>...</actions> 整个节点(Jenkins 自动生成的,提交回去会导致 500 错误)
# 4. 获取 CSRF Crumb
CRUMB=$(curl -s -u "用户名:API_TOKEN" \
"https://JENKINS_URL/crumbIssuer/api/json" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['crumb'])")
# 5. 上传配置
curl -s -X POST -u "用户名:API_TOKEN" \
-H "Jenkins-Crumb: $CRUMB" \
-H "Content-Type: application/xml; charset=utf-8" \
--data-binary @config.xml \
"https://JENKINS_URL/job/JOB_NAME/config.xml"API 方式注意事项:
- POST config.xml 时必须带 Jenkins-Crumb(GET 不需要)
- 必须删除 XML 中的
<actions>节点,否则返回 500 <authToken>是 Remote Build Trigger 的 token,和 GitLab Plugin 的<secretToken>是两回事- 提交包含中文的 config.xml 时,需指定
Content-Type: application/xml; charset=utf-8,否则中文会乱码
2.2 GitLab 端配置
方式 A:通过 GitLab UI
- 进入仓库 → Settings → Webhooks
- 填写:
- URL:
https://JENKINS_URL/project/JOB_NAME - Secret token: 与 Jenkins 端设置的 Secret Token 一致
- Trigger: 勾选 Push events
- Push events branch filter(可选):填写分支名
- Enable SSL verification: 内部网络可取消勾选
- URL:
- 点击 Add webhook
方式 B:通过 API
# 创建 webhook
curl -s --header "PRIVATE-TOKEN: <GitLab-Token>" \
-X POST "https://GITLAB_URL/api/v4/projects/<项目ID>/hooks" \
-H "Content-Type: application/json" \
-d '{
"url": "https://JENKINS_URL/project/JOB_NAME",
"push_events": true,
"push_events_branch_filter": "master",
"enable_ssl_verification": false,
"token": "与Jenkins一致的SecretToken"
}'
# 更新已有 webhook
curl -s --header "PRIVATE-TOKEN: <GitLab-Token>" \
-X PUT "https://GITLAB_URL/api/v4/projects/<项目ID>/hooks/<Webhook-ID>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://JENKINS_URL/project/JOB_NAME",
"push_events": true,
"push_events_branch_filter": "master",
"enable_ssl_verification": false,
"token": "与Jenkins一致的SecretToken"
}'获取项目 ID:
curl -s --header "PRIVATE-TOKEN: <Token>" \
"https://GITLAB_URL/api/v4/projects/<namespace>%2F<project>" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])"
# 示例:group/project-name → group%2Fproject-name三、验证步骤
3.1 测试 Webhook 连通性
# 通过 GitLab API 发送测试 push 事件
curl -s --header "PRIVATE-TOKEN: <Token>" \
-X POST "https://GITLAB_URL/api/v4/projects/<项目ID>/hooks/<Webhook-ID>/test/push_events"
# 期望结果:{"message":"201 Created"}
# 返回 403 "anonymous is missing the Job/Build permission" → Webhook URL 格式错误(混用了 /project/ 和 /build?token=)
# 返回 404 → Jenkins Job 名称不匹配3.2 确认构建触发
# 查看最新构建的触发来源
curl -s -u "用户名:API_TOKEN" \
"https://JENKINS_URL/job/JOB_NAME/lastBuild/api/json" \
| python3 -c "
import sys,json
data=json.load(sys.stdin)
for a in data.get('actions',[]):
if a.get('_class')=='hudson.model.CauseAction':
for c in a.get('causes',[]):
print(c.get('shortDescription',''))
"
# Webhook 触发显示:Started by GitLab push by <用户名>
# 手动触发显示:Started by user <用户名>3.3 查看 Webhook 投递记录
GitLab UI → Settings → Webhooks → 点击 Edit → 查看 Recent events
四、完整示例
4.1 Pipeline 脚本(仅拉取代码 + 飞书通知)
最小可用的 Declarative Pipeline:
pipeline {
agent any
triggers {
gitlab(
triggerOnPush: true,
branchFilterType: 'NameBasedFilter',
includeBranchesSpec: 'master',
secretToken: 'your-secret-token'
)
}
stages {
stage('Checkout') {
steps {
checkout([$class: 'GitSCM',
branches: [[name: '**']],
userRemoteConfigs: [[
url: 'git@GITLAB_URL:group/project.git',
credentialsId: 'gitlab-ssh'
]]
])
}
}
stage('Notify') {
steps {
script {
def msg = '构建完成通知\n项目: ' + env.JOB_NAME + '\n构建号: #' + env.BUILD_NUMBER
def payload = '{"msg_type":"text","content":{"text":"' + msg + '"}}'
writeFile file: 'feishu.json', text: payload, encoding: 'UTF-8'
sh "curl -s -X POST -H 'Content-Type: application/json; charset=utf-8' -d @feishu.json 'https://open.feishu.cn/open-apis/bot/v2/hook/<飞书机器人ID>'"
}
}
}
}
}飞书通知小贴士:不要在
sh中直接拼接 JSON 字符串,shell 引号嵌套极易导致格式错误(尤其是包含中文时)。推荐用writeFile写入 JSON 文件,再用curl -d @filename发送。
4.2 一键配置脚本
把 GitLab webhook 创建、连通性测试串成一个脚本,常用于批量初始化。脚本仅生成 GitLab 端 webhook,Jenkins 端的 Secret Token 仍需手动或通过 API 同步设置,两端必须一致。
#!/bin/bash
# 使用方法: ./setup-webhook.sh <GitLab项目路径> <Jenkins-Job名> <Secret-Token> [分支过滤]
# 示例: ./setup-webhook.sh group/project JOB_NAME my-secret-token master
set -e
GITLAB_PROJECT="$1"
JENKINS_JOB="$2"
SECRET_TOKEN="$3"
BRANCH_FILTER="${4:-}"
# 按实际环境填写
GITLAB_URL="https://GITLAB_URL"
JENKINS_URL="https://JENKINS_URL"
GITLAB_TOKEN="<你的GitLab-PRIVATE-TOKEN>"
# URL 编码项目路径(group/project → group%2Fproject)
ENCODED_PROJECT=$(echo "$GITLAB_PROJECT" | sed 's|/|%2F|g')
# 获取项目 ID
PROJECT_ID=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$GITLAB_URL/api/v4/projects/$ENCODED_PROJECT" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
echo "Project ID: $PROJECT_ID"
# 拼接分支过滤字段(可选)
if [ -n "$BRANCH_FILTER" ]; then
EXTRA_FIELD="\"push_events_branch_filter\": \"$BRANCH_FILTER\","
else
EXTRA_FIELD=""
fi
# 创建 GitLab Webhook
RESULT=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
-X POST "$GITLAB_URL/api/v4/projects/$PROJECT_ID/hooks" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"$JENKINS_URL/project/$JENKINS_JOB\",
\"push_events\": true,
$EXTRA_FIELD
\"enable_ssl_verification\": false,
\"token\": \"$SECRET_TOKEN\"
}")
HOOK_ID=$(echo "$RESULT" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id','FAILED'))" 2>/dev/null)
echo "Webhook ID: $HOOK_ID"
# 测试一次 push 事件
echo "Testing webhook..."
curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
-X POST "$GITLAB_URL/api/v4/projects/$PROJECT_ID/hooks/$HOOK_ID/test/push_events"
echo ""
echo "Done. 记得在 Jenkins Job 的 GitLab trigger 配置中填入相同的 Secret Token。"期望输出:
Project ID: 123
Webhook ID: 456
Testing webhook...
{"message":"201 Created"}
Done. ...如果 Testing webhook... 输出不是 201 Created,参考下一章排查清单。
五、排查清单
按现象快速定位问题。原理性的两个深坑见 §1.3 常见陷阱。
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 401 Invalid token | Secret Token 不匹配 | 确认 GitLab webhook 的 token 与 Jenkins GitLabPushTrigger 的 secretToken 完全一致 |
| 403 anonymous is missing permission | Webhook URL 错误(混用了 /project/ 和 /build?token=) | URL 改为 /project/JOB_NAME,去掉 /build?token=,详见 §1.3.1 |
| 403 首次成功后续全部失败 | Pipeline triggers{} 块未指定 secretToken,每次构建后被清空 | 在 triggers { gitlab(...) } 中加 secretToken: 'xxx',详见 §1.3.2 |
| 201 但没有触发构建 | 分支不匹配 | 检查 Jenkins 的 includeBranchesSpec 和 GitLab 的 push_events_branch_filter |
| 404 | Job 名称不对 | 检查 URL 中的 Job 名称是否与 Jenkins 完全一致(区分大小写) |
| SSL 证书错误 | 内部 CA 不被信任 | GitLab webhook 设置中取消 SSL verification |
| Webhook 显示为 disabled | 连续失败次数过多被 GitLab 自动禁用 | 修复后在 GitLab webhook 页面重新启用 |
| Jenkins 500(API 更新配置时) | XML 中包含 <actions> 节点 | 上传前删除 <actions>...</actions> |
| Jenkins 403(API 更新配置时) | 缺少 CSRF Crumb | POST 请求添加 Jenkins-Crumb Header |
六、核心结论
/project/和/job/是两套独立的触发机制,不能混用。
- GitLab 仓库 → Jenkins:只用
https://JENKINS_URL/project/JOB_NAME,Secret Token 走 HTTP Header- 脚本 / cron → Jenkins:只用
https://JENKINS_URL/job/JOB_NAME/build?token=TOKEN,Auth Token 走 URL 参数- 看到
/project/后面跟了/build或?token=,一定是错的- Declarative Pipeline 必须在
triggers{}块里显式写secretToken,否则首次构建后会被清空