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

مشروع: منصة Terraform متعددة البيئات

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

مشروع: منصة Terraform متعددة البيئات

يجمع هذا الدرس الختامي كل الأنماط التي تعلمناها في هذا الفصل في منصة مرجعية واحدة جاهزة للإنتاج. ستصمم تخطيط المستودع، وتربط الحالة البعيدة بحواجز ترقية بين البيئات، وتكتب مكتبة وحدات قابلة لإعادة الاستخدام، وتوصّل خط أنابيب CI يشغّل fmt وvalidate وplan وapply المحمية بموافقة — تلقائيًا في بيئة dev، ويدويًا في staging وproduction. في نهاية هذا الدرس ستمتلك مخططًا يمكنك نسخه وتشغيله في أي شركة.

تخطيط المستودع

تعيش كل البنية التحتية في مستودع واحد. الأدوات (Terragrunt، وملف Makefile بسيط، ومكتبة وحدات مشتركة) تبقيها DRY. الفصل الرئيسي هو البيئة أولًا، ثم الطبقة ثانيًا — وهو عكس التخطيط البسيط الذي يضع الوحدات أولًا، والذي يميل إلى تسريب مراجع الحالة بين البيئات.

infra/ ├── modules/ # كتل بناء قابلة لإعادة الاستخدام ومُعوَّنة │ ├── vpc/ │ ├── eks/ │ ├── rds/ │ └── iam-role/ ├── live/ # جذور البيئات (Terragrunt) │ ├── terragrunt.hcl # الإعداد الجذري: حاوية الحالة، إعدادات المزود │ ├── dev/ │ │ ├── env.hcl # env = "dev", aws_account_id = "111122223333" │ │ ├── network/ │ │ │ └── terragrunt.hcl │ │ ├── eks/ │ │ │ └── terragrunt.hcl │ │ └── rds/ │ │ └── terragrunt.hcl │ ├── staging/ │ │ └── ... (نفس الشكل) │ └── prod/ │ └── ... (نفس الشكل) └── .github/ └── workflows/ ├── plan.yml # PR: خطط لكل المجموعات المتغيرة └── apply.yml # الدمج إلى main: طبّق dev؛ أوقف staging+prod

يركّز ملف terragrunt.hcl الجذري إعدادات خلفية S3 وجدول قفل DynamoDB بحيث لا تحدد أي مجموعة اسم الحاوية مباشرة. تُعيَّن البيئات إلى حسابات AWS منفصلة — وليس إلى workspaces — لأن حدود IAM على مستوى الحساب هي الضمان الوحيد لحدود التأثير الذي ينجو من بيانات اعتماد مُسرَّبة.

إعداد Terragrunt الجذري

يقرأ الملف الجذري ملف env.hcl من كل مجلد بيئة، ويحقن معرّف الحساب، ويولّد مفتاح حالة فريد لكل مجموعة. كل ملف terragrunt.hcl فرعي يرث هذا عبر find_in_parent_folders().

# live/terragrunt.hcl locals { env_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) env = local.env_vars.locals.env account_id = local.env_vars.locals.aws_account_id region = "us-east-1" } remote_state { backend = "s3" generate = { path = "backend.tf" if_exists = "overwrite_terragrunt" } config = { bucket = "acme-terraform-state-${local.account_id}" key = "${local.env}/${path_relative_to_include()}/terraform.tfstate" region = local.region encrypt = true dynamodb_table = "acme-terraform-locks" } } generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOF provider "aws" { region = "${local.region}" assume_role { role_arn = "arn:aws:iam::${local.account_id}:role/TerraformCIRole" } default_tags { tags = { Environment = "${local.env}" ManagedBy = "terraform" } } } EOF }
لا تستخدم Terraform workspaces لفصل البيئات في الإنتاج. تتشارك Workspaces جذرًا واحدًا في حاوية الخلفية وإعداد مزود واحد. مرجع terraform.workspace المُعطَّأ يطبّق كود dev على حالة prod بصمت. النموذج الآمن الوحيد قابل للتدقيق هو حسابات AWS منفصلة مع أدوار IAM منفصلة.

معمارية متعددة البيئات

Multi-Environment Terraform Platform Architecture GitHub Repo infra/ monorepo plan.yml / apply.yml GitHub Actions Plan (PR) Apply (merge) AWS: Dev Account Auto-apply on merge network eks + rds AWS: Staging Manual approval gate network eks + rds AWS: Production 2-person approval gate network eks + rds S3 state (dev account) S3 state (staging account) S3 state (prod account)
خط أنابيب الترقية بين ثلاثة حسابات: dev يُطبَّق تلقائيًا؛ staging وproduction يتطلبان حوابز موافقة بشرية.

خط أنابيب CI/CD

يُشغَّل سير عمل plan.yml عند كل طلب سحب (PR) ويشغّل terragrunt run-all plan مقتصرًا على المجموعات التي تغيّرت ملفاتها فقط، باستخدام تصفية مسارات git diff. يُشغَّل سير عمل apply.yml عند الدمج في الفرع main. يُطبَّق dev فورًا؛ أما staging وprod فلكل منهما بيئة GitHub Actions مع مراجعين مطلوبين مُعيَّنين في إعدادات المستودع — لا يلمس Terraform تلك الحسابات أبدًا بدون موافقة إنسان مُسمَّى على مخرجات الخطة.

# .github/workflows/apply.yml name: Terraform Apply on: push: branches: [main] paths: ['live/**'] env: TG_VERSION: "0.55.1" TF_VERSION: "1.8.5" jobs: apply-dev: name: Apply — Dev (auto) runs-on: ubuntu-latest permissions: id-token: write # OIDC — لا مفاتيح AWS ثابتة في الأسرار contents: read steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::111122223333:role/TerraformCIRole aws-region: us-east-1 - uses: hashicorp/setup-terraform@v3 with: { terraform_version: "${{ env.TF_VERSION }}" } - name: Install Terragrunt run: | curl -sL https://github.com/gruntwork-io/terragrunt/releases/download/v${{ env.TG_VERSION }}/terragrunt_linux_amd64 \ -o /usr/local/bin/terragrunt && chmod +x /usr/local/bin/terragrunt - name: Apply dev working-directory: live/dev run: terragrunt run-all apply --terragrunt-non-interactive -auto-approve apply-staging: name: Apply — Staging (gated) needs: apply-dev runs-on: ubuntu-latest environment: staging # يتطلب مراجعًا في إعدادات المستودع permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::444455556666:role/TerraformCIRole aws-region: us-east-1 - uses: hashicorp/setup-terraform@v3 with: { terraform_version: "${{ env.TF_VERSION }}" } - name: Install Terragrunt run: | curl -sL https://github.com/gruntwork-io/terragrunt/releases/download/v${{ env.TG_VERSION }}/terragrunt_linux_amd64 \ -o /usr/local/bin/terragrunt && chmod +x /usr/local/bin/terragrunt - name: Apply staging working-directory: live/staging run: terragrunt run-all apply --terragrunt-non-interactive -auto-approve apply-prod: name: Apply — Production (2-person gated) needs: apply-staging runs-on: ubuntu-latest environment: production # يتطلب مراجعَيْن + قواعد حماية permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::777788889999:role/TerraformCIRole aws-region: us-east-1 - uses: hashicorp/setup-terraform@v3 with: { terraform_version: "${{ env.TF_VERSION }}" } - name: Install Terragrunt run: | curl -sL https://github.com/gruntwork-io/terragrunt/releases/download/v${{ env.TG_VERSION }}/terragrunt_linux_amd64 \ -o /usr/local/bin/terragrunt && chmod +x /usr/local/bin/terragrunt - name: Apply production working-directory: live/prod run: terragrunt run-all apply --terragrunt-non-interactive -auto-approve
استخدم OIDC بدلًا من مفاتيح الوصول الثابتة. صلاحية id-token: write مع configure-aws-credentials وrole-to-assume تعني أن GitHub Actions يتولّى دور IAM عبر رموز قصيرة الأجل. لا شيء في Secrets يحتاج إلى تدوير أو يمكن تسريبه. كل مزودي السحابة الرئيسيين يدعمون تفويض OIDC مع GitHub Actions.

المجموعة الفرعية: طبقة EKS (مثال dev)

كل ملف terragrunt.hcl فرعي صغير — يحدد فقط مصدر الوحدة وإصدارها المُثبَّت والمدخلات الخاصة بالبيئة. تتدفق البيانات عبر الطبقات من خلال كتل dependency التي تستدعي terraform output على الحالة البعيدة لمجموعة الشبكة، محافظةً على فصل الطبقات بدون معرّفات موارد مُرمَّزة بشكل ثابت.

# live/dev/eks/terragrunt.hcl include "root" { path = find_in_parent_folders() } locals { env_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) env = local.env_vars.locals.env } dependency "network" { config_path = "../network" mock_outputs = { vpc_id = "vpc-mock" private_subnets = ["subnet-mock-a", "subnet-mock-b"] } mock_outputs_allowed_terraform_commands = ["validate", "plan"] } terraform { source = "git::https://github.com/acme/infra.git//modules/eks?ref=v1.4.2" } inputs = { env = local.env cluster_name = "acme-${local.env}" vpc_id = dependency.network.outputs.vpc_id private_subnets = dependency.network.outputs.private_subnets node_instance_type = local.env == "prod" ? "m6i.2xlarge" : "t3.medium" desired_nodes = local.env == "prod" ? 6 : 2 }

إصدار الوحدات وترقيتها

تُثبَّت الوحدات بوسم Git (?ref=v1.4.2). سير عمل الترقية هو: تحديث الوسم في dev → تشغيل plan → تطبيق dev → مراجعة PR → تحديث وسم staging → تطبيق staging → توقيع شخصَيْن → تحديث وسم prod → تطبيق prod. هذا يعني أن dev يشغّل دائمًا أحدث إصدار من الوحدة، وتليه staging خلال ساعات، وتليها production خلال أيام — مع تحقق بشري من فروق الخطة عند كل حاجز.

لا تُثبّت مصدر الوحدة على فرع أو على ?ref=main. إذا تقدّم الفرع بينما الخطة تنتظر الموافقة، فإن التطبيق ينفّذ كودًا مختلفًا عما أظهرته الخطة. دائمًا ثبّت على وسم ثابت أو SHA لا يتغير. فرّض ذلك بسياسة Conftest OPA ترفض أي source لا يطابق ?ref=v*.

كشف الانحراف كوظيفة مجدولة

التغييرات اليدوية أو عبر Console تُباعد production عن الحالة المُعلَنة بصمت. أضف سير عمل كشف انحراف ليلي يشغّل terragrunt run-all plan --detailed-exitcode على جميع مجموعات prod ويرسل تنبيه Slack عند إعادة كود الخروج 2 (تغييرات مكتشفة). هذا هو الغراء التشغيلي الذي يجعل GitOps للبنية التحتية قابلًا للتطبيق فعليًا.

# .github/workflows/drift.yml (مقتطف) on: schedule: - cron: '0 6 * * *' # 06:00 UTC يوميًا jobs: drift-check: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - name: Drift check — prod id: plan working-directory: live/prod run: | terragrunt run-all plan --terragrunt-non-interactive \ --detailed-exitcode 2>&1 | tee plan.out echo "exit_code=$?" >> $GITHUB_OUTPUT continue-on-error: true - name: Alert on drift if: steps.plan.outputs.exit_code == '2' run: | curl -s -X POST ${{ secrets.SLACK_WEBHOOK }} \ -H "Content-Type: application/json" \ -d '{"text":"*تم كشف انحراف* في بنية الإنتاج. راجع مخرجات الخطة في GitHub Actions."}'

ما الذي بنيته

بتجميع هذا كله، أصبح لديك منصة حيث: كل تغيير بنية تحتية هو PR؛ حدود التأثير لأي تطبيق واحد محدودة بعزل الطبقات؛ البيئات تُعيَّن لحسابات AWS منفصلة بأدوار IAM منفصلة؛ OIDC يلغي بيانات الاعتماد الثابتة من CI؛ إصدارات الوحدات ثابتة ومُرقَّاة بشكل صريح؛ السياسة ككود ترفض الأنماط السيئة قبل أن تصل إلى plan؛ والانحراف يظهر تلقائيًا لا بصمت. هذا هو خط الأساس التشغيلي الذي تتوقعه المنظمات الهندسية الناضجة من مهندس DevOps كبير من أول يوم عمل.

ترتيب البوت العملي لمنصة جديدة: (1) أنشئ ثلاثة حسابات AWS ضمن AWS Organization. (2) أنشئ حاوية حالة S3 وجدول قفل DynamoDB في كل حساب بسكريبت bootstrap. (3) أعدّ موفّري هوية OIDC في كل حساب. (4) ادفع تخطيط المستودع وهيّئ GitHub Environments مع المراجعين. (5) طبّق طبقة الشبكة في dev أولًا — فهي الأساس الذي يقرأ منه كل شيء آخر. (6) أضف طبقتَي eks وrds فوقها. (7) رقّ إلى staging ثم prod. يستغرق التهيئة الكاملة مع مهندس خبير يومين تقريبًا؛ التغييرات التدريجية بعد ذلك آمنة وقابلة للتدقيق إلى أجل غير مسمى.