为什么 GitHub webhook 对 VPN 后面的 Jenkins 会超时
GitHub 把 webhook 发向内网,因此保护 Jenkins 的 VPN 会拒绝它们。请通过 relay 把投递向外转发,或把 webhook 换成会轮询的 agent。
投递页面显示超时,而 Jenkins 没有记录任何内容,这正是连接从未到达的特征。GitHub 把 webhook 作为出站 HTTP POST 从它自己的基础设施发往你配置的 payload URL。VPN 通过拒绝这类连接来保护 Jenkins。两边都没有坏。问题在于请求的方向。
投递实际尝试了什么
GitHub 解析 payload URL 中的主机名,并从公网连接它。如果该 URL 指向私有地址,或指向只能在你网络内部解析的名字,尝试会在发出任何请求之前就失败。如果它指向你的边缘所丢弃的公网名字,尝试则会改为超时。
这一区别对诊断很重要。DNS 失败和被过滤的端口看起来都像 Jenkins 一侧什么都没发生,而两者在 GitHub 上都显示为附带不同原因的投递失败。先读那个原因,再改 URL。
向外拨号的隧道并不会创建监听器。人们期待以不同方式工作的正是这一点。
修正连接方向的三种方法
每种方案都改变谁发起连接,而形式只有三种。
| Approach | Who initiates | What you give up |
|---|---|---|
| Relay service | An outbound client inside the network | A third party sees every payload |
| Reverse tunnel or ingress | The public edge, into a controlled listener | An inbound surface you must secure |
| Runner or polling | The agent, outbound to GitHub | Instant delivery, replaced by a schedule |
轮询是最不优雅、也最容易运维的做法。relay 不需要开放端口就能保持近乎即时的反馈。当 payload 的机密性排除了第三方时,runner 通常是正确答案,因为任务可以跑在一台已经在网络内部的机器上。
按你能运维的方式选,而不是按听起来现代的方式选。
用 smee 把投递向外转发
在 relay 服务上创建一个 channel 并保留它的 URL。在网络内部安装客户端,把投递转发到 Jenkins 期望的路径。仓库设置里的 payload URL 变成 channel URL,内容类型设为 application/json。
sudo npm install --global smee-client
smee -u CHANNEL_URL --path /github-webhook/ --port 8080三个细节决定它能否工作。路径必须与 Jenkins 对 GitHub 事件的期望一致,默认是 /github-webhook/。Jenkins 必须在你交给客户端的端口上监听。而任务里配置的 Jenkins URL 必须与该路径一致,否则事件到达后会被丢弃。
结尾斜杠不一致会产生 404,读起来像投递失败,但其实不是。
让它撑过这一周
前台客户端会随终端一起死掉,webhook 工作一天之后就停了。给它一个带 Restart=always 的 unit,以 Jenkins 用户运行,并在第一次真实投递后读 journal。正是最后这一步区分了能工作的 relay 和只是配置好的 relay。
[Unit]
Description=smee webhook relay
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=2
User=jenkins
ExecStart=/usr/bin/smee -u CHANNEL_URL --path /github-webhook/ --port 8080
[Install]
WantedBy=multi-user.target用真实的 push 测试,而不是播放按钮。重放演练的是 relay 存储的 payload,而不是从 GitHub 到你 channel 的路径,所以它可能通过,而实时投递仍在失败。
unit 停止时告警。挂掉的 relay 按设计就是沉默的,而沉默正是你已经在排查的东西。
relay 能看到什么,以及何时不该用它
relay 会终止 payload,因此它能看到仓库名、commit 数据以及事件携带的其他一切。把 channel URL 当作公开端点。保持 Jenkins secret token 已配置,以便伪造投递被拒绝,并把 Jenkins 使用的 token 权限限制到所需最低。
如果 payload 的机密性排除了这一点,自托管 runner 或经过认证的反向隧道才是答案。
relay 现在是基础设施,它值得和它触发的 build 一样的审查。如果泄露就轮换 channel,并记下你上次验证实时投递的日期。把那个日期写在 runbook 里 channel URL 旁边。一年没人审查的 relay 就是一个没人拥有的公开端点。