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 جو ٹنل رکھیں، یعنی کم جگہیں جہاں پرانا روٹ چھپ سکے۔