إدارة التكوين عبر بيئات متعددة دون صداع
تطبيقك يعمل في بيئات التطوير (dev) والاختبار (staging) والإنتاج (production). في بيئة التطوير، تحتاج إلى قاعدة بيانات محلية ببيانات اختبار. في بيئة الاختبار، تتصل بنسخة طبق الأصل من الإنتاج ولكن بمفاتيح API مختلفة. في الإنتاج، كل شيء يستخدم بنية تحتية حقيقية وبيانات اعتماد لا يعرفها إلا عدد قليل من الأشخاص.
النهج الواضح هو إنشاء ملفات تكوين منفصلة لكل بيئة. يحصل التطوير على config.dev.yaml، والاختبار على config.staging.yaml، والإنتاج على config.prod.yaml. يحتوي كل ملف على التكوين الكامل لتلك البيئة.
هذا يعمل حتى تحتاج إلى إضافة معامل جديد. تقوم بتحديث ملف التطوير، ثم الاختبار، ثم الإنتاج. إذا نسيت أحدها، فإن تلك البيئة تتعطل بصمت. إذا كان لديك خمس بيئات، فإنك تقوم بتحديث خمسة ملفات. في كل مرة. يصبح التكرار مصدراً للأخطاء، وليس حلاً.
المشكلة الحقيقية: معظم التكوين متطابق
إليك ما لا يقوله أحد بصوت عالٍ: معظم التكوين الخاص بك متطابق عبر البيئات. أسماء الجداول، هياكل البيانات، عناوين URL الداخلية، قيم المهلة (timeout)، منطق إعادة المحاولة، والمعاملات الفنية نادراً ما تتغير بين التطوير والإنتاج. ما يختلف فعلياً هو حفنة من القيم: أسماء المضيفين (hostnames)، المنافذ (ports)، أسماء المستخدمين، كلمات المرور، ومفاتيح API.
عندما تخزن ملفات تكوين كاملة لكل بيئة، فإنك تكرر الـ 90% التي تبقى كما هي فقط لتجاوز الـ 10% التي تتغير. كل تغيير هيكلي يتطلب لمس كل ملف. تحديث واحد مفقود، وستحصل على بيئة تتصرف بشكل مختلف أو تفشل تماماً.
نهج أنظف: قالب وتراكب
بدلاً من تكرار كل شيء، قسّم التكوين الخاص بك إلى طبقتين: قالب وتراكبات خاصة بالبيئة.
الـ قالب يحتوي على هيكل التكوين الكامل مع قيم افتراضية أو عناصر نائبة (placeholders). هذا هو المصدر الوحيد للحقيقة لما يتوقعه تطبيقك. تقوم بتغييره مرة واحدة عند إضافة معامل جديد، وكل بيئة ترث هذا التغيير.
الـ تراكب يحتوي فقط على القيم التي تختلف لبيئة معينة. هذه ملفات صغيرة، سهلة المراجعة، ويصعب إفسادها.
إليك ما يبدو عليه هذا عملياً.
يظهر الرسم البياني التالي كيف يجمع القالب والتراكبات لإنتاج التكوين النهائي لكل بيئة.
قد يحتوي ملف القالب الخاص بك على:
database.host = {{DB_HOST}}
database.port = 5432
database.name = myapp
database.timeout = 30
database.pool.size = 10
تراكب التطوير الخاص بك:
DB_HOST = localhost
تراكب الاختبار الخاص بك:
DB_HOST = staging.db.internal
DB_POOL_SIZE = 20
تراكب الإنتاج الخاص بك:
DB_HOST = prod.db.internal
DB_USER = prod_admin
DB_PASSWORD = {{VAULT_REF}}
أثناء النشر، يقرأ النظام القالب، ويطبق التراكب للبيئة المستهدفة، وينتج التكوين النهائي. القيم غير الموجودة في التراكب تحتفظ بالقيم الافتراضية من القالب. القيم الحساسة مثل كلمات المرور تأتي من خزنة (vault) أو مدير أسرار (secret manager)، وليس من ملف التراكب نفسه.
لماذا يعمل هذا بشكل أفضل
مكان واحد لتغيير الهيكل. عند إضافة معامل جديد، تقوم بتحديث القالب مرة واحدة. كل بيئة تلتقطه تلقائياً. لا مزيد من البحث في خمسة ملفات لإجراء نفس التعديل.
مساحة سطح أصغر للأخطاء. ملفات التراكب تحتوي فقط على القيم التي تتغير. الخطأ المطبعي في اسم مضيف يسهل اكتشافه في ملف من ثلاثة أسطر مقارنة بدفنه في ملف تكوين من 200 سطر.
التحكم في الوصول بشكل طبيعي. تراكب الإنتاج يحتوي فقط على قيم خاصة بالبيئة، لكن هذه القيم تتضمن بيانات اعتماد وأسماء مضيفين داخلية. يمكنك تخزين التراكب في مستودع بوصول مقيد. يمكن للمطورين العمل مع القالب وتراكب التطوير دون رؤية بيانات اعتماد الإنتاج أبداً. أعضاء الفريق الجدد يبدأون البرمجة باستخدام تراكب التطوير فقط وبدون الوصول إلى أسرار الإنتاج.
فروقات أنظف في التحكم بالإصدارات. عندما تراجع طلب سحب (pull request) يغير قالباً، فإنك تعلم أن التغيير يؤثر على جميع البيئات. عندما تراجع تغييراً في تراكب، فإنك تعلم أنه يؤثر فقط على بيئة واحدة. الهدف من التغيير واضح من الملف الذي تنظر إليه.
خطوة أبعد: التسلسل الهرمي للبيئات
تأخذ بعض الفرق هذا النمط أبعد من ذلك باستخدام تراكبات متعددة الطبقات. بدلاً من تراكب واحد لكل بيئة، يقومون بتجميع طبقات متعددة:
- تراكب عام بقيم تنطبق في كل مكان.
- تراكب إقليمي لإعدادات خاصة بمركز البيانات.
- تراكب محلي لمثيل واحد.
يقوم النظام بدمج هذه الطبقات بالترتيب. القيم من الطبقات الأكثر تحديداً تتجاوز القيم من الطبقات الأكثر عمومية. هذا مفيد عندما يعمل تطبيقك عبر مناطق متعددة بتكوين متطابق في الغالب ولكن مع اختلافات إقليمية صغيرة مثل خوادم DNS، إعدادات المنطقة الزمنية، أو علامات الامتثال التنظيمي.
على سبيل المثال، قد يحدد تراكب عام timeout = 30. تراكب منطقة آسيا يتجاوزه إلى timeout = 45 بسبب زمن الوصول الأعلى. تراكب مثيل طوكيو يحدد timezone = Asia/Tokyo. التكوين النهائي لمثيل طوكيو يجمع الطبقات الثلاث، مع أخذ القيم الخاصة بطوكيو الأولوية.
تحذير بخصوص التراكبات
التراكبات ليست بديلاً عن التحقق من الصحة. التكوين المدمج النهائي لا يزال بحاجة إلى فحص المخطط (schema) قبل استخدامه. الخطأ المطبعي في قيمة تراكب يصبح مرئياً فقط بعد الدمج. إذا كتب شخص ما DB_HOST = db.prod,internal بدلاً من db.prod.internal، فإن القالب سينتج بسعادة تكويناً معطلاً. تحقق من صحة النتيجة المدمجة، وليس فقط الملفات الفردية.
قائمة تحقق عملية
قبل اعتماد هذا النمط، تحقق من هذه النقاط:
- هل يمكن لأدوات النشر الخاصة بك دمج ملفات القالب والتراكب؟ معظم أدوات إدارة التكوين تدعم هذا أصلاً. إذا لم يكن الأمر كذلك، فإن سكريبت بسيط يستبدل العناصر النائبة يعمل بشكل جيد.
- هل يتم تخزين التراكبات الخاصة بك مع ضوابط الوصول المناسبة؟ تراكبات الإنتاج تحتاج إلى وصول مقيد. تراكبات التطوير يمكن أن تكون عامة.
- هل لديك التحقق من صحة التكوين المدمج؟ أضف فحص مخطط أو خطوة تشغيل تجريبي (dry-run) في خط أنابيبك (pipeline) تلتقط الأخطاء قبل وصولها إلى البيئة.
- هل القالب هو المصدر الوحيد للحقيقة؟ إذا كان بإمكان شخص ما تجاوز القالب وتغيير قيمة مباشرة في تراكب، فإن النمط ينهار. فرض ذلك في عملية المراجعة أو الأتمتة.
الخلاصة
توقف عن تكرار ملفات التكوين لكل بيئة. افصل ما يبقى كما هو عما يتغير. احتفظ بالهيكل المشترك في قالب واحد والقيم الخاصة بالبيئة في ملفات تراكب صغيرة. ستكون عمليات النشر الخاصة بك أكثر قابلية للتنبؤ، ومراجعاتك ستكون أسرع، وستتوقف عن كسر بيئة الاختبار لأنك نسيت تحديث ملف واحد من أصل خمسة.