8- Jobs

الفصل الثامن: Jobs بالتفصيل

وصلنا الآن إلى أحد أهم المفاهيم في GitHub Actions.

بعد فهم:

Event

Workflow

ننتقل إلى:

Job

وهنا يبدأ التفكير الاحترافي الحقيقي.


ما هو Job؟

الـ Job عبارة عن:

مجموعة Steps تعمل على Runner واحد

أو بشكل أبسط:

مهمة كاملة لها هدف محدد

مثال من الحياة الواقعية

تخيل أنك تبني منزلاً.

لديك:

بناء الجدران
تركيب الكهرباء
الدهان

كل واحدة تعتبر:

Job

داخل مهمة الكهرباء مثلاً:

تمديد الأسلاك
تركيب المفاتيح
اختبار الكهرباء

هذه تعتبر:

Steps

إذن العلاقة:

Workflow

├── Job 1
│ ├── Step
│ ├── Step
│ └── Step

├── Job 2
│ ├── Step
│ └── Step

└── Job 3
├── Step
└── Step

أول Job حقيقية

jobs:
hello:
runs-on: ubuntu-latest

steps:
- run: echo "Hello"

دعنا نفككها.


jobs

jobs:

تعني:

الآن سأعرف مجموعة مهام

hello

hello:

اسم الـ Job.


يمكن أن يكون:

test:

أو:

build:

أو:

deploy:

الاسم هنا مجرد معرف.


runs-on

runs-on: ubuntu-latest

يعني:

أنشئ Ubuntu Runner

steps

steps:

الخطوات التي ستنفذ داخل Job.


كيف يعمل GitHub داخلياً؟

عندما يحدث Push:

Push

Workflow

Create Runner

Run Job

Run Steps

Destroy Runner

لماذا نحتاج أكثر من Job؟

في المشاريع الحقيقية لدينا مراحل مختلفة.

مثلاً:

اختبار المشروع
بناء Docker Image
النشر

لو وضعناها كلها في Job واحدة:

Test

Build

Deploy

ستعمل.

لكننا نفقد الكثير من المزايا.


لهذا نقسمها.


تشغيل عدة Jobs

مثال:

jobs:

test:
runs-on: ubuntu-latest

steps:
- run: echo "Testing"

build:
runs-on: ubuntu-latest

steps:
- run: echo "Building"

deploy:
runs-on: ubuntu-latest

steps:
- run: echo "Deploying"

هنا لدينا:

Workflow

├── test
├── build
└── deploy

ثلاث Jobs مستقلة.


ماذا يحدث داخلياً؟

GitHub يرى:

test
build
deploy

ولأنها مستقلة:

يمكن تشغيلها بالتوازي

أي:

Runner 1 → test

Runner 2 → build

Runner 3 → deploy

ملاحظة مهمة جداً

كل Job تحصل على Runner جديد.


مثال:

jobs:

test:
runs-on: ubuntu-latest

build:
runs-on: ubuntu-latest

GitHub لا يستخدم نفس السيرفر.


بل:

VM 1 → test

VM 2 → build

وهذه نقطة تسبب أخطاء كثيرة للمبتدئين.


خطأ شائع

المطور يفعل:

test:
steps:
- run: touch test.txt

ثم:

build:
steps:
- run: cat test.txt

فيعتقد أن الملف موجود.


لكن:

test Runner ≠ build Runner

الملف اختفى.


لأن كل Job تعمل على جهاز منفصل.


ترتيب Jobs

هنا نصل للجزء المهم.


هل تعمل Jobs بالترتيب؟

الإجابة:

لا

بشكل افتراضي:

تعمل بالتوازي

مثال:

jobs:
test:
build:
deploy:

GitHub يفهمها:

شغل الجميع معاً

وليس:

test

build

deploy

كيف نجعل Job تنتظر Job أخرى؟

باستخدام:

needs:

وهذا من أهم الكلمات في GitHub Actions.


Job Dependencies

Dependency تعني:

اعتماد

مثال:

build:
needs: test

المعنى:

لا تبدأ build

حتى تنتهي test

مثال كامل:

jobs:

test:
runs-on: ubuntu-latest

steps:
- run: echo "Testing"

build:
needs: test

runs-on: ubuntu-latest

steps:
- run: echo "Building"

التنفيذ:

test

build

ماذا لو فشلت test؟

مثلاً:

test:
steps:
- run: exit 1

الناتج:

test ❌

وبالتالي:

build

لن تبدأ أبداً.


وهذا مهم جداً.


مثال Laravel حقيقي

Run Tests

Build Docker Image

Deploy

نكتب:

jobs:

test:

build:
needs: test

deploy:
needs: build

الشجرة:

test

build

deploy

Multiple Dependencies

يمكن أن تعتمد Job على أكثر من Job.


مثال:

deploy:
needs:
- test
- build

المعنى:

deploy

تنتظر

test
+
build

الشكل:

       test


deploy


build

لن تبدأ deploy إلا بعد نجاح الاثنين.


Parallel Jobs

من أقوى مزايا GitHub Actions.


مثال:

لديك:

PHP Tests
JavaScript Tests
Security Scan

لا يوجد سبب لانتظار بعضها.


يمكن تشغيلها معاً.


مثال:

jobs:

php-tests:

js-tests:

security-scan:

GitHub ينفذ:

Runner 1 → PHP

Runner 2 → JS

Runner 3 → Security

في نفس الوقت.


لماذا Parallel Jobs مهمة؟

لنفترض:

PHP Tests = 10 دقائق

JS Tests = 8 دقائق

Security = 5 دقائق

إذا كانت Sequential:

10 + 8 + 5

= 23 دقيقة

إذا كانت Parallel:

أطول Job فقط

= 10 دقائق

لاحظ الفرق.


مثال حقيقي

jobs:

php-tests:
runs-on: ubuntu-latest

js-tests:
runs-on: ubuntu-latest

lint:
runs-on: ubuntu-latest

GitHub سيشغلها كلها معاً.


Sequential Jobs

العكس تماماً.


تعني:

Job بعد Job

مثال:

Tests

Build

Deploy

نكتب:

jobs:

test:

build:
needs: test

deploy:
needs: build

التنفيذ:

test

build

deploy

مقارنة مهمة

Parallel

test

build

deploy

الكل يبدأ مباشرة.


Sequential

test

build

deploy

كل واحدة تنتظر السابقة.


نموذج احترافي Laravel + Docker

هذا قريب مما ستستخدمه فعلياً.

jobs:

phpunit:
runs-on: ubuntu-latest

phpstan:
runs-on: ubuntu-latest

pint:
runs-on: ubuntu-latest

build:
needs:
- phpunit
- phpstan
- pint

deploy:
needs: build

التنفيذ:

            phpunit


phpstan


pint


build


deploy

لكن فعلياً أول ثلاث Jobs تعمل بالتوازي:

 phpunit

phpstan

pint

ثم:

build

ثم:

deploy

كيف يفكر مهندس DevOps محترف؟

لا يفكر:

كم Job سأكتب؟

بل يفكر:

ما هي المراحل المنطقية؟

مثلاً:

Code Quality

Testing

Build

Security

Release

Deploy

ثم يحول كل مرحلة إلى Job مستقلة.


قاعدة ذهبية

كل Job يجب أن تمثل:

هدفاً واحداً واضحاً

مثال جيد:

test
build
deploy

مثال سيء:

do-everything

لأنك ستفقد:

  • الوضوح
  • سهولة التصحيح
  • إعادة الاستخدام
  • Parallel Execution

الصورة الذهنية النهائية

عندما ترى:

jobs:

test:

build:
needs: test

deploy:
needs: build

يجب أن تراها هكذا:

Workflow


test


build


deploy

وعندما ترى:

jobs:

phpunit:

phpstan:

pint:

يجب أن تراها:

phpunit    phpstan    pint
│ │ │
└──────────┼─────────┘

أي أنها تعمل بالتوازي على Runners مختلفة.

في الفصل القادم سندخل إلى Steps بالتفصيل ونفهم الفرق العميق بين:

run:

و

uses:

وكيف تتبادل Steps البيانات فيما بينها، وكيف تعمل البيئة داخل الـ Job الواحدة، وهي من أكثر النقاط التي تميز المبتدئ عن الشخص الذي يستطيع بناء Pipelines احترافية كاملة.