GitHub Actions بعمق

المُشغِّلون وبيئة التنفيذ

18 دقيقة الدرس 2 من 30

المُشغِّلون وبيئة التنفيذ

في الدرس الأول تعلّمت أن الـ workflow هو ملف YAML مؤلف من jobs وsteps تُطلَق بواسطة أحداث. لكن من الذي يُنفّذ تلك الخطوات فعلياً؟ الجواب هو المُشغِّل (runner) — خادم (مادي أو افتراضي أو حاوية) يستمع للعمل من GitHub Actions، وينفّذ كل خطوة داخل job، ويُرسل النتيجة إلى GitHub. كل قرار تتخذه بشأن الـ runners — أي نوع، أي نظام تشغيل، كم عدده، كيفية اتصاله بالشبكة — له عواقب مباشرة على سرعة الـ pipeline وتكلفته وأمانه وامتثاله.

المُشغِّلون المستضافون لدى GitHub

تحتفظ GitHub بأسطول عالمي من الأجهزة الافتراضية المؤقتة (ephemeral). كل job يعمل على مُشغِّل مستضاف يبدأ من VM منشأ حديثاً — لا يوجد أي حالة متبقية من job سابق، لأي فريق أو أي مستودع. هذه هي خاصية الأمان الأساسية للمُشغِّلين المستضافين: كل تشغيل يبدأ من صفحة نظيفة.

ثلاث عائلات رئيسية ومواصفاتها الحالية:

  • ubuntu-24.04 / ubuntu-22.04 — Linux (x64). 4 vCPU، 16 GB RAM، 14 GB SSD. الحصان الأقوى لمعظم أحمال CI. أسرع إقلاع، أرخص سعراً في الدقيقة، أفضل أدوات مثبتة مسبقاً.
  • windows-2022 / windows-2025 — Windows Server (x64). 4 vCPU، 16 GB RAM. مطلوب لأهداف .NET Framework وMSBuild واختبارات Windows المحددة. يُفاتَر بمعدل ضعفين من معدل Linux.
  • macos-15 / macos-14 — macOS (arm64 على أجهزة M1). مطلوب لبناء تطبيقات iOS وmacOS (Xcode). يُفاتَر بمعدل عشرة أضعاف من معدل Linux — أغلى خيار مستضاف بكثير.

تقدّم GitHub أيضاً مُشغِّلين أكبر حجماً — 8 أو 16 أو 32 أو 64 vCPU — للمؤسسات التي تحتاجها. تتطلب خطة تنظيم أو مؤسسة وتُكوَّن تحت Settings > Actions > Runner groups.

الفكرة الأساسية — ثبّت إصدار نظام تشغيل الـ runner. الوسم ubuntu-latest هو مؤشر متغير تقوم GitHub بتحديثه عند صدور نسخة LTS جديدة. في الماضي، انتقلت GitHub بـ ubuntu-latest من 20.04 إلى 22.04 وكسرت pipelines تعتمد على إصدارات حزم النظام. ثبّت دائماً على إصدار صريح (ubuntu-24.04) في workflows الإنتاج حتى تكون الترقيات مقصودة لا عرضية.

تأتي بيئة الـ runner مع قائمة برامج كبيرة — Node.js وPython وJava وDocker وkubectl وTerraform وعشرات غيرها — لكن الإصدارات المثبتة تتغير مع كل إصدار صورة جديدة. لأي أداة يعتمد عليها الـ pipeline، ثبّتها صراحةً في خطوة (باستخدام action من نوع actions/setup-*) بدلاً من الاعتماد على ما قد يكون مثبتاً مسبقاً.

المُشغِّلون المستضافون ذاتياً

المُشغِّل المستضاف ذاتياً هو أي جهاز تقوم بتوفيره وتسجيله مع GitHub وصيانته بنفسك. وكيل runner الخاص بـ GitHub Actions هو برنامج ثنائي صغير مفتوح المصدر مكتوب بـ Go يستمع لـ API الخاصة بـ GitHub للوظائف المخصصة له. يعمل على Linux وWindows وmacOS وARM.

ثلاثة أسباب جوهرية تدفع الفرق للانتقال إلى مُشغِّلين مستضافين ذاتياً:

  1. الوصول إلى الشبكة. اختبارات التكامل أو نصوص النشر أو فحوصات الأمان يجب أن تصل إلى موارد خاصة — قاعدة بيانات داخلية، سجل حزم artifacts داخلي، مجموعة Kubernetes مستضافة في VPC — لا يمكن الوصول إليها من نطاقات IP العامة لـ GitHub.
  2. التكلفة على نطاق واسع. عند الحجم الكبير (آلاف دقائق CI يومياً)، تتجاوز تكلفة الدقيقة للمُشغِّلين المستضافين التكلفة المستهلكة لامتلاك أو استئجار حوسبة مخصصة. كثير من المؤسسات الهندسية الكبيرة تُشغّل مُشغِّليها على VMs spot/preemptible لخفض التكلفة أكثر.
  3. أجهزة مخصصة. تدريب ML بتسريع GPU، أجهزة Apple Silicon لبناء iOS، أهداف FPGA، أو اختبار الأجهزة الحقيقية — كل هذه تتطلب أجهزة تحضرها بنفسك.
مصيدة إنتاجية — المُشغِّلون المستضافون ذاتياً هم حدود أمان تمتلكها أنت. المُشغِّلون المستضافون مؤقتون ومعزولون. المُشغِّلون الذاتيون دائمون ومشتركون (ما لم تُكوّن الوضع المؤقت صراحةً). يمكن لـ pull request خبيث من fork تنفيذ كود عشوائي على مُشغِّلك إن قبلت pull requests من مساهمين غير موثوقين. لا تُشغِّل مُشغِّلين ذاتيين على مستودعات عامة إلا إن أتمتّ كل ضوابط الأمان المذكورة في دليل التصليب من GitHub (مُشغِّلون مؤقتون، لا حالة مشتركة بين الـ jobs، لا أسرار على الجهاز). للمستودعات الداخلية فقط، المُشغِّلون الذاتيون هم الممارسة القياسية.

تسجيل مُشغِّل ذاتي يستغرق أقل من خمس دقائق:

# شغّل هذا على الجهاز الذي تريد تسجيله كـ runner. # انتقل إلى: GitHub repo (أو org) > Settings > Actions > Runners > New self-hosted runner # نزّل وكيل الـ runner (مثال Linux x64 — راجع واجهة المستخدم للحصول على رابط الإصدار الحالي) mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64-2.316.0.tar.gz -L \ https://github.com/actions/runner/releases/download/v2.316.0/actions-runner-linux-x64-2.316.0.tar.gz tar xzf ./actions-runner-linux-x64-2.316.0.tar.gz # تكوين (الرمز يُنشأ في واجهة GitHub، صالح لمدة ساعة) ./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \ --token AABBT3EXAMPLE \ --labels self-hosted,linux,x64,production \ --name runner-prod-01 \ --work _work \ --unattended # تثبيت كخدمة systemd ليبقى بعد إعادة التشغيل sudo ./svc.sh install sudo ./svc.sh start # تحقق من أنه يستمع sudo ./svc.sh status

في الإنتاج، لا تسجّل مُشغِّلاً واحداً أبداً. سجّل أسطولاً خلف وسم واترك GitHub يوزّع الـ jobs عبر الأسطول. استخدم مجموعات الـ runner للتحكم في أي مستودعات يمكنها الوصول إلى أي مُشغِّلين — حدود عزل حرجة في المؤسسات متعددة الفرق.

مجموعات الـ Runner: التحكم في الوصول على نطاق واسع

مجموعات الـ runner هي بنية على مستوى المؤسسة (أو المشروع) تتيح لك تعيين مُشغِّلين لمجموعات ثم منح مستودعات محددة الوصول إلى تلك المجموعات. هكذا تمنع مؤسسات الهندسة الكبيرة مستودع فريق من جدولة jobs عن طريق الخطأ على مُشغِّلي فريق آخر الباهظة الثمن أو، الأسوأ، على مُشغِّلين تحمل أوراق اعتماد الإنتاج.

# مثال: تقييد مجموعة runner على المستودعات التي تحتاجها فقط # يُعيَّن عبر GitHub API (أو واجهة المستخدم: Org Settings > Actions > Runner groups) curl -X PUT \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ https://api.github.com/orgs/MY_ORG/actions/runner-groups/42/repositories \ -d '{ "selected_repository_ids": [123456789, 987654321] }' # في الـ workflow، استهدف المجموعة عبر وسمها (لا باسم المجموعة): # jobs: # deploy: # runs-on: [self-hosted, production-gcp] # # الـ runner المسجَّل بوسم "production-gcp" يجب أن ينتمي لمجموعة الـ runner # التي تمنح الوصول لهذا المستودع. # الـ jobs من مستودعات أخرى ستنتظر إلى الأبد -- لن تتطابق أبداً.
ممارسة احترافية — مُشغِّلون ذاتيون مؤقتون. كوّن المُشغِّلين الذاتيين بخيار --ephemeral (متاح منذ إصدار runner v2.293). في الوضع المؤقت، يُلغي الـ runner تسجيل نفسه بعد إكمال job واحد، وتُوفّر البنية التحتية كـ كود (Terraform أو Pulumi أو متحكم مخصص) VM جديداً للـ job التالي. هذا يمنحك خاصية الأمان للمُشغِّلين المستضافين (لا حالة مشتركة بين الـ jobs) مع الاحتفاظ بمزايا الذاتي الاستضافة (الوصول للشبكة، الأجهزة المخصصة). أدوات مثل actions-runner-controller (ARC) لـ Kubernetes تؤتمت هذا النمط على نطاق واسع.

الحاويات كـ Jobs: مفتاح container

تدعم GitHub Actions تشغيل job كامل داخل حاوية Docker، بدلاً من تشغيله مباشرة على مضيف الـ runner. هذا يختلف عن خطوة تُشغّل أمر Docker بالصدفة — كامل الـ job يُنفَّذ داخل الصورة المحددة، مما يمنحك بيئة قابلة للتكرار تماماً تطابق حاوية الإنتاج.

GitHub Actions Runner Architecture: Hosted vs Self-Hosted vs Container Job GitHub Actions Service GitHub-Hosted ubuntu-24.04 VM Ephemeral & Isolated Fresh VM per job No residual state Self-Hosted Your VM / Bare Metal Private Network Access Custom labels & groups You manage lifecycle Container Job Docker image as job env Exact prod image Service containers Reproducible env Choosing a Runner Type Public repo / quick start Private network / GPU / cost at scale Exact runtime match / DB sidecar needed → GitHub-Hosted → Self-Hosted (ephemeral mode) → Container Job + service containers
ثلاثة نماذج للـ runner في GitHub Actions: VMs مستضافة (مُدارة بالكامل، مؤقتة)، أجهزة ذاتية الاستضافة (الوصول للشبكة، على مسؤوليتك)، وـ jobs الحاويات (صورة Docker كبيئة الـ job).

مفتاح container على مستوى الـ job يلفّ كل خطوة في حاوية Docker. الخطوات لا تعمل على نظام التشغيل المضيف — بل تعمل داخل الحاوية مع مساحة العمل مثبّتة فيها. هذا يعني إن استخدمت صورة python:3.12-slim، كل خطوة لها تلك النسخة بالضبط من Python، بغض النظر عما ثُبّت على نظام تشغيل الـ runner الأساسي. لا حاجة لـ actions/setup-python.

# .github/workflows/test-in-container.yml # يعمل كامل الـ job داخل صورة Docker المحددة. # الـ runner المضيف يحتاج فقط لتثبيت Docker. name: Test in container on: [push, pull_request] jobs: pytest: runs-on: ubuntu-24.04 # الـ runner المضيف — يجب أن يحتوي Docker container: image: python:3.12-slim # كل خطوة تعمل داخل هذه الحاوية options: --cpus 2 # مرّر علامات Docker run إضافية عند الحاجة # حاويات الخدمة — حاويات sidecar متاحة عبر شبكة Docker bridge. # اسم الخادم هو مفتاح الخدمة (postgres، redis). services: postgres: image: postgres:16-alpine env: POSTGRES_DB: testdb POSTGRES_USER: app POSTGRES_PASSWORD: secret options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-retries 5 env: DATABASE_URL: postgresql://app:secret@postgres/testdb REDIS_URL: redis://redis:6379 steps: - uses: actions/checkout@v4 - name: Install dependencies run: pip install --no-cache-dir -r requirements.txt - name: Run tests with coverage run: pytest --cov=. --cov-report=xml -q - name: Upload coverage uses: codecov/codecov-action@v4 with: files: ./coverage.xml

حاويات الخدمة تُشغَّل جنباً إلى جنب مع حاوية الـ job وتكون متاحة باسم الخادم (postgres، redis) عبر شبكة Docker الداخلية. خيارات --health-* تضمن أن Actions تنتظر حتى تكون الخدمة جاهزة قبل تشغيل خطواتك — تفصيل حرج يمنع الاختبارات المتذبذبة الناجمة عن تسابق بين كود التطبيق وقاعدة بيانات لم تقبل الاتصالات بعد.

ممارسة احترافية — استخدم حاويات الخدمة بدلاً من المحاكاة. المصدر الأكثر شيوعاً لنتائج CI الخضراء الزائفة هو الاختبارات التي تحاكي طبقة قاعدة البيانات أو ذاكرة التخزين المؤقت. المحاكاة تُرجع ما أخبرتها بإرجاعه، لا ما يفعله النظام الحقيقي. حاويات الخدمة تتيح لك التشغيل ضد Postgres حقيقي وRedis حقيقي وRabbitMQ حقيقي في CI — نفس الهيكل كالإنتاج. هذا النهج يكشف فئة كاملة من الأخطاء (سلوك مخطط الاستعلام، حالات حافة التسلسل، استنفاد مجموعة الاتصالات) لن تكشفها المحاكاة أبداً.

إطار قرار اختيار الـ Runner

في الإنتاج، يتبع اختيار الـ runner شجرة قرار واضحة:

  • ابدأ بالمستضاف من GitHub (ubuntu-24.04) لكل شيء. هذا هو الافتراضي الصحيح. التكلفة متوقعة، عبء الصيانة صفر، ونموذج الأمان قوي.
  • انتقل للذاتي الاستضافة فقط عند وجود متطلب ملموس لا يمكن للمُشغِّلين المستضافين الوفاء به: الوصول للشبكة الخاصة، الأجهزة المخصصة، أو توفير التكاليف المدفوع بالحجم. استخدم مُشغِّلين مؤقتين (ARC أو متحكم مخصص) للحفاظ على خصائص أمان المُشغِّلين المستضافين.
  • أضف container: عندما يجب أن تطابق بيئة الاختبار بيئة تشغيل محددة بدقة، أو عندما تحتاج خدمات sidecar (قواعد بيانات، وسطاء رسائل، خوادم SMTP) يكلّف محاكاتها الصحيحة الكثير.
  • استخدم مجموعات الـ runner فور وجود أكثر من فريق أو أكثر من مستوى حساسية للـ runner (مثلاً مُشغِّلون بأوراق اعتماد سحابية للإنتاج مقابل مُشغِّلون بدون أوراق اعتماد).
نمط فشل شائع — سلسلة أدوات قديمة على مُشغِّل ذاتي طويل الأمد. الفرق التي تُشغّل مُشغِّلاً ذاتياً دائماً واحداً تصطدم بنمط فشل متكرر حيث workflow كان يعمل منذ ستة أشهر يكسر صامتاً لأن شيئاً تغيّر على الـ runner: Python النظام رُقّي، أداة أُزيلت بواسطة job آخر، أو نفد مساحة القرص. الحل هو التعامل مع الـ runners كماشية لا حيوانات أليفة. أتمت توفير الـ runner بـ Terraform أو pipeline صورة سحابية، ادمج إصدارات أدواتك في الصورة، واستبدل بدلاً من الإصلاح. إن لم تستطع ذلك بعد، على الأقل ثبّت كل أداة يحتاجها الـ pipeline صراحةً في خطوات الـ workflow — لا تعتمد على حالة بيئية موجودة مسبقاً.

فهم الـ runners بعمق هو أساس كل ما يلي في هذا الدرس التعليمي — استراتيجيات التخزين المؤقت وبناءات المصفوفة والمصادقة OIDC مع السحابة وتحديد نطاق الأسرار — كل هذه تتفاعل مباشرة مع أين وكيف تُنفَّذ الـ jobs. في الدرس 3 ننتقل إلى التعبيرات والسياقات — لغة القوالب التي تتيح لمنطق الـ workflow التكيّف ديناميكياً مع الحدث والفرع وبيئة الـ runner في وقت التشغيل.