# হোস্টেড CI যখন সবচেয়ে ধীর ধাপ হয়ে উঠল, বিল্ডগুলো ঘরে ফিরল

Company: Webanion
Service: ডেভঅপস ইঞ্জিনিয়ারিং
Period: 2026-08 - 2026-09
Tech: GitHub Actions, CI/CD, Docker, zot, Verdaccio, Kubernetes, Go, SQLite, Linux, systemd
Canonical: https://webanion.com/bn/portfolio/replacing-hosted-ci-with-a-home-runner-pool-and-private-registries

বিল্ড আর ডিপ্লয় সরে এসেছে GitHub-এর হোস্টেড রানার থেকে, যেখানে প্রতিটি রান শুরু হতো নতুন মেশিনে আর রিলিজ অপেক্ষা করত লাইন ও বিভ্রাটের জন্য, ঘরের একটি রানার পুলে: একটি বিল্ড মেশিন যা চালু থাকা অবস্থায় প্রতিটি জব নেয়, আর সবসময় চালু একটি সার্ভারে স্ট্যান্ডবাই রানার, এমন একটি ওয়াচডগের পেছনে যা দ্বিধার সময় শিপিং চালু রাখার দিকেই ঝোঁকে। ইমেজ যায় একটি প্রাইভেট কনটেইনার রেজিস্ট্রিতে আর স্টুডিওর লাইব্রেরিগুলো একটি প্রাইভেট npm রেজিস্ট্রিতে, দুটোই ভবনের ভেতরে, আর প্রতিটি রান GitHub-এর রিটেনশনের পরেও আর্কাইভে থাকে। সবচেয়ে ধীর ডিপ্লয় আট মিনিট চব্বিশ সেকেন্ড থেকে নেমে এসেছে তিন মিনিট পাঁচ সেকেন্ডে, আর CI হয়েছে এক-তৃতীয়াংশ দ্রুত।

## পুরো গল্পটা

হোস্টেড পাইপলাইন একটি ব্যবসার কাছ থেকে অপেক্ষার মূল্য হিসেবে কী আদায় করে, আর বিল্ড, ইমেজ ও প্যাকেজ এক ছাদের নিচে আসার পর কী বদলাল।

### পাইপলাইন ছিল অন্য কারও মেশিন, আর খারাপ দিনটাও অন্য কারও

<p>২০২৬ সালের সেপ্টেম্বরের মাঝামাঝি পর্যন্ত প্রতিটি বিল্ড ও ডিপ্লয় চলত GitHub-এর হোস্টেড রানারে, আর হোস্টেড রানার মানে প্রতিবার একটি নতুন ভার্চুয়াল মেশিন: এক লাইনের পরিবর্তনের জন্যও বেস ইমেজ, প্রতিটি ডিপেনডেন্সি আর বিল্ডের প্রতিটি লেয়ার আবার ডাউনলোড হতো, তারপর ফেলে দেওয়া হতো। এরপর ইমেজটি তৈরি হতো সবসময় চালু প্রোডাকশন সার্ভারে, কারণ ফলাফল কেবল সেখানেই চলতে পারত, ফলে যে মেশিন ডেটাবেস আর পাবলিক সাইটগুলো চালাচ্ছিল, সে-ই তখন কম্পাইল করছিল। সবচেয়ে ধীর ডিপ্লয়ের মিডিয়ান ছিল আট মিনিট চব্বিশ সেকেন্ড, যার প্রায় পুরোটাই বিল্ড, মার্জগুলো একটার পেছনে আরেকটা লাইনে দাঁড়াত, আর কোনো ডিপেনডেন্সির ডাউনলোড টাইম আউট হলেই একটি রিলিজ ব্যর্থ হতে পারত, এমন ব্যর্থতা যার সঙ্গে কোডের কোনো সম্পর্ক নেই। রেকর্ডও টিকত না: GitHub ডিফল্টভাবে ওয়ার্কফ্লো লগ নব্বই দিন রাখে।</p><p>নিচের সার্ভিসটিরও নিজের একটা কঠিন বছর গেছে। ২ ফেব্রুয়ারি GitHub জানায় যে তাদের হোস্টেড রানার পাওয়া যাচ্ছিল না, আর জবগুলো লাইনে অপেক্ষা করতে করতে টাইম আউট হয়ে যায়: "Actions jobs queued and timed out while waiting to acquire a hosted runner" (<a href="https://www.githubstatus.com/incidents/xwn6hjps36ty">ঘটনা</a>)। ৫ মার্চ পঁচানব্বই শতাংশ ওয়ার্কফ্লো রান সময়মতো শুরু হয়নি: "failed to start within 5 minutes with an average delay of 30 minutes" (<a href="https://www.githubstatus.com/incidents/g5gnt5l5hf56">ঘটনা</a>)। ৯ জুলাই প্রায় দশ ঘণ্টা ধরে জবগুলো দেরিতে শুরু হয় বা শুরুই হয়নি: Actions "experienced delayed and failed job starts on GitHub-hosted runners" (<a href="https://www.githubstatus.com/incidents/cstx3v63mklm">ঘটনা</a>)। ২৬ আগস্ট জব শুরু হয়নি, "Actions jobs failed to start", আর তার পরের দুই ঘণ্টা রানগুলো পাঁচ মিনিটের বেশি দেরিতে শুরু হয়েছে: "were delayed starting by more than 5 minutes as the system caught up with delayed load" (<a href="https://www.githubstatus.com/incidents/y1t7p9fzrlj2">ঘটনা</a>)। ১৩ সেপ্টেম্বর GitHub জানায় Actions-সহ প্রায় ২৮টি সার্ভিসে প্রাপ্যতা কমে গিয়েছিল: "degraded availability across approximately 28 services, including Issues, Pull Requests, Actions" (<a href="https://www.githubstatus.com/incidents/0rn90wk115q9">ঘটনা</a>)। <a href="https://www.githubstatus.com/history">তাদের স্ট্যাটাস পেজ</a> থেকে গুনলে জানুয়ারি থেকে সেপ্টেম্বরের মধ্যে পঞ্চাশটির বেশি ঘটনা Actions-কে ছুঁয়েছে। রিপোজিটরি, রিভিউ আর ওয়ার্কফ্লোর সংজ্ঞাগুলো GitHub-এই থাকে; সরেছে শুধু সেই মেশিন যেখানে কাজটা চলে, আর সেই মেশিন যা কিছু ডাউনলোড করে ফেলে দিচ্ছিল তার সবটুকু।</p>

### ডিপ্লয় আটের জায়গায় তিন মিনিটে, মেপে দেখা

<p>১৭ সেপ্টেম্বর হোস্টেড মিডিয়ানের সঙ্গে মেপে দেখা হয়: CMS-এর ডিপ্লয় ৮ মিনিট ২৪ সেকেন্ড থেকে নেমে ৩ মিনিট ০৫ সেকেন্ড, ওয়েবসাইটের ৬ মিনিট ১২ সেকেন্ড থেকে ২ মিনিট ২৭ সেকেন্ড, আর MCP সার্ভারের ১ মিনিট ৪৫ সেকেন্ড থেকে ৪৫ সেকেন্ড। একসঙ্গে মার্জ হওয়া দুটি রিলিজ ঘড়ির হিসাবে ৩ মিনিট ২২ সেকেন্ডে পৌঁছে যায়, আর তিনটি সার্ভিসের CI একসঙ্গে চালালে তা এক-তৃতীয়াংশ দ্রুত হয়।</p>

### প্রতিটি রান সেই মেশিনে, যে আগেরটি বানিয়েছিল

<p>হোস্টেড রানার প্রতিবার একটি নতুন মেশিন। ঘরের পুল প্রতিটি জবে একই মেশিন, তাই তাদের ইমেজ লেয়ার আর প্যাকেজ ক্যাশ আগে থেকেই থাকে, ক্লাস্টারে যাওয়ার পথে ইমেজ কখনো ইন্টারনেট পার হয় না, আর বিল্ড চলে বিল্ডের জন্য তৈরি মেশিনে, সাইটগুলো চালানো মেশিনে নয়। একই বিল্ড ঠান্ডা ক্যাশ থেকে মাপা হয় ৫২৮ সেকেন্ড, আর গরম ক্যাশ থেকে ১৯২। ঘরের প্রথম রানগুলো তিন গুণ ধীর ছিল, যতক্ষণ না একটি ধাপ বাদ দেওয়া হয়: প্রতিটি বিল্ড তার ডিপেনডেন্সি ক্যাশ বাসার কানেকশন দিয়ে আবার GitHub-এ আপলোড করছিল, ওয়েবসাইটে তাতে যেত ৪১১ সেকেন্ড, যা অর্থবহ যখন রানারটি অন্য কারও মেশিন, আর একেবারেই নয় যখন ক্যাশ আগে থেকেই সেখানে থাকে যেখানে পরের বিল্ড চলবে।</p><p>Runs ভিউ নজরে রাখা প্রতিটি রিপোজিটরির প্রতিটি রান দেখায় এবং প্রতিটি রান শেষ হওয়া মাত্র আর্কাইভ করে রাখে, তাই তা GitHub-এর রিটেনশনের পরেও টিকে থাকে। স্ক্রিনশটের দিন সেখানে দেখা যাচ্ছিল ৪৯ সেকেন্ড, ৩ মিনিট ১৫ সেকেন্ড আর ২ মিনিট ৫১ সেকেন্ডের ডিপ্লয়, দুই থেকে তিন মিনিটের CI রান, আর ব্যর্থতাগুলোও চোখের সামনে ছিল: একটি ডিপ্লয়ের দুটি ব্যর্থ চেষ্টা, তারপর তৃতীয়টি, যা পাস করে।</p>

### একটি ডিপ্লয়, ধাপে ধাপে, নিজের সময়রেখায়

<p>একটি রান খুললে প্রতিটি জব ও ধাপ দেখা যায় সেই রানের নিজস্ব সময়রেখায়, যা মাপা হয় GitHub-এর টাইমস্ট্যাম্প থেকে, পাশে থাকে রানটি যে ইমেজ পুশ করেছে তার digest, সাইজ ও প্ল্যাটফর্ম, আর সেই ইমেজ এখন কোথায় চলছে। পুনরায় চেষ্টাগুলো মুছে না দিয়ে রাখা হয়: স্ক্রিনশটের ডিপ্লয়টি প্রথম দুই চেষ্টায় ব্যর্থ হয়ে তৃতীয়টিতে পাস করে, প্রতিটি চেষ্টা আলাদাভাবে খোলে, আর দ্বিতীয়টিতে দেখা যায় ডিপ্লয় ধাপটি ছেচল্লিশ সেকেন্ড পর ব্যর্থ হচ্ছে, তাই সমাধানের পরও ব্যর্থতার কারণটি সেখানে থেকে যায়।</p><p>পাস করা চেষ্টাটি দেখায় ঘরের পুলে সময় কোথায় যায়: checkout আর ভার্সন বাড়াতে কয়েক সেকেন্ড, আর বিল্ড ও ক্লাস্টারে ডিপ্লয়ে আড়াই মিনিটের সামান্য কম। এটি আরও দেখায় এমন কিছু যা একটি কনসোল বলতে পারে কিন্তু CI-এর লগ পারে না: সেই ইমেজ আর রেজিস্ট্রিতে নেই, কারণ রিটেনশন শুধু সাম্প্রতিক বিল্ডগুলো রাখে, আর ক্লাস্টারে এখন আর কিছুই সেই digest চালাচ্ছে না।</p>

### বিল্ড মেশিন সরে গেলেও কাজ থেমে থাকে না

<p>রানার পুলে একটি প্রাইমারি আর একটি স্ট্যান্ডবাই আছে। বিল্ড মেশিন চালু থাকলে তার রানাররা প্রতিটি জব নেয়, আর সবসময় চালু সার্ভারে থাকে স্ট্যান্ডবাই রানার, যা চালু হয় কেবল তখন, যখন একটি ওয়াচডগ সিদ্ধান্ত নেয় যে বিল্ড মেশিনটি আর নেই। মেশিনটিকে ডাউন ধরার আগে ওয়াচডগের দরকার একাধিক ব্যর্থ পরীক্ষা, আর ফিরে এসেছে ধরার আগে একাধিক সফল পরীক্ষা, তাই নেটওয়ার্কের ক্ষণিকের বিঘ্নে কিছুই এদিক-ওদিক দোলে না, আর হস্তান্তর দুই দিকেই দু-এক মিনিটের মধ্যে হয়ে যায়। এটি কখনো কোনো জবকে মাঝপথে কাটে না: বন্ধ হওয়ার আগে চলমান জবের জন্য অপেক্ষা করা হয়, আর স্ট্যান্ডবাই সরে দাঁড়ায় কেবল তখনই, যখন সে খালি।</p><p>ওয়াচডগ যদি GitHub-এ পৌঁছাতেই না পারে, তবুও সে স্ট্যান্ডবাই চালু করে দেয়, কারণ নজরদারির এমন ব্যর্থতা যা ডিপ্লয় থামিয়ে দেয়, তা যে বিভ্রাটের ওপর নজর রাখছিল তার চেয়েও খারাপ। কনসোলের ওভারভিউতে রানার পুল প্যানেল জানায় কোন দিক জবগুলো নিচ্ছে আর কোন স্ট্যান্ডবাই রানারগুলো প্রস্তুত।</p>

### ইমেজ আর প্যাকেজ, এক ছাদের নিচ থেকে

<p>সবসময় চালু সার্ভারে একটি কনটেইনার রেজিস্ট্রি আর একটি npm রেজিস্ট্রি পাশাপাশি চলে, কারণ কনটেইনার ইমেজ আর npm প্যাকেজ এমন প্রোটোকলে কথা বলে যাদের মধ্যে কোনো মিল নেই। কনটেইনার রেজিস্ট্রি লোকাল নেটওয়ার্কে পুলের বানানো প্রতিটি ইমেজ নেয়: পুশ করতে পারে শুধু একটি অ্যাকাউন্ট, যা কিছু টেনে নেয় তার জন্য রিড-অনলি অ্যাকাউন্ট, কোনো বেনামি প্রবেশাধিকার নেই, আর একটি ট্যাগ দুটি প্রসেসর আর্কিটেকচারই বহন করতে পারে, যাতে প্রতিটি নোড নিজের সঙ্গে মেলে এমন বিল্ড টেনে নেয়। রিটেনশন প্রতিটি সার্ভিসের সবচেয়ে সাম্প্রতিক দুটি বিল্ড আর সবশেষে টানা বিল্ডটি রাখে, আর এটাই রোলব্যাকের সুযোগ, যা ধরে নেওয়া হয় না, স্পষ্ট করে বলা থাকে।</p><p>npm রেজিস্ট্রি স্টুডিওর নিজস্ব লাইব্রেরি পরিবারকে পরিবেশন করে। প্রতিটি লাইব্রেরির রিলিজ তার GitHub Release থেকে বাইট-বাই-বাইট সেখানে প্রকাশিত হয়, ক্লাস্টারের ভেতরের একটি সিঙ্কের মাধ্যমে প্রতি পাঁচ মিনিটে, প্রতিটি রিডের জন্য লগইন লাগে, পাবলিক নামে লেখা প্রত্যাখ্যাত হয়, আর রাতের একটি জব প্রতিটি প্রকাশিত ভার্সনকে তার রিলিজের সঙ্গে মিলিয়ে দেখে। কোনো রেজিস্ট্রিরই নিজস্ব ইন্টারফেস নেই, তাই কনসোলই তাদের পর্দা: Estate ভিউ কনটেইনার রেজিস্ট্রির রিপোজিটরিগুলো তাদের সাইজ ও প্ল্যাটফর্মসহ দেখায়, আর এই লাইব্রেরি ব্যবহার করা যেকোনো প্রজেক্টে ইনস্টল ভবনের ভেতর থেকেই মিটে যায়।</p>

### একটি মার্জ থেকে চলমান পড পর্যন্ত, আর ফেরার একটি লাইন

<p>একটি মার্জ পুলে বিল্ডকে লাইনে দাঁড় করায়, ইমেজ লোকাল নেটওয়ার্কে রেজিস্ট্রিতে যায়, আর ডিপ্লয় চলে এমন একটি পরিচয়ে যা একটি namespace-এ সীমাবদ্ধ, যার মুছে ফেলার অধিকার নেই এবং যার আছে একটিমাত্র নির্দিষ্ট secret। প্রতিটি নোড নিজের সঙ্গে মেলে এমন বিল্ড টেনে নেয়, আর প্রতিটি রিপোজিটরির একটি ভেরিয়েবল কোনো সার্ভিসকে তার পরের push-এ হোস্টেড রানারে ফেরত পাঠায়।</p>

### কাজের সপ্তাহে এর প্রভাব, আর এটা কাদের জন্য

<p>মার্জগুলো আর একে অপরের জন্য অপেক্ষা করে না: বিল্ড মেশিন চালু থাকলে রিলিজগুলো পাশাপাশি বিল্ড ও ডিপ্লয় হয়, আর স্ট্যান্ডবাইতে একটি লক ভারী ধাপগুলো একে একে চালায়, যাতে কিছু ধাক্কা না খায়। একটি সমাধান আটের বদলে প্রায় তিন মিনিটে বেরিয়ে যায়, সবসময় চালু সার্ভার কম্পাইল করে কেবল তখন, যখন বিল্ড মেশিন নেই, আর GitHub মুছে ফেলার পরেও প্রতিটি রানের রেকর্ড ভবনের ভেতরেই থেকে যায়। রিপোজিটরি, রিভিউ আর ওয়ার্কফ্লোর সংজ্ঞা সবই GitHub-এ রয়ে গেছে; সরেছে কেবল মেশিন, ক্যাশ, ইমেজ আর প্যাকেজ।</p><p>সীমাবদ্ধতাটা শুরুতেই বলে রাখা হয়: বিল্ডের মাঝপথে বিদ্যুৎ চলে গেলে সেই বিল্ড ব্যর্থ হয়, আর সেটি আবার চালানো হয়। যে দলের রিলিজ লাইনে দাঁড়াতে শুরু করেছে, যাদের কোড আর ইমেজ ভবনের বাইরে যাওয়া উচিত নয়, আর যে প্রতিষ্ঠাতা কোনো সমাধানকে অন্যের লাইনে আটকে থাকতে দেখেছেন, তাদের জন্য এটি সঠিক পদক্ষেপ। মেশিনগুলোর দায়িত্ব নেওয়ার কেউ নেই এমন দলের জন্য এটি ভুল পদক্ষেপ, আর সেটা সোজাসুজি বলা দরকার। নিচের ক্লাস্টারটি <a href="/portfolio/scaling-a-homelab-from-one-node-to-a-multi-node-cluster">একটি নোডের ক্লাস্টার হয়ে ওঠার</a> গল্প, তাকে ঘিরে থাকা প্ল্যাটফর্মটি <a href="/portfolio/self-hosted-homelab-that-runs-the-studio-infrastructure">স্টুডিও চালানো হোমল্যাব</a>, আর একই পদ্ধতি একটি ক্লায়েন্টের প্ল্যাটফর্মে প্রয়োগ হলে তা হয় <a href="/portfolio/fully-booked-zero-downtime-migration-to-self-hosted-kubernetes">সেলফ-হোস্টেড Kubernetes-এ ডাউনটাইম ছাড়া মাইগ্রেশন</a>।</p>
