الفصل الثامن: 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 احترافية كاملة.