مشروع التخرج: منصة إنتاج بمستوى الشركات الكبرى

طبقة البنية التحتية كرمز

18 دقيقة الدرس 3 من 30

طبقة البنية التحتية كرمز

تُعدّ طبقة البنية التحتية كرمز (IaC) هيكل العمود الفقري للمنصة بأكملها. كل شيء آخر — Kubernetes وخطوط أنابيب CI/CD وعوامل المراقبة وأدوات الأمان — يعتمد على ما يُوفّره Terraform وكيفية هيكلته. إذا نُفِّذت هذه الطبقة بشكل سيئ، تتحوّل IaC إلى أبطأ جزء وأخطره في سلسلة التسليم: تعارض أقفال الحالة يُعيق الفرق، وحوادث الأثر التدميري الواسع تُوقف الإنتاج، والانجراف بين البيئات يُنتج أخطاءً يستحيل إعادة إنتاجها. إذا نُفِّذت بشكل صحيح، تصبح غير مرئية — يُقدّم المهندسون طلبات سحب، تُطبّقها خطوط الأنابيب بأمان، وتُصلح المنصة نفسها.

تغطي هذه الدرس ثلاثة أشياء تمتلكها كل فرقة منصة في شركات التكنولوجيا الكبرى: تخطيط المستودع الذي يتوسّع لما يزيد عن 50 مهندساً، واستراتيجية الحالة التي تجعل الأثر التدميري قيداً تصميمياً من الدرجة الأولى، وكتالوج الوحدات الداخلية الذي يمنع كل فريق من إعادة اختراع مجموعة عقد EKS ذاتها.

تخطيط المستودع: نهج Monorepo

في Netflix وAirbnb وStripe، تعيش البنية التحتية في مستودع أحادي واحد مع تطبيق صارم لقواعد CODEOWNERS. يُقدّم نهج polyrepo حدوداً أمنية أحدّ (لا يستطيع مهندسو المنتج حتى قراءة HCL للطبقة الأساسية) لكنه يفقد القدرة على تتبّع التبعيات عبر الطبقات ومراجعة التغييرات الذرية. في هذا المشروع الختامي نستخدم الـ monorepo. شجرة الدلائل أدناه هي التخطيط القياسي — كل مسار يعكس قراراً متعمداً:

infra/ ├── modules/ # وحدات قابلة لإعادة الاستخدام — لا تُطبَّق مباشرةً أبداً │ ├── eks-nodegroup/ # مجموعة عقد محسوبة: IMDSv2، تسميات Karpenter، قالب الإطلاق │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ └── README.md │ ├── rds-postgres/ # RDS مع التشفير ومجموعة المعاملات وسياسة اللقطة النهائية │ ├── s3-secure/ # S3 + سياسة الدلو + تسجيل الوصول مُفعَّل │ ├── iam-role-workload/ # دور جاهز لـ IRSA + قالب سياسة الثقة │ ├── vpc/ # VPC ثلاثية الطبقات: عامة / خاصة / داخلية │ └── alb-ingress/ # ALB + ربط WAFv2 + دلو سجلات الوصول │ ├── environments/ # وحدات جذرية — واحدة لكل حساب-بيئة │ ├── global/ # موارد تمتد عبر جميع المناطق (Route53، سياسات IAM) │ │ ├── main.tf │ │ ├── backend.tf │ │ └── outputs.tf │ ├── prod/ │ │ ├── 01-foundation/ # VPC، الشبكات الفرعية، ربط TGW، قواعد محلل DNS │ │ ├── 02-platform/ # مستوى تحكم EKS، مجموعات RDS، ElastiCache، ALBs │ │ └── 03-workloads/ # أدوار IAM لكل خدمة، دلاء S3، طوابير SQS │ ├── staging/ │ │ ├── 01-foundation/ │ │ ├── 02-platform/ │ │ └── 03-workloads/ │ └── dev/ │ ├── 01-foundation/ │ ├── 02-platform/ │ └── 03-workloads/ │ ├── policy/ # سياسات OPA / Conftest — تُطبَّق في CI قبل plan │ ├── required_tags.rego │ ├── no_public_s3.rego │ └── approved_regions.rego │ └── .github/ ├── CODEOWNERS # فريق المنصة يمتلك modules/ وجميع مسارات */01-foundation/ └── workflows/ ├── terraform-plan.yml └── terraform-apply.yml

دلائل الطبقات المرقّمة (01-foundation و02-platform و03-workloads) تُشفّر ترتيب التبعية. قاعدة CI ترفض أي طلب سحب يحاول مراجعة مخرجات باتجاه أعلى — يمكن لـ workloads قراءة مخرجات platform، لكن يجب ألا تعتمد platform أبداً على ملف حالة workload.

CODEOWNERS هو ضابط أمني، ليس مجرد مجاملة. يجب أن تتطلّب دليل modules/ وجميع مسارات 01-foundation/ موافقة فريق المنصة. لا ينبغي لمهندس منتج أن يتمكن من دمج تغيير على CIDR الـ VPC أو إصدار مستوى تحكم EKS دون مراجعة. يُنفّذ GitHub CODEOWNERS مع حماية الفروع و"مراجعات مطلوبة من أصحاب الكود" هذا الأمر تلقائياً.

استراتيجية الحالة: العزل كقيد من الدرجة الأولى

الحالة هي حدّ الأثر التدميري. يمكن لأمر terraform apply واحد تدمير ما يعيش في ملف حالته فحسب — لا شيء آخر. الانقسام الثلاثي الطبقات في شجرة الدلائل يُقابله مباشرةً ثلاثة ملفات حالة منفصلة لكل بيئة. الطبقة الأولى (foundation) تُلمَس مرة كل ربع سنة؛ الطبقة الثالثة (workloads) تُلمَس عشرات المرات يومياً. يجب ألا تتشارك قفلاً أبداً.

Three-Layer State Isolation with Remote State Outputs Layer 1 — Foundation VPC · Subnets · TGW · Route53 · IAM org roles | State: s3://tfstate/prod/foundation/terraform.tfstate Blast radius: entire AWS account network | Changed: quarterly Layer 2 — Platform EKS · RDS · ElastiCache · ALB | State: s3://tfstate/prod/platform/terraform.tfstate Blast radius: all app workloads in cluster | Changed: monthly Layer 3 — Workloads IRSA roles · SQS · app S3 | State: s3://tfstate/prod/workloads/payments/terraform.tfstate Blast radius: one service | Changed: daily by product teams reads remote_state outputs reads remote_state outputs
عزل حالة Terraform الثلاثي الطبقات: لكل طبقة ملف حالتها وقفلها ونطاق أثرها التدميري الخاص؛ تقرأ الطبقات المخرجات من الطبقة الأدنى عبر الحالة البعيدة.

ملف backend.tf الخاص بكل طبقة يتبع نمطاً متطابقاً. دلو S3 ذاته يُوفَّر بواسطة طبقة foundation في حساب الأمان، مع التحقق من الإصدار، والتشفير من جهة الخادم (SSE-KMS)، وحذف MFA. يُوفّر DynamoDB جدول قفل الحالة:

# environments/prod/02-platform/backend.tf terraform { backend "s3" { bucket = "company-tfstate-prod" key = "prod/platform/terraform.tfstate" region = "us-east-1" encrypt = true kms_key_id = "arn:aws:kms:us-east-1:111122223333:key/mrk-abc123" dynamodb_table = "company-tfstate-locks" # افترض هذا الدور في حساب prod — دور OIDC لخط الأنابيب لديه sts:AssumeRole عليه role_arn = "arn:aws:iam::444455556666:role/terraform-backend-access" } required_providers { aws = { source = "hashicorp/aws" version = "~> 5.50" } } required_version = ">= 1.8" }
لا تستخدم terraform_remote_state عبر حدود الثقة. إذا استطاع فريق المنتج A قراءة ملف حالة foundation مباشرةً، يمكنه قراءة كل مخرج حساس — بيانات اعتماد RDS الرئيسية، معرّفات الشبكات الفرعية الخاصة، أسماء موارد KMS. النمط المعتمد هو نشر القيم غير الحساسة عبر الطبقات إلى SSM Parameter Store كجزء من تطبيق الطبقة الأدنى، وجعل الطبقات العليا تقرأها عبر مصادر بيانات aws_ssm_parameter.

لمشاركة المخرجات عبر الطبقات، يبدو النمط هكذا — تكتب طبقة platform وتقرأ طبقات workloads:

# في 02-platform/outputs_to_ssm.tf — تكتب platform مخرجاتها إلى SSM resource "aws_ssm_parameter" "eks_cluster_name" { name = "/infra/prod/platform/eks_cluster_name" type = "String" value = module.eks.cluster_name tier = "Standard" } resource "aws_ssm_parameter" "eks_cluster_endpoint" { name = "/infra/prod/platform/eks_cluster_endpoint" type = "SecureString" # نقطة النهاية حساسة — احتفظ بها مشفرة value = module.eks.cluster_endpoint key_id = var.kms_ssm_key_arn } # ----------------------------------------------------------- # في 03-workloads/payments/data.tf — يقرأ فريق المنتج ما يحتاجه فقط data "aws_ssm_parameter" "eks_cluster_name" { name = "/infra/prod/platform/eks_cluster_name" } # تستخدم وحدة workload القيمة data.aws_ssm_parameter.eks_cluster_name.value # لا يملك أبداً وصولاً لملف حالة platform الخام

كتالوج الوحدات الداخلية

في أي شركة بها أكثر من ثلاثة مهندسين للمنصة، تُنتج كتابة الوحدات العشوائية تكاثراً لمجموعات عقد EKS غير متوافقة خفياً، ومثيلات RDS بلا لقطات نهائية، ودلاء S3 مع تمكين الوصول العام بصمت. يحل كتالوج الوحدات هذا بجعل الشيء الصحيح هو الشيء السهل: يكتب مهندس المنتج module "payments_db" ويحصل على التشفير، ومجموعات المعاملات، وحماية الحذف، وتنبيهات CloudWatch مجاناً.

للوحدة الداخلية الاحترافية ثلاث خصائص تميّزها عن الغلاف السريع:

  • إعدادات افتراضية محكومة تُشفّر السياسة. التشفير مُفعَّل افتراضياً ولا يمكن تعطيله عبر متغير. حماية الحذف في RDS تأخذ القيمة الافتراضية true وتتطلّب تغيير allow_major_version_upgrade = false صراحةً. الأمان خروج، لا دخول — وحتى عند ذلك، تحجب بوابات سياسات Conftest التكوينات غير الآمنة في CI قبل الموافقة على أي plan.
  • مُصدَّرة بإصدارات ومتتبَّعة في سجل التغييرات. تعيش الوحدات تحت modules/ في الـ monorepo. لكل وحدة ملف CHANGELOG.md. يُشير المستهلكون إلى وسم git: source = "git::https://github.com/company/infra//modules/rds-postgres?ref=v3.2.1". تُعلَن رفعات الإصدار الرئيسية في النشرة الداخلية للمنصة؛ تُهاجر الفرق في جدولها الخاص، لكن فريق المنصة يُعلن انتهاء صلاحية الإصدارات القديمة بتاريخ إيقاف صارم.
  • المخرجات تغطي 100% مما يحتاجه المستهلكون. وحدة لا تُخرج ARN الخاص بها تُجبر المستهلكين على استخدام مصادر البيانات وتُنشئ اقتراناً ضمنياً بين ترتيب التطبيق وملف الحالة. يجب أن يكون لكل مورد تُوفّره الوحدة مخرج مقابل له.

فيما يلي واجهة وحدة rds-postgres الداخلية المُجرَّدة لكن الممثِّلة للإنتاج. الوحدة الكاملة تمتد لنحو 180 سطراً من HCL؛ الواجهة هي ما يهم:

# modules/rds-postgres/variables.tf (الواجهة — تُنفِّذ الوحدة الكاملة الباقي) variable "identifier" { type = string } variable "engine_version" { type = string; default = "16.2" } variable "instance_class" { type = string; default = "db.t4g.medium" } variable "allocated_storage" { type = number; default = 100 } variable "db_name" { type = string } variable "subnet_ids" { type = list(string) } variable "vpc_security_group_ids" { type = list(string) } # مُنفَّذة بالسياسة — لا يوجد متغير لتعطيل هذه # - storage_encrypted = true (مفتاح KMS يُنشأ تلقائياً إلا إذا قُدّم kms_key_id) # - deletion_protection = true (يتطلّب متغير بيئة خط الأنابيب للتجاوز في staging/dev) # - backup_retention_period = 7 (prod)، 1 (غير prod، مُستنتج من var.environment) # - enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"] # - iam_database_authentication_enabled = true # - performance_insights_enabled = true variable "environment" { type = string; validation { condition = contains(["prod","staging","dev"], var.environment); error_message = "يجب أن تكون prod أو staging أو dev." } } variable "tags" { type = map(string); default = {} }
اعتمد Semver بدقة لوحداتك. الإصدار الرقعي (v3.2.1 → v3.2.2) يجب أن يكون متوافقاً للخلف — إضافة متغير اختياري بقيمة افتراضية هو تغيير رقعي. الإصدار الثانوي يُضيف موارد جديدة. الإصدار الرئيسي يكسر الواجهة أو يزيل موارد. عند دمج رفعة رئيسية، افتح قضية تتبّع تسرد كل بيئة لا تزال على الإصدار القديم، وحدّد موعد انتهاء صلاحية 60 يوماً.

تُنشئ استراتيجية الحالة وكتالوج الوحدات معاً حلقة فضيلة: لأن كل وحدة جذرية صغيرة (طبقة واحدة، بيئة واحدة)، تكون الخطط سريعة (أقل من 30 ثانية لوحدة workload)، وتعمل سياسات Conftest على JSON الخطة قبل أن يوافق أي إنسان. تنكشف دلو S3 المُهيَّأة بشكل خاطئ أو العلامة المطلوبة المفقودة بواسطة conftest test --policy policy/ plan.json في خط أنابيب طلب السحب، لا في وقت التطبيق، وبالتأكيد لا في الإنتاج.

مع استقرار هذا الأساس IaC، تُوفّر الدرس التالي منصة Kubernetes فوقه — تكوين مجموعة EKS وإدارة الإضافات عبر Helm وTerraform، ومُوسِّع العقد Karpenter الذي يجعل طبقة الحوسبة مرنة على نطاق فائق.