Почему webhook-и GitHub упираются в таймаут при обращении к Jenkins за VPN
GitHub отправляет webhook-и внутрь, поэтому VPN, защищающий Jenkins, их отклоняет. Пробросьте доставку наружу через relay или замените webhook агентом, который опрашивает.
Страница доставки показывает таймаут, а 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 обеспечивает почти мгновенную обратную связь, не открывая порт. Runner обычно правильный ответ, когда конфиденциальность payload исключает третью сторону, потому что задача может выполняться на машине, которая уже находится внутри сети.
Выбирайте по тому, чем можете управлять, а не по тому, что звучит современно.
Пробросьте доставку наружу с помощью smee
Создайте канал в сервисе relay и сохраните его URL. Внутри сети установите клиент и пересылайте доставки на путь, которого ждёт Jenkins. Payload URL в настройках репозитория становится 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-и работают день, а потом останавливаются. Дайте ему unit с Restart=always, запустите его от пользователя Jenkins и прочитайте журнал после первой реальной доставки. Именно этот последний шаг отличает работающий 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, а не кнопкой повтора. Повтор прогоняет сохранённый payload relay, а не путь от GitHub до вашего канала, поэтому он может пройти, пока живая доставка всё ещё сбоит.
Оповещайте, когда unit останавливается. Упавший relay молчит по своей конструкции, и тишина — это именно то, что вы уже отлаживаете.
Что relay может видеть и когда его лучше не использовать
Relay терминирует payload, поэтому видит имена репозиториев, данные commit и всё остальное, что несёт событие. Относитесь к URL канала как к публичной конечной точке. Держите секретный token Jenkins настроенным, чтобы поддельные доставки отклонялись, и ограничьте token, который использует Jenkins, необходимым минимумом.
Если конфиденциальность payload это исключает, self-hosted runner или аутентифицированный обратный туннель — это ответ.
Relay теперь инфраструктура, и он заслуживает такой же проверки, как build, который он запускает. Меняйте канал, если он утёк, и записывайте дату, когда вы последний раз проверяли живую доставку. Поставьте эту дату в runbook рядом с URL канала. Relay, который никто не проверял год, — это публичная конечная точка, которой никто не владеет.