لماذا تنتهي مهلة webhooks الخاصة بـ GitHub أمام Jenkins خلف VPN
يرسل GitHub الـ webhooks واردةً، لذا يرفضها VPN يحمي Jenkins. مرّر التسليم إلى الخارج عبر relay، أو استبدل الـ webhook بوكيل يستطلع.
تعرض صفحة التسليم مهلة، ولا يسجّل Jenkins شيئًا، وهذه بصمة اتصال لم يصل قط. يرسل GitHub الـ webhook كطلب HTTP POST صادر من بنيته التحتية إلى عنوان payload URL الذي أعددته. يحمي VPN خدمة Jenkins برفض هذا النوع من الاتصالات تحديدًا. لا شيء معطّل في أي من الجانبين. الاتجاه الذي يسلكه الطلب هو المشكلة.
ما الذي يحاول التسليم فعله
يحل GitHub اسم المضيف في payload URL ويتصل به من الإنترنت العام. وإذا كان ذلك العنوان يشير إلى عنوان خاص، أو إلى اسم لا يُحل إلا داخل شبكتك، تفشل المحاولة قبل إرسال أي طلب. وإذا كان يشير إلى اسم عام تُسقطه حافتك، تنتهي مهلة المحاولة بدلًا من ذلك.
التمييز مهم للتشخيص. فشل DNS ومنفذ مُرشّح يبدوان وكأن شيئًا لم يحدث على جانب Jenkins، ويظهران معًا على GitHub كتسليم فاشل مع سبب مختلف مرفق. اقرأ ذلك السبب قبل أن تغيّر العنوان.
النفق الذي يتصل إلى الخارج لا ينشئ مستمعًا. وهذا هو الجزء الذي يتوقع الناس أن يعمل بشكل مختلف.
ثلاث طرق لإصلاح اتجاه الاتصال
كل حل يغيّر من يبدأ الاتصال، ولا توجد إلا ثلاثة أشكال.
| 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 واحتفظ بعنوانها. داخل الشبكة، ثبّت العميل ومرّر التسليمات إلى المسار الذي يتوقعه Jenkins. يصبح payload 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 يُقرأ كفشل تسليم وليس كذلك.
تشغيله بحيث يصمد أسبوعًا
عميل يعمل في المقدمة يموت مع الطرفية، فتعمل الـ webhooks يومًا ثم تتوقف. أعطه وحدة مع Restart=always، وشغّله بمستخدم Jenkins، واقرأ السجل بعد أول تسليم حقيقي. تلك الخطوة الأخيرة هي ما يفصل 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اختبر بدفعة حقيقية لا بزر إعادة التشغيل. فإعادة التشغيل تمارس الـ payload المخزّن لدى الـ relay، لا المسار من GitHub إلى قناتك، لذا قد تنجح بينما يفشل التسليم الحيّ.
نبّه عندما تتوقف الوحدة. فالـ relay المتوقف صامت بحكم التصميم، والصمت هو ما تشخّصه بالفعل.
ما يمكن للـ relay رؤيته، ومتى تتجنبه
الـ relay ينهي الـ payload، لذا يرى أسماء المستودعات وبيانات الـ commit وكل ما يحمله الحدث. تعامل مع عنوان القناة كطرف عام. أبقِ الـ secret token الخاص بـ Jenkins مهيأً حتى تُرفض التسليمات المزيفة، وحدّد نطاق الـ token الذي يستخدمه Jenkins إلى الحد الأدنى.
إذا كانت سرية الـ payload تستبعد ذلك، فإن runner مستضافًا ذاتيًا أو نفقًا عكسيًا موثّقًا هو الجواب.
الـ relay أصبح بنية تحتية الآن، ويستحق المراجعة نفسها التي يحصل عليها البناء الذي يشغّله. بدّل القناة إذا تسربت، وسجّل التاريخ الذي تحققت فيه آخر مرة من تسليم حيّ. ضع ذلك التاريخ في دليل التشغيل بجوار عنوان القناة. relay لم يراجعه أحد لمدة سنة هو طرف عام لا يملكه أحد.