ما الذي يجب أن يكون خط أنابيب CI/CD قادرًا على فعله حقًا (بعيدًا عن الضجة)

تدفع الكود، يتحول خط الأنابيب إلى اللون الأخضر، ويتم النشر. لكن عندما يحدث خطأ، تكتشف أن خط الأنابيب لم يُصمم أبدًا للتعامل معه. ترحيل قاعدة البيانات نُفذ بترتيب خاطئ. القطعة البرمجية (artifact) من بيئة الاختبار (staging) تختلف عن تلك التي نُشرت في الإنتاج. وماذا عن التراجع (rollback)؟ لم يخطط له أحد.

هذه هي الفجوة بين امتلاك خط أنابيب وبين امتلاك خط أنابيب يعمل فعلاً. أدوات مثل Jenkins و GitHub Actions و GitLab CI و ArgoCD تدّعي جميعها أنها تحل مشكلة التسليم، لكن المشكلة ليست في الأداة نفسها أبدًا. المشكلة هي في القدرات المفقودة. إذا كان خط الأنابيب الخاص بك يفتقر إلى اللبنات الأساسية الصحيحة، فلا يمكن لأي أداة إصلاح ذلك.

فيما يلي القدرات الست الأساسية التي يجب أن يمتلكها كل خط أنابيب CI/CD. ليست من الكماليات. ليست ميزات تُضاف عندما يتوفر الوقت. هذه هي الحدود الدنيا المطلوبة لنقل التغييرات من الكود إلى الإنتاج بأمان.

البناء (Build): تحويل الكود إلى شيء قابل للتشغيل

في كل مرة يدفع فيها مطور تغييرًا، يجب على خط الأنابيب تحويل هذا الكود إلى شيء يمكن تشغيله فعليًا. بالنسبة للغات المترجمة مثل Go و Rust و Java، يعني هذا ترجمة المصدر إلى ملفات تنفيذية (binaries). بالنسبة للغات المفسّرة مثل Python أو JavaScript، يعني البناء التحقق من بناء الجملة (syntax)، وتجميع الوحدات (modules)، وحل التبعيات (dependencies)، وتحضير بيئة التشغيل (runtime environment).

البناء هو البوابة الأولى. إذا لم يتم بناء الكود، فلا شيء آخر يهم. يجب أن يفشل خط الأنابيب بسرعة هنا، وألا يضيع الوقت في تشغيل الاختبارات على كود لا يمكن حتى ترجمته.

من الأخطاء الشائعة معاملة خطوة البناء على أنها خطوة بسيطة تعمل دائمًا. لكن بيئات البناء تختلف. فالبناء الذي ينجح على حاسوب المطور المحمول قد يفشل في خط الأنابيب بسبب نقص مكتبات النظام، أو اختلاف إصدارات الأدوات، أو متغيرات البيئة. يجب أن تكون خطوة البناء في خط الأنابيب قابلة للتكرار (reproducible) ومعزولة (isolated)، بحيث ما يعمل في CI يعمل في كل مكان.

الاختبار (Test): اكتشاف المشاكل قبل وصولها للمستخدمين

بعد بناء ناجح، يجب على خط الأنابيب تشغيل اختبارات آلية. هذا لا يقتصر على اختبارات الوحدة (unit tests) التي تستغرق ميلي ثانية. خط الأنابيب السليم يشغل طبقات متعددة من الاختبارات:

  • اختبارات الوحدة التي تتحقق من السلوكيات الفردية
  • اختبارات التكامل (integration tests) التي تتحقق من كيفية عمل المكونات معًا
  • اختبارات شاملة (end-to-end tests) تحاكي سيناريوهات المستخدم الحقيقية

كل طبقة تكتشف نوعًا مختلفًا من المشاكل. اختبارات الوحدة تكتشف أخطاء المنطق. اختبارات التكامل تكتشف عدم التوافق بين الخدمات. الاختبارات الشاملة تكتشف فشل سير العمل الذي يمتد عبر أنظمة متعددة.

المفتاح هو الأتمتة. يجب أن تُجرى الاختبارات دون تدخل بشري. إذا كان على شخص ما تشغيل الاختبارات يدويًا أو تفسير النتائج، يفقد خط الأنابيب قيمته الأساسية: السرعة والاتساق. كل اختبار يُجرى تلقائيًا هو أمر أقل يجب على الإنسان تذكره للتحقق منه.

التغليف (Package): إنشاء قطعة برمجية قابلة للنشر وذات إصدار

بمجرد بناء الكود واجتياز الاختبارات، يجب على خط الأنابيب تغليف النتيجة في قطعة برمجية (artifact) يمكن نشرها. يعتمد تنسيق القطعة البرمجية على ما تقوم بشحنه:

  • صورة حاوية (container image) للخدمات المصغرة (microservices)
  • ملف تنفيذي (binary file) لتطبيقات سطح المكتب
  • ملف APK أو IPA لتطبيقات الجوال
  • أرشيف مضغوط (zip archive) للدوال عديمة الخادم (serverless functions)
  • مخطط Helm (Helm chart) لنشر Kubernetes

يجب أن يكون لكل قطعة برمجية إصدار فريد (unique version). ليس مجرد طابع زمني (timestamp) أو رقم بناء (build number)، بل إصدار يرتبط بالـ commit المحدد، وتشغيلة خط الأنابيب، ونتائج الاختبارات. هذا التتبع (traceability) هو ما يسمح لك بمعرفة بالضبط ما يعمل في الإنتاج وما الذي تغير بين الإصدارات.

يجب تخزين القطعة البرمجية في سجل (registry) أو مستودع (repository) مركزي يمكن لمرحلة النشر الوصول إليه. إذا قمت بإعادة بناء القطعة البرمجية في وقت النشر، فستفقد الاتساق. القطعة البرمجية التي اجتازت الاختبارات يجب أن تكون نفس القطعة البرمجية التي يتم نشرها بالضبط.

النشر (Deploy): وضع القطعة البرمجية في البيئة المستهدفة

النشر هو أكثر من مجرد نسخ الملفات. إنها عملية وضع إصدار جديد في بيئة وجعله يخدم حركة المرور. بالنسبة لبيئة الاختبار (staging)، يعني النشر تثبيت الإصدار الجديد للاختبار. بالنسبة للإنتاج، يعني استبدال الإصدار الحالي دون تعطيل المستخدمين.

توجد استراتيجيات نشر مختلفة لمستويات مخاطر مختلفة:

  • التحديث التدريجي (Rolling update): استبدال الحالات (instances) واحدة تلو الأخرى
  • الأزرق والأخضر (Blue-green): تبديل حركة المرور بين بيئتين متطابقتين
  • الكناري (Canary): إرسال نسبة صغيرة من حركة المرور إلى الإصدار الجديد أولاً
  • أعلام الميزات (Feature flags): نشر الكود ولكن إبقائه مخفيًا خلف مفتاح تشغيل/إيقاف

يجب أن يدعم خط الأنابيب الاستراتيجية الصحيحة لكل بيئة. يمكن لبيئة الاختبار استخدام استبدال بسيط. غالبًا ما يحتاج الإنتاج إلى طرح تدريجي مع مراقبة. يجب أن يقوم خط الأنابيب بأتمتة العملية بأكملها، وليس فقط نسخ الملفات.

الترحيل (Migrate): التعامل مع تغييرات قاعدة البيانات بأمان

إذا كان تطبيقك يستخدم قاعدة بيانات، يجب على خط الأنابيب التعامل مع ترحيلات المخطط (schema migrations). إضافة عمود، تغيير نوع بيانات، أو إنشاء جدول جديد يتطلب تشغيل نصوص الترحيل (migration scripts) بترتيب محدد. لا يمكن خلط هذه الترحيلات عشوائيًا مع نشر التطبيق.

الجزء الصعب هو الترتيب. أحيانًا يجب تشغيل الترحيل قبل نشر كود التطبيق الجديد. على سبيل المثال، إضافة عمود يمكن أن يكون فارغًا (nullable column) سيستخدمه الكود الجديد. في أحيان أخرى، يجب تشغيل الترحيل بعد نشر الكود الجديد. على سبيل المثال، إزالة عمود قديم لا يزال الكود القديم يشير إليه.

يجب أن يعرف خط الأنابيب هذا الترتيب وينفذه بشكل صحيح. الترحيل الذي يعمل في الوقت الخطأ يمكن أن يتسبب في توقف الخدمة (downtime)، أو فقدان البيانات، أو كليهما. هذه واحدة من أكثر القدرات التي يتم تجاهلها في خطوط أنابيب CI/CD، وواحدة من أكثرها خطورة عند ارتكاب الخطأ فيها.

التراجع (Rollback): التراجع عن التغيير عندما تسوء الأمور

ليس كل نشر ينجح. عندما يتسبب إصدار جديد في أخطاء، أو تدهور في الأداء، أو تلف في البيانات، يجب أن يكون خط الأنابيب قادرًا على العودة إلى الإصدار السابق. التراجع ليس مجرد إعادة نشر القطعة البرمجية القديمة. إنه يتضمن:

  • إعادة التطبيق إلى الإصدار السابق
  • تشغيل ترحيلات عكسية (reverse migrations) على قاعدة البيانات
  • استعادة تكوين البنية التحتية
  • التحقق من أن التراجع قد عمل بالفعل

يجب التخطيط للتراجع قبل النشر الأول. إذا قمت بتصميم خط الأنابيب دون النظر في كيفية التراجع عن تغيير، ستجد نفسك تندفع لكتابة نصوص تراجع (rollback scripts) بينما الإنتاج معطل. هذا هو أسوأ وقت لمعرفة ذلك.

بالنسبة لترحيلات قاعدة البيانات، يعني التراجع وجود ترحيلات هبوطية (down migrations) تعكس الترحيلات الصعودية (up migrations). بالنسبة للبنية التحتية، يعني الاحتفاظ بملفات الحالة السابقة أو استخدام أدوات البنية التحتية ككود (infrastructure-as-code) التي تدعم التراجع عن الحالة. بالنسبة للتطبيقات، يعني الاحتفاظ بالقطعة البرمجية السابقة متاحة وامتلاك استراتيجية نشر تدعم التبديل الفوري.

تجميع كل ذلك معًا

هذه القدرات الست - البناء، الاختبار، التغليف، النشر، الترحيل، والتراجع - تشكل أساس أي خط أنابيب CI/CD حقيقي. اعتمادًا على ما تقوم بشحنه، قد تبدو بعض القدرات مختلفة. خطوط أنابيب البنية التحتية قد تستبدل البناء والتغليف بالتحقق من صحة التكوين (configuration validation) وتحضير الحالة (state preparation). خطوط أنابيب تطبيقات الجوال قد تضيف توقيع الكود (code signing) وتقديم المتجر (app store submission). لكن الوظائف الأساسية تبقى كما هي.

فيما يلي مثال مبسط لخط أنابيب GitLab CI يربط كل قدرة بمرحلة:

stages:
  - build
  - test
  - package
  - deploy
  - migrate
  - rollback

build:
  stage: build
  script:
    - go build -o app

test:
  stage: test
  script:
    - go test ./...

package:
  stage: package
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .
    - docker push registry.example.com/myapp:$CI_COMMIT_SHA

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/myapp myapp=registry.example.com/myapp:$CI_COMMIT_SHA

migrate:
  stage: migrate
  script:
    - ./run_migrations up

rollback:
  stage: rollback
  script:
    - ./run_migrations down
    - kubectl rollout undo deployment/myapp
  when: manual

يوضح مخطط التدفق التالي كيفية اتصال هذه القدرات الست في خط أنابيب نموذجي:

flowchart TD A[دفع الكود] --> B[بناء] B --> C[اختبار] C --> D{تغليف} D --> E[نشر] E --> F[ترحيل قاعدة البيانات] F --> G{فحص الصحة} G -- نجاح --> H[اكتمال] G -- فشل --> I[تراجع] I --> J[استعادة قاعدة البيانات] J --> K[إعادة نشر الإصدار السابق] K --> L[تحقق]

قبل أن تختار أداة CI/CD أو تعيد تصميم خط الأنابيب الخاص بك، حدد أيًا من هذه القدرات تمتلكها وأيها مفقودة. الأداة التي تعد بكل شيء ولكنها لا تتعامل مع ترحيلات قاعدة البيانات أو التخطيط للتراجع ستتركك مكشوفًا.

قائمة التحقق السريعة للقدرات

  • البناء يعمل في بيئة معزولة وقابلة للتكرار
  • الاختبارات تُجرى تلقائيًا على مستويات متعددة
  • القطع البرمجية ذات إصدارات ومخزنة في سجل مركزي
  • النشر يدعم الاستراتيجية الصحيحة لكل بيئة
  • ترحيلات قاعدة البيانات مرتبة بشكل صحيح بالنسبة لنشر التطبيق
  • التراجع مُختبر ويعمل للتطبيق وقاعدة البيانات والبنية التحتية

الخلاصة العملية

خط الأنابيب ليس مجرد مجموعة من الخطوات. إنه نظام يجب أن يتعامل مع دورة الحياة الكاملة للتغيير: من الكود إلى الخدمة العاملة، والعودة مرة أخرى إذا لزم الأمر. إذا كان خط الأنابيب الخاص بك لا يستطيع البناء والاختبار والتغليف والنشر والترحيل والتراجع، فهو غير مكتمل. ابدأ بسد القدرة المفقودة، وليس بتغيير الأدوات.