
اتوماسیون CI با Temporal و Nomad: اجرای خودکار و هوشمند رانرهای GitHub Actions
چطور با ترکیب Temporal، Nomad و یک اپلیکیشن GitHub، یک سیستم مقیاسپذیر و خودکار برای اجرای رانرهای CI ساختیم که منابع را تنها در زمان نیاز مصرف میکند و مدیریت دستی را حذف میکند.
نکات کلیدی
- رانرهای CI بهصورت لحظهای (On-Demand) و فقط در صورت وجود Job در صف ایجاد و پس از اتمام حذف میشوند.
- سیستم با منطق تطبیق بر اساس عمق صف (Queue Depth) از قفل شدن Jobها جلوگیری میکند و نیازی به ردیابی هر Job بهصورت مجزا نیست.
- احراز هویت امن از طریق GitHub App و توکنهای نصب (Installation Token) کوتاهمدت و محدود به هر مخزن انجام میشود.
مدیریت رانرهای خودمیزبان (Self-Hosted Runners) در حسابهای شخصی GitHub خیلی زود به یک دردسر بزرگ تبدیل میشود. در این حالت، هر مخزن (Repository) به یک رانر اختصاصی نیاز دارد که باید دستی ثبت شود. برای شش مخزن، یعنی شش رانر با توکنهای جداگانه که بیشتر روز، بخشی از منابع خوشه (Cluster) هوملب را بیاستفاده نگه میدارند. راهحل این مقاله، استفاده از Temporal و Nomad برای ساخت یک سیستم مقیاسپذیر (Scaler) است که رانرها را بهصورت لحظهای (On-Demand) و بر اساس صف کارها ایجاد و بعد از اتمام، حذف میکند.
معماری سیستم: از صف GitHub تا اجرا در Nomad
هدف اصلی، جایگزینی رانرهای همیشهآماده با رانرهای زودگذر (Ephemeral) بود که فقط برای اجرای یک Job ساخته شده و بعد حذف میشوند. هستهی سیستم، یک گردش کار (Workflow) Temporal به نام PollAndDispatch است که هر ۳۰ ثانیه طبق یک زمانبندی (Schedule) اجرا میشود.
مراحل کلیدی گردش کار اصلی:
۱. بارگذاری پیکربندی: تنظیمات هر مخزن (مثل سقف رانرهای همزمان و نوع احراز هویت) از Consul KV خوانده میشود.
۲. بررسی صف کارها: سیستم با APIهای GitHub یا Forgejo، Jobهای در انتظار (Queued) که به رانر خودمیزبان نیاز دارند را برای هر مخزن فهرست میکند.
۳. شمارش رانرهای فعال: با پرسوجو از Nomad، تعداد رانرهایی که قبلا برای هر مخزن (و مجموعه برچسبهای مشخص) ایجاد شدهاند و هنوز در حال اجرا یا در وضعیت Pending هستند، شمارش میشود.
۴. ایجاد رانرهای جدید: برای هر Job در صف که رانر فعالی برایش نیست، یک گردش کار فرزند (Child Workflow) به نام HandleRunner راهاندازی میشود.
گردش کار فرزند: زندگی یک رانر از شروع تا پایان
هر نمونه از HandleRunner چرخهی حیات یک رانر را کامل مدیریت میکند.
فاز اول: Dispatch (اعزام): در این مرحله، یک توکن ثبتنام (Registration Token) کوتاهمدت برای مخزن هدف ساخته میشود. سپس یک Job پارامتری (Parameterized Batch Job) در Nomad با استفاده از این توکن و متادیتای مربوط (آدرس مخزن و برچسبها) اجرا (Dispatch) میشود. این Job همان کانتینر رانر GitHub Actions است.
مشخصات Job رانر در Nomad:
- نوع: Batch (دستهای).
- سیاست راهاندازی مجدد: غیرفعال؛ رانر پس از اتمام نباید دوباره راهاندازی شود.
- منابع: رزرو ثابت CPU و حافظه. چون رانر موقتی است، نیازی به
memory_maxبرای اضافهبرداشت (Oversubscription) نیست. - احراز هویت: توکن Nomad محدود (Scoped) از Vault خوانده میشود تا رانر بتواند Jobهای دیگر را در Nomad اعتبارسنجی (Validate) یا برنامهریزی (Plan) کند.
فاز دوم: Wait (انتظار): سیستم منتظر میماند تا تخصیص (Allocation) رانر در Nomad به وضعیت پایانی (Terminal) برسد. برای این انتظار، حداکثر یک ساعت زمان در نظر گرفته شده است. اگر رانر به هر دلیل قفل کند یا نتواند Job را بردارد، بعد از این مهلت، فرآیند ادامه پیدا میکند.
فاز سوم: پاکسازی (Reap): پس از اتمام رانر، Job اجراشده در Nomad متوقف و از حالت ثبت خارج (Deregister) میشود. این کار از جمع شدن Jobهای تمامشده در state در Nomad جلوگیری میکند.
یک نکتهی حیاتی این است که Activity مربوط به Dispatch هرگز بازپخش (Retry) نمیشود؛ چون هر بار اجرای آن یک رانر جدید میسازد و اگر پاسخی در بازپخش از دست برود، رانر تکراری ایجاد میشود.
حل چالشهای هماهنگی (Reconciliation)
نسخهی اولیه، هر رانر را به یک Job صفشدهی مشخص گره میزد. اما این منطق با رفتار GitHub سازگار نبود. وقتی چند Job با برچسبهای یکسان در صف باشند، GitHub رانر تازه را به هرکدام که بخواهد هدایت میکند، نه لزوما همان Job اول. نتیجه این بود که Job اصلی ممکن بود برای همیشه در صف بماند.
راهحل نهایی: تطبیق بر اساس عمق صف سیستم دیگر رانرها را به Jobهای خاص متصل نمیکند. در عوض، در هر چرخه: ۱. Jobهای در صف بر اساس «مخزن» و «مجموعه برچسبها» گروهبندی میشوند. ۲. رانرهای فعال برای هر گروه شمارش میشوند. ۳. اگر تعداد Jobهای در صف یک گروه از رانرهای فعال آن بیشتر بود، به تعداد اختلاف، رانر جدید ایجاد میشود.
این منطق ساده و بدون حالت (Stateless) از قفل شدن Jobها جلوگیری میکند. حتی اگر یک رانر Job دیگری را بردارد، Job اصلی در چرخهی بعدی هنوز در صف است و سیستم برایش رانر جدید میسازد.
مدیریت احراز هویت امن با GitHub App
برای مخازنی که در حساب شخصی هستند و نمیتوانند رانر مشترک داشته باشند، سیستم از یک GitHub App استفاده میکند که روی همهی مخازن نصب شده است.
مکانیسم توکنهای نصب (Installation Tokens): کلاینت اپلیکیشن فقط یک JWT امضاشده با کلید خصوصی اپ را نگه میدارد. برای هر عملیات روی یک مخزن (مثلا ساخت توکن ثبتنام یا خواندن صف)، ابتدا یک توکن نصب (Installation Token) کوتاهمدت و محدود به همان مخزن و با دقیقا دسترسیهای لازم ساخته میشود. این توکن سپس برای احراز هویت درخواست به API GitHub استفاده میشود. این الگو اصل حداقل دسترسی را رعایت میکند.
برای مخازنی که نصب اپ روی آنها ممکن نیست (مثلا مخازن همکاری)، از توکنهای دسترسی شخصی (PAT) که در Vault ذخیره شدهاند استفاده میشود. نکتهی مهم این است که توکن با دسترسی ادمین (برای ثبت رانر) هرگز به سرویس سکیلر نمیرسد و مستقیم توسط Job رانر از Vault خوانده میشود.
پیکربندی انعطافپذیر و پروفایلهای رانر
تنظیمات هر مخزن در یک سند JSON در مسیر runners/config روی Consul KV نگهداری میشود. این تنظیمات شامل موارد زیر است:
mode: نوع احراز هویت (app،forgejo،vault).maxConcurrent: سقف کلی رانرهای همزمان برای آن مخزن.profiles: آرایهای از پروفایلها برای مسیریابی Jobها به استخرهای رانر مختلف.
پروفایلها مسیریابی هوشمند را ممکن میکنند. هر پروفایل یک label (یا مجموعهای از برچسبها) و یک job مربوطه در Nomad دارد. وقتی یک Job در GitHub با برچسب خاصی در صف قرار میگیرد، سیستم اولین پروفایل منطبق را پیدا کرده و Job رانر مربوطه را در Nomad اجرا میکند. اگر هیچ پروفایلی منطبق نباشد، از Job پیشفرض (ci-runner) استفاده میشود.
یک نمونهی اجرای واقعی
در یک اجرای ثبتشده، سیستم پس از پیمایش مخازن، یک Job در صف با برچسب [build] روی مخزن alex/s3-orchestrator (میزبانیشده روی Forgejo) پیدا کرد. چون هیچ رانر فعالی برای این گروه وجود نداشت، یک گردش کار فرزند HandleRunner راهاندازی شد.
زمانبندی:
21:01:01: پیمایش انجام شد و یک رانر جدید ایجاد شد.21:01:02: Job رانر در Nomad با نامforgejo-build-runner/dispatch-1790370061-44e4b15bاجرا شد.21:03:07: رانر کار خود را تمام کرد و Job آن در Nomad متوقف و پاکسازی (Purge) شد.
کل چرخهی حیات این رانر موقتی حدود ۱۲۵ ثانیه طول کشید و بعد از آن هیچ ردپایی در Nomad باقی نماند.
چرا Temporal و Nomad؟
انتخاب Temporal چند دلیل روشن داشت: ۱. ماندگاری حالت (Durable State): وضعیت هر رانر (گردش کار فرزند) در برابر ریاستارت کارگر (Worker) مقاوم است و نیازی به ذخیرهسازی حالت بیرونی نیست. ۲. تایماوت و بازپخش کنترلشده: امکان تنظیم دقیق سیاست بازپخش برای هر Activity وجود دارد؛ مثلا «بدون بازپخش» برای Dispatch. ۳. قابلیت مشاهده (Observability): رابط کاربری Temporal وضعیت گردش کارها، فعالیتها و خطاها را بهصورت گرافیکی نشان میدهد.
استفاده از Nomad هم منطقی بود؛ چون خوشه از قبل با آن مدیریت میشد و کتابخانههای کلاینت مشترک برای Nomad، Consul و Vault از قبل در پروژه موجود بود.
جمعبندی: پایان مدیریت دستی رانرها
این سیستم با خودکارسازی چرخهی حیات رانرهای CI، مدیریت دستی توکنها و ثبت رانرها را حذف کرده است. منابع محاسباتی فقط وقتی مصرف میشوند که واقعا Jobی در صف باشد و بعد از اتمام، بلافاصله آزاد میشوند. رویکرد تطبیق بر اساس عمق صف (Queue Depth) بهجای ردیابی Jobهای مشخص، جلوی دامهای هماهنگی را میگیرد. معماری مبتنی بر GitHub App و توکنهای نصب کوتاهمدت نیز امنیت را بالا نگه میدارد و اصل حداقل دسترسی را رعایت میکند. خروجی، یک سیستم CI مقیاسپذیر، بهصرفه و کاملا خودکار است که بار عملیاتی را بهطور محسوسی کم میکند.
ابزارهای مرتبط والور
سوالات متداول
مزیت اصلی رانرهای لحظهای (Ephemeral) نسبت به رانرهای همیشهآماده چیست؟
مهمترین مزیت، صرفهجویی در منابع است. رانرهای لحظهای فقط زمانی ایجاد میشوند که واقعا Jobی در صف اجرا وجود داشته باشد و بلافاصله پس از اتمام حذف میشوند. این برخلاف رانرهای همیشهآماده است که حتی در زمان بیکاری هم بخشی از منابع خوشه (مثل CPU و حافظه) را مشغول نگه میدارند.
سیستم چگونه از ایجاد رانرهای تکراری برای یک Job جلوگیری میکند؟
سیستم از منطق تطبیق بر اساس عمق صف (Queue Depth) استفاده میکند، نه ردیابی Jobهای خاص. در هر چرخه، تعداد Jobهای در صف هر گروه (مخزن + برچسب) با تعداد رانرهای فعال همان گروه مقایسه میشود و فقط در صورت کمبود، رانر جدید ساخته میشود. این روش Stateless است و حتی اگر یک رانر Job دیگری را بردارد، Job اصلی در چرخهی بعدی دوباره شمارش شده و برایش رانر ساخته میشود.
نقش GitHub App در این معماری چیست؟
GitHub App برای احراز هویت امن و مبتنی بر اصل حداقل دسترسی استفاده میشود. اپ یک JWT کلی دارد، اما برای هر عمل روی یک مخزن (مثلا ساخت توکن ثبتنام)، یک توکن نصب (Installation Token) کوتاهمدت و محدود به همان مخزن میسازد. این توکن موقت سپس برای فراخوانی API GitHub استفاده میشود و بعد منقضی میشود؛ بنابراین خطر افشای توکن دائمی کاهش مییابد.
منابع
- On-Demand CI Runners on Nomad with Temporal — DEV Community
این مقاله با کمک هوش مصنوعی از منابع بالا تدوین و توسط امیرعلی کریمی بازبینی شده است. قوانین محتوایی والور

بنیانگذار و سردبیر والور
امیرعلی کریمی بنیانگذار و سردبیر والور است. مقالههای والور با کمک هوش مصنوعی و بر پایهی چند منبع معتبر بینالمللی تدوین میشوند و من پیش از انتشار، هر مطلب را از نظر درستی اطلاعات، روانی متن و منابع بازبینی و تایید میکنم. منابع هر مقاله در انتهای همان صفحه فهرست شدهاند.
مطالب مرتبط
رونمایی از Dots؛ دستیار هوش مصنوعی همیشهروشن OpenAI که آماده رقابت با Muse متا است
OpenAI از Dots، دستیار هوش مصنوعی شخصی و «همیشهروشن» خود رونمایی کرد. این دستیار بر پایه GPT-6 Astra ساخته شده، در پسزمینه کار میکند و رقیب Muse متا است.
توافق رهبرانهوش مصنوعی برای کاهش سرعت؛ پیمان امنیتی یا کارتل انحصاری؟
رهبران شرکتهای بزرگ هوش مصنوعی از جمله مدیرعاملان OpenAI و Anthropic بهطور غیررسمی موافقت کردهاند که توسعه این فناوری را کند کنند. اما انگیزه اصلی آنها چیست؟ امنیت یا حفظ برتری؟
هشدار مدیرعامل Anthropic: سوارم هوش مصنوعی ظرف یک سال میتواند کل اینترنت را تسخیر کند
داریو آمودی، مدیرعامل Anthropic، هشدار داده که توسعهی شتابان هوش مصنوعی خطر از دست دادن کنترل را به همراه دارد. او از احتمال شکلگیری باتنتهای خودمختار توسط سوارمهای هوش مصنوعی تا ۱۲ ماه آینده