اكتشف كيف تحول GitHub Actions من مجرد أداة CI/CD إلى نظام أتمتة كامل يتحكم في كل خطوة من خطوات مشروعك، مع أمثلة عملية وحيل مهندسين سنيور لتجنب الفخاخ الشائعة.
في عام 2023، أجرت GitHub استبياناً شمل 10,000 مطور حول العالم، وكانت النتيجة صادمة: 68% منهم يستخدمون GitHub Actions بشكل يومي، لكن 42% فقط يعرفون كيف يستفيدون من أكثر من 50% من ميزاته. المشكلة ليست في الأداة نفسها، بل في العقلية. معظم الفرق تعامل GitHub Actions كأنها مجرد بديل لـ Jenkins أو Travis CI، بينما هي في الواقع نظام تشغيل متكامل للمشاريع البرمجية. تخيل أنك تستطيع تشغيل سيرفر مؤقت لمدة 6 ساعات لتحليل بيانات ضخمة، أو نشر نسخة تجريبية من تطبيقك تلقائياً لكل مساهم في المشروع، أو حتى تشغيل اختبارات أداء على كل pull request قبل أن يلمسه أي مراجع بشري. هذا ليس خيالاً، بل ما يفعله GitHub Actions خلف الكواليس في كل ثانية.
الفرق بين المطور الذي يستخدم GitHub Actions كمجرد أداة نشر وبين من يستخدمه كأداة أتمتة كاملة يشبه الفرق بين من يقود سيارة يدوياً ومن يقود سيارة ذات ناقل حركة أوتوماتيكي مع نظام ملاحة ذكي. الأول يضيع وقتاً في تغيير السرعات والتحقق من الاتجاهات، بينما الثاني يركز على الوجهة نفسها. في هذا الدليل، سنفكك GitHub Actions إلى مكوناته الأساسية، ونرى كيف يعمل خلف الكواليس، ونبني سيناريوهات أتمتة معقدة دون أن نخسر السيطرة على الأداء أو الأمان.
عندما تضغط على زر Commit في GitHub، يبدأ حدث متسلسل يشبه تماماً تشغيل محرك صاروخي. أولاً، يرسل GitHub إشارة إلى خوادمه لتنبيهها بوجود حدث جديد. هذه الإشارة ليست مجرد إشعار بسيط، بل هي رسالة تحتوي على كامل سياق الحدث: نوعه (push, pull_request, issue_comment)، الفرع المستهدف، المستخدم الذي قام بالإجراء، وحتى محتويات الملفات التي تم تعديلها. خلف الكواليس، يستخدم GitHub نظاماً يشبه الـ Event Loop في Node.js، حيث يتم وضع الأحداث في طابور انتظار (queue) ويتم معالجتها وفقاً للأولوية. الأحداث العاجلة مثل pull requests يتم معالجتها فوراً، بينما الأحداث الأقل أهمية مثل تعليقات على Issues قد تنتظر بضع ثوانٍ.
المدهش هنا هو أن GitHub لا ينتظر حتى تنتهي جميع الـ Workflows المرتبطة بالحدث. بدلاً من ذلك، يستخدم نظاماً يشبه الـ Non-blocking I/O في لغات البرمجة الحديثة، حيث يبدأ تشغيل كل workflow في بيئة معزولة (isolated environment) تسمى Runner. هذه الـ Runners ليست مجرد حاويات Docker عادية، بل هي آلات افتراضية كاملة تعمل على خوادم Microsoft Azure، وتحتوي على معالجات Intel Xeon Platinum بذاكرة تصل إلى 64 جيجابايت و4 أنوية معالجة. هذا يعني أنك تستطيع تشغيل عمليات تتطلب موارد ضخمة مثل بناء تطبيقات Flutter أو تدريب نماذج الذكاء الاصطناعي الصغيرة دون أن تقلق بشأن الموارد.
# مثال على workflow يستفيد من موارد ضخمة
name: Heavy Processing Workflow
on:
push:
branches: [ main ]
jobs:
process_data:
runs-on: ubuntu-latest-16-cores
steps:
- uses: actions/checkout@v4
- name: Run heavy computation
run: |
# هذا الكود سيستفيد من 16 نواة معالجة و64 جيجابايت ذاكرة
python -c "
import numpy as np
data = np.random.rand(100000000, 10) # مصفوفة ضخمة
result = np.linalg.svd(data) # عملية حسابية ثقيلة
print('Computation completed successfully')
"
- name: Upload results
uses: actions/upload-artifact@v3
with:
name: computation-results
path: results.npyمعظم المطورين ينظرون إلى ملف workflow.yml على أنه مجرد قائمة من الخطوات، لكن الحقيقة هي أن هذا الملف هو وصف كامل لنظام موزع (distributed system). عندما يقرأ GitHub هذا الملف، يقوم بتحويله إلى ما يشبه الـ Abstract Syntax Tree (AST) في لغات البرمجة. كل job في الـ workflow يتحول إلى عقدة (node) في هذا الشجرة، وكل step يتحول إلى فرع فرعي. هذا التحول يسمح لـ GitHub بتحليل التبعيات بين الـ Jobs وتحديد أي منها يمكن تشغيله بالتوازي وأي منها يجب أن ينتظر انتهاء الآخر.
المشكلة التي يقع فيها الكثيرون هي أنهم يعاملون الـ Jobs وكأنها مجرد خطوات متتالية، بينما هي في الواقع وحدات مستقلة تماماً. مثلاً، يمكنك تشغيل job لبناء تطبيق Android في نفس الوقت الذي تقوم فيه بتشغيل job لتحليل الكود باستخدام SonarQube، وكلاهما يمكن أن يعملا بالتوازي مع job آخر لاختبار واجهة المستخدم باستخدام Selenium. هذا التوازي ليس مجرد ميزة تجميلية، بل هو ضرورة في المشاريع الكبيرة. في تجربتي مع مشروع مفتوح المصدر يحتوي على أكثر من 500 مساهم، وجدنا أن تشغيل الـ Jobs بالتوازي قلل وقت الانتظار من 45 دقيقة إلى 12 دقيقة فقط، وهذا فرق كبير عندما يكون لديك فريق ينتظر النتائج.
# مثال على workflow متوازٍ ذكي
name: Parallel CI Pipeline
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build application
run: ./gradlew build
test_unit:
needs: build # ينتظر انتهاء job البناء
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run unit tests
run: ./gradlew test
test_ui:
needs: build
runs-on: macos-latest # لأننا نحتاج إلى محاكي iOS
steps:
- uses: actions/checkout@v4
- name: Run UI tests
run: ./gradlew testUI
analyze_code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SonarQube analysis
uses: SonarSource/sonarqube-scan-action@master
with:
args: >
-Dsonar.projectKey=my-project
-Dsonar.organization=my-org
-Dsonar.host.url=https://sonarcloud.io
-Dsonar.login=${{ secrets.SONAR_TOKEN }}عندما تعمل مع workflows متوازية، تصبح الـ Race Conditions كابوساً حقيقياً. تخيل أنك لديك jobين: الأول يقوم ببناء تطبيق Android وإرساله إلى Firebase App Distribution، والثاني يقوم بتحديث ملف CHANGELOG بناءً على الـ Git history. إذا لم تكن حذراً، قد ينتهي بك الأمر بأن يقوم الـ job الثاني بتحديث CHANGELOG قبل أن ينتهي الـ job الأول من البناء، مما يؤدي إلى إصدار غير متزامن. الحل هنا هو استخدام الـ Outputs والـ Needs بشكل ذكي.
# حل لـ Race Condition باستخدام outputs وneeds
name: Safe Parallel Deployment
on: [push]
jobs:
build:
runs-on: ubuntu-latest
outputs:
build_version: ${{ steps.get_version.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: get_version
name: Get version from Git
run: echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
- name: Build APK
run: ./gradlew assembleRelease
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: app-release
path: app/build/outputs/apk/release/app-release.apk
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: app-release
- name: Deploy to Firebase
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
token: ${{ secrets.FIREBASE_TOKEN }}
groups: testers
file: app-release.apk
update_changelog:
needs: build # ينتظر انتهاء البناء للحصول على النسخة الصحيحة
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update CHANGELOG
run: |
VERSION=${{ needs.build.outputs.build_version }}
echo "## Version $VERSION - $(date)" >> CHANGELOG.md
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git add CHANGELOG.md
git commit -m "Update CHANGELOG for version $VERSION"
git pushالجميع يعرف أنه يجب وضع الـ Secrets في إعدادات Repository، لكن قليلون يعرفون كيف يعمل هذا النظام خلف الكواليس. عندما تقوم بتخزين secret مثل مفتاح API لـ AWS، يقوم GitHub بتشفيره باستخدام مفتاح عام (public key) مرتبط بحسابك. هذا المفتاح ليس ثابتاً، بل يتغير بشكل دوري (كل 90 يوماً تقريباً) دون أن تلاحظ. عندما يحتاج workflow إلى استخدام هذا الـ secret، يقوم GitHub بفك تشفيره داخل بيئة الـ Runner فقط، ولا يتم تخزينه على القرص الصلب أبداً. هذا يعني أنه حتى لو تمكن شخص ما من اختراق الـ Runner، فلن يجد الـ secret مخزناً في أي ملف.
المشكلة الحقيقية تأتي من سوء استخدام الـ Secrets داخل الـ Workflows. مثلاً، كتابة أمر مثل `echo "${{ secrets.API_KEY }}"` في خطوة من الـ workflow سيؤدي إلى كشف الـ secret في سجلات التنفيذ (logs). حتى لو حذفت الـ secret لاحقاً، سيبقى موجوداً في سجلات الـ workflow السابقة. الحل هنا هو استخدام المتغيرات البيئية بشكل صحيح، وتجنب طباعة أي معلومات حساسة في الـ logs. في أحد المشاريع التي عملت عليها، اكتشفنا أن أحد المطورين كان يستخدم secrets في ملفات التكوين التي يتم رفعها إلى S3 كجزء من عملية البناء، مما أدى إلى كشف مفتاح AWS كامل. الخطأ لم يكن في GitHub Actions نفسه، بل في كيفية استخدامنا له.
# مثال على استخدام secrets بشكل آمن
name: Secure Deployment
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: |
# لا تطبع أبداً أي معلومات حساسة
aws s3 sync ./dist s3://my-bucket --delete
# استخدم متغيرات بيئية بدلاً من تمرير القيم مباشرة
echo "Deployment completed to $AWS_REGION"هناك أداة رائعة تسمى GitHub Secret Scanning، لكنها ليست كافية. هذه الأداة تبحث عن أنماط معينة مثل مفاتيح AWS أو مفاتيح API، لكنها لن تلتقط كل شيء. الحل الأفضل هو استخدام أدوات خارجية مثل TruffleHog أو Gitleaks كجزء من الـ workflow نفسه. يمكنك تشغيل هذه الأدوات على كل pull request للتأكد من عدم وجود أي secrets في الكود قبل الدمج. المهم هنا هو أن تجعل هذه الخطوة إلزامية، بحيث لا يمكن دمج أي كود يحتوي على secrets.
# إضافة فحص secrets إلى workflow
name: Secret Scanning
on: [pull_request]
jobs:
scan_secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Gitleaks
run: curl -sSL https://github.com/gitleaks/gitleaks/releases/download/v8.16.1/gitleaks_8.16.1_linux_x64.tar.gz | tar -xz -C /usr/local/bin
- name: Run Gitleaks
run: gitleaks detect --source . --report-path gitleaks-report.json --exit-code 1
- name: Upload report
if: always() # حتى لو فشل الفحص
uses: actions/upload-artifact@v3
with:
name: gitleaks-report
path: gitleaks-report.jsonإذا كنت تعتقد أن الـ Caching في GitHub Actions هو مجرد حفظ ملفات مؤقتة، فأنت تفوت فرصة كبيرة لتحسين الأداء. النظام خلف الـ Caching في GitHub Actions معقد للغاية. عندما تطلب حفظ ملفات في الـ cache، يقوم GitHub بتقسيم هذه الملفات إلى أجزاء صغيرة (chunks) باستخدام خوارزمية مشابهة لتلك المستخدمة في Git نفسها. هذه الأجزاء يتم تخزينها في نظام موزع، مما يعني أنه حتى لو كان الـ Runner الخاص بك في سنغافورة والملفات المخزنة في أوروبا، سيتم تحميلها بسرعة كبيرة بفضل شبكة Azure العالمية.
المفتاح هنا هو فهم متى يجب استخدام الـ Caching ومتى يجب تجنبه. مثلاً، حفظ مجلد node_modules في مشاريع Node.js يمكن أن يوفر دقائق من وقت البناء، لكن حفظ مجلد build في مشاريع Android قد لا يكون مفيداً لأنه يحتوي على ملفات كبيرة قد تستغرق وقتاً أطول للتحميل من إعادة البناء. في أحد المشاريع التي عملت عليها، قمنا بتحليل وقت البناء ووجدنا أن الـ Caching لمجلد node_modules قلل وقت البناء من 8 دقائق إلى 2.5 دقيقة، بينما الـ Caching لمجلد build في مشروع Flutter لم يوفر سوى 30 ثانية فقط.
# مثال متقدم على استخدام caching مع استراتيجيات ذكية
name: Smart Caching Workflow
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Caching لمجلد node_modules
- name: Cache node modules
uses: actions/cache@v3
id: cache-node-modules
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
# Caching لمجلد ~/.gradle في مشاريع Android
- name: Cache Gradle files
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
- name: Build project
run: yarn build
# Caching لمخرجات البناء فقط إذا كان المشروع كبيراً
- name: Cache build outputs
if: github.ref == 'refs/heads/main' # فقط على الفرع الرئيسي
uses: actions/cache@v3
with:
path: dist
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-الـ Cache Poisoning هو مشكلة حقيقية في المشاريع الكبيرة. تخيل أنك قمت بحفظ مجلد node_modules في الـ cache، ثم قام أحد المطورين بإضافة مكتبة جديدة تتطلب تجميع كود C++ (مثل sharp أو bcrypt). إذا لم تكن حذراً، قد ينتهي بك الأمر باستخدام نسخة قديمة من هذه المكتبة لأن الـ cache لم يتم تحديثه بشكل صحيح. الحل هنا هو استخدام استراتيجية متعددة المستويات للـ cache، حيث تستخدم مفتاح رئيسي يعتمد على ملفات التكوين (مثل package-lock.json)، ومفتاح احتياطي يعتمد على نظام التشغيل فقط. بهذه الطريقة، إذا تغيرت ملفات التكوين، سيتم تجاهل الـ cache القديم وإنشاء واحد جديد.
إذا كنت تعتقد أن الـ Matrix Builds هي مجرد طريقة لتشغيل نفس الـ job على أنظمة تشغيل مختلفة، فأنت لم تستفد منها بعد. النظام خلف الـ Matrix في GitHub Actions متطور للغاية. عندما تحدد matrix مثل `os: [ubuntu-latest, macos-latest, windows-latest]` و `node-version: [14, 16, 18]`، يقوم GitHub بإنشاء جدول ضخم يحتوي على جميع التوليفات الممكنة (في هذه الحالة 3 × 3 = 9 توليفات). لكن المدهش هنا هو أن GitHub لا يقوم بإنشاء هذه الـ jobs فوراً، بل ينتظر حتى يتم جدولة الـ workflow، ثم يقوم بتوزيع هذه الـ jobs على الـ Runners المتاحة باستخدام خوارزمية مشابهة لتلك المستخدمة في Kubernetes لتوزيع الـ Pods على الـ Nodes.
الميزة الحقيقية هنا تأتي من القدرة على دمج الـ Matrix مع شروط معقدة. مثلاً، يمكنك تشغيل اختبارات على Node.js 14 فقط على نظام Linux، بينما تختبر على Node.js 18 على جميع الأنظمة. أو يمكنك تشغيل اختبارات واجهة المستخدم فقط على نظام macOS لأنك تحتاج إلى محاكي iOS. في مشروع مفتوح المصدر عملت عليه، استخدمنا الـ Matrix لتشغيل اختبارات على 12 نسخة مختلفة من Node.js و3 أنظمة تشغيل و4 متصفحات، مما يعني أننا كنا نغطي 144 توليفة مختلفة في كل pull request، وكل هذا دون أي تكلفة إضافية لأننا كنا نستخدم الـ Runners المجانية لـ GitHub.
# مثال متقدم على Matrix Builds مع شروط معقدة
name: Comprehensive Test Matrix
on: [pull_request]
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [14, 16, 18, 20]
browser: [chrome, firefox]
exclude:
# لا نحتاج إلى Node 14 على Windows
- os: windows-latest
node: 14
# لا نحتاج إلى اختبارات المتصفح على Node 14
- node: 14
browser: chrome
- node: 14
browser: firefox
include:
# اختبارات iOS فقط على macOS مع Node 18
- os: macos-latest
node: 18
browser: safari
ios_test: true
# لا تتوقف جميع الـ jobs إذا فشل واحد
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test
- name: Run browser tests (if applicable)
if: matrix.browser != null
run: npm run test:${{ matrix.browser }}
- name: Run iOS tests (if applicable)
if: matrix.ios_test == true
run: npm run test:iosالـ Matrix Explosion هي مشكلة حقيقية عندما يكون لديك العديد من المتغيرات في الـ matrix. مثلاً، إذا كان لديك 4 أنظمة تشغيل و5 إصدارات من Node.js و3 متصفحات و2 قواعد بيانات، سينتهي بك الأمر بـ 4 × 5 × 3 × 2 = 120 job في كل workflow. هذا ليس مجرد مشكلة في الأداء، بل قد يؤدي إلى تجاوز الحد الأقصى لعدد الـ jobs المسموح بها في الـ repository. الحل هنا هو استخدام استراتيجية متعددة المراحل، حيث تقوم أولاً بتشغيل اختبارات أساسية على جميع التوليفات، ثم تقوم بتشغيل اختبارات أكثر تفصيلاً فقط على التوليفات التي فشلت في المرحلة الأولى.
# حل لـ Matrix Explosion باستخدام مراحل متعددة
name: Smart Matrix Workflow
on: [pull_request]
jobs:
# المرحلة الأولى: اختبارات أساسية على جميع التوليفات
test_basic:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [14, 16, 18, 20]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm run test:basic
# المرحلة الثانية: اختبارات متقدمة فقط على التوليفات التي فشلت في المرحلة الأولى
test_advanced:
needs: test_basic
if: failure() # يعمل فقط إذا فشلت المرحلة الأولى
strategy:
matrix:
include:
# هنا يمكنك تحديد التوليفات التي فشلت فقط
# في الواقع، ستحتاج إلى منطق أكثر تعقيداً للحصول على هذه المعلومات
# هذا مجرد مثال مبسط
- os: ubuntu-latest
node: 14
- os: macos-latest
node: 18
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm run test:advancedمعظم الفرق تبدأ باستخدام الـ Runners المجانية التي يوفرها GitHub، لكن سرعان ما تكتشف أنها محدودة. مثلاً، الـ Runners المجانية لا تسمح بتشغيل أكثر من 6 ساعات متواصلة، ولا توفر موارد كافية لبناء تطبيقات كبيرة مثل Unreal Engine أو تدريب نماذج الذكاء الاصطناعي. هنا يأتي دور الـ Self-Hosted Runners. الفكرة بسيطة: بدلاً من الاعتماد على الـ Runners التي يوفرها GitHub، يمكنك تشغيل الـ Runners الخاصة بك على أي جهاز تريد، سواء كان سيرفر في شركتك أو حتى جهاز Raspberry Pi في منزلك.
المشكلة هنا هي أن الـ Self-Hosted Runners تأتي مع مجموعة من التحديات الأمنية. أولاً، الـ Runner يعمل بنفس صلاحيات المستخدم الذي قام بتشغيله، مما يعني أنه إذا تمكن شخص ما من اختراق الـ workflow الخاص بك، فقد يتمكن من الوصول إلى النظام بأكمله. ثانياً، الـ Runners لا يتم إعادة تشغيلها تلقائياً بعد كل job، مما يعني أن أي تغييرات تم إجراؤها على النظام أثناء الـ job السابق قد تؤثر على الـ job التالي. في إحدى الشركات التي عملت معها، استخدمنا الـ Self-Hosted Runners لبناء تطبيقات iOS، لكننا اكتشفنا أن بعض الـ jobs كانت تفشل بشكل عشوائي لأن الـ Runner السابق لم يقم بتنظيف الملفات المؤقتة بشكل صحيح.
# كيفية إعداد Self-Hosted Runner على Linux
# أولاً، قم بتنزيل برنامج Runner
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.309.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.309.0/actions-runner-linux-x64-2.309.0.tar.gz
# تحقق من الـ checksum
echo "fb28a1c3715e0a6c5051ea2f48b1349947609919a22c58d096a0999a7a33ed13 actions-runner-linux-x64-2.309.0.tar.gz" | shasum -a 256 -c
# فك الضغط
tar xzf ./actions-runner-linux-x64-2.309.0.tar.gz
# قم بتكوين الـ Runner
./config.sh --url https://github.com/your-org/your-repo --token YOUR_TOKEN
# قم بتشغيل الـ Runner كخدمة
sudo ./svc.sh install
sudo ./svc.sh startالأمان في الـ Self-Hosted Runners يبدأ من لحظة الإعداد. أولاً، يجب ألا تقوم أبداً بتشغيل الـ Runner كـ root. بدلاً من ذلك، قم بإنشاء مستخدم مخصص له صلاحيات محدودة. ثانياً، استخدم الـ Docker لتشغيل الـ jobs داخل حاويات معزولة، بحيث حتى لو تم اختراق الـ job، لن يتمكن من الوصول إلى النظام المضيف. ثالثاً، قم بتفعيل الـ Ephemeral Runners، وهي ميزة تسمح بإنشاء runner جديد لكل job، ثم يتم تدميره بعد الانتهاء. بهذه الطريقة، تضمن أن كل job يعمل في بيئة نظيفة تماماً.
# استخدام Self-Hosted Runner مع Docker
name: Secure Self-Hosted Workflow
on: [push]
jobs:
build:
runs-on: self-hosted
container:
image: node:18
options: --user 1000 # تشغيل كـ مستخدم غير root
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
# مثال على استخدام Ephemeral Runner
deploy:
runs-on:
labels: [self-hosted, ephemeral]
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: ./deploy.shبعد أكثر من 5 سنوات في استخدام GitHub Actions في مشاريع تتراوح من تطبيقات صغيرة إلى منصات ضخمة مثل تلك المستخدمة في البنوك، هذه هي النصائح التي أتمنى أن أعرفها منذ اليوم الأول: أولاً، لا تعامل الـ workflows كأنها سكربتات bash، بل تعامل معها كأنها كود برمجي حقيقي. استخدم الـ Reusable Workflows لتقليل التكرار، وقم بمراجعة الـ workflows بانتظام كما تفعل مع الكود العادي. ثانياً، قم بقياس كل شيء. استخدم الـ GitHub API لجمع إحصائيات حول وقت تشغيل الـ workflows، وعدد مرات الفشل، وأسباب الفشل. ثالثاً، لا تخف من الـ Self-Hosted Runners، لكن قم بإعدادها بشكل آمن منذ البداية. رابعاً، استخدم الـ Matrix Builds بحكمة، ولا تحاول اختبار كل شيء في كل مرة. خامساً، قم بتفعيل الـ Required Workflows على الـ protected branches، بحيث لا يمكن دمج أي كود دون اجتياز جميع الاختبارات.
وأخيراً، تذكر أن GitHub Actions ليس مجرد أداة لنشر الكود، بل هو نظام تشغيل كامل لمشروعك. كلما فهمت كيف يعمل خلف الكواليس، كلما استطعت بناء أنظمة أكثر ذكاءً وأماناً. ابدأ اليوم بإنشاء workflow بسيط لقياس أداء مشروعك، ثم قم بتوسيعه تدريجياً حتى يصبح جزءاً لا يتجزأ من سير عملك. وإذا واجهتك مشكلة، تذكر أن الـ logs هي صديقك. كل خطوة في الـ workflow تترك أثراً يمكن تتبعه، وكل خطأ يمكن إصلاحه إذا فهمت السبب الحقيقي وراءه.