Terraform المتقدم وأنماط البنية ككود

هيكلة Terraform على نطاق واسع

18 دقيقة الدرس 1 من 28

هيكلة Terraform على نطاق واسع

في شركة ناشئة صغيرة، يمكنك الاكتفاء بملف main.tf واحد وملف حالة (state) مشترك. لكن حين يصل عدد المهندسين إلى 50 موزعين على 10 فرق منتجات تدير ستة حسابات AWS، ينهار هذا النهج في غضون أسابيع: تعارض في قفل الحالة، وأعطال بنطاق تدمير واسع، وحوادث مناوبة ناجمة عن فريق خاطئ يلمس الموارد الخاطئة. تُعلّمك هذه الدرس تخطيط المستودع، وفصل البيئات، واستراتيجية الحالة المتعددة الطبقات التي تعتمدها كبرى شركات التقنية للحفاظ على سلامة تغييرات البنية التحتية وإمكانية مراجعتها واستقلالية الفرق فيها.

القيد الجوهري: نطاق التدمير

كل قرار هيكلي في Terraform على نطاق واسع ينبثق من سؤال واحد: إذا حدث خطأ في هذا terraform apply، ما أقصى ضرر ممكن؟ وحدة جذرية (root module) أحادية تدير الشبكات وصلاحيات IAM وقواعد البيانات وKubernetes في ملف حالة واحد يمكنها تعطيل الإنتاج بخطأ واحد في count. العلاج هو عزل الحالة — تقسيم البنية التحتية إلى طبقات، حيث كل طبقة وحدة جذرية منفصلة بـ backend خاص بها.

الحالة = حد نطاق التدمير. أي شيء داخل ملف حالة واحد يمكن حذفه عن طريق الخطأ بأمر terraform apply واحد. صمّم طبقاتك بحيث يؤثر فقدان أي ملف حالة على نطاق منطقي واحد فقط، مثل موارد حوسبة تطبيق واحد، لا VPC كامل.

نموذج الطبقات الثلاث

يقسّم النمط المعياري الصناعي البنية التحتية إلى ثلاث طبقات تُطبَّق من الأسفل للأعلى. كل طبقة تقرأ فقط مخرجات الطبقات الأدنى منها — لا أفقياً ولا للأعلى.

Three-Layer Terraform State Model Layer 1 — Foundation (accounts, VPCs, DNS, IAM org roles) Managed by Platform/Infra team · Changed rarely · Blast radius: entire AWS account Layer 2 — Platform (EKS/RDS clusters, secrets manager, shared ALB) Managed by Platform team · Changed monthly · Blast radius: all apps in cluster Layer 3 — Application (per-service compute, queues, app DBs) Managed by product teams · Changed daily · Blast radius: one service reads outputs reads outputs
نموذج الطبقات الثلاث في Terraform: Foundation ثم Platform ثم Application، كل منها بحالة خاصة ونطاق تدمير مستقل.

تكشف الطبقات الأدنى عن مخرجاتها — معرّفات VPC، معرّفات الشبكات الفرعية، نقاط نهاية الكلاسترات — عبر terraform_remote_state أو (الأفضل على النطاق الواسع) نمط مخزن المعاملات / SSM، بحيث لا تحتاج فرق التطبيقات إلى الوصول لملف حالة الطبقة الأساسية أبداً.

Monorepo مقابل Polyrepo

كلا النموذجين يعمل. القرار تنظيمي لا تقني.

  • Monorepo — كل Terraform في مستودع واحد، مجلدات لكل طبقة وبيئة. ممتاز لسهولة الاكتشاف والتغييرات الذرية عبر الطبقات. يتطلب قواعد CODEOWNERS قوية ومشغّلات CI لكل مسار لمنع تغيير في layer1/ من تشغيل خطة لكل وحدة تطبيقية.
  • Polyrepo — كل طبقة (أو فريق) يمتلك مستودعه الخاص. حدود أمنية طبيعية: فرق المنتجات حرفياً لا ترى HCL الطبقة الأساسية. يصعب تتبع التبعيات عبر الطبقات. شائع في الشركات الكبيرة التي تمتلك ملكية أمنية وامتثالية منفصلة للبنية التحتية الأساسية.
ابدأ بـ monorepo. التقسيم لاحقاً أسهل بكثير من الدمج. معظم شركات المرحلة المتقدمة تعتمد monorepo للبنية التحتية وتضيف أتمتة CODEOWNERS لفرض حدود الفرق.

تخطيط Monorepo القياسي

التخطيط أدناه محكم إنتاجياً عبر مئات بيئات AWS. كل مسار مقصود:

infra/ ├── modules/ # وحدات داخلية قابلة لإعادة الاستخدام (ليست root modules) │ ├── vpc/ │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ └── README.md │ ├── eks-cluster/ │ └── rds-postgres/ │ ├── layer1-foundation/ # Root module — واحدة لكل حساب AWS │ ├── production/ │ │ ├── main.tf # تستدعي modules/vpc, modules/org-iam │ │ ├── backend.tf # S3 key: "layer1/production/terraform.tfstate" │ │ ├── terraform.tfvars │ │ └── outputs.tf │ └── staging/ │ ├── main.tf │ ├── backend.tf # S3 key: "layer1/staging/terraform.tfstate" │ └── terraform.tfvars │ ├── layer2-platform/ # Root module — لكل بيئة │ ├── production/ │ │ ├── eks.tf │ │ ├── rds.tf │ │ ├── backend.tf # S3 key: "layer2/production/terraform.tfstate" │ │ └── data.tf # data "terraform_remote_state" "foundation" { ... } │ └── staging/ │ └── layer3-apps/ # Root module — لكل خدمة في كل بيئة ├── payments-api/ │ ├── production/ │ │ ├── main.tf │ │ ├── backend.tf # S3 key: "layer3/payments-api/production/terraform.tfstate" │ │ └── variables.tf │ └── staging/ └── notifications-svc/ ├── production/ └── staging/

استراتيجيات فصل البيئات

ثمة ثلاثة مناهج لفصل البيئات في Terraform. فهم المقايضات يمنع عمليات ترحيل مكلفة لاحقاً.

  1. مجلد لكل بيئة (كما هو موضح أعلاه) — كل بيئة وحدة جذرية منفصلة في مجلدها بـ backend.tf و.tfvars الخاصين بها. هذا الأسلوب الأكثر أماناً وصراحة. لا يمكنك تطبيق إعداد staging على الإنتاج عن طريق الخطأ. التكلفة: بعض التكرار في HCL تحدّه الوحدات المشتركة.
  2. Workspaces — وحدة جذرية واحدة، مساحات عمل متعددة، ملف حالة واحد لكل مساحة. تناسب البيئات الصغيرة المتطابقة فعلاً (dev/test). تنهار حين تتباين البيئات: أحجام instances مختلفة، شبكات فرعية مختلفة، نطاقات DNS مختلفة. تجنّب استخدامها للإنتاج/staging على نطاق واسع.
  3. حسابات منفصلة — المعيار الذهبي وفق AWS Well-Architected للصناعات المنظّمة. الإنتاج والـ staging والـ sandbox وأدوات الأمان تعيش في حسابات AWS منفصلة مرتبطة تحت AWS Organizations. كل حساب له وحدة layer1 جذرية خاصة به.
لا تشارك ملفات الحالة أبداً بين البيئات. أمر terraform destroy مُشغَّل ضد staging مع ملف حالة مشترك أفضى إلى حذف قواعد بيانات RDS للإنتاج في شركات متعددة. عزل الحالة غير قابل للتفاوض. مفتاح backend واحد = بيئة واحدة.

إعداد الـ Backend على نطاق واسع

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

# layer3-apps/payments-api/production/backend.tf terraform { backend "s3" { bucket = "acme-terraform-state-prod" key = "layer3/payments-api/production/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "acme-terraform-locks" kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123" } } # قراءة مخرجات الطبقة الأساسية دون منح هذا الفريق صلاحية الوصول لملف حالتها data "aws_ssm_parameter" "vpc_id" { name = "/infra/production/layer1/vpc_id" } data "aws_ssm_parameter" "private_subnets" { name = "/infra/production/layer1/private_subnet_ids" }

يتفوق نمط SSM على terraform_remote_state للاستهلاك عبر الفرق لأنه يفصل الوصول لملف الحالة عن استهلاك القيمة. فريق الأساسيات يكتب المخرجات في SSM؛ فرق التطبيقات تقرأ من SSM. لا تحتاج فرق التطبيقات لأي صلاحيات IAM على bucket S3 الخاص بالأساسيات، ويمكن لفريق الأساسيات إعادة هيكلة بنيته الداخلية دون تغيير عقود مفاتيح SSM.

CODEOWNERS وتشغيل CI لكل مسار

لا تفرض بنية المجلدات حدود الفرق إلا إذا نفّذها نظام CI/CD أيضاً. الإعداد الجاهز للإنتاج يجمع:

  • .github/CODEOWNERSlayer1/ يتطلب موافقة @infra-platform؛ layer3-apps/payments-api/ يتطلب @team-payments.
  • مشغّلات CI لكل مسار — GitHub Actions on.push.paths أو Atlantis حتى تُشغَّل terraform plan فقط للوحدات الجذرية المتأثرة في كل PR.
  • قواعد الفرع المحمي — لا دفع مباشر لـ main؛ نتيجة الخطة تُنشر تعليقاً على PR؛ terraform apply يعمل فقط بعد الدمج على مُشغِّل CI، لا من جهاز كمبيوتر مطوّر في الإنتاج أبداً.
لا تشغّل terraform apply من جهاز كمبيوتر مطوّر ضد الإنتاج أبداً. يجب أن تحتفظ مُشغّلات CI ببيانات اعتماد الإنتاج؛ المطورون يمتلكون فقط أدوار القراءة التي تسمح بـ terraform plan. هذه السياسة الواحدة تمنع أكثر فئات حوادث الإنتاج الناجمة عن الخطأ البشري شيوعاً.

أنماط الفشل الشائعة

في هذه المرحلة ترتكب الفرق عادةً ثلاثة أخطاء هيكلية:

  • الوحدة الإلهية (God module) — وحدة واحدة تنشئ كل شيء. يأخذ استدعاء module.app 80 مدخلاً ويدير 400 مورد. إعادة هيكلته في منتصف الطريق مشروع جراحة حالة يمتد لأسابيع. تفكيك مبكراً أساسي.
  • معرّفات الحسابات المُضمَّنة في الوحدات المشتركة — يجب ألا تشير الوحدات المشتركة أبداً لمعرّفات حسابات محددة أو نصوص مناطق. مرّرها كمتغيرات. وحدة تحتوي على 123456789012 مُضمَّن يستحيل إعادة استخدامها عبر الحسابات.
  • غياب قفل الحالة — مهندسان يشغّلان terraform apply في آنٍ واحد، يكتب الثاني فوق حالة الأول، وتفقد تتبع الموارد التي يعرفها Terraform. دائماً أعدّ جدول DynamoDB للقفل. تكلفته لا تُذكر وتمنع تلف الحالة الكارثي.

التخطيط والانضباط المُرسَيان هنا هما الأساس الذي يقوم عليه باقي هذا البرنامج التعليمي: مساحات العمل، والوحدات المتقدمة، والاختبارات، وTerragrunt — كلها تفترض أنك تمتلك بالفعل فصلاً نظيفاً للطبقات وحالة معزولة لكل بيئة.