التكامل المستمرّ
معمارية تطبيقات المصادر المفتوحة (المجلد الأول)التكامل المستمر
معمارية تطبيقات المصادر المفتوحة (المجلد الأول) التكامل المستمر
C. Titus Brown وRosangela Canino-Koning
إذا استمتعت بهذه الكتب، فقد يعجبك أيضًا التصميم البرمجي بالأمثلة في بايثون (Software Design by Example in Python) والتصميم البرمجي بالأمثلة في جافاسكربت (Software Design by Example in JavaScript).
أنظمة التكامل المستمر (Continuous Integration أو CI) هي أنظمة تبني البرمجيات وتختبرها آليًّا وبشكل دوري. ورغم أن أبرز فوائدها يكمن في تجنّب الفترات الطويلة بين عمليات البناء والاختبار، فإن أنظمة التكامل المستمر تستطيع أيضًا تبسيط تنفيذ كثير من العمليات التي تكون بخلاف ذلك مملّة ومرهقة، وتنفيذها آليًّا. ومن هذه العمليات: الاختبار عبر المنصّات، والتشغيل الدوري للاختبارات البطيئة أو كثيفة البيانات أو الصعبة الإعداد، والتحقّق من سلامة الأداء على المنصّات القديمة (legacy)، وكشف الاختبارات التي تفشل نادرًا، والإنتاج الدوري لإصدارات محدَّثة. وإلى جانب ذلك، ولأن أتمتة البناء والاختبار شرط ضروري لتنفيذ التكامل المستمر، فإن التكامل المستمر يمثّل غالبًا الخطوة الأولى نحو إطار النشر المستمر (continuous deployment) الذي يتيح نشر تحديثات البرمجيات بسرعة إلى الأنظمة الحيّة بعد اجتياز الاختبارات.
التكامل المستمر موضوعٌ في أوانه، ولا يقلّ ذلك عن حضوره البارز في منهجية البرمجيات الرشيقة (Agile). وقد شهد السنوات الأخيرة انفجارًا في أدوات التكامل المستمر مفتوحة المصدر، بلغ لغات متعدّدة ولأغراض متنوّعة، وطبّقت مجموعة هائلة من المزايا ضمن سياق نماذج معمارية متنوّعة. والغرض من هذا الفصل هو وصف المجموعات الشائعة من المزايا التي تنفّذها أنظمة التكامل المستمر، ومناقشة الخيارات المعمارية المتاحة، وفحص المزايا التي قد تكون سهلة التنفيذ أو صعبة التنفيذ بحسب اختيار البنية.
في ما يلي، سنصف بإيجاز مجموعة من الأنظمة تمثّل طرفي الخيارات المعمارية المتاحة عند تصميم نظام تكامل مستمر. فأوّلها Buildbot وهو نظام رئيسي/تابع (master/slave)، وثانيها CDash وهو نموذج خادم تقارير، وثالثها Jenkins يستخدم نموذجًا هجينًا، أما الرابع Pony-Build فهو خادم تقارير لامركزي مبنيّ على بايثون سنستخدمه كخلفية مقارنة لبقية المناقشة.
9.1. المشهد العام
يبدو أن مجال معماريات أنظمة التكامل المستمر يهيمن عليه طرفان متقابلان: معماريات رئيسي/تابع (master/slave)، يكون فيها خادم مركزي هو من يوجّه البناءات البعيدة ويضبطها؛ ومعماريات التقارير، يجمع فيها خادم مركزي تقارير البناءات التي يرسلها العملاء. وقد اختارت كل أنظمة التكامل المستمر التي نعرفها مزيجًا من مزايا هاتين المعماريتين.
يتكوّن مثالنا على البنية المركزية، وهو Buildbot، من جزأين: الخادم المركزي، أو الخادم الرئيسي (buildmaster)، الذي يجدول البناءات وينسّقها بين عميل واحد أو أكثر متصل به؛ والعملاء، أو خوادم البناء التابعة (buildslaves)، الذين ينفّذون البناءات. ويوفّر الخادم الرئيسي نقطة اتصال مركزية، إضافةً إلى معلومات الإعداد التي تحدّد أي العملاء ينبغي أن ينفّذوا أي الأوامر وبأي ترتيب. وتتصل خوادم البناء التابعة بالخادم الرئيسي وتتلقّى تعليمات مفصّلة. ويتكوّن إعداد الخادم التابع من تثبيت البرمجية، وتحديد هوية الخادم الرئيسي، وتوفير بيانات اعتماد الاتصال ليصل العميل بالخادم الرئيسي. ويجدول الخادم الرئيسي البناءات، في حين تُبثّ المخرجات من خوادم البناء التابعة إلى الخادم الرئيسي وتُحفظ على الخادم الرئيسي لتُقدَّم عبر الويب وأنظمة التقارير والإشعارات الأخرى.
على الطرف المقابل من طيف البنى المعمارية يقع CDash، الذي تستخدمه شركة Kitware, Inc. في مشروعي أدوات التصوير المرئي (Visualization Toolkit، VTK) وأدوات الرؤية (Insight Toolkit، ITK). وهو في جوهره خادم تقارير، صُمِّم لتخزين المعلومات الواردة من حواسيب العملاء التي تشغّل CMake وCTest وعرضها. ومع CDash، يبدأ العملاء عملية البناء ومجموعة الاختبارات، ويسجّلون نتائج البناء والاختبار، ثم يتّصلون بخادم CDash لإيداع تلك المعلومات من أجل التقارير المركزية.
وأخيرًا، يوفّر نظام ثالث، وهو Jenkins (المعروف باسم Hudson قبل تغيير الاسم عام 2011)، نمطَي التشغيل معًا. ومع Jenkins، إمّا أن تُنفَّذ البناءات بصورة مستقلة مع إرسال النتائج إلى الخادم الرئيسي، وإمّا أن تُخضَع العقد لخادم Jenkins الرئيسي الذي يجدول البناءات ويوجّه تنفيذها عندها.
يشترك النموذجان المركزي واللامركزي في بعض المزايا، وكما يُظهر Jenkins، يمكن للنموذجين أن يتعايشا في تطبيقٍ واحد. غير أن Buildbot وCDash يتباينان تباينًا حادًّا: فباستثناء القواسم المشتركة الخاصة ببناء البرمجيات والإبلاغ عن البناءات، تختلف كلّ جانب آخر من جوانب البنية المعمارية بصورة جوهرية. لماذا؟
إضافةً إلى ذلك، إلى أي مدى يبدو أن اختيار البنية المعمارية يجعل بعض المزايا أسهل أو أصعب في التنفيذ؟ هل تنبثق بعض المزايا طبيعيًّا من نموذج مركزي؟ وإلى أي مدى قابلة للتوسّع التنفيذات القائمة—هل يمكن تعديلها بسهولة لتوفير آليات تقارير جديدة، أو للتوسّع لتشمل حزمًا كثيرة، أو لتشغيل البناءات والاختبارات في بيئة سحابية؟
9.1.1. ماذا تفعل برمجيات التكامل المستمر؟
الوظيفة الجوهرية لنظام تكامل مستمر بسيطة: ابنِ البرمجيات، وشغّل الاختبارات، وأبلغ عن النتائج. ويمكن للبناء والاختبار والإبلاغ أن ينفّذها سكربت يعمل من مهمة مجدولة أو من وظيفة cron: إذ يكفي أن يسحب هذا السكربت نسخة جديدة من شيفرة المصدر من نظام التحكّم بالإصدارات (VCS)، ثم يبني، ثم يشغّل الاختبارات. وتُدوَّن المخرجات في ملف، ثم تُحفظ إمّا في موقع معياري أو تُرسَل عبر البريد الإلكتروني في حال فشل البناء. وهذا الأمر سهل التنفيذ: في UNIX مثلًا، يمكن تنفيذ هذه العملية برمتها بالنسبة لمعظم حزم بايثون في سكربت من سبعة أسطر:
cd /tmp && \
svn checkout http://some.project.url && \
cd project_directory && \
python setup.py build && \
python setup.py test || \
echo build failed | sendmail notification@project.domain
cd /tmp && rm -fr project_directory
في الشكل 9.1، تمثّل المستطيلات غير المظلَّلة الأنظمة الفرعية المنفصلة والوظائف داخل النظام. وتُظهر الأسهم تدفّق المعلومات بين المكوّنات المختلفة. أمّا السحابة فتمثّل التنفيذ البعيد المحتمل لعمليات البناء. وتمثّل المستطيلات المظلَّلة الترابط المحتمل بين الأنظمة الفرعية؛ فمثلًا قد يشمل مراقبة البناء مراقبة عملية البناء نفسها وجوانب من صحّة النظام (حِمل المعالج، وحِمل الإدخال/الإخراج، واستهلاك الذاكرة، إلخ)

الشكل 9.1: داخل نظام تكامل مستمر
غير أن هذه البساطة مضلِّلة. فأنظمة التكامل المستمر في العالم الحقيقي تفعل الكثير أكثر من ذلك. وإلى جانب بدء عمليات البناء البعيدة أو تلقّي نتائجها، قد تدعم برمجيات التكامل المستمر أيًّا من المزايا الإضافية التالية:
- سحب الشيفرة وتحديثها: في المشاريع الكبيرة، قد يكون سحب نسخة جديدة من شيفرة المصدر مكلفًا من حيث عرض النطاق والوقت. وعادةً تحدّث أنظمة التكامل المستمر نسخة عمل قائمة في مكانها، في لا يُرسَل سوى الفروق منذ التحديث السابق. ومقابلًا لهذا التوفير، على النظام أن يتابع نسخة العمل ويعرف كيف يحدّثها، وهو ما يعني عادةً تكاملًا لا يقلّ عن الحد الأدنى مع نظام تحكّم بالإصدارات.
- وصفات البناء المجرّدة: يجب كتابة وصفة ضبط/بناء/اختبار (configure/build/test) للبرمجية المعنية. وستكون الأوامر الأساسية مختلفة في الغالب بين أنظمة التشغيل المختلفة، مثل Mac OS X مقابل Windows مقابل UNIX، ما يعني إمّا الحاجة إلى كتابة وصفات متخصّصة (وهي تسبّب أخطاء محتملة أو انفصالًا عن بيئة البناء الفعلية) أو إلزام نظام إعداد التكامل المستمر بتوفير مستوى مناسب من التجريد للوصفات.
- تخزين حالة السحب/البناء/الاختبار: قد يكون من المرغوب تخزين تفاصيل السحب (الملفات المحدَّثة، وإصدار الشيفرة) والبناء (التحذيرات أو الأخطاء) والاختبار (تغطية الشيفرة، والأداء، واستهلاك الذاكرة) واستخدامها في تحليل لاحق. ويمكن استخدام هذه النتائج للإجابة عن أسئلة عبر معماريات البناء المختلفة (هل أثّر آخر إيداع (check-in) تأثيرًا ملموسًا في الأداء على معمارية بعينها؟) أو عبر الزمن (هل تغيّرت تغطية الشيفرة تغيّرًا كبيرًا خلال الشهر الماضي؟). وكما في وصفة البناء، فإن آليات هذا النوع من الاستبطان وأنواع بياناته تكون خاصة عادةً بالمنصّة أو بنظام البناء.
- حزم الإصدار: قد تنتج البناءات حزمًا ثنائية أو غيرها من النواتج التي يجب إتاحتها خارجيًا. فمثلًا قد يريد المطوّرون الذين لا يملكون وصولًا مباشرًا إلى آلة البناء اختبار آخر بناء على معمارية محدّدة؛ ولغرض ذلك يحتاج نظام التكامل المستمر إلى القدرة على نقل نواتج البناء إلى مستودع مركزي.
- بناء معماريات متعدّدة: لأن أحد أهداف التكامل المستمر هو البناء على معماريات متعدّدة لاختبار الوظائف عبر المنصّات، قد تحتاج برمجيات التكامل المستمر إلى تتبّع المعمارية الخاصة بكل آلة بناء، وربط البناءات ونتائجها بكل عميل.
- إدارة الموارد: إذا كانت خطوة بناء ما كثيفة الاستهلاك للموارد على آلة واحدة، فقد يفضّل نظام التكامل المستمر تشغيلها بشروط. فمثلًا قد تنتظر البناءات حتى خلوّ الآلة من بناءات أو مستخدمين آخرين، أو تؤجَّل حتى يبلغ حِمل المعالج أو الذاكرة حدًّا معيّنًا.
- تنسيق الموارد الخارجية: قد تعتمد اختبارات التكامل على موارد غير محلّية مثل قاعدة بيانات تجريبية أو خدمة ويب بعيدة. لذلك قد يحتاج نظام التكامل المستمر إلى تنسيق البناءات بين عدّة آلات لتنظيم الوصول إلى هذه الموارد.
- تقارير التقدّم: بالنسبة لعمليات البناء الطويلة، قد يكون الإبلاغ الدوري عن سير البناء مهمًّا أيضًا. فإذا كان اهتمام المستخدم الأساسي بالنتائج خلال أول 30 دقيقة من عملية بناء واختبار تستغرق خمس ساعات، فإن جعله ينتظر حتى نهاية التشغيل لرؤية أي نتيجة سيكون هدرًا للوقت.
تعرض الشكل 9.1 نظرة عامّة عالية المستوى على جميع هذه المكوّنات المحتملة لنظام التكامل المستمر. ولا تنفّذ برمجيات التكامل المستمر سوى مجموعة جزئية من هذه المكوّنات عادةً.
9.1.2. التفاعلات الخارجية
تحتاج أنظمة التكامل المستمر أيضًا إلى التفاعل مع أنظمة أخرى. وهناك عدّة أنواع من التفاعلات المحتملة:
- إشعار البناء: تحتاج نتائج البناءات عادةً إلى توصيلها للعملاء المعنيين، إمّا عبر السحب (الويب، وRSS، وRPC، إلخ) أو الإشعار الفوري (البريد الإلكتروني، وTwitter، وPubSubHubbub، إلخ). وقد يشمل ذلك إشعارًا بكل البناءات، أو بالبناءات الفاشلة فقط، أو بالبناءات التي لم تُنفَّذ خلال فترة معيّنة.
- معلومات البناء: قد يلزم استرجاع تفاصيل البناء ونواتجه، عادةً عبر RPC أو نظام تنزيل بالجملة. فمثلًا قد يكون من المرغوب وجود نظام تحليل خارجي يجري تحليلًا معمّقًا أو أكثر استهدافًا، أو يرفع تقريرًا عن تغطية الشيفرة أو نتائج الأداء. وإلى جانب ذلك، قد يُستعانة بمستودع خارجي لنتائج الاختبارات لمتابعة الاختبارات الفاشلة والناجحة بمعزل عن نظام التكامل المستمر.
- طلبات البناء: قد يلزم معالجة طلبات بناء واردة من مستخدمين أو من مستودع شيفرة. فمعظم أنظمة التحكّم بالإصدارات تملك خطّافات ما بعد الإيداع (post-commit hooks) قادرة على تنفيذ نداء RPC لبدء بناء، مثلًا. أو قد يطلب المستخدمون البناءات يدويًّا عبر واجهة ويب أو عبر نداء RPC آخر يبدأه المستخدم.
- التحكّم عن بُعد بنظام التكامل المستمر: وبشكل أعمّ، قد يكون بالإمكان تعديل بيئة التشغيل بأكملها عبر واجهة RPC محدّدة إلى حدٍّ معقول. وسواء كانت امتدادات عفوية (ad hoc) أم واجهة محدّدة رسميًّا أكثر، فيلزم أن تكون قادرة على تشغيل البناءات على منصّات محدّدة، وتحديد فروع مصدر بديلة من أجل البناء برقع (patches) متنوّعة، وتنفيذ بناءات إضافية بشروط. وهذا مفيد لدعم أنظمة سير عمل أكثر شمولًا، مثل السماح بالإيداعات فقط بعد اجتيازها مجموعة اختبارات التكامل المستمر كاملة، أو اختبار الرقع على مجموعة واسعة من الأنظمة قبل الدمج النهائي. ونظرًا لتنوّع متتبّعات الأخطاء وأنظمة الرقع والأنظمة الخارجية الأخرى المستخدَمة، فقد لا يكون من المنطقي إدراج هذا المنطق داخل نظام التكامل المستمر نفسه.
9.2. البنى المعمارية
اختار Buildbot وCDash معماريتين متقابلتين، ونفّذا مجموعتي مزايا متداخلة لكن متمايزة. وفي ما يلي نفحص مجموعتي المزايا هاتين، ونناقش كيف تكون المزايا أسهل أو أصعب في التنفيذ بحسب اختيار البنية.
9.2.1. نموذج التنفيذ: Buildbot

الشكل 9.2: معمارية Buildbot
يستخدم Buildbot معمارية رئيسي/تابع، بخادم مركزي واحد وعدة خوادم بناء تابعة. والتنفيذ البعيد مُبرمَج بالكامل من الخادم الرئيسي في الزمن الحقيقي: إذ يحدّد إعداد الخادم الرئيسي الأمر الذي ينبغي تنفيذه على كل نظام بعيد، وينفّذه عند انتهاء كل أمر سابق. والتنسيق وطلبات البناء لا تُنسَّق عبر الخادم الرئيسي فحسب بل تُوجَّه بالكامل بواسطة الخادم الرئيسي. ولا يوجد أي تجريد مدمج للوصفات، باستثناء التكامل الأساسي مع أنظمة التحكّم بالإصدارات («شيفرتنا موجودة في هذا المستودع») والتمييز بين الأوامر التي تعمل على مجلد البناء وتلك التي تعمل داخله. أمّا الأوامر الخاصة بنظام التشغيل فتُحدَّد مباشرةً في الإعداد عادةً.
يحافظ Buildbot على اتصال دائم مع كل خادم بناء تابع، ويدير تنفيذ المهام بينها وينسّقه. وإدارة الآلات البعيدة عبر اتصال دائم يضيف تعقيدًا عمليًّا كبيرًا إلى التنفيذ، وكان منذ زمن طويل مصدرًا للأخطاء. فالحفاظ على اتصالات شبكية متينة طويلة الأمد ليس بالأمر البسيط، واختبار التطبيقات التي تتفاعل مع واجهة رسومية محلّية عبر اتصال شبكي مسألة شاقة. أمّا نوافذ التنبيه الخاصة بنظام التشغيل فهي أصعب ما يمكن التعامل معه. غير أن هذا الاتصال الدائم يجعل تنسيق الموارد والجدولة في غاية البساطة، لأن الخوادم التابعة تعود بالكامل إلى تصرّف الخادم الرئيسي في تنفيذ المهام.
يجعل هذا النوع من التحكّم المحكم المصمَّم في نموذج Buildbot تنسيق البناءات المركزي بين الموارد سهلًا جدًا. وينفّذ Buildbot أقفالًا على مستويي الخادم الرئيسي والخادم تابع (master/slave locks) داخل الخادم الرئيسي نفسه، بحيث تستطيع البناءات تنسيق موارد النظام العامة وموارد الآلة المحلّية. وهذا يجعل Buildbot مناسبًا بصفة خاصة للمنشآت الكبيرة التي تشغّل اختبارات تكامل النظام، مثل الاختبارات التي تتفاعل مع قواعد البيانات أو غيرها من الموارد المكلفة.
غير أن الإعداد المركزي يسبّب مشكلات لنموذج الاستخدام الموزّع. إذ يجب السماح صراحةً بكل خادم بناء تابع جديد في إعداد الخادم الرئيسي، ما يجعل استحالةً على خوادم البناء التابعة الجديدة أن ترتبط بالخادم المركزي ديناميكيًّا وتقدّم خدمات البناء أو نتائجه. وإلى جانب ذلك، ولأن كل خادم بناء تابع تقوده الخادم الرئيسي بالكامل، فإن عملاء البناء معرَّضون لإعدادات خبيثة أو خاطئة بالعرض: إذ يتحكّم الخادم الرئيسي في العميل تحكّمًا كاملًا، ضمن قيود الأمان الخاصة بنظام تشغيل العميل.
إحدى القيود المفروضة على Buildbot هي عدم وجود طريقة بسيطة لإرجاع نواتج البناء إلى الخادم المركزي. فمثلًا تبقى إحصاءات تغطية الشيفرة والبناءات الثنائية على خادم البناء التابع البعيد، ولا توجد واجهة برمجية (API) لنقلها إلى الخادم الرئيسي المركزي لتجميعها وتوزيعها. وليس سبب غياب هذه الميزة واضحًا. فقد تكون نتيجةً لمجموعة أوامر التجريد المحدودة المرفقة بـ Buildbot، والتي تركّز على تنفيذ الأوامر البعيدة على خوادم البناء التابعة. أو قد تكون نتيجةً للقرار باستخدام الاتصال بين الخادم الرئيسي والخادم التابع بوصفه نظام تحكّم لا وسيلة RPC.
ومن النتائج الأخرى لنموذج الخادم الرئيسي/التابع ولقناة الاتصال المحدودة هذه أن خوادم البناء التابعة لا تُبلّغ عن استهلاك النظام، ولا يمكن إعداد الخادم الرئيسي ليكون على عِلم بالحمول العالية على الخوادم التابعة.
تُدار الإشعارات الخارجية عن نتائج البناءات بالكامل من جهة الخادم الرئيسي، ويلزم تنفيذ خدمات الإشعار الجديدة داخل الخادم الرئيسي نفسه. وعلى النحو نفسه، يجب تمرير طلبات البناء الجديدة مباشرةً إلى الخادم الرئيسي.
9.2.2. نموذج التنفيذ: CDash

الشكل 9.3: معمارية CDash
على عكس Buildbot، ينفّذ CDash نموذج خادم التقارير. وفي هذا النموذج، يعمل خادم CDash بوصفه مستودعًا مركزيًا للمعلومات عن البناءات المنفَّذة عن بُعد، مع تقارير مرافقة عن إخفاقات البناء والاختبار، وتحليل تغطية الشيفرة، واستهلاك الذاكرة. وتُشغَّل البناءات على عملاء بعيدين وفق جداولها الخاصة، وتُرسل تقارير البناء بصيغة XML. ويمكن إرسال البناءات من عملاء البناء «الرسميين»، ومن مطوّرين أو مستخدمين غير أساسيين يشغّلون عملية البناء المنشورة على أجهزتهم الخاصة.
يصبح هذا النموذج البسيط ممكنًا بفضل التكامل المفاهيمي المحكم بين CDash وبقية عناصر بنية البناء التابعة لـ Kitware: CMake وهو نظام ضبط للبناء، وCTest وهو مُشغّل اختبارات، وCPack وهو نظام تعبئة. وتوفّر هذه البرمجيات آلية تُتيح تنفيذ وصفات البناء والاختبار والتعبئة عند مستوى تجريد مرتفع إلى حدٍّ ما، وبطريقة لا تعتمد على نظام التشغيل.
تبسّط عملية CDash المقادةً من جهة العميل جانبًا كثيرًا من عملية التكامل المستمر في جهة العميل. إذ يتّخذ عملاء البناء قرار تشغيل البناء، بحيث يمكن للعميل أن يراعي ظروفه المحلّية (وقت اليوم، والحمول العالية، إلخ) قبل بدء البناء. ويمكن للعملاء أن يظهروا ويختفيوا حسب رغبتهم، مما يتيح بسهولة البناءات التطوّعية والبناءات «في السحابة». ويمكن إرسال نواتج البناء إلى الخادم المركزي عبر آلية رفع مباشرة.
غير أن CDash، المقابلَ لنموذج التقارير هذا، يفتقر إلى كثير من مزايا Buildbot المريحة. فلا يوجد تنسيق مركزي للموارد، ولا يمكن تنفيذ هذا بسهولة في بيئة موزّعة ذات عملاء غير موثوقين. كما لم تُنفَّذ تقارير التقدّم: إذ يتعيّن على الخادم عندها أن يسمح بالتحديث التزايدي لحالة البناء. وطبيعيًّا، لا توجد طريقة لطلب البناء على المستوى العامّ مع ضمان أن يتولّى العملاء المجهولون تنفيذ ذلك البناء استجابةً لإيداعٍ—يجب اعتبار العملاء غير موثوقين.
أضاف CDash مؤخرًا وظيفة تتيح نظام بناء سحابي بعنوان «@Home»، يقدّم فيه العملاء خدمات البناء إلى خادم CDash. فيستعلم العملاء من الخادم عن طلبات البناء، وينفّذونها عند الطلب، ويعيدون النتائج إلى الخادم. وفي التنفيذ الحالي (أكتوبر 2010)، يجب طلب البناءات يدويًّا من جهة الخادم، ويجب أن يكون العملاء متصلين كي يعرض الخادم خدماتهم. غير أن توسيع هذا إلى نموذج أكثر عمومية للبناء المجدول أمر مباشر، إذ يطلب الخادم البناءات تلقائيًّا كلما توفّر عميل ملائم. والنظام «@Home» مشابه جدًا من حيث المفهوم لنظام Pony-Build الموصوف لاحقًا.
9.2.3. نموذج التنفيذ: Jenkins
Jenkins نظام تكامل مستمر مستخدَم على نطاق واسع، منطوب بلغة Java؛ وكان معروفًا حتى مطلع عام 2011 باسم Hudson. وهو قادر على العمل إمّا بوصفه نظام تكامل مستمر مستقلًّا ينفّذ البناءات على نظام محلّي، أو منسّقًا للبناءات البعيدة، أو حتى متلقّيًا سلبيًّا لمعلومات البناءات البعيدة. وهو يستفيد من معيار JUnit XML في الإبلاغ عن اختبارات الوحدات وتغطية الشيفرة لدمج تقارير من أدوات اختبار متنوّعة. وانطلق Jenkins من Sun، لكنه مستخدَم على نطاق واسع جدًا ويرتبط به مجتمع مفتوح المصدر قويّ.
يعمل Jenkins في وضع هجين، إذ ينفّذ البناءات افتراضيًّا عبر خادم رئيسي، مع السماح بعدّة طرق لتنفيذ البناءات البعيدة، بما فيها البناءات التي يبدأها الخادم وتلك التي يبدأها العميل. غير أنه، مثل Buildbot، صُمِّم أساسًا للتحكّم من جهة خادم مركزي، وإن كان قد عُدِّل ليُعطي دعمًا لمجموعة واسعة من آليات بدء المهام الموزّعة، بما في ذلك إدارة الآلات الافتراضية.
يستطيع Jenkins إدارة عدّة آلات بعيدة عبر اتصال يبدأه الخادم الرئيسي عبر اتصال SSH، أو من جهة العميل عبر JNLP (Java Web Start). وهذا الاتصال ثنائي الاتجاه، ويدعم تبادل الكائنات والبيانات عبر ناقل تسلسلي.
يمتلك Jenkins بنية إضافات (plugins) متينة تجرّد تفاصيل هذا الاتصال، وهو ما أتاح تطوير كثير من إضافات الطرف الثالث لدعم إرجاع البناءات الثنائية وبيانات نتائج أكبر حجمًا.
أمّا المهام التي يتحكّم بها خادم مركزي، فلدى Jenkins إضافة «locks» تثبّط المهام عن العمل على التوازي، وإن كانت، كما في يناير 2011، لم تكتمل تطويرًا بعد.
9.2.4. نموذج التنفيذ: Pony-Build

الشكل 9.4: معمارية Pony-Build
Pony-Build نظام تكامل مستمر لامركزي مكتوبٌ بلغة بايثون أُنجز لإثبات جدوى الفكرة. وهو يتكوّن من ثلاثة مكوّنات جوهرية يوضّحها الشكل 9.4. ويعمل خادم النتائج بوصفه قاعدة بيانات مركزية تضمّ نتائج البناءات الواردة من العملاء الفرديين. أمّا العملاء فيحتوي كلٌّ منهم بصورة مستقلة على كامل معلومات الإعداد وسياق البناء، مدعومًا بمكتبة خفيفة في جهة العميل تساعد على الوصول إلى مستودعات التحكّم بالإصدارات، وإدارة عملية البناء، وتوصيل النتائج إلى الخادم. وخادم التقارير اختياري، ويحتوي واجهة ويب بسيطة، تخدم للإبلاغ عن نتائج البناءات، وقد تخدم أيضًا لطلب بناءات جديدة. وفي تنفيذنا، يعمل خادم التقارير وخادم النتائج في عملية واحدة متعددة الخيوط، لكنهما مترابطان ارتباطًا ضعيفًا على مستوى واجهة البرمجة، ويمكن تعديلهما بسهولة ليعملا بصورة مستقلة.
يُزيَّن هذا النموذج الأساسي بمجموعة متنوّعة من خطّافات الويب (webhooks) وآليات RPC لتسهيل إشعار البناء والتغييرات واستبطان البناء. فمثلًا، بدلًا من ربط إشعار تغيّرات التحكّم بالإصدارات من مستودع الشيفرة مباشرةً بنظام البناء، تُوجَّه طلبات البناء البعيدة إلى نظام التقارير، الذي يوصلها إلى خادم النتائج. وعلى النحو نفسه، بدلًا من بناء الإشعار الفوري بالبناءات الجديدة نحو البريد الإلكتروني والمراسلة الفورية والخدمات الأخرى مباشرةً داخل خادم التقارير، يُدار الإشعار باستخدام بروتوكول الإشعار النشط PubSubHubbub (PuSH). ويتيح ذلك لمجموعة واسعة من التطبيقات المستهلكة تلقي إشعارات الأحداث «المثيرة للاهتمام» (وهي محدودة حاليًا بالبناءات الجديدة والبناءات الفاشلة) عبر خطّاف PuSH.
مزايا هذا النموذج شديد الانفصال كبيرة:
- سهولة التواصل: المكوّنات المعمارية الأساسية وبروتوكولات خطّافات الويب سهلة التنفيذ إلى حدٍّ كبير، ولا تتطلّب سوى معرفة أساسية ببرمجة الويب.
- سهولة التعديل: تنفيذ طرق إشعار جديدة أو واجهة جديدة لخادم التقارير في غاية البساطة.
- دعم لغات متعدّدة: لأن المكوّنات المختلفة تستدعي بعضها عبر خطّافات الويب التي تدعمها معظم لغات البرمجة، يمكن تنفيذ المكوّنات المختلفة بلغات مختلفة.
- قابلية الاختبار: يمكن عزل كل مكوّن تمامًا ومحاكاته، لذا فإن النظام سهل الاختبار إلى حدٍّ كبير.
- سهولة الإعداد: المتطلّبات في جهة العميل ضئيلة، إذ لا يلزم سوى ملف مكتبة واحد إضافةً إلى بايثون نفسها.
- حِمل خادمي أدنى: لأن الخادم المركزي لا يتولّى أي مسؤوليات تحكّم تقريبًا في العملاء، يمكن للعملاء المعزولين أن يعملوا على التوازي دون الاتّصال بالخادم ودون تحميله أي حِمل مقابل، إلا وقت الإبلاغ.
- التكامل مع التحكّم بالإصدارات (VCS): إعداد البناء موجود بالكامل في جهة العميل، ما يسمح بإدراجه ضمن نظام التحكّم بالإصدارات.
- سهولة الوصول إلى النتائج: يمكن كتابة التطبيقات التي ترغب في استهلاك نتائج البناء بأي لغة قادرة على إرسال طلب XML-RPC. ويمكن منح مستخدمي نظام البناء صلاحية الوصول إلى خادمي النتائج والتقارير على مستوى الشبكة، أو عبر واجهة مخصّصة في خادم التقارير. ولا يحتاج عملاء البناء سوى صلاحية إيداع النتائج في خادم النتائج.
وللأسف، هناك أيضًا عيوبٌ خطيرة كثيرة، كما في نموذج CDash:
- صعوبة طلب البناءات: تنشأ هذه الصعوبة من كون عملاء البناء مستقلّين تمامًا عن خادم النتائج. فيمكن للعملاء أن يستطلعوا خادم النتائج لمعرفة ما إذا كان طلب بناء ساريًا، لكن هذا يسبّب حِملًا مرتفعًا وزمن استجابة كبيرًا. وبدلًا من ذلك، يلزم إنشاء اتصالات للأمر والتحكّم تسمح للخادم بإشعار العملاء بطلبات البناء مباشرةً. وهذا يضيف تعقيدًا أكبر إلى النظام ويلغي مزايا عملاء البناء المنفصلين.
- ضعف دعم أقفال الموارد: من السهل توفير آلية RPC للاحتفاظ بأقفال الموارد وتحريرها، لكن فرض سياسات العملاء أصعب بكثير. فبينما تفترض أنظمة التكامل المستمر مثل CDash حسن النيّة في جهة العميل، قد يفشل العملاء من غير قصد وبشكل سيّئ، مثلًا من دون تحرير الأقفال. وتنفيذ نظام أقفال موزّع متين أمر صعب ويضيف تعقيدًا غير مرغوب به إلى حدٍّ كبير. فمثلًا، لكي يوفّر الخادم الرئيسي أقفال موارد مع عملاء غير موثوقين، يجب أن تكون لدى متحكّم أقفال الخادم الرئيسي سياسة تعامل مع العملاء الذين يأخذون قفلًا ولا يحرّرونه أبدًا، سواء بسبب تعطُّلهم أو بسبب وقوع حالة مسدّ (deadlock).
- ضعف دعم المراقبة اللحظية: تنفيذ المراقبة اللحظية للبناء والتحكّم في عملية البناء نفسها مسألة شاقة في نظام بلا اتصال دائم. ومن مزايا Buildbot المهمة على النموذج المقادَ من جهة العميل سهولة الفحص الوسيط للبناءات الطويلة، لأن نتائج البناء تُبلَّغ إلى واجهة الخادم الرئيسي على دفعات متزايدة. وإلى جانب ذلك، ولأن Buildbot يحتفظ باتصال تحكّم، فإن بناءً طويلًا يسوء في منتصفه بسبب خطأ في الإعداد أو إيداع سيّئ يمكن مقاطعته وإيقافه. أمّا إضافة هذه الميزة إلى Pony-Build، الذي لا يملك فيه خادم النتائج وسيلة مضمونة للتواصل مع العملاء، فستتطلّب إما استطلاعًا دائمًا من جانب العملاء، أو إضافة اتصال دائم معهم.
كان من جملة الجوانب الأخرى للتكامل المستمر التي أثارها Pony-Build كيفية تنفيذ الوصفات على أفضل نحو، وكيفية إدارة الثقة. وهذان مسألتان متشابكتان، لأن الوصفات تنفّذ شيفرة عشوائية على عملاء البناء.
9.2.5. وصفات البناء
تضيف وصفات البناء مستوى تجريد مفيدًا، ولا سيما للبرمجيات المبنيّة بلغة عابرة للمنصّات أو باستخدام نظام بناء متعدّد المنصّات. فمثلًا يعتمد CDash على نوع صارم من الوصفات؛ إذ تُبنى معظم البرمجيات التي تستخدم CDash، أو ربما كلّها، باستخدام CMake وCTest وCPack، وقد بُنيت هذه الأدوات للتعامل مع مسألة تعدّد المنصّات. وهذه هي الحالة المثالية من وجهة نظر نظام تكامل مستمر، إذ يمكن لنظام التكامل المستمر أن يفوّض جميع المسائل إلى سلسلة أدوات البناء.
غير أن هذا ليس صحيحًا مع كل اللغات وبيئات البناء. ففي منظومة بايثون، شهد كلٌّ من distutils وdistutils2 تقييسًا متزايدًا في بناء البرمجيات وتعبئتها، لكن لم يظهر حتى الآن معيار لاكتشاف الاختبارات وتشغيلها وتجميع نتائجها. وإلى جانب ذلك، تضيف كثير من حزم بايثون الأكثر تعقيدًا منطق بناء متخصّصًا إلى نظامها، عبر آلية امتداد في distutils تسمح بتنفيذ شيفرة عشوائية. وهذا هو الحال النمطي في معظم سلاسل أدوات البناء: فرغم وجود مجموعة قياسية إلى حدٍّ ما من الأوامر التي ينبغي تشغيلها، هناك دائمًا استثناءات وامتدادات.
لذلك فإن وصفات البناء والاختبار والتعبئة تُشكّل مشكلة، لأنها يجب أن تحل مشكلتين: أولًا، ينبغي أن تُحدَّد بطريقة مستقلة عن المنصّة، بحيث يمكن استخدام وصفة واحدة لبناء البرمجيات على عدّة أنظمة؛ وثانيًا، يجب أن تكون قابلة للتخصيص بحسب البرمجية موضوع البناء.
9.2.6. الثقة
وهذا يثير مسألة ثالثة. فالاستخدام الواسع للوصفات من قِبل نظام التكامل المستمر يقدّم طرفًا ثانيًا يجب أن يثق به النظام: فلا بدّ من أن تكون البرمجيات نفسها جديرة بالثقة (لأن عملاء التكامل المستمر ينفّذون شيفرة عشوائية)، كما يجب أن تكون الوصفات جديرة بالثقة أيضًا (لأنها تحتاج بدورها إلى القدرة على تنفيذ شيفرة عشوائية).
أما مسائل الثقة هذه فسهلة المعالجة في بيئة محكومة ضبطًا محكمة، مثل شركة يكون فيها عملاء البناء ونظام التكامل المستمر جزءًا من عملية داخلية. وفي بيئات التطوير الأخرى، قد رغب أطراف ثالثة مهتمة، مثلًا مشاريع مفتوحة المصدر، في تقديم خدمات بناء. والحل المثالي هو دعم إدراج وصفات بناء قياسية في البرمجيات على مستوى المجتمع، وهو اتجاه يسير عليه مجتمع بايثون مع distutils2. أما الحل البديل فيتمثّل في السماح باستخدام وصفات موقَّعة رقميًّا، بحيث يتمكّن الأفراد الموثوقون من كتابة وتوزيع وصفات موقَّعة، ويستطيع عملاء التكامل المستمر التحقّق مما إذا ينبغي لهم الوثوق بهذه الوصفات.
9.2.7. اختيار النموذج
من خبرتنا، فإن نموذج التكامل المستمر المرتكز على استدعاءات RPC أو استدعاءات خطّافات الويب المرجعية (callback-based) بطريقة ضعيفة الترابط سهل التنفيذ إلى حدٍّ كبير، ما دام نتجاهل أي متطلّبات تنسيق محكم تتضمّن ترابطًا معقّدًا. فالتنفيذ الأساسي لسحب النسخ وبنائها عن بُعد يخضع لقيود التصميم نفسها سواء كان البناء يُقاد محلّيًّا أو عن بُعد؛ وجمع المعلومات عن البناء (النجاح/الفشل، إلخ) تقوده أساسًا متطلّبات جهة العميل؛ وتتبّع المعلومات حسب المعمارية والنتيجة يخضع للمتطلّبات الأساسية نفسها. وعليه، يمكن تنفيذ نظام تكامل مستمر أساسي بسهولة تامة باستخدام نموذج التقارير.
وجدنا النموذج ضعيف الترابط مرنًا وقابلًا للتوسّع إلى حدٍّ كبير كذلك. فإضافة تقارير النتائج الجديدة وآليات الإشعار ووصفات البناء الجديدة سهلة لأن المكوّنات منفصلة بوضوح ومستقلة إلى حدٍّ كبير. ولكل مكوّن منفصل مهام محدَّدة يؤدّيها، كما أنه سهل الاختبار وسهل التعديل.
الجانب الوحيد الصعب في البناءات البعيدة ضمن نموذج ضعيف الترابط شبيه بـ CDash هو تنسيق البناءات: بدء البناءات وإيقافها، والإبلاغ عن البناءات الجارية، وتنسيق أقفال الموارد بين العملاء المختلفين؛ فهو متطلّب تقنيًّا مقارنةً ببقية جوانب التنفيذ.
من السهل أن نصل إلى استنتاج بأن النموذج ضعيف الترابط «أفضل» من كل وجه، لكن هذا صحيح بطبيعة الحال فقط إذا لم تكن هناك حاجة إلى تنسيق البناءات. وهذا القرار ينبغي اتخاذه بناءً على احتياجات المشاريع التي تستخدم نظام التكامل المستمر.
9.3. المستقبل
أثناء التفكير في Pony-Build، توصّلنا إلى بضع مزايا نودّ رؤيتها في أنظمة التكامل المستمر المستقبلية.
- مجموعة وصفات بناء محايدة تجاه اللغة: حاليًا، يعيد كل نظام تكامل مستمر اختراع العجلة بتوفير لغة إعداد بناء خاصة به، وهو أمر ساذج إلى حدٍّ صريح؛ فأنظمة البناء المستخدَمة على نطاق واسع أقل من اثني عشر، ومشغّلو الاختبارات على الأرجح بضع عشرات فحسب. ومع ذلك، يملك كل نظام تكامل مستمر طريقة جديدة ومختلفة لتحديد أوامر البناء والاختبار التي ستُشغَّل. وفي الواقع، يبدو أن هذا أحد أسباب وجود هذا العدد الكبير من أنظمة التكامل المستمر المتشابهة إلى حدٍّ كبير: فكل لغة ومجتمع ينفّذ نظام إعداد خاصًّا به، مضبوطًا على أنظمة البناء والاختبار الخاصة به، ثم يضيف فوق ذلك النظام المجموعة نفسها من المزايا. وبالتيجة، فإن بناء لغة خاصة بمجال محدّد (DSL) قادرة على تمثيل الخيارات المستخدَمة في بضع عشرات من سلاسل أدوات البناء والاختبار الشائعة سيُسهم مساهمة كبرى في تبسيط مشهد التكامل المستمر.
- تنسيقات مشتركة للإبلاغ عن البناء والاختبار: لا يوجد اتفاق يُذكر على تحديد ما هي المعلومات، وبأي تنسيق، التي يحتاج نظام البناء والاختبار إلى توفيرها. ولو أمكن تطوير تنسيق مشترك أو معيار، لكان الأمر سيجعل من الأسهل كثيرًا لأن تعرض أنظمة التكامل المستمر عروضًا تفصيلية وملخّصة معًا عبر البناءات. ويُعدّ بروتوكول Test Anywhere Protocol، أي TAP (من مجتمع Perl)، وتنسيق مخرجات اختبارات JUnit XML (من مجتمع Java) خيارين مثيرين للاهتمام قادرين على ترميز معلومات عن عدد الاختبارات المنفَّذة، والنجاحات والإخفاقات، وتفاصيل تغطية الشيفرة لكل ملف.
- زيادة الدقّة في التقارير وفي الاستبطان: وبشكل بديل، سيكون ملائمًا أن توفّر منصّات البناء المختلفة مجموعة خطّافات موثّقة توثيقًا جيّدًا تمتدّ إلى أنظمة إعدادها وترجمتها واختبارها. وهذا يوفّر واجهة برمجية (API) — بدلًا من تنسيق مشترك — تستطيع أنظمة التكامل المستمر استخدامها لاستخراج معلومات أكثر تفصيلًا عن البناءات.
9.3.1. خاتمة
نفّذت أنظمة التكامل المستمر الموصوفة أعلاه مزايا تتوافق مع بنيتها المعمارية، في حين بدأ نظام Jenkins الهجين بنموذج رئيسي/تابع لكنه أضاف مزايا من بنية التقارير الأكثر ضعفًا في الترابط.
من المغري أن نصل إلى استنتاج أن البنية المعمارية تحدّد الوظيفة. وهذا كلام فارغ بالطبع. بل يبدو أن اختيار البنية المعمارية يوجّه التطوير نحو مجموعة معيّنة من المزايا. ففي حالة Pony-Build، فوجئنا بالمقدار الذي قاد به اختيارنا الأولي لبنية تقارير على نمط CDash قرارات التصميم والتنفيذ اللاحقة. وقد رأست بعض خيارات التنفيذ، مثل تجنّب نظام مركزي للإعداد والجدولة في Pony-Build، حالات الاستخدام لدينا: إذ احتجنا إلى السماح بالارتباط الديناميكي لعملاء البناء البعيدين، وهو أمر يصعب دعمه مع Buildbot. أمّا المزايا الأخرى التي لم ننفّذها، مثل تقارير التقدّم والأقفال المركزي للموارد في Pony-Build، فكانت مرغوبة لكنها ببساطة شديدة التعقيد على أن تُضاف دون متطلّب مُلحّ.
قد ينطبق منطق مماثل على Buildbot وCDash وJenkins. ففي كل حالة هناك مزايا مفيدة غائبة، ربما بسبب عدم التوافق المعماري. لكن من خلال المناقشات مع أعضاء مجتمعي Buildbot وCDash، ومن خلال قراءة موقع Jenkins، يبدو مرجّحًا أن المزايا المرغوبة اختيرت أولًا، ثم طُوِّر النظام باستخدام بنية معمارية سمحت بتنفيذ تلك المزايا بسهولة. فمثلًا، يخدم CDash مجتمعًا صغيرًا نسبيًّا من المطوّرين الأساسيين الذين يبنون برمجياتهم وفق نموذج مركزي. ويعتبرون الأساسي هو إبقاء البرمجيات عاملة على مجموعة أساسية من الآلات، ومن ثمّ يأتي في المرتبة الثانية تلقّي بلاغات الأخطاء من المستخدمين المتمرّسين تقنيًّا. وفي المقابل، يُستخدم Buildbot بصورة متزايدة في بيئات بناء معقّدة فيها عملاء كثيرون يحتاجون إلى تنسيق كي يحصلوا على موارد مشتركة. وتنسيق ملف الإعداد الأكثر مرونة في Buildbot، بخياراته الكثيرة للجدولة وإشعار التغييرات وأقفال الموارد، يلائم هذه الحاجة أفضل من الخيارات الأخرى. وأخيرًا، يبدو أن Jenkins يستهدف سهولة الاستخدام والتكامل المستمر البسيط، مع واجهة رسومية كاملة لضبطه وخيارات إعداد لتشغيله على الخادم المحلّي.
لا يزال علم اجتماع تطوير البرمجيات مفتوح المصدر عاملًا مُربكًا آخر عند ربط البنية المعمارية بالمزايا: فلنفترض أن المطوّرين يختارون مشاريع مفتوحة المصدر بناءً على مدى ملاءمة بنية المشروع ومزايه لحالتهم الاستخدامية؟ إن كان الأمر كذلك، فإن مساهماتهم ستعكس عمومًا توسيعًا لحالة استخدام تتّفق بالفعل مع المشروع. وبذلك قد تنغلق المشاريع ضمن مجموعة معيّنة من المزايا، لأن المساهمين يختارون أنفسهم وقد يتجنّبون المشاريع ذات البنى التي لا تتوافق مع المزايا التي يريدونها. وهذا صحيح بلا شك بالنسبة إلينا حين اخترنا تنفيذ نظام جديد، Pony-Build، بدلًا من المساهمة في Buildbot: فبنية Buildbot ببساطة غير مناسبة لبناء مئات أو آلاف الحزم.
تبنى أنظمة التكامل المستمر القائمة عمومًا حول واحدة من بنيتين مختلفتين إلى حدٍّ كبير، ولا تنفّذ سوى مجموعة جزئية من المزايا المرغوبة. ومع نضج أنظمة التكامل المستمر ونمو مجموعات المستخدمين لها، نتوقّع أن تكتسب مزايا إضافية؛ غير أن تنفيذ هذه المزايا قد تقيده البنية المعمارية الأساس التي اختيرت. وسيكون من المثير للاهتمام رؤية كيف يتطوّر هذا المجال.
9.3.2. شكر وتقدير
نشكر Greg Wilson وBrett Cannon وEric Holscher وJesse Noller وVictoria Laidler على مناقشاتهم المثيرة حول أنظمة التكامل المستمر عمومًا، وحول Pony-Build على وجه الخصوص. وساهم عدد من الطلبة في تطوير Pony-Build، ومنهم Jack Carlson وFatima Cherkaoui وMax Laite وKhushboo Shakya.