GitHub Actions आपके VPN तक नहीं पहुँचता: अपना runner या टनल
होस्ट किया गया runner आपके नेटवर्क से बाहर एक नया VM है। या तो उस नेटवर्क के अंदर runner रखें, या job से टनल बनाएँ और असली रूट का इंतज़ार करें।
आपका deploy चरण किसी आंतरिक hostname पर अटक जाता है, जबकि वही कमांड आपके लैपटॉप से तुरंत जवाब देता है, और दोनों मशीनों पर client build एक जैसा है। job GitHub के होस्ट किए गए runner पर चल रहा है: एक वर्चुअल मशीन जो उसी एक job के लिए GitHub के क्लाउड में बनाई गई है, आपके निजी पता स्थान में कोई रूट नहीं, आंतरिक ज़ोन के लिए कोई resolver प्रविष्टि नहीं और कोई प्रमाणपत्र नहीं जिसे निजी endpoint स्वीकार करे। इस बाधा में केवल दो डिज़ाइन फ़िट होते हैं, और workflow को दोबारा बनाए बिना बाद में कोई भी चालू नहीं किया जा सकता। YAML की पहली पंक्ति से पहले इनमें से चुनें, क्योंकि बाद में माइग्रेशन पहली कोशिश से ज़्यादा महंगा पड़ता है।
चुनाव उस runner के बीच है जो वहाँ रखा जाता है जहाँ नेटवर्क पहले ही ख़त्म होता है, और उस टनल के बीच जो एक job की अवधि तक रहती है। दोनों सालों से production deploy उठा रहे हैं, और अलग-अलग तरीक़ों से विफल होते हैं।
सीमा असल में कहाँ है
आप लैपटॉप पर जो कुछ इंस्टॉल करते हैं वह लैपटॉप पर रुक जाता है, और job के दौरान जो इंस्टॉल करते हैं वह runner के साथ ग़ायब हो जाता है। कोई स्थायी नेटवर्क namespace नहीं है जिससे client रनों के बीच जुड़ा रहे, इसलिए हर रन उसी अगम्य स्थिति से शुरू होता है। Azure यह सीमा Microsoft के होस्ट किए गए agents के लिए दर्ज करता है, जहाँ निजी नेटवर्क में VPN और ExpressRoute लिंक समर्थित नहीं हैं, और GitHub के होस्ट किए गए runners उसी तरह की पाबंदी उठाते हैं न कि कोई अलग तंत्र। इसे उत्पाद का गुण पढ़ें, न कि ऐसा दोष जिसे बायपास करना हो।
repository की कोई सेटिंग इसे ढीला नहीं करती। job का लेबल तय करता है कौन सा pool काम उठाएगा, और होस्ट किया गया लेबल पूरे रन के लिए आपके दायरे से बाहर की मशीन मानता है।
निजी पहुँच इसलिए किसी ऐसी चीज़ से आनी चाहिए जो आपकी हो। runner वहाँ रखा गया जहाँ नेटवर्क पहले ही ख़त्म होता है, या टनल जो एक job जीती है। बाक़ी लेख इन दोनों में से एक के लिए सोच-समझकर भुगतान करने के बारे में है, बजाय इसके कि बिल production में मिले।
रन की आवृत्ति से तय करें, जान-पहचान से नहीं
जब deploy दिन में कई बार आएँ, जब job एक से ज़्यादा निजी पोर्ट छुए, या जब प्लेटफ़ॉर्म टीम पहले से patching और credential rotation की ज़िम्मेदार हो, तो VPC या दफ़्तर के नेटवर्क में अपना runner लें। उस मशीन पर job किसी भी दूसरे host की तरह firewall के पीछे resolve और route करता है। नेटवर्क का काम एक बार, इंस्टॉल के समय होता है, हर रन में नहीं।
जब पहुँच कभी-कभार हो और आप ऐसा लंबे समय तक जीने वाला agent न चाहें जो आपके नेटवर्क में credential रखे, तो टनल job के अंदर बनाएँ। job कुछ मिनट के लिए अपना नेटवर्क client बन जाता है और पीछे कुछ नहीं छोड़ता, जो अक्सर पूरा मक़सद होता है।
टनल चुनने से पहले उसकी लागत का अंदाज़ा लगाएँ। हर रन profile बनाने, client शुरू करने, तैयारी और ढहाने का दाम देता है। ऐसी टनल जो उठती है मगर कभी सत्यापित नहीं होती, ऐसा deploy बनाती है जो आधे रास्ते में विफल हो और निजी स्थिति ऐसी शक्ल में छोड़े जिसकी किसी ने योजना नहीं बनाई थी। उसके बाद दोबारा कोशिश कम ही साफ़ होती है, और दूसरी विफलता का इल्ज़ाम अक्सर client के संस्करण पर डाला जाता है।
profile को ऐसे लिखना कि चरण रुक न सके
profiles और credentials को repository से बाहर रखें और उन्हें secrets से उसी चरण में बनाएँ जिसे उनकी ज़रूरत हो। क्रम तय करता है कि job बचेगा या नहीं: sudo के तहत बनी फ़ाइल root की होती है, और चरण का ग़ैर-विशेषाधिकार वाला shell फिर उसमें नहीं लिख सकता।
- name: Bring up OpenVPN
run: |
umask 077
profile="$RUNNER_TEMP/client.ovpn"
printf '%s\n' "$VPN_PROFILE" > "$profile"
: > "$RUNNER_TEMP/openvpn.log"
printf '%s\n' "$VPN_CREDENTIALS" > "$RUNNER_TEMP/credentials.txt"
sudo openvpn --daemon openvpn-job \
--config "$profile" \
--auth-user-pass "$RUNNER_TEMP/credentials.txt" \
--writepid "$RUNNER_TEMP/openvpn.pid" \
--log "$RUNNER_TEMP/openvpn.log"उस ब्लॉक के दोनों विवरण बाद के चरणों के लिए हैं। umask 077 के साथ profile मोड 0600 में runner उपयोगकर्ता के स्वामित्व में आता है, sudo अब भी उसे पढ़ सकता है, और client अपना log ऐसी फ़ाइल में लिखता है जिसका मालिक runner पहले से है, इसलिए तैयारी का लूप दूसरे sudo के बिना वह log पढ़ सकता है। --writepid उस PID को दर्ज करता है जिसे ढहाना निशाना बनाएगा।
जो profile client प्रमाणपत्र से प्रमाणित होती है उसे अपनी कुंजी उसी तरह लिखी चाहिए, और static-challenge profile को मेल खाता auth मोड चाहिए। DNS विकल्प push करने वाली profiles को Ubuntu और Debian पर openvpn-systemd-resolved चाहिए, क्योंकि अकेला push बेस इमेज पर कुछ नहीं बदलता।
तैयारी एक जाँच है, ठहराव नहीं
तय sleep ठीक तब तक टिकता है जब तक concentrator धीमा न हो जाए, और फिर deploy चरण रूट बनने से पहले चल जाता है। विफलता फिर deploy टूल के अंदर निजी host के विरुद्ध timeout के रूप में सामने आती है, जो टनल के बारे में कुछ नहीं कहती।
- name: Await private endpoint
run: |
for attempt in $(seq 1 30); do
if getent hosts private.example.internal > /dev/null && \
timeout 3 bash -c 'exec 3<>/dev/tcp/private.example.internal/8443' 2>/dev/null; then
echo "endpoint answered after $attempt attempts"
exit 0
fi
sleep 2
done
grep -Ei 'error|verify|auth' "$RUNNER_TEMP/openvpn.log" | tail -40
exit 1लूप दो चीज़ें देखता है क्योंकि इनमें से कोई भी टूटा हुआ आधा हो सकता है। getent hosts साबित करता है कि push किया गया resolver उस नाम के लिए जवाब देता है, और socket का खुलना साबित करता है कि उस पोर्ट पर कोई सुन रहा है जिसे deploy इस्तेमाल करेगा; सिर्फ़ रूट वाली profile को शाब्दिक पता और मिटाई गई getent पंक्ति चाहिए।
जब लूप ख़त्म होता है, log ही अकेली चीज़ है जो तीन अलग ख़राबियों को अलग करती है: अस्वीकृत प्रमाणपत्र, ग़लत auth मोड और ऐसा रूट जो कभी नहीं आया। tail से पहले फ़िल्टर करना उस आउटपुट को इतना छोटा रखता है कि job की विफलता के दौरान पढ़ा जा सके। पहली त्रुटि पढ़ें न कि आख़िरी पंक्ति, क्योंकि जो client प्रमाणित नहीं हो सकता वह ख़ुद को दोहराता है जब तक लूप हार न जाए। ip -4 route show dev tun0 इस आसान सवाल का जवाब देता है कि क्या कोई रूट push हुआ था, और उसी लूप में एक पंक्ति का दाम है।
ढहाना और artifact का सवाल
job को ऐसे चरण से बंद करें जो if: always() से सुरक्षित हो और sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" और कुछ न चलाए। साझा अपने runner पर कभी प्रक्रिया के नाम पर मत मारें: कोई दूसरा job ऐसा client रख सकता है जिसका binary और तर्क मिलते हों। फिर विफलता उसी job पर पड़ती है जो दूसरे नंबर पर चला, और उसका log ऐसे workflow की ओर इशारा करता है जिसने कुछ ग़लत नहीं किया।
सुरक्षा इसलिए मायने रखती है कि विफल deploy फिर भी ज़िंदा टनल छोड़ती है। always() के बिना सफ़ाई का चरण ठीक तब छोड़ दिया जाता है जब उसकी ज़रूरत हो, और अगला रन ऐसे client के सामने शुरू होता है जो पहले से जुड़ा है।
कोई भी डिज़ाइन अपनाने से पहले एक सवाल बाक़ी है: क्या build चरण को सचमुच निजी रूट चाहिए? जो deployment agent पहले से नेटवर्क के अंदर बैठा है वह मंज़ूरशुदा artifact खींच सकता है, जो आम builds को निजी रास्ते से दूर रखता है। कम job जो टनल रखें, यानी कम जगहें जहाँ पुराना रूट छिप सके।