GitHub Actions بعمق

بنية المصفوفة والتوازي في GitHub Actions

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

بنية المصفوفة والتوازي في GitHub Actions

في شركات مثل Google وMeta وAmazon، تختبر خطوط CI الكود ذاته مقابل عشرات إصدارات Node.js وثلاثة أنظمة تشغيل وعلامتَي بناء مختلفتين — وذلك بالتوازي. يُتيح GitHub Actions هذا من خلال استراتيجية المصفوفة: تعريف وظيفة واحدة يتوسّع وقت التشغيل إلى مجموعة من الوظائف المتوازية. إتقان بنية المصفوفة هو الفارق بين خط أنابيب يستغرق 40 دقيقة وآخر يُنجز في 6 دقائق.

ما هي استراتيجية المصفوفة؟

المصفوفة هي خريطة متغيرات تُعرَّف تحت strategy.matrix. يحسب GitHub Actions الضرب الديكارتي لكل أبعاد المصفوفة وينشئ وظيفة واحدة لكل تركيبة. تستقبل كل وظيفة قيم صفها عبر سياق matrix — مثل ${{ matrix.os }} أو ${{ matrix.node }}.

المثال التالي يُنشئ ست وظائف متوازية (3 أنظمة تشغيل × إصداران من Node):

name: Multi-version CI on: [push, pull_request] jobs: test: strategy: matrix: os: [ubuntu-24.04, windows-latest, macos-14] node: ['20', '22'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: 'npm' - run: npm ci - run: npm test
الضرب الديكارتي: يُقرن كل عنصر من os مع كل عنصر من node. ثلاثة أنظمة تشغيل وإصداران = 3 × 2 = 6 وظائف متزامنة. إضافة بُعد ثالث من 4 علامات بناء ستُنتج 3 × 2 × 4 = 24 وظيفة. راعِ حدود الاستخدام (256 وظيفة كحد أقصى للمصفوفة).

الإدراج والاستثناء (includes & excludes)

نادرًا ما يكون الضرب الديكارتي هو ما تريده بالضبط. يوفر GitHub Actions خيارَين للتحكم:

  • include — يُضيف وظائف إضافية أو يُلحق متغيرات إضافية بتركيبات قائمة.
  • exclude — يحذف تركيبات محددة من الناتج.
jobs: build: strategy: matrix: os: [ubuntu-24.04, windows-latest] node: ['18', '20', '22'] # استثناء تركيبة معطوبة exclude: - os: windows-latest node: '18' # إضافة تركيبة منفردة خارج نطاق الضرب include: - os: ubuntu-24.04 node: '22' experimental: true - os: macos-14 node: '22' runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental == true }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm ci && npm test

يمكن لـ include إلحاق أي مفتاح إضافي بالتركيبة — كـ experimental: true هنا — ثم تستخدمه في تعبيرات مثل continue-on-error. هذا نمط قياسي في كبرى شركات التقنية للسماح لبنايات الكاناري بالفشل دون حجب حالة الوظيفة الكلية.

fail-fast: إيقاف سريع عند الفشل

بشكل افتراضي، يُضبط fail-fast: true على كل مصفوفة. بمجرد فشل أي تركيبة، يُلغي GitHub Actions جميع الوظائف الجارية. هذا هو الإعداد الصحيح لحلقات التغذية الراجعة للمطورين — تريد أن تعرف بسرعة أن شيئًا ما معطوب بدلًا من انتظار 23 وظيفة من أصل 24.

اضبط fail-fast: false حين تحتاج الصورة الكاملة — مثلًا في خطوط أنابيب الإصدار التي تختبر كل البيئات المدعومة وتحتاج إلى تقرير كامل بالإخفاقات قبل إصدار النسخة.

strategy: fail-fast: false # شغّل كل التركيبات واجمع صورة الإخفاقات كاملة matrix: os: [ubuntu-24.04, windows-latest, macos-14] python: ['3.10', '3.11', '3.12', '3.13']
نمط إنتاجي: استخدم fail-fast: true في CI للفروع الميزاتية (تغذية راجعة سريعة) وfail-fast: false في بنايات الإصدار والنوكتورنية (تغطية انحدار كاملة).

مجموعات التزامن (Concurrency Groups)

التوازي داخل سير العمل رائع — لكنك تحتاج أيضًا للتحكم في التوازي عبر تشغيلات سير العمل. بدون التحكم في التزامن، يُشغّل كل ضغط على PR تشغيلًا جديدًا، وتتراكم عشر تشغيلات قديمة تستهلك الدقائق بلا فائدة.

يُعرّف مفتاح concurrency مجموعة. يضمن GitHub Actions أن تشغيلًا واحدًا فقط لكل مجموعة يكون نشطًا في وقت واحد:

name: PR CI on: push: branches: ['**'] pull_request: # تشغيل واحد نشط لكل فرع. ألغِ التشغيل الجاري عند وصول commit جديد. concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: test: strategy: matrix: node: ['20', '22'] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm ci && npm test

مع cancel-in-progress: true، بمجرد وصول ضغطة جديدة على الفرع ذاته، تُلغى كل الوظائف الجارية في المجموعة. هذه هي أكبر وفورات الوقت في CI الحقيقي وتُستخدم بشكل شامل في الشركات ذات تكرار الضغط العالي.

لا تُلغِ وظائف النشر. اضبط cancel-in-progress: false (أو استخدم مجموعة تزامن منفصلة) للوظائف التي تكتب على بنية التحتية الإنتاجية. إلغاء نشر في منتصفه قد يترك الموارد في حالة تحديث جزئي. النمط الشائع هو group: deploy-${{ github.ref }}-${{ github.sha }} مع cancel-in-progress: false لترتيب عمليات النشر في قائمة دون إلغائها.

كيف يتكامل كل شيء معًا

يُظهر الرسم التالي تشغيل CI نموذجي بمصفوفة: سير عمل واحد يُشغّل ست وظائف اختبار متوازية (3 أنظمة تشغيل × إصداران)، كلها مُقيَّدة بمجموعة تزامن تُلغي التشغيلات القديمة عند الضغطات الجديدة.

Matrix strategy fan-out to parallel jobs Push / PR Concurrency Group strategy.matrix Cartesian product → 6 jobs ubuntu / Node 20 Runner: ubuntu-24.04 ubuntu / Node 22 Runner: ubuntu-24.04 windows / Node 20 Runner: windows-latest windows / Node 22 Runner: windows-latest macos / Node 20 Runner: macos-14 macos / Node 22 Runner: macos-14
تتوسع مصفوفة من 3 أنظمة تشغيل × إصداران من Node إلى 6 وظائف متوازية عبر عدّائين مستقلين. تضمن مجموعة التزامن إلغاء التشغيلات القديمة عند الضغطات الجديدة.

max-parallel: ضبط سقف التوازي

أحيانًا لا تريد تشغيل كل التركيبات دفعة واحدة — مثلًا إذا كانت كل وظيفة تضغط على قاعدة بيانات تجهيزية مشتركة أو API خارجي محدود المعدل. استخدم max-parallel للحدّ من التزامن:

strategy: max-parallel: 4 # لا تعمل أكثر من 4 وظائف في آنٍ واحد matrix: env: [dev, staging, qa, perf, canary, prod-eu, prod-us]

مع سبع بيئات وmax-parallel: 4، تبدأ الأربع الأولى فورًا؛ الثلاث الباقية تُوضع في قائمة انتظار وتبدأ مع تحرر الفتحات. هذا أمر بالغ الأهمية لاختبارات التكامل التي تشترك في البنية التحتية.

أنماط إنتاجية حقيقية

  • أبقِ أبعاد المصفوفة صغيرة. مصفوفة 5 × 5 × 5 = 125 وظيفة متزامنة تستنفد حصص معظم المؤسسات. اختصر إلى الحد الأدنى الذي يكتشف الأخطاء فعلًا: عادةً نظامَا تشغيل وإصداران من بيئة التشغيل.
  • ثبّت إصدارات العدّائين. ubuntu-latest يتغير بصمت. في خطوط الإنتاج، ثبّت على ubuntu-24.04 لتجنب مفاجآت تحديث صورة العدّاء وسط السباق.
  • احذر من outputs لوظائف المصفوفة. يبقى فقط ناتج آخر وظيفة تنتهي. اجمع النتائج عبر أثر (artifact) أو وظيفة لاحقة بـ needs.
  • التزامن على الفرع الرئيسي. لا تضبط cancel-in-progress: true على ضغطات main أو master أبدًا. إلغاء بناية الفرع الرئيسي في المنتصف يُفسد خطوط النشر. خصّص الإلغاء للفروع الميزاتية فقط.
وعي بالتكلفة: في عدّائي GitHub المستضافة، تُحتسب دقائق macOS بمعدل 10 أضعاف Linux. مصفوفة 3 OS × 10 إصدار مع وظائف بـ 30 دقيقة قد تستهلك 3 ساعات macOS لكل تشغيل. فضّل المصفوفات القائمة على Linux فقط ما لم تحتج تغطية OS حقيقية.