ليلة خميس لن أنساها: قصة فشل ذريع
أذكرها وكأنها البارحة. كانت ليلة خميس، ونهاية أسبوع عمل طويل ومرهق. كنا على وشك إطلاق تحديث كبير لتطبيق أحد العملاء المهمين. الأجواء كانت مشحونة، الكل يريد أن ينهي عمله ويعود إلى بيته. بصفتي قائد الفريق التقني وقتها، كانت مهمة النشر اليدوي تقع على عاتقي.
فتحت الطرفية (Terminal)، وبدأت طقوس النشر المعتادة: سحب آخر نسخة من الكود، تشغيل أوامر البناء (build) على جهازي المحلي، ثم الاتصال بالخادم عبر بروتوكول SSH، وأخيراً، رفع الملفات الجديدة يدوياً باستخدام scp. كانت يداي تتحركان بشكل تلقائي تقريباً من كثرة ما كررت هذه العملية.
ضغطت زر الإدخال (Enter) الأخير، وأنا أتمتم “بسم الله”. فتحت الموقع لأتأكد أن كل شيء على ما يرام… وإذ به صفحة بيضاء فارغة ورسالة خطأ “500 Internal Server Error”. شعرت بقطرة عرق باردة تسيل على ظهري. يا زلمة، شو اللي صار؟
بدأت رحلة البحث عن السبب في سجلات الأخطاء (logs) على الخادم مباشرة. بعد ساعة من التوتر والضغط، اكتشفت الكارثة: لقد نسيت تحديث إحدى المكتبات (dependencies) على الخادم، ونسخة الكود الجديدة التي رفعتها تعتمد عليها. خطأ بشري بسيط، لكنه كلفنا توقف الموقع لساعات، ومكالمات غاضبة من العميل، ونهاية أسبوع قضيتها في إصلاح المشكلة بدلاً من قضائها مع عائلتي.
في تلك الليلة، وأنا أصارع الأكواد على الخادم الإنتاجي مباشرة (وهو ما لا يجب أن يفعله أحد!)، أقسمت أن هذه ستكون آخر مرة أقوم فيها بنشر يدوي بهذا الشكل البدائي. كانت تلك هي اللحظة التي قررت فيها الغوص عميقاً في عالم الـ DevOps وخطوط أنابيب CI/CD.
ما هي “خطوط أنابيب CI/CD” التي يتحدث عنها الجميع؟
ببساطة، تخيل أن عملية نشر الكود الخاص بك هي خط إنتاج في مصنع. بدلاً من أن يقوم عامل واحد بكل شيء يدوياً (البناء، الاختبار، التغليف، الشحن)، هناك سير متحرك وآلات متخصصة تقوم بكل خطوة بشكل آلي ودقيق. هذا السير الآلي هو ما نسميه “خط أنابيب CI/CD”.
المصطلح يتكون من جزأين رئيسيين:
التكامل المستمر (Continuous Integration – CI)
هذا هو الجزء الأول من خط الأنابيب. الفكرة هي أن كل مطور في الفريق يقوم بدمج (merge) التغييرات التي أجراها على الكود في المستودع الرئيسي (مثل GitHub) عدة مرات في اليوم. كل عملية دمج تؤدي تلقائياً إلى:
- بناء المشروع (Build): التأكد من أن الكود يمكن تجميعه بدون أخطاء.
- تشغيل الاختبارات (Test): تنفيذ مجموعة من الاختبارات الآلية (Unit Tests, Integration Tests) للتأكد من أن التغييرات الجديدة لم تكسر أي شيء في النظام.
الهدف؟ نكتشف المشكلة وهي لسا صغيرة قبل ما تكبر وتعمل مصيبة. إذا فشل البناء أو الاختبار، يحصل الفريق على تنبيه فوري لإصلاح المشكلة. هذا يمنع تراكم الأخطاء ويجعل عملية الدمج أسهل بكثير.
التسليم/النشر المستمر (Continuous Delivery/Deployment – CD)
هذا هو الجزء الثاني، ويأتي بعد نجاح مرحلة الـ CI. هنا يوجد نوعان:
- التسليم المستمر (Continuous Delivery): بعد أن ينجح الكود في كل الاختبارات، يتم “تجهيزه” للنشر بشكل آلي. قد يعني هذا وضعه في حاوية Docker، أو تجميعه في حزمة جاهزة. ثم يتم نشره تلقائياً على بيئة شبيهة بالإنتاج (تسمى Staging أو UAT). لكن، النشر الفعلي على بيئة الإنتاج الحقيقية يتطلب ضغطة زر يدوية. هذا يعطيك نقطة تحكم أخيرة قبل إطلاق الكود للمستخدمين.
- النشر المستمر (Continuous Deployment): هذا هو المستوى المتقدم. إذا نجح الكود في كل المراحل السابقة (بناء، اختبار، نشر على staging)، يتم نشره تلقائياً على بيئة الإنتاج بدون أي تدخل بشري. هذا يتطلب ثقة هائلة في جودة اختباراتك الآلية.
نصيحة من أبو عمر: ابدأ دائماً بالتسليم المستمر (Continuous Delivery). لا تقفز مباشرة إلى النشر المستمر. امنح نفسك وفريقك وقتاً لبناء الثقة في العملية الآلية. وجود “زر الأمان” النهائي يمنح راحة بال لا تقدر بثمن في البداية.
رحلتي في بناء أول خط أنابيب: من الفوضى إلى النظام
قررت استخدام GitHub Actions لبناء أول خط أنابيب لي، لأنه مدمج مباشرة في GitHub وسهل التعلم. سأريكم مثالاً عملياً لمشروع Node.js بسيط، لكن المبادئ تنطبق على أي لغة أو إطار عمل.
كل شيء يبدأ بإنشاء ملف بصيغة YAML داخل مجلد .github/workflows في مشروعك.
المرحلة الأولى: بناء واختبار التكامل المستمر (CI)
أولاً، نريد أن نتأكد أن كل عملية push إلى المستودع تؤدي إلى بناء واختبار الكود. أنشأنا ملفاً اسمه ci.yml.
# .github/workflows/ci.yml
name: CI Pipeline
# متى يتم تشغيل هذا الخط؟
# هنا، عند أي عملية push لأي فرع
on: [push]
jobs:
build-and-test:
# على أي نظام تشغيل سيعمل؟
runs-on: ubuntu-latest
steps:
# الخطوة 1: سحب الكود من المستودع
- name: Checkout code
uses: actions/checkout@v3
# الخطوة 2: إعداد بيئة Node.js
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18' # حدد نسخة Node التي تستخدمها
# الخطوة 3: تثبيت المكتبات (Dependencies)
- name: Install dependencies
run: npm install
# الخطوة 4: تشغيل الاختبارات الآلية
- name: Run tests
run: npm test
بهذا الملف البسيط، كل مرة يقوم أي مطور بدفع كود جديد، سيقوم GitHub تلقائياً بتشغيل هذه الخطوات. إذا فشلت أي خطوة، سيظهر خطأ أحمر بجانب الـ commit، وسيعرف الجميع أن هناك مشكلة يجب حلها فوراً.
المرحلة الثانية: التحضير والنشر على بيئة الاختبار (Staging)
الآن، لنضف مرحلة النشر على خادم الاختبار (Staging Server). سنفترض أن هذه المرحلة تعمل فقط عند الدمج في الفرع الرئيسي (main).
سنعدل الملف ليصبح أكثر تعقيداً قليلاً. سنضيف وظيفة (job) جديدة تعتمد على نجاح الوظيفة الأولى.
# .github/workflows/main.yml (غيرنا الاسم ليعكس الغرض)
name: CI/CD Pipeline
on:
push:
branches: [ main ] # يعمل فقط عند الدفع إلى الفرع الرئيسي
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm test
deploy-to-staging:
# هذه الوظيفة تعتمد على نجاح الوظيفة السابقة
needs: build-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
# خطوة بناء نسخة الإنتاج من المشروع (مثلاً في React أو Vue)
- run: npm run build
# خطوة النشر: هنا نستخدم scp لنسخ الملفات إلى الخادم
# هذه طريقة مبسطة، في الواقع قد تستخدم Docker أو أدوات أخرى
- name: Deploy to Staging Server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USERNAME }}
key: ${{ secrets.STAGING_SSH_KEY }}
source: "dist/" # المجلد الذي يحتوي على ملفات البناء
target: "/var/www/staging-app"
نصيحة من أبو عمر: شايفين
${{ secrets.STAGING_HOST }}؟ هذه هي الطريقة الصحيحة للتعامل مع المعلومات الحساسة. إياك ثم إياك أن تكتب كلمات المرور أو مفاتيح SSH مباشرة في الملف! استخدم “Secrets” التي يوفرها GitHub في إعدادات المستودع. هاي فضيحة أمنية لو تركتها في الكود!
المرحلة الثالثة: النشر على الإنتاج بضغطة زر
لتحقيق “التسليم المستمر” (Continuous Delivery)، سنجعل النشر على الإنتاج (Production) يتطلب موافقة يدوية. يمكن تحقيق ذلك باستخدام “Environments” في GitHub.
- اذهب إلى إعدادات المستودع (Settings) -> Environments.
- أنشئ بيئة جديدة باسم
production. - في إعدادات البيئة، أضف “Required reviewers” واختر نفسك أو مدير المشروع.
الآن، أضف وظيفة النشر على الإنتاج إلى ملف الـ YAML:
# ... (نفس محتوى الملف السابق)
deploy-to-production:
needs: deploy-to-staging
runs-on: ubuntu-latest
# نحدد أن هذه الوظيفة تنشر على بيئة الإنتاج
environment:
name: production
url: https://my-awesome-app.com # رابط موقعك
steps:
# ... (نفس خطوات البناء والتحضير)
- uses: actions/checkout@v3
- run: npm install
- run: npm run build
# النشر على خادم الإنتاج باستخدام Secrets الخاصة به
- name: Deploy to Production Server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USERNAME }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
source: "dist/"
target: "/var/www/production-app"
الآن، عندما ينجح النشر على بيئة الاختبار، سيتوقف خط الأنابيب مؤقتاً. سيصلك إشعار من GitHub يطلب منك الموافقة على النشر في بيئة الإنتاج. بعد مراجعة كل شيء على بيئة الاختبار، يمكنك الموافقة بضغطة زر واحدة، وستقوم الآلة ببقية العمل بأمان ودقة.
النتائج والثمار: كيف تغيرت حياتي (وحياة فريقي)
بعد تطبيق هذه الأتمتة، تغيرت الأمور جذرياً:
- الثقة والهدوء: اختفى الرعب من عمليات النشر. صرت أنام ليلة الخميس وأنا مرتاح البال.
- السرعة والكفاءة: ما كان يأخذ ساعات من العمل اليدوي والتوتر، أصبح يتم في دقائق وبشكل آلي.
- جودة أعلى: الاختبارات الآلية تكتشف الأخطاء مبكراً، مما يمنع وصولها إلى المستخدم النهائي.
- التركيز على المهم: بدلاً من إضاعة الوقت في عمليات روتينية ومكافحة الحرائق، أصبح الفريق يركز على بناء ميزات جديدة وإضافة قيمة حقيقية للمنتج.
- الشفافية: أي شخص في الفريق يمكنه رؤية حالة النشر، ومن الذي وافق عليه، ومتى تم. لا مزيد من الغموض.
خلاصة الحكاية ونصيحة من أخوكم أبو عمر 👨💻
الانتقال من النشر اليدوي إلى خطوط أنابيب CI/CD لم يكن مجرد تحسين تقني، بل كان تغييراً في ثقافة العمل بأكملها. لقد حول عملية النشر من حدث مخيف ومحفوف بالمخاطر إلى جزء روتيني وآمن من يومنا.
إذا كنت لا تزال تنشر الكود يدوياً، فأتمنى أن تكون قصتي هذه حافزاً لك للبدء. لا تخف من التعقيد الظاهري، فالفائدة التي ستحصل عليها تفوق بكثير الجهد المبدئي المطلوب للتعلم.
نصيحتي الأخيرة لك: ابدأ صغيراً. لا تحاول بناء خط أنابيب مثالي ومعقد من اليوم الأول. ابدأ بأتمتة خطوة واحدة فقط، مثل تشغيل الاختبارات (CI). عندما تتقنها، أضف خطوة البناء. ثم خطوة النشر على بيئة الاختبار، وهكذا. كل خطوة صغيرة هي انتصار، وكل انتصار سيوفر عليك ساعات لا تحصى من “وجع الراس” في المستقبل. شدوا حيلكم يا شباب!