تصميم الوحدات المتقدم
تصميم الوحدات المتقدم
وحدات Terraform هي الوحدة الأساسية لإعادة الاستخدام والتجريد في أي قاعدة كود IaC كبيرة. المبتدئ يكتب وحدة تغلّف بعض الموارد ويعتبرها منجزة. أما المهندس المتمرس في شركة كـ Stripe أو Airbnb فيصمم الوحدات كـ APIs — بواجهات مدروسة، وعقود ثابتة، وإشارات ميزات اختيارية، وحدود تركيب محددة. هذا الدرس يعلمك المستوى الثاني.
الوحدة كـ API منشور
كل وحدة تشاركها عبر الفرق يجب أن تُعامَل كمكتبة ذات إصدارات. متغيرات الإدخال هي توقيع الدالة؛ والمخرجات هي قيم الإرجاع؛ وREADME هو التوثيق. يعتمد المستدعون على الواجهة لا على التفاصيل الداخلية. هذا يعني أنك تستطيع إعادة هيكلة داخل الوحدة — استبدال aws_lb بـ aws_alb، تغيير نمط التسمية، إضافة التشفير — دون كسر كل مستهلك، طالما تحافظ على عقود المتغيرات والمخرجات الخارجية.
الانضباط حول هذه الواجهة يهم بشكل هائل على نطاق واسع. في Google، تخضع وحدات Terraform الداخلية المنشورة في السجل الداخلي لنفس قواعد الإصدار الدلالي كأي مكتبة أخرى: التغييرات الجذرية تستلزم رفع الإصدار الرئيسي، والمستدعون يُثبّتون النطاق، والترقية هجرة مقصودة وليست أثراً جانبياً عرضياً لتغيير أحد الزملاء.
التركيب: وحدات تستدعي وحدات
أقوى نمط معماري في Terraform المتقدم هو التركيب — تجميع وحدات صغيرة أحادية الغرض في وحدات أكبر تمثل شريحة قابلة للنشر من البنية التحتية. فكر في الأمر كـ LEGO: وحدة vpc، ووحدة rds، ووحدة ecs-service تبقى كل منها نحيلة ومركزة. وحدة app-stack عالية المستوى تجمعها في وحدة نشر متماسكة. والوحدة الجذرية (دليل بيئتك) تجمع حزمة أو أكثر.
القاعدة الأساسية: كل طبقة تعرف فقط الطبقة التي مباشرة تحتها، لا أعمق من ذلك. الوحدة الجذرية تُنشئ app-stack. الـ app-stack تُنشئ vpc وrds وecs-service. تلك الوحدات الطرفية تدير موارد AWS المباشرة. لا طبقة تمتد إلى شقيقة أو تتخطى مستوى. هذا يجعل نطاق التأثير ضيقاً والإعادة الهيكلية آمنة.
تصميم الواجهة: المتغيرات
واجهة متغيرات الوحدة هي المكان الذي تقع فيه معظم أخطاء التصميم. اتبع هذه القواعد لبناء واجهات تصمد مع الزمن.
استخدم objects للتكوين المتجمع، لا متغيراً مسطحاً لكل حقل. بدلاً من خمسة عشر متغيراً منفصلاً مثل var.enable_deletion_protection وvar.backup_retention_days وغيرها، جمّع الإعدادات المترابطة منطقياً في كائن منظوم. هذا يبقي الوحدة المستدعية نظيفة ويسمح لك بإضافة حقول اختيارية جديدة دون تغيير كل موقع استدعاء قائم.
صرّح بأنواع صريحة. متغير من نوع string مقابل object({ ... }) يوثق نفسه ويكتشف الأخطاء في وقت التخطيط بدلاً من تمرير قيم خاطئة بصمت. استخدم optional() داخل أنواع الكائنات (متاح منذ Terraform 1.3) لتحديد الحقول التي لها قيم افتراضية.
الميزات الاختيارية عبر إشارات الميزات
الوحدات الحقيقية تحتاج إلى خدمة حالات استخدام متعددة دون أن تصبح وحدة مختلفة لكل حالة. النمط الاحترافي هو تقييد الموارد الفرعية الاختيارية خلف متغيرات منطقية أو كائنية. عندما تكون الإشارة في قيمتها الصفرية (false أو null)، يكون عدد الموارد صفراً — لا وجود لها. عند التمكين، تُنشأ. وسيطا count وfor_each في Terraform يجعلان هذا ممكناً.
null على false للكائنات الاختيارية. القيمة المنطقية enable_alarm = false لا تزال تجبر المستدعي على توفير جميع حقول تكوين التنبيه (ARN الموضوع، العتبة). أما alarm_config = null الافتراضية فتعني أن المستدعي يوفر صفراً من الحقول إلا إذا اختار الاشتراك. هذا يجعل استدعاء الوحدة أنظف بكثير في الحالة الشائعة.
المخرجات: العقد مع المستدعين
المخرجات ليست لاحقة بالفكر. إنها الواجهة التي من خلالها تستهلك الوحدات الأم والوحدات الجذرية نتائج وحدتك. صدّر كل ما قد يحتاجه المستدعي بشكل معقول: معرّفات الموارد، ARNs، أسماء DNS، معرّفات مجموعات الأمان. لا تكشف تفاصيل التنفيذ الداخلية (مثل locals الوسيطة أو الأسماء المحسوبة التي قد تتغير). ضع علامة على المخرجات الحساسة بـ sensitive = true لكي تُحجب القيم من مخرج التخطيط والسجلات.
الأنماط المضادة التي يجب إزالتها
تظهر هذه الأنماط باستمرار في قواعد كود Terraform في الشركات التي نمت بسرعة دون حوكمة. تعلم كيف تتعرف عليها وتصلحها.
- وحدة "الإله": وحدة واحدة تُوفر كل شيء — الشبكات، والحوسبة، وقاعدة البيانات، ونظام أسماء النطاقات، وإدارة الهوية والوصول — لبيئة كاملة. لديها أكثر من 200 متغير إدخال، تستغرق 45 دقيقة للتخطيط، ومن المستحيل تغييرها بأمان. الحل: التحليل إلى وحدات طرفية ذات مسؤولية واحدة تجمعها جذر نحيل.
- القيم المُضمَّنة داخل الوحدات: وحدة تفترض
us-east-1، أو معرف AMI محدد، أو معرف حساب محدد. تعمل مع صاحبها وتفشل مع كل فريق آخر. الحل: اجعل القيمة متغيراً؛ يوفر المستدعي السياق. - تمرير تكوينات الموفر الكاملة إلى الوحدات: وحدة تأخذ
var.aws_access_keyوتضبط موفرها الخاص. هذا يكسر تسمية الموفر بالاسم المستعار، ويجعل أنماط assume-role مستحيلة، ويكسر نموذج توريث الموفر القياسي في Terraform. الحل: يجب ألا تضبط الوحدات الموفرين أبداً — هذه مهمة الوحدة الجذرية. - اعتبار
terraform.tfvarsالواجهة الوحيدة: ملفات.tfvarsمسطحة ضخمة بمئات المتغيرات السائبة دون تطبيق للنوع. الحل: متغيرات كائنية منظومة مع كتل validation.
count على وحدة تُنشئ موارد متعددة مميزة إذا كان الترتيب مهماً. إذا استخدمت count = length(var.envs) على وحدة وأضفت لاحقاً بيئة جديدة في الموضع 0، سيخطط Terraform لتدمير وإعادة إنشاء كل شيء من الفهرس 0 فصاعداً. استخدم for_each مع خريطة أو مجموعة من السلاسل بدلاً من ذلك — الموارد مفهرسة بمفتاح الخريطة لا بالموضع، لذا إضافة مفتاح جديد تُنشئ فقط المورد الجديد.
تجميع الأمور معاً: مثال استدعاء
إليك كيف تُنشئ وحدة جذرية مثيلاً من وحدة rds المصممة أعلاه — نظيف، صريح، وسهل المراجعة في طلب السحب:
ثبّت على وسم Git محدد (لا على فرع) في مصادر وحدات الإنتاج. مرجع الفرع يعني أن تغيير زميل غير مراجَع يمكن أن يغير بصمت ما يوفره terraform apply التالي. الوسوم ثابتة؛ الفروع ليست كذلك.