تطبيق الاثني عشر عاملاً
تطبيق الاثني عشر عاملاً
في عام 2012، صاغ مهندسو Heroku تجارب سنوات من تشغيل تطبيقات SaaS واسعة النطاق في وثيقة مرجعية: تطبيق الاثني عشر عاملاً. تُعرّف هذه المنهجية اثنتي عشرة ممارسة تُنتج معًا برمجيات قابلة للنقل والتوسع، ومتوقعة السلوك في بيئة الإنتاج. كل عامل يُجيب في جوهره على سؤال واحد: كيف نجعل هذه الخدمة آمنةً للتشغيل وسهلةَ الفهم في الساعة الثالثة صباحًا أثناء حادثة طارئة؟
يتناول هذا الدرس كل عامل بشكل تفصيلي، ويشرح المنطق التشغيلي وراءه، ويوضح كيف تبدو المطابقة وعدم المطابقة في الأنظمة الحقيقية. مقاييس DORA من الدرس الخامس تقيس النتائج؛ أما الاثنا عشر عاملاً فهم الانضباط الهندسي الذي يجعل تحقيق تلك النتائج ممكنًا.
العامل الأول — قاعدة الشيفرة: مستودع واحد، عمليات نشر متعددة
يُتتبَّع تطبيق الاثني عشر عاملاً في مستودع واحد للتحكم في الإصدارات. تُنشر قاعدة الشيفرة الواحدة هذه في بيئات متعددة — development وstaging وproduction — من نفس الـ commit. وجود قواعد شيفرة متعددة يعني نظامًا موزعًا لا تطبيقًا واحدًا. المكتبات المشتركة تنتمي إلى مدير الحزم، لا إلى كل خدمة على حدة.
نمط الفشل الشائع: الفرق التي تحتفظ بـ"فرع إنتاج" منفصل مع إصلاحات عاجلة لا تُدمج أبدًا تتراكم لديها فجوات. في غضون أسابيع، لا أحد يعرف ما يعمل فعلًا في الإنتاج.
العامل الثاني — التبعيات: أعلن عنها صراحةً وعزلها
لا تعتمد قط على الحزم المثبتة على مستوى النظام. أعلن عن جميع التبعيات في ملف بيان (package.json، requirements.txt، go.mod، Gemfile) واستخدم آلية عزل (venv، node_modules، Go modules، Bundler) حتى يُنتج استنساخ المستودع الجديد مع أمر تثبيت واحد بيئةً كاملةً قابلةً للتشغيل.
curl | bash لأدوات النظام داخل وقت تشغيل تطبيقك. إذا كان Dockerfile الخاص بك يُشغّل apt-get install curl ثم يُنزّل مثبتًا، فلديك تبعية ضمنية غير مُعدَّلة الإصدار ستتعطل بصمت عند تغيير المثبت المصدر.العامل الثالث — الإعدادات: خزنها في البيئة
كل ما يتغير بين عمليات النشر — عناوين URL لقواعد البيانات ومفاتيح API وأعلام الميزات ومستويات السجلات — يجب أن يعيش في متغيرات البيئة، لا في الشيفرة أو ملفات الإعداد المُودَعة في المستودع. اختبار المعيار: هل يمكنك الآن فتح الشيفرة المصدرية للعالم دون كشف أي سر؟ إذا كانت الإجابة لا، فقد تسرّبت الإعدادات إلى الشيفرة.
العامل الرابع — الخدمات المساندة: تعامل معها كموارد مرفقة
قواعد البيانات والطوابير والذواكر المؤقتة وخوادم البريد — تعامل مع الجميع كـموارد مرفقة يُصل إليها عبر URL في البيئة. MySQL المحلية وRDS المستضاف متبادلان من منظور التطبيق. التبديل من Redis محلي إلى ElastiCache لا يتطلب سوى تغيير متغير بيئة، لا تغيير شيفرة.
العامل الخامس — البناء والإصدار والتشغيل: فصل صارم
حوّل قاعدة الشيفرة إلى نشر جارٍ عبر ثلاث مراحل منفصلة وغير قابلة للعكس:
- البناء (Build): الترجمة وجلب التبعيات وإنتاج نتيجة (صورة Docker، JAR، ثنائي).
- الإصدار (Release): دمج نتيجة البناء مع إعدادات النشر. كل إصدار يحصل على معرف ثابت لا يتغير.
- التشغيل (Run): تشغيل العمليات من الإصدار في بيئة التنفيذ.
الشيفرة لا تتغير في وقت التشغيل أبدًا. نمط "النشر ثم تعديل الإعداد على الخادم" ينتهك هذا العامل ويجعل الرجوع إلى إصدار سابق أمرًا يستعصي على الفهم.
العامل السادس — العمليات: نفّذها كعمليات عديمة الحالة لا تتشارك شيئًا
عمليات التطبيق عديمة الحالة ولا تتشارك شيئًا. أي بيانات يجب الاحتفاظ بها تعيش في خدمة مساندة — قاعدة البيانات أو ذاكرة التخزين المؤقت. حالة الجلسة في الذاكرة والرفع إلى نظام الملفات المحلي والذاكرات المؤقتة داخل العملية كلها تنتهك هذا العامل. عندما يوجّه موازن الحمل الطلب التالي إلى نسخة عملية مختلفة، تُفقد كل الحالة اللزجة.
التطبيق الفعلي: الجلسات اللاصقة نمط مضاد. الملفات المرفوعة إلى /tmp على pod واحد لن تكون مرئية لـpod آخر. انقل الجلسات إلى Redis أو DynamoDB والرفوعات إلى تخزين الكائنات (S3، GCS) من اليوم الأول.
العامل السابع — ربط المنافذ: صدّر الخدمات عبر ربط المنافذ
التطبيق مكتفٍ بذاته ويُصدّر HTTP (أو أي بروتوكول) بربط منفذ. لا يعتمد على حقن وقت تشغيل (مثل Apache mod_php). تطبيق Node يُشغّل خادم HTTP الخاص به؛ تطبيق Python يُشغّل Gunicorn داخليًا. هذا يجعل التطبيق قابلًا للتركيب بسهولة: يصبح خدمةً مساندةً لأي تطبيق آخر.
العامل الثامن — التزامن: وسّع أفقيًا عبر نموذج العمليات
وسّع التطبيق أفقيًا بتشغيل المزيد من العمليات، لا بضخ الموارد في عملية مونوليثية واحدة. قسّم العمل إلى أنواع عمليات مُسمّاة — web (يخدم HTTP)، worker (يعالج المهام المُدرجة)، clock (المهام المجدولة). Kubernetes Deployment replicas تُطبّق هذا العامل مباشرةً. كل نوع عملية يتوسع بشكل مستقل.
العامل التاسع — التخلص السريع: بدء سريع وإيقاف آمن
يجب أن تبدأ العمليات في ثوانٍ وتتوقف بأمان عند استقبال SIGTERM. تُنهي عملية الويب طلبها الحالي ثم تخرج. تُعيد عملية العامل مهمتها الحالية إلى الطابور قبل الخروج. هذا يُمكّن التوسع السريع والنشر والتعافي من الأعطال.
العامل العاشر — تماثل البيئات: ابقِ البيئات متشابهة قدر الإمكان
الفجوة بين بيئة التطوير والإنتاج هي السبب الجذري لأخطاء "يعمل على جهازي". ثلاثة أبعاد للتماثل: الوقت (انشر بكثرة لتقليص التأخر)، الأفراد (المطورون الذين يكتبون الشيفرة ينشرونها ويراقبونها)، الأدوات (نفس محرك قاعدة البيانات، نفس الذاكرة المؤقتة، نفس الطابور في التطوير والإنتاج). استخدام SQLite في التطوير وPostgreSQL في الإنتاج انتهاك كلاسيكي — فروق طفيفة في لهجة SQL تُسبب أخطاءً لا تظهر إلا في الإنتاج.
العامل الحادي عشر — السجلات: تعامل معها كتدفقات أحداث
لا يهتم التطبيق بتوجيه إخراجه أو تخزينه. يكتب ببساطة إلى stdout، دون تخزين مؤقت. تلتقط بيئة التنفيذ هذا التدفق وتوجهه إلى وجهته — مجمعات السجلات (Datadog، Splunk، Loki) أو التخزين طويل الأمد أو الطرفية أثناء التطوير. لا تفتح ملفات السجلات مباشرةً، ولا تدير تدوير السجلات داخل التطبيق.
العامل الثاني عشر — مهام الإدارة: شغّلها كعمليات منفردة
ترحيلات قواعد البيانات وإصلاحات البيانات الفردية وفحوصات وحدة التحكم — شغّلها كعمليات منفردة في نفس بيئة عمليات التطبيق العادية، مستخدمًا نفس نتيجة البناء وإعداداتها. يجب أن تكون مُودَعة في المستودع، لا منفَّذة كجلسات SSH عشوائية.
تطبيق الاثني عشر عاملاً عمليًا: قائمة تشخيص
عند التعامل مع خدمة جديدة، يمر المهندسون في الشركات الكبرى على هذه الأسئلة بسرعة:
- هل يمكنني استنساخ التطبيق وتشغيله بدون خطوات يدوية سوى
docker compose up؟ (I، II) - هل يمكنني النشر في الاختبار والإنتاج بتغيير متغير بيئة فقط؟ (III، IV)
- هل كل إصدار ثابت ومُوسوم؟ هل يمكنني الرجوع للخلف بأمر واحد؟ (V)
- هل سينجو التطبيق من موت أي عملية وإعادة تشغيلها على مضيف مختلف؟ (VI، IX)
- هل تذهب السجلات إلى
stdoutوتلتقطها المنصة؟ (XI) - هل مهام الإدارة سكريبتات قابلة للتكرار في التحكم بالإصدارات؟ (XII)