تخيل أنك تضغط زراً واحداً في نهاية اليوم فينشر تطبيقك تلقائياً على الإنتاج دون خوف من الأخطاء. هذا ليس حلماً، بل هو CI/CD عملي خطوة بخطوة للمطور الفردي الذي يريد التحرر من النشر اليدوي دون الدخول في دوامة الأدوات المعقدة.
كل مرة ترفع فيها كوداً يدوياً إلى الإنتاج تشعر وكأنك تلعب الروليت الروسي. زر واحد خاطئ، سطر واحد ناقص، أو حتى نسيان تشغيل اختبارات الوحدة قد يكلفك ساعات من التصحيح تحت ضغط الوقت. في عام 2023، أظهرت دراسة من GitLab أن 60% من المطورين لا يزالون يعتمدون على النشر اليدوي، رغم أن 82% منهم يعانون من أخطاء نشر أسبوعية على الأقل. المشكلة ليست في قلة الأدوات، بل في التعقيد الذي يحيط بها. معظم أدلة CI/CD تبدأ بشرح مفاهيم مجردة مثل Pipelines وArtifacts قبل أن تصل إلى أول سطر كود عملي. هذا المقال سيفعل العكس: سنبدأ بكود حقيقي ونبني حوله نظام نشر آلي كامل، خطوة بخطوة، دون أي تعقيد غير ضروري.
لن نتحدث هنا عن Kubernetes أو Helm Charts أو حتى Docker Swarm. سنركز على ما يحتاجه المطور الفردي أو الفريق الصغير: نظام CI/CD خفيف الوزن، سهل الإعداد، وقابل للتوسع عندما ينمو المشروع. سنستخدم GitHub Actions لأنه متكامل مع GitHub، مجاني للمشاريع العامة والخاصة الصغيرة، ولا يتطلب إعداد سيرفرات منفصلة. لكن المبادئ التي سنتعلمها تنطبق على أي أداة أخرى مثل GitLab CI أو CircleCI.
عندما تسمع مصطلح CI/CD، قد تظن أنه شيء معقد يتطلب فريقاً كاملاً من مهندسي DevOps. الحقيقة أبسط بكثير. CI (Continuous Integration) يعني ببساطة أن كل تغيير في الكود يتم اختباره تلقائياً فور رفعه إلى المستودع. CD (Continuous Deployment/Delivery) يعني أن الكود الذي اجتاز الاختبارات يتم نشره تلقائياً إلى بيئة الإنتاج أو التطوير. خلف الكواليس، هناك ثلاثة مكونات رئيسية تعمل معاً: المستودع (Repository)، خادم CI/CD، وبيئة النشر. عندما ترفع كوداً إلى فرع معين (مثل main)، يرسل المستودع حدثاً إلى خادم CI/CD الذي بدوره يشغل سلسلة من الأوامر المحددة مسبقاً. هذه الأوامر قد تشمل تثبيت التبعيات، تشغيل الاختبارات، بناء المشروع، ثم نشره إلى السيرفر أو خدمة الاستضافة.
المشكلة التي يواجهها معظم المطورين ليست في فهم هذه المفاهيم، بل في ترجمة هذه المفاهيم إلى كود حقيقي. مثلاً، قد تقرأ عن أهمية تشغيل الاختبارات في كل عملية دمج، لكن عندما تحاول تطبيقه تجد نفسك تائهاً بين ملفات YAML المعقدة ومتغيرات البيئة الغامضة. لهذا السبب سنبدأ بمثال عملي بسيط: تطبيق Node.js صغير يحتوي على اختبارات وحدة، وسنبني له نظام CI/CD كامل من الصفر. سنرى معاً كيف يتحول الكود من مجرد ملفات على جهازك المحلي إلى تطبيق يعمل على الإنتاج دون تدخل يدوي.
# مثال مبسط لملف CI/CD باستخدام GitHub Actions
# .github/workflows/deploy.yml
name: Node.js CI/CD
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
NODE_VERSION: '20'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm run build
- name: Deploy to Vercel
run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}لنفترض أنك تعمل على تطبيق Node.js بسيط يستخدم Express لعرض صفحة رئيسية. لديك ملف app.js يحتوي على الكود التالي:
// app.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from CI/CD!');
});
if (require.main === module) {
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}
module.exports = app;هذا الكود يعمل بشكل مثالي على جهازك المحلي، لكن كيف نجعله يعمل على الإنتاج؟ أولاً، نحتاج إلى اختبارات وحدة للتأكد من أن التطبيق لا ينكسر مع كل تغيير. سنستخدم Jest لكتابة اختبار بسيط يتحقق من أن المسار الرئيسي (/) يعيد استجابة صحيحة:
// __tests__/app.test.js
const request = require('supertest');
const app = require('../app');
describe('GET /', () => {
it('responds with json', async () => {
const resp await request(app)
.get('/')
.expect('Content-Type', /text\/html/)
.expect(200);
expect(response.text).toBe('Hello from CI/CD!');
});
});الآن لدينا تطبيق واختبار، لكننا ما زلنا ننشر يدوياً. الخطوة التالية هي إعداد ملف CI/CD الذي سيشغل الاختبار تلقائياً مع كل تغيير، وإذا نجح الاختبار، سينشر التطبيق تلقائياً. في المثال السابق الذي رأيناه في ملف YAML، هناك نقطتان حاسمتان يجب فهمهما جيداً: الأولى هي استخدام secrets.VERCEL_TOKEN، والثانية هي مفهوم Jobs والـ needs. المتغير VERCEL_TOKEN هو رمز مصادقة يسمح لـ GitHub Actions بنشر التطبيق على Vercel دون الحاجة إلى إدخال بيانات الاعتماد يدوياً في كل مرة. هذا مهم جداً لأسباب أمنية، حيث لا يجب أبداً تخزين كلمات المرور أو الرموز السرية في ملفات الكود العامة.
عندما تضع قيمة حساسة مثل رمز API أو كلمة مرور في ملف YAML عادي، فإنها تصبح مرئية لأي شخص لديه وصول إلى المستودع. حتى لو كان المستودع خاصاً، فإن تخزين هذه القيم في الكود يعتبر ممارسة سيئة لأنه يزيد من خطر التسريب. GitHub يوفر نظام secrets آمن يخزن هذه القيم مشفرة على خوادمه، ويمكن الوصول إليها فقط من داخل workflows الخاصة بمستودعك. عندما تريد استخدام رمز مثل VERCEL_TOKEN، يجب أولاً إضافته إلى إعدادات المستودع في GitHub تحت Settings > Secrets and variables > Actions، ثم يمكنك الإشارة إليه في ملف YAML باستخدام syntax secrets.VERCEL_TOKEN.
في ملف YAML السابق، لدينا وظيفتان (jobs): test وdeploy. الوظيفة الأولى تشغل الاختبارات، والثانية تنشر التطبيق. لكن لاحظ استخدام needs: test في وظيفة deploy. هذا يعني أن وظيفة النشر لن تبدأ إلا إذا نجحت وظيفة الاختبار. هذا أمر بالغ الأهمية لأنه يمنع نشر كود غير مختبر إلى الإنتاج. تخيل لو أنك نسيت هذه السطر، سينشر التطبيق حتى لو فشلت الاختبارات، وهذا بالضبط ما نريد تجنبه. في مشاريع أكبر، قد يكون لديك عدة وظائف تعتمد على بعضها البعض، مثل بناء الواجهة الأمامية، بناء الواجهة الخلفية، ثم نشرهما معاً. في هذه الحالة، يمكنك استخدام needs لإنشاء سلسلة من الوظائف التي تعمل بالتسلسل.
عندما تبدأ في إعداد CI/CD لأول مرة، ستواجه مجموعة من المشاكل التي لا تذكرها معظم الأدلة. مثلاً، قد تجد أن workflow يعمل بشكل مثالي على جهازك المحلي لكنه يفشل على خادم GitHub Actions. السبب الشائع هو اختلاف بيئة التنفيذ. على جهازك المحلي، قد يكون لديك Node.js الإصدار 20 مثبتاً، لكن خادم GitHub Actions يستخدم الإصدار الافتراضي الذي قد يكون أقدم. لهذا السبب من المهم دائماً تحديد إصدار Node.js أو أي أداة أخرى تستخدمها في ملف YAML، كما فعلنا في المثال السابق باستخدام NODE_VERSION: '20'.
مشكلة أخرى شائعة هي الـ cache. عندما تشغل npm ci في كل مرة، فإنه يعيد تحميل جميع التبعيات من الصفر، وهذا قد يستغرق وقتاً طويلاً في المشاريع الكبيرة. GitHub Actions يوفر نظام cache يمكن استخدامه لتخزين مجلد node_modules بين عمليات التشغيل، مما يقلل وقت التنفيذ بشكل كبير. إليك كيف يمكنك تعديل ملف YAML لاستخدام cache:
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache node modules
uses: actions/cache@v3
id: cache
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
if: steps.cache.outputs.cache-hit != 'true'في هذا المثال، نستخدم actions/cache لتخزين مجلد node_modules. المفتاح (key) يعتمد على نظام التشغيل وعلى محتوى ملف package-lock.json، مما يعني أنه سيتم إعادة استخدام الكاش فقط إذا لم يتغير ملف القفل. إذا تغير الملف، سيتم تحميل التبعيات من الصفر. هذه التقنية يمكن أن تقلل وقت التنفيذ من عدة دقائق إلى ثوانٍ فقط في المشاريع الكبيرة.
الآن بعد أن أصبح لدينا نظام CI/CD يعمل، حان وقت نشر التطبيق. هناك العديد من الخيارات للنشر، لكننا سنركز على ثلاثة خيارات شائعة للمطورين الفرديين: Vercel، GitHub Pages، وخادم VPS بسيط. كل خيار له مميزاته وعيوبه، وسنرى كيف يمكن تكييف ملف CI/CD لتناسب كل منها.
Vercel هو الخيار الأمثل للمشاريع الصغيرة والمتوسطة التي تريد نشراً سريعاً وسهلاً. الميزة الرئيسية لـ Vercel هي التكامل السلس مع GitHub، حيث يمكنك ربط مستودعك بـ Vercel وسيقوم تلقائياً بنشر كل تغيير في فرع main. لكننا هنا نريد التحكم الكامل في عملية النشر من خلال CI/CD، لذلك سنستخدم CLI الخاص بـ Vercel داخل workflow كما رأينا في المثال السابق. لإعداد Vercel، تحتاج أولاً إلى تثبيت CLI محلياً باستخدام npm install -g vercel، ثم تسجيل الدخول باستخدام vercel login. بعد ذلك، يمكنك الحصول على رمز المصادقة باستخدام vercel token وإنشاء secret في GitHub باسم VERCEL_TOKEN كما شرحنا سابقاً.
إذا كنت تعمل على موقع ثابت (مثل مدونة أو موقع شخصي)، GitHub Pages هو خيار مجاني وسهل. الفرق الرئيسي هنا هو أن GitHub Pages لا يدعم Node.js أو التطبيقات الخلفية، بل يعمل فقط مع الملفات الثابتة مثل HTML وCSS وJavaScript. إليك كيف يمكن تعديل ملف CI/CD لنشر موقع ثابت على GitHub Pages:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
npm ci
npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./distفي هذا المثال، نفترض أن لديك أمر build في package.json ينشئ مجلد dist يحتوي على الملفات الثابتة. الأداة peaceiris/actions-gh-pages هي Action جاهزة تنشر محتويات مجلد dist إلى فرع gh-pages الذي يستخدمه GitHub Pages لعرض الموقع. لاحظ أننا نستخدم GITHUB_TOKEN الذي يوفره GitHub تلقائياً لكل workflow، لذلك لا نحتاج إلى إنشاء secret خاص.
إذا كنت تريد تحكماً كاملاً في بيئة النشر، يمكنك استخدام خادم VPS مثل DigitalOcean أو Linode. هذا الخيار يتطلب المزيد من الإعداد، لكنه يمنحك مرونة كاملة في تكوين السيرفر. إليك مثال على كيفية نشر تطبيق Node.js إلى خادم VPS باستخدام SSH:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
npm ci
npm run build
- name: Deploy to VPS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /var/www/my-app
git pull origin main
npm ci --production
pm2 restart my-appفي هذا المثال، نستخدم Action تسمى appleboy/ssh-action للاتصال بالخادم عبر SSH وتنفيذ سلسلة من الأوامر. لاحظ أننا نستخدم ثلاثة secrets: VPS_HOST (عنوان IP للخادم)، VPS_USERNAME (اسم المستخدم)، وVPS_SSH_KEY (المفتاح الخاص SSH). هذه الطريقة تتطلب إعداد SSH مسبقاً على الخادم، وتثبيت أدوات مثل PM2 لإدارة العمليات. الميزة الرئيسية لهذا النهج هي التحكم الكامل في بيئة النشر، لكن العيب هو أنك مسؤول عن صيانة الخادم وتحديثاته.
الآن بعد أن أصبح لديك نظام CI/CD يعمل، لا تتوقف عند هذا الحد. هناك العديد من التحسينات التي يمكنك إضافتها لجعل النظام أكثر كفاءة وموثوقية. مثلاً، يمكنك إضافة إشعارات عبر Slack أو Email عند فشل النشر، أو إضافة خطوات لفحص جودة الكود باستخدام أدوات مثل ESLint أو SonarQube. يمكنك أيضاً إضافة خطوات لاختبار الأداء أو فحص الثغرات الأمنية قبل النشر.
من تجربتي الشخصية، أحد أكثر التحسينات فائدة هو إضافة بيئة staging. بدلاً من نشر كل تغيير مباشراً إلى الإنتاج، يمكنك إنشاء فرع staging ينشر إلى بيئة اختبار تشبه الإنتاج تماماً. هذا يسمح لك باختبار التغييرات في بيئة واقعية قبل نشرها للعملاء. إليك كيف يمكن تعديل ملف YAML لدعم بيئتين:
on:
push:
branches: [ "main", "staging" ]
env:
NODE_VERSION: '20'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm run build
- name: Deploy to Staging
if: github.ref == 'refs/heads/staging'
run: npx vercel --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy to Production
if: github.ref == 'refs/heads/main'
run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}في هذا المثال، نستخدم شرط if لتحديد البيئة التي يجب النشر إليها بناءً على الفرع الذي تم الدفع إليه. إذا كان الفرع staging، سيتم النشر إلى بيئة staging. إذا كان الفرع main، سيتم النشر إلى الإنتاج. هذه الطريقة تمنحك مرونة كبيرة في اختبار التغييرات قبل نشرها للعملاء.
إذا أخذت شيئاً واحداً فقط من هذا المقال، فليكن هذا: ابدأ صغيراً ثم طور. لا تحاول إعداد نظام CI/CD مثالي من المرة الأولى. ابدأ بأبسط workflow ممكن: اختبارات فقط. ثم أضف خطوة بناء. ثم أضف خطوة نشر. ثم أضف بيئة staging. ثم أضف إشعارات. بهذه الطريقة، ستتعلم كيف يعمل النظام دون أن تغرق في التعقيد. تذكر أن الهدف من CI/CD ليس إنشاء نظام مثالي، بل تقليل الأخطاء اليدوية وجعل عملية النشر أقل إرهاقاً. عندما تبدأ تشعر أن النشر أصبح تلقائياً وموثوقاً، عندها تكون قد نجحت.
وأخيراً، لا تنسَ مراجعة سجلات workflow بانتظام. GitHub Actions يوفر سجلات مفصلة لكل تشغيل، ويمكنك استخدامها لتحديد المشاكل وتحسين الأداء. إذا وجدت أن workflow يستغرق وقتاً طويلاً، ابحث عن طرق لتحسينه مثل استخدام cache أو تقسيم الوظائف إلى خطوات أصغر. وإذا وجدت أن هناك خطوات تفشل باستمرار، فهذا مؤشر على أن هناك مشكلة في الكود أو في بيئة الاختبار تحتاج إلى حل.
الآن بعد أن فهمت المبادئ الأساسية لـ CI/CD، حان وقت التطبيق العملي. اختر مشروعاً صغيراً لديك على GitHub، وأنشئ ملف .github/workflows/deploy.yml يحتوي على أبسط workflow ممكن: تشغيل الاختبارات فقط. ثم أضف خطوة بناء، ثم خطوة نشر. لا تنتظر حتى يصبح كل شيء مثالياً. ابدأ الآن، وستجد نفسك بعد أسبوعين من الآن تتساءل كيف كنت تعيش بدون CI/CD من قبل.