يا الله شو بتذكر هداك اليوم… كانت الساعة 2 بعد نص الليل، وإحنا في المكتب بنحاول نطلق تحديث ضروري على واحد من أكبر تطبيقاتنا. قهوة باردة، عيون حمرا، والتوتر واصل للسما. كان المفروض إنه تحديث بسيط، مجرد إصلاح لعلّة (bug) مزعجة اشتكى منها العملاء.
كانت عملية النشر وقتها أشبه بطقوس السحرة: واحد منا بيعمل SSH على السيرفر، الثاني بيذكّره يعمل نسخة احتياطية من قاعدة البيانات، والثالث بيصرخ ” espir ya zalameh, la tinsa el environment variables!” (اصبر يا زلمة، لا تنسى متغيرات البيئة!). كنت أنا المسؤول عن سحب آخر نسخة من الكود من Git. سحبت الكود، عملت build، وشغّلت السيرفر… وفجأة، الشاشة صارت بيضا. الموقع وقع.
ساعتين من الجحيم ونوبات الهلع، مكالمات من المدير، ومحاولات يائسة للعودة للنسخة القديمة يدويًا. اكتشفنا في النهاية إني سحبت الكود من الـ branch الغلط. يومها، بعد ما رجّعنا كل شي لوضعه الطبيعي وقررنا ننام في المكتب، حلفت يمين إننا ما رح نضل هيك. كانت هاي هي الشرارة اللي خلتنا نبحث عن حل جذري، حل اسمه CI/CD.
ما هي قصة الـ CI/CD؟ شو يعني هالمصطلحات؟
خليني أبسطها عليكم زي ما بنقعد على فنجان قهوة وبنشرح لبعض. الـ CI/CD هو اختصار لمفهومين متكاملين، هما مش مجرد أدوات، بل طريقة تفكير ومنهجية عمل بتغير كل شي.
التكامل المستمر (Continuous Integration – CI)
تخيل فريق شغال على مشروع، كل واحد بيكتب كود في فرع (branch) خاص فيه. في الطريقة التقليدية، بضل كل واحد شغال لحاله أيام أو أسابيع، ولما ييجوا يدمجوا شغلهم مع بعض بتصير “مجزرة”. أكواد بتتعارض، ومشاكل ما إلها أول من آخر.
الـ CI بيجي بحل هاي المشكلة. الفكرة هي إن كل مبرمج، كل ما يعمل تغيير بسيط على الكود تبعه، بيدمجه مباشرة على الفرع الرئيسي (زي main أو develop). هون بتبدأ السحر: بمجرد ما يتم الدمج، فيه سيرفر أوتوماتيكي (زي GitHub Actions أو GitLab CI) بيلقط التغيير هذا وبيعمل ثلاث شغلات أساسية:
- بناء المشروع (Build): بيتأكد إنه الكود الجديد ما كسر عملية البناء الأساسية.
- تشغيل الاختبارات (Tests): بيشغّل كل الاختبارات الآلية (Unit tests, integration tests) عشان يتأكد إنه الكود الجديد ما خرب وظائف كانت شغالة قبل.
- إعطاء تقرير فوري: إذا فشل البناء أو أي اختبار، النظام بيرسل تنبيه فوري للفريق عشان يصلحوا المشكلة وهي لسا “طازة”.
ببساطة، الـ CI هو شرطي المرور الذكي اللي بيضمن إنه ما في سيارة خربانة بتدخل على الشارع الرئيسي وبتعمل أزمة. “بنكتشف المشاكل بدري بدري”.
التسليم والنشر المستمر (Continuous Delivery/Deployment – CD)
بعد ما الـ CI يتأكد إنه الكود سليم وناجح في كل الاختبارات، بيجي دور الـ CD. وهون فيه نوعين لازم نميز بينهم:
- التسليم المستمر (Continuous Delivery): بعد ما الكود ينجح في كل مراحل الـ CI، النظام بيجهزه وبيحزمه وبيخليه جاهز للنشر على البيئة النهائية (الإنتاج – Production). لكن، عملية النشر الفعلية ما بتتم إلا بضغطة زر يدوية. هاي بتعطينا تحكم إضافي، ممكن نختار الوقت المناسب للنشر (مثلاً خارج أوقات الذروة).
- النشر المستمر (Continuous Deployment): هذا هو المستوى المتقدم. هون ما في أي تدخل بشري. بمجرد ما الكود ينجح في كل الاختبارات، بيتم نشره تلقائيًا على سيرفر الإنتاج مباشرة. هاي الطريقة بتتطلب ثقة عالية جدًا في اختباراتك الآلية، لكنها بتوصلك لأقصى درجات السرعة والكفاءة.
الهدف من الـ CD هو جعل عملية النشر حدث روتيني، ممل، وآمن، بدل ما يكون مغامرة محفوفة بالمخاطر.
رحلتنا من الفوضى إلى الأتمتة: خطوات عملية
طيب يا أبو عمر، حكي جميل، بس كيف بنطبق هالحكي على أرض الواقع؟ خليني أعطيكم خارطة الطريق اللي مشيناها.
الخطوة الأولى: اختيار الأدوات المناسبة
السوق مليان أدوات: Jenkins, GitLab CI/CD, CircleCI, Travis CI, GitHub Actions وغيرها. نصيحتي كالتالي: “ما فيه أداة هي الأحسن بالمطلق، فيه الأداة الأنسب لمشروعك وفريقك”.
إذا كنتوا بتستخدموا GitHub، فالبداية مع GitHub Actions هي أسهل وأذكى خيار لأنه متكامل مع نظامكم. إذا كنتوا على GitLab، استخدموا GitLab CI/CD. ابدأوا بالأداة الأقرب إليكم لتجنب تعقيدات الإعداد الأولية.
الخطوة الثانية: بناء أول خط أنابيب (Pipeline) بسيط (مرحلة الـ CI)
ابدأوا بالأساسيات. هدفنا الأول هو أتمتة البناء والاختبارات. لنفترض عندك تطبيق Node.js، ملف الـ pipeline على GitHub Actions ممكن يكون بالبساطة هاي:
بتعمل ملف جديد في مشروعك اسمه .github/workflows/ci.yml وبتكتب فيه التالي:
# اسم الـ workflow اللي بيظهر في GitHub
name: CI Pipeline
# متى يشتغل هاد الـ workflow؟
# هنا، عند أي عملية push على فرع main أو أي pull request بستهدف main
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# الوظائف (Jobs) اللي رح تتنفذ
jobs:
# اسم الوظيفة، ممكن يكون أي اسم
build-and-test:
# نوع الجهاز الافتراضي اللي رح يشتغل عليه الكود
runs-on: ubuntu-latest
# الخطوات اللي رح تتنفذ بالترتيب
steps:
# 1. سحب الكود من المستودع (Repository)
- name: Checkout code
uses: actions/checkout@v3
# 2. إعداد بيئة Node.js بالإصدار المطلوب
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
# 3. تنزيل الاعتماديات (Dependencies)
- name: Install dependencies
run: npm install
# 4. تشغيل الاختبارات الآلية
- name: Run tests
run: npm test
هذا الملف البسيط بيعمل العجايب! الآن كل دفعة كود جديدة رح تمر عبر هاي البوابة، ولو فيه أي مشكلة رح تعرفها خلال دقايق، مش بعد أسبوعين.
الخطوة الثالثة: إضافة مرحلة النشر (مرحلة الـ CD)
بعد ما صار عنا ثقة في مرحلة الـ CI، بننتقل للمرحلة اللي بعدها: أتمتة النشر. رح نضيف وظيفة (job) جديدة اسمها deploy بتعتمد على نجاح وظيفة build-and-test.
ملاحظة مهمة: قبل هاي الخطوة، لازم تخزن معلومات الدخول للسيرفر (مثل مفتاح SSH) في الـ Secrets الخاصة بمستودعك على GitHub. إياك ثم إياك تكتبها مباشرة في الملف!
هيك بيصير شكل الملف بعد إضافة مرحلة النشر:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
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:
# هاي الوظيفة ما بتشتغل إلا بعد نجاح build-and-test
needs: build-and-test
# ما بدنا ننشر إلا لما يكون التغيير على فرع main مباشرة
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to production server
# هاي action جاهزة بتسهل عملية الـ SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# الأوامر اللي كانت تتنفذ يدويًا على السيرفر
cd /var/www/my-app
git pull origin main
npm install --omit=dev
# استخدام أداة مثل pm2 لإعادة تشغيل التطبيق بدون downtime
pm2 restart app
وهيك، كل الأوامر اليدوية اللي كانت بتسبب لنا نوبات هلع صارت مؤتمتة، موثوقة، وبتتنفذ بنفس الطريقة كل مرة.
نصائح من قلب “الورشة”
خلال رحلتنا، تعلمنا كم شغلة “باللحم الحي”، وبدي أشارككم إياها:
- ابدأ صغيراً (Start Small): لا تحاول أتمتة كل شيء من أول يوم. ابدأ بالـ CI فقط. خلي فريقك يتعود على فكرة إن الاختبارات لازم تنجح دايماً. بعدين ضيف خطوة Linting (فحص تنسيق الكود). بعدين ضيف النشر على بيئة تجريبية (Staging). “شوي شوي” بتوصل.
- الاختبارات هي العمود الفقري: pipeline بدون اختبارات آلية قوية هو مجرد “أتمتة للفوضى”. استثمروا وقت في كتابة unit tests و integration tests. “الأتمتة بدون اختبارات زي اللي بيسوق سيارة سباق وهو مغمض عينيه”.
- أسراركم في بئر أمين (Keep Your Secrets Safe): تعلمت بالطريقة الصعبة أهمية الـ Secrets. كل أدوات الـ CI/CD بتوفر مكان آمن لحفظ كلمات المرور ومفاتيح الـ API. استخدموها دايماً.
- اجعل الفشل واضحاً: إعدادات التنبيهات (Notifications) ضرورية. اربط الـ pipeline مع Slack أو بريد الفريق. لما يفشل build، لازم الكل يعرف فوراً عشان تصير مسؤولية جماعية.
- الـ Rollback مش عيب: الأتمتة مش بس للنشر، هي كمان للتراجع. فكروا كيف ممكن تعملوا pipeline يعكس عملية النشر ويرجعكم للنسخة المستقرة السابقة بضغطة زر في حالات الطوارئ.
الخلاصة: من نوبات الهلع إلى الثقة بالنفس
الانتقال إلى CI/CD ما كان مجرد تغيير تقني، بل كان تغييراً ثقافياً في الفريق. زر النشر بطل مصدر رعب، وصار صديقنا. صرنا ننشر تحديثات 5 أو 10 مرات في اليوم أحياناً، بثقة تامة. تحررنا من الأعمال الروتينية المملة وركزنا على اللي بنحبه: كتابة كود رائع وحل مشاكل المستخدمين.
إذا كنتوا لسا بتعانوا من جحيم الإصدارات اليدوية، فاليوم هو يوم التغيير. ابدأوا بأبسط خطوة، وشوفوا كيف رح تتحول عملية تطوير البرمجيات عندكم من مصدر قلق لمصدر فخر وإنجاز.
تذكروا يا جماعة، أفضل كود هو الكود اللي بيوصل للمستخدمين وبحل مشاكلهم. والـ CI/CD هو الجسر اللي بيوصل هالكود بأمان وسرعة. يلا، شدّوا حيلكم وخلونا نبني جسور قوية! 💪