أذكرها جيداً تلك الليلة، كانت ليلة خميس وكنا على وشك إطلاق تحديث كبير لأحد عملائنا المهمين. الفريق كله كان على أهبة الاستعداد، أنا وثلاثة مبرمجين آخرين، كل واحد فينا في بيته، متصلين عبر مكالمة فيديو. قائمة المهام (Checklist) أمامنا، طويلة كشارع يافا، وكل خطوة فيها يجب أن تتم يدوياً وبترتيب دقيق.
بدأنا العملية. “أبو عمر، دورك ترفع الملفات الجديدة على السيرفر”، قالها زميلي أحمد. قمت بفتح عميل الـ FTP، وبدأت في سحب وإفلات الملفات، وعيني على شريط التحميل الذي كان يتحرك ببطء قاتل. بعد دقائق طويلة، انتهيت. “تمام يا جماعة، الملفات ارتفعت”. الخطوة التالية كانت تشغيل بعض الأوامر على السيرفر عبر SSH لتحديث قاعدة البيانات. تولى زميل آخر المهمة، لكنه أخطأ في كتابة أحد الأوامر… وفجأة، صمت مطبق. الموقع تعطل تماماً وأصبح يعرض صفحة خطأ بيضاء مرعبة.
ساد التوتر، وبدأنا رحلة البحث عن الخطأ. “شو اللي صار؟”، “ارجع اعمل rollback!”، “أنا قلتلك انسخ الأمر نسخ!”. أصواتنا تعلو، والضغط النفسي في ذروته. بعد ساعة من الزمن، شعرنا أنها دهر، تمكنا من إصلاح المشكلة وإعادة الموقع للعمل. انتهت ليلتنا بإنهاك شديد وشعور بالهزيمة رغم أننا “نجحنا” في نشر التحديث. في تلك اللحظة، قلت لنفسي وللفريق: “يا جماعة الخير، لازم نلاقي حل. الشغل هيك ما بكمل”. هذه الحادثة كانت نقطة التحول التي دفعتنا للبحث عن حل جذري، وكان الحل هو الأتمتة، وتحديداً GitHub Actions.
جحيم النشر اليدوي: أكثر من مجرد كبسة زر
قبل أن نغوص في الحلول، خلّينا نكون صريحين ونعترف بحجم المشكلة. عملية النشر اليدوي (Manual Deployment) ليست مجرد عملية مملة، بل هي وصفة شبه مؤكدة للكوارث. من خبرتي، هذه أبرز مشاكلها:
- الخطأ البشري وارد دائماً: كما حدث معنا، نسيان خطوة، خطأ إملائي في أمر، أو تحديث ملف خاطئ يمكن أن يؤدي إلى انهيار النظام بأكمله. نحن بشر، والبشر يخطئون، خاصة تحت الضغط.
- انعدام التناسق (Inconsistency): قد يقوم مطور بنشر الكود بطريقة تختلف قليلاً عن زميله، مما يؤدي إلى بيئات تطوير ونشر غير متطابقة ومشاكل غامضة وصعبة التشخيص.
- وقت ضائع = مال ضائع: الوقت الذي يقضيه فريقك في تنفيذ خطوات النشر اليدوية هو وقت كان من الممكن استغلاله في كتابة كود جديد أو إصلاح مشاكل برمجية حقيقية.
- صعوبة التوسع (Scalability): إذا كانت عملية النشر تستغرق ساعة لفريق من 4 أشخاص، فكم ستستغرق لفريق من 20 شخصاً يعملون على عدة خدمات (Microservices)؟ العملية ببساطة لا تتوسع بشكل جيد.
- المعرفة الحبيسة (Tribal Knowledge): غالباً ما يكون هناك شخص واحد فقط “الخبير” الذي يعرف كل أسرار عملية النشر. ماذا لو كان هذا الشخص في إجازة أو ترك الشركة؟
هذه العملية كانت بمثابة احتفال مرعب؛ نكون سعداء بإنجاز الميزات الجديدة، لكننا نرتعب من فكرة نشرها للعلن.
المنقذ: تعرف على CI/CD و GitHub Actions
هنا يأتي دور ما يسمى بالـ CI/CD. لا تدع الاسم يرهبك، فالمفهوم بسيط جداً.
- CI – التكامل المستمر (Continuous Integration): هي عملية أتمتة دمج التغييرات البرمجية من عدة مطورين في مستودع مركزي واحد. عند كل عملية دمج، يتم تشغيل بناء (Build) واختبارات (Tests) تلقائياً للتأكد من أن الكود الجديد لم يكسر أي شيء موجود.
- CD – النشر المستمر (Continuous Deployment/Delivery): بعد أن تنجح مرحلة التكامل المستمر، تقوم هذه العملية تلقائياً بنشر الكود على بيئة التشغيل (Staging أو Production).
GitHub Actions هي أداة مدمجة مباشرة في GitHub تسمح لك بتطبيق مفهوم الـ CI/CD بسهولة ويسر. إنها كأن يكون لديك روبوت شخصي يعيش داخل مستودع الكود الخاص بك، ومستعد لتنفيذ أي مجموعة من المهام التي تطلبها منه تلقائياً عند حدوث حدث معين (مثل push للكود).
مكونات GitHub Actions الأساسية
لتفهم كيف تعمل، عليك معرفة هذه المصطلحات:
- Workflow (سير العمل): هو العملية المؤتمتة التي تريد تنفيذها. يتم تعريفه في ملف بصيغة YAML يتم وضعه داخل مجلد
.github/workflowsفي مشروعك. - Event (الحدث): هو المسبب أو “الزناد” الذي يطلق الـ Workflow. يمكن أن يكون
pushللكود، إنشاءpull_request، أو حتى جدول زمني معين. - Job (المهمة): هي مجموعة من الخطوات التي يتم تنفيذها على بيئة افتراضية. يمكن أن يحتوي الـ Workflow على مهمة واحدة أو أكثر.
- Runner (المشغّل): هي الآلة (سيرفر افتراضي) التي تقوم بتنفيذ المهمة (Job). توفر GitHub مشغّلات تعمل على Linux, Windows, و macOS.
- Step (الخطوة): هي أمر أو مهمة فردية داخل الـ Job. يمكن أن تكون أمراً بسيطاً في الـ command line (مثل
npm install) أو action جاهزة. - Action (الإجراء): هي قطعة كود قابلة لإعادة الاستخدام لتنفيذ مهام معقدة. هناك الآلاف من الـ Actions الجاهزة في GitHub Marketplace (مثل تسجيل الدخول إلى سحابة AWS أو نشر تطبيق على Heroku).
من النظرية إلى التطبيق: لنبني أول Workflow لنا
الكلام النظري جميل، لكن “الشوربة ما بتيجي إلا بلحمها”. دعنا نأخذ مثالاً عملياً لتطبيق Node.js بسيط. هدفنا هو تحقيق أمرين:
- CI: عند فتح أي Pull Request، نريد التأكد من أن الكود يعمل وأن جميع الاختبارات تنجح.
- CD: عند دمج الكود في الفرع الرئيسي (
main)، نريد نشر التطبيق تلقائياً على سيرفر الإنتاج الخاص بنا.
المرحلة الأولى: بناء سير عمل التكامل المستمر (CI)
سنقوم بإنشاء ملف جديد في مشروعنا بالمسار التالي: .github/workflows/ci.yml. هذا الملف سيحتوي على التعليمات الخاصة بالتحقق من الكود.
# .github/workflows/ci.yml
# اسم الـ Workflow الذي سيظهر في واجهة GitHub
name: Continuous Integration
# تحديد الأحداث التي ستطلق هذا الـ Workflow
on:
# عند فتح أو تحديث أي Pull Request يستهدف الفرع main
pull_request:
branches: [ main ]
# تحديد المهام (Jobs) التي سيتم تنفيذها
jobs:
# اسم المهمة (يمكنك تسميتها ما تشاء)
build-and-test:
# تحديد نظام التشغيل الذي ستعمل عليه المهمة
runs-on: ubuntu-latest
# مجموعة الخطوات (Steps) التي ستنفذ بالترتيب
steps:
# الخطوة الأولى: جلب الكود من المستودع
# نستخدم هنا Action جاهزة من GitHub
- name: Checkout code
uses: actions/checkout@v4
# الخطوة الثانية: إعداد بيئة Node.js
# نستخدم Action جاهزة لتثبيت إصدار معين من Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20' # حدد إصدار Node.js الذي تستخدمه
# الخطوة الثالثة: تثبيت الاعتماديات (Dependencies)
# نستخدم npm ci لأنه أسرع وأكثر أماناً للخوادم من npm install
- name: Install dependencies
run: npm ci
# الخطوة الرابعة: تشغيل الاختبارات
- name: Run tests
run: npm test
الآن، في كل مرة يفتح فيها مطور Pull Request، ستقوم GitHub تلقائياً بتشغيل هذا الـ Workflow. إذا فشل أي من الاختبارات، ستظهر علامة X حمراء بجانب الـ Pull Request، وسيعرف الجميع أن هناك مشكلة قبل حتى أن تتم مراجعة الكود. يا سلام! أول خطوة نحو راحة البال.
المرحلة الثانية: بناء سير عمل النشر المستمر (CD)
هذه هي المرحلة التي أنقذتنا من جحيم النشر اليدوي. سنقوم بإنشاء ملف آخر: .github/workflows/deploy.yml.
نقطة مهمة جداً: لن نضع كلمات المرور أو مفاتيح SSH مباشرة في الملف. هذا خطأ أمني فادح. بدلاً من ذلك، سنستخدم ميزة “Secrets” في GitHub. اذهب إلى مستودعك على GitHub ثم Settings > Secrets and variables > Actions. هنا يمكنك إضافة معلومات حساسة مثل مفتاح SSH الخاص بسيرفرك.
نصيحة أبو عمر: قبل البدء، تأكد من إعداد اتصال SSH بدون كلمة مرور بين GitHub وسيرفرك باستخدام مفاتيح SSH. قم بإنشاء زوج مفاتيح (عام وخاص)، أضف المفتاح العام إلى ملف
~/.ssh/authorized_keysعلى سيرفرك، وأضف المفتاح الخاص كـ Secret في GitHub باسمSSH_PRIVATE_KEY.
# .github/workflows/deploy.yml
name: Continuous Deployment
# يشتغل فقط عند الدفع (push) إلى الفرع الرئيسي
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
# هذه Action رائعة تسهل الاتصال عبر SSH
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_PRIVATE_KEY }}
known_hosts: 'just-a-placeholder-so-we-dont-get-errors'
- name: Adding Known Hosts
run: ssh-keyscan ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
# الاتصال بالسيرفر وتنفيذ أوامر النشر
- name: Deploy to Server
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} << 'EOF'
cd /var/www/my-awesome-app
git pull origin main
npm install --production
pm2 restart app-name
echo "Deployment successful!"
EOF
ماذا يفعل هذا الكود؟ ببساطة:
- ينتظر أي عملية
pushعلى الفرعmain. - يقوم بجلب الكود.
- يقوم بتثبيت مفتاح SSH الخاص الذي حفظناه بأمان في الـ Secrets.
- يتصل بالسيرفر الخاص بك عبر SSH.
- ينفذ سلسلة من الأوامر على السيرفر: يذهب إلى مجلد المشروع، يسحب آخر التغييرات، يثبت الاعتماديات الخاصة بالإنتاج فقط، ثم يعيد تشغيل التطبيق باستخدام أداة مثل PM2.
وهكذا، وبشكل سحري، كل دمج للفرع الرئيسي يعني أن الكود الجديد أصبح يعمل على السيرفر في غضون دقائق، بدون أي تدخل يدوي، وبدون أي توتر.
نصائح من الخبير: كيف تحترف GitHub Actions؟
بعد استخدام Actions لسنوات، تعلمت بعض الحيل التي توفر الكثير من الوقت والجهد. إليكم بعضها:
نصيحة 1: استخدم التخزين المؤقت (Caching)
عملية npm ci يمكن أن تكون بطيئة. يمكنك تسريعها بشكل كبير عن طريق تخزين مجلد node_modules مؤقتاً. استخدم actions/cache لتجنب إعادة تحميل كل شيء في كل مرة.
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
أضف هذه الخطوة قبل خطوة npm ci، وستلاحظ فرقاً كبيراً في سرعة الـ Workflow.
نصيحة 2: لا تكرر نفسك (DRY – Don’t Repeat Yourself)
إذا كان لديك خطوات متكررة في عدة Workflows، يمكنك استخدام “Reusable Workflows” أو “Composite Actions” لتعريفها مرة واحدة واستدعائها عند الحاجة. هذا يجعل الصيانة أسهل بكثير في المشاريع الكبيرة.
نصيحة 3: استخدم الـ Matrix Builds
هل تريد اختبار الكود الخاص بك على إصدارات مختلفة من Node.js (مثلاً 18, 20, 22)؟ بدلاً من إنشاء ثلاث مهام منفصلة، استخدم مصفوفة الاختبار (Strategy Matrix).
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# ... باقي خطوات الاختبار
سيقوم GitHub تلقائياً بتشغيل ثلاث مهام متوازية، واحدة لكل إصدار.
الخلاصة: من الفوضى إلى الأتمتة ✨
الانتقال من النشر اليدوي إلى الأتمتة باستخدام GitHub Actions كان أحد أفضل القرارات التقنية التي اتخذناها كفريق. لقد حولنا عملية “الاحتفال المرعب” إلى عملية روتينية ومملة، وهذا أفضل شيء يمكن أن تصف به عملية نشر الكود. أصبحت ثقتنا في الكود الذي نكتبه أكبر، وأصبحنا نطلق الميزات الجديدة بشكل أسرع وبضغط نفسي أقل بكثير.
إذا كنت أنت وفريقك ما زلتم تعانون من جحيم النشر اليدوي، فأتمنى أن تكون هذه المقالة قد أقنعتكم بالبدء في رحلة الأتمتة. قد يستغرق الإعداد الأولي يوماً أو يومين، لكنه سيوفر عليكم مئات الساعات من العمل اليدوي وليالي لا حصر لها من القلق والتوتر. ابدأ صغيراً، أتمِتْ عملية الاختبار أولاً، ثم انتقل إلى النشر. صدقني، لن تندم أبداً. 🚀