GitHub Actions আপনার VPN পর্যন্ত পৌঁছায় না: নিজের runner বা টানেল
হোস্টেড runner আপনার নেটওয়ার্কের বাইরে একটি নতুন VM। হয় ওই নেটওয়ার্কের ভিতরে runner রাখুন, নাহলে job থেকে টানেল খুলে সত্যিকারের রুটের অপেক্ষা করুন।
আপনার deploy ধাপ একটি অভ্যন্তরীণ hostname-এ আটকে থাকে, অথচ একই কমান্ড আপনার ল্যাপটপ থেকে সঙ্গে সঙ্গে উত্তর দেয়, আর দুই মেশিনে client build হুবহু এক। job চলছে GitHub-হোস্টেড runner-এ: GitHub-এর ক্লাউডে ওই একক job-এর জন্য তৈরি একটি ভার্চুয়াল মেশিন, যার আপনার ব্যক্তিগত ঠিকানা-জগতে কোনো রুট নেই, অভ্যন্তরীণ জোনের জন্য কোনো 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 firewall-এর পিছনে অন্য যেকোনো host-এর মতো resolve ও route করে। নেটওয়ার্কের কাজ একবার, ইনস্টলের সময়েই হয়, প্রতিটি রানে নয়।
যখন প্রবেশ মাঝে মাঝে আর আপনি আপনার নেটওয়ার্কে credential ধরে রাখা দীর্ঘজীবী agent চান না, তখন টানেল job-এর ভিতরে বানান। job কয়েক মিনিটের জন্য নিজের নেটওয়ার্ক client হয়ে ওঠে এবং পিছনে কিছু রাখে না, যা প্রায়ই পুরো উদ্দেশ্য।
টানেল বাছার আগে তার খরচ আন্দাজ করুন। প্রতিটি রান profile তৈরি, client চালু, প্রস্তুতি ও ভাঙার দাম দেয়। যে টানেল ওঠে কিন্তু কখনো যাচাই হয় না, তা এমন deploy বানায় যা মাঝপথে ব্যর্থ হয় এবং ব্যক্তিগত অবস্থা এমন আকারে রেখে যায় যা কেউ পরিকল্পনা করেনি। তারপরের পুনঃপ্রচেষ্টা কমই পরিষ্কার হয়, আর দ্বিতীয় ব্যর্থতা সাধারণত client সংস্করণের ঘাড়ে চাপে।
profile এমনভাবে লেখা যাতে ধাপটি থামানো না যায়
profiles ও credentials repository-র বাইরে রাখুন এবং যে step-এর প্রয়োজন সেখানেই secrets থেকে সেগুলো তৈরি করুন। ক্রম ঠিক করে job বাঁচবে কি না: sudo-র অধীনে তৈরি ফাইল root-এর, আর step-এর সুবিধাহীন 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-এর কাছে একই binary আর মিলতে-মিলতে আর্গুমেন্টের client থাকতে পারে। তখন ব্যর্থতা দ্বিতীয় যে job চলেছে তার উপর পড়ে, আর তার log এমন workflow-এর দিকে দেখায় যা কিছু ভুল করেনি।
সুরক্ষাটি জরুরি কারণ ব্যর্থ deploy তবুও জীবন্ত টানেল রেখে যায়। always() ছাড়া পরিষ্কারের ধাপ ঠিক যখন দরকার তখনই বাদ পড়ে, আর পরের রান আগেই সংযুক্ত client-এর সামনে শুরু হয়।
যেকোনো একটি নকশা নেওয়ার আগে একটি প্রশ্ন বাকি: build ধাপের কি আদৌ ব্যক্তিগত রুট দরকার? যে deployment agent ইতিমধ্যেই নেটওয়ার্কের ভিতরে বসে আছে সে অনুমোদিত artifact টানতে পারে, যা সাধারণ build-গুলোকে ব্যক্তিগত পথ থেকে দূরে রাখে। কম job টানেল ধরে রাখা মানে কম জায়গা যেখানে পুরনো রুট লুকাতে পারে।