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

الانجراف والاستيراد والبنية التحتية البرمجية في البيئات الحالية

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

الانجراف والاستيراد والبنية التحتية البرمجية في البيئات الحالية

من أكثر الحقائق إزعاجاً في هندسة البنية التحتية أن السحابة لا تنتظر Terraform. يعالج المهندسون الأعطال في الساعة الثانية صباحاً عبر واجهة المستخدم، وتُصلح فرق الأمان قواعد مجموعات الأمان مباشرةً عبر AWS CLI، وتُنشئ أحداث التوسع التلقائي موارد لم يعلم بها Terraform قط. بمرور الوقت، يتسع الفجوة بين ما يظن Terraform أنه موجود وما هو موجود فعلاً — المعروفة بـانجراف الإعداد — بصمت حتى تتسبب في حادثة إنتاجية. تُعلّمك هذه الدرس كيف تكشف المؤسسات الكبرى الانجراف مبكراً، وكيف تستورد البنية التحتية الحالية (brownfield) إلى تحكم Terraform، وكيف تُدير حملة تبني brownfield كاملة دون تعطيل الإنتاج.

ما هو الانجراف؟

الانجراف هو أي اختلاف بين الحالة المطلوبة المسجّلة في ملف حالة Terraform والحالة الفعلية في موفر السحابة. يقع في ثلاث فئات:

  • انجراف الخصائص — المورد موجود في الحالة وفي السحابة لكن تم تغيير إحدى خصائصه خارج Terraform: شخص ما رفّع نوع EC2 instance من t3.medium إلى t3.large عبر الواجهة الرسومية.
  • انجراف المورد المفقود — ملف حالة Terraform يقول بوجود مورد لكنه حُذف خارج Terraform (حذف عرضي عبر واجهة المستخدم، أو استبداله بعملية أخرى).
  • انجراف الموارد غير المُدارة — مورد موجود في السحابة لم يره Terraform قط: حاوية S3 أُنشئت يدوياً، RDS قديم، مجموعة أمان من حقبة ما قبل IaC.
الانجراف مشكلة صحة وصواب، ليس مجرد مسألة جمالية. قاعدة مجموعة أمان مُنجرفة قد تكون الشيء الوحيد الذي يسمح لعوامل المراقبة بالوصول للإنتاج. عند تطبيق Terraform التالي، سيُعيد تلك القاعدة للوراء صامتاً — ويتوقف لوح المراقبة عن العمل. لهذا يجب أن يكون كشف الانجراف آلياً، لا يدوياً.

كشف الانجراف: الأدوات

يوفر Terraform آليتين أصليتين لكشف الانجراف. الأولى هي terraform plan -refresh-only، التي تُجري تحديثاً كاملاً لـ API الموفر لكل مورد في الحالة وتُنشئ خطة تُظهر فقط ما تغيّر في الواقع — دون اقتراح أي تغييرات في الإعداد.

# خطة refresh-only — عملية قراءة آمنة بالكامل terraform plan -refresh-only -out=drift.tfplan # مراجعة تقرير الانجراف terraform show drift.tfplan # تطبيق اختياري لتحديث الحالة لتطابق الواقع # (لا يُغيّر البنية التحتية الفعلية — يُحدّث ملف الحالة فقط) terraform apply drift.tfplan

الآلية الثانية هي terraform plan -detailed-exitcode، التي تخرج بالكود 2 إذا كانت هناك تغييرات، و0 إذا لم تكن، و1 عند الأخطاء. هذا كود الخروج هو نقطة التعليق للأتمتة.

Automated Drift Detection Pipeline Cron / Scheduler Every 6 hours CI Runner plan -refresh-only Exit 0 No drift — done Exit 2 Drift detected Alert + PR Slack + GitHub Issue triggers no change drift found
خط أنابيب كشف الانجراف الآلي: مهمة CI مجدولة تُشغّل خطط refresh-only كل 6 ساعات وتُطلق تنبيهات عند اكتشاف انجراف.

أتمتة كشف الانجراف في CI

النمط الإنتاجي هو مهمة CI مجدولة — لا يدوية. تعمل كل 6 ساعات، تغطي كل وحدة جذرية، وتُنبّه فريق المناوبة عند اكتشاف انجراف. في GitHub Actions:

# .github/workflows/drift-detection.yml name: Drift Detection on: schedule: - cron: '0 */6 * * *' workflow_dispatch: jobs: detect-drift: runs-on: ubuntu-latest strategy: matrix: environment: [production, staging] layer: [layer1-foundation, layer2-platform] steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/terraform-read-only aws-region: us-east-1 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: 1.8.x - name: Terraform Init working-directory: ${{ matrix.layer }}/${{ matrix.environment }} run: terraform init -input=false - name: Detect Drift id: drift working-directory: ${{ matrix.layer }}/${{ matrix.environment }} run: | set +e terraform plan -refresh-only -detailed-exitcode \ -out=drift.tfplan -no-color 2>&1 | tee drift-output.txt EXIT_CODE=$? echo "exit_code=${EXIT_CODE}" >> $GITHUB_OUTPUT exit 0 - name: Post Drift Alert if: steps.drift.outputs.exit_code == '2' uses: slackapi/slack-github-action@v1 with: payload: | {"text": ":rotating_light: Drift in ${{ matrix.layer }}/${{ matrix.environment }}"} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DRIFT_WEBHOOK }}
استخدم دور IAM للقراءة فقط في كشف الانجراف. مهمة الكشف تحتاج فقط لقراءة API الموفر — امنحها دوراً بـ ReadOnlyAccess وصلاحية قراءة backend S3. هذا يحدّ من نطاق الضرر إذا تُعرّضت المهمة المجدولة للاختراق، ويمنع أي مهمة منفلتة من إجراء تغييرات غير مقصودة.

استيراد الموارد الحالية

حين تكتشف مورداً غير مُدار — موجود في السحابة لكن ليس له حالة Terraform — أمامك خياران: الحذف وإعادة الإنشاء عبر Terraform (مُعطّل للإنتاج)، أو استيراده إلى الحالة (الأفضل). terraform import يقرأ المورد الفعلي من API الموفر ويكتبه في ملف الحالة. لا يُنشئ HCL تلقائياً — يجب كتابة الإعداد المطابق أولاً، ثم الاستيراد، ثم التكرار حتى يصبح فرق الخطة صفراً.

قدّم Terraform 1.5 كتلة import، التي تجعل الاستيراد تصريحياً قابلاً للمراجعة في الكود — النهج الحديث المفضّل بدلاً من أمر CLI.

# النهج الحديث: كتلة import (Terraform 1.5+) # imports.tf import { to = aws_s3_bucket.audit_logs id = "acme-audit-logs-prod" } import { to = aws_security_group.legacy_app id = "sg-0a1b2c3d4e5f" } # Terraform 1.7+: توليد HCL تلقائياً من المورد الفعلي # terraform plan -generate-config-out=generated.tf # راجع generated.tf ونظّفه، ثم: terraform apply # استيراد CLI الكلاسيكي (لا يزال صالحاً، أقل قابلية للتدقيق) terraform import aws_s3_bucket.audit_logs acme-audit-logs-prod
الاستيراد لا يُتحقق من تطابق HCL مع الواقع. بعد الاستيراد، شغّل دائماً terraform plan وتحقق من نتيجة صفر اختلافات. إذا أظهرت الخطة تغييرات، سيُطبّقها Terraform في المرة القادمة — وقد يُستبدل مورد إنتاجي حرج. صحّح كل خلاف في الخصائص قبل تفعيل التطبيق الآلي.

دليل تبني البيئات الحالية (Brownfield)

تبني Terraform لبيئة إنتاجية حالية هو أحد أعلى العمليات خطورةً تُجريها فرقة المنصة. في الشركات الكبيرة هذه حملة تمتد لأشهر. النمط الآمن هو تبني المُخنقة (strangler-fig): استيراد الموارد طبقةً طبقة، التشغيل في وضع plan-only لأسابيع قبل تفعيل apply، والحفاظ على مسار تراجع في كل خطوة.

  1. الجرد والتصنيف. استخدم AWS Config أو AWS CLI مع أوامر describe-* أو أداة مثل terraformer لحصر كل مورد في الحساب. صنّف: مُدار مقابل غير مُدار. رتّب حسب الخطورة — استورد الشبكات أخيراً (أعلى نطاق تدمير)، ابدأ بالحوسبة عديمة الحالة.
  2. اكتب HCL قبل الاستيراد. اكتب إعداد المورد، أودعه في الكود، احصل على مراجعة. الاستيراد لا رجعة فيه بمعنى أن ملف الحالة يتتبع المورد الآن — خطأ في HCL قد يتسبب في حذف أو استبدال المورد الفعلي.
  3. الاستيراد في فرع، والخطة في CI. أنشئ PR مع كتلة الاستيراد وHCL. سيعرض CI الفرق. ادمج فقط حين تكون الخطة صفر اختلافات.
  4. فعّل apply تدريجياً. ابدأ بتشغيل CI بوضع plan-only لأسبوعين على الموارد المستوردة حديثاً. تحقق أن كشف الانجراف لا يجد تغييرات غير متوقعة. فقط بعد ذلك افتح بوابة apply.
  5. وثّق كل استيراد. أضف تعليقاً في HCL أو رسالة git تشرح سبب الاستيراد: التذكرة الأصلية، من أنشأ المورد يدوياً، ومتى استُورد. هذه الذاكرة المؤسسية تمنع الفرق المستقبلية من الاعتقاد بأن المورد آمن للحذف.
استخدم lifecycle { prevent_destroy = true } على كل مورد مُستورد من brownfield لأول 90 يوماً. هذا الحارس يمنع terraform destroy العرضي أو for_each المُعدّ خطأً من حذف قاعدة بيانات إنتاجية سابقة لـ IaC. أزله فقط بعد أن تكتسب الفريق ثقةً كاملة في إعداد HCL.

ignore_changes: مخرج الطوارئ

بعض الخصائص تُدار مشروعاً خارج Terraform: عدد ECS المطلوب (يديره auto-scaling)، AMI لـ EC2 instance (تديره خطوط بناء AMI)، كلمة مرور RDS (تديرها AWS Secrets Manager). لهذه الحالات، استخدم ignore_changes لمنع Terraform من التراجع عن التغيير الخارجي:

resource "aws_ecs_service" "api" { name = "payments-api" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.api.arn desired_count = 3 lifecycle { ignore_changes = [ desired_count, # يديره Application Auto Scaling task_definition, # يُحدّثه خط نشر، لا Terraform ] } } resource "aws_db_instance" "postgres" { identifier = "acme-prod-postgres" # ... إعداد آخر ... lifecycle { ignore_changes = [password] # تديره Secrets Manager prevent_destroy = true # لا تسمح بالحذف العرضي أبداً } }

يُستخدم ignore_changes باعتدال ويُوثَّق دائماً بتعليق يشرح لماذا تُدار الخاصية خارجياً. الإفراط في استخدامه يُفضي إلى تسلل إعداد صامت حيث يتوقف Terraform عن كونه مصدر الحقيقة للخصائص المهمة.

كتل moved لإعادة الهيكلة الآمنة

عند إعادة هيكلة HCL — إعادة تسمية مورد، نقله إلى وحدة، تغيير count إلى for_each — يرى Terraform افتراضياً المورد القديم كمحذوف والجديد كمُنشأ. للموارد الإنتاجية هذا يعني دورة حذف/إعادة إنشاء. تُخبر كتلة moved، التي قُدِّمت في Terraform 1.1، Terraform أن نفس المورد الفعلي يُشار إليه الآن بعنوان مختلف:

# قبل: resource "aws_instance" "web" { ... } # بعد إعادة الهيكلة: resource "aws_instance" "web_servers" { ... } # أضف إلى moves.tf لمنع الحذف/إعادة الإنشاء: moved { from = aws_instance.web to = aws_instance.web_servers } # لنقل الوحدات: moved { from = aws_security_group.legacy_sg to = module.networking.aws_security_group.app }

أنماط الفشل الإنتاجي

أكثر كوارث brownfield تتبع أنماطاً متكررة يمكن تجنبها:

  • الاستيراد ثم التطبيق دون مراجعة الخطة. HCL المُستورد يحتوي خاصية خاطئة (vpc_id غلط، engine_version مختلف). التطبيق التالي يستبدل المورد. راجع دائماً حتى تحصل على خطة صفر اختلافات قبل تفعيل apply.
  • عكس قواعد مجموعة الأمان المُنجرفة بصمت. فريق أمان أضاف قاعدة طارئة بعد هجوم. Terraform يعكسها في التطبيق التالي. القاعدة كانت الشيء الوحيد الذي يحجب ناقل الهجوم. شغّل كشف الانجراف قبل كل apply.
  • حذف جماعي للموارد بسبب تغيير مفاتيح for_each. مورد brownfield مُستورد بـ count. شخص يعيد الهيكلة إلى for_each بمفاتيح نصية. Terraform يرى كل الموارد المُفهرسة بـ count كمحذوفة وينشئ جديدة. استخدم كتل moved لربط العناوين القديمة بالجديدة.

كشف الانجراف وتبني brownfield هما المهارتان اللتان تفصلان الفرق التي تستخدم Terraform كمسرحية امتثال عن الفرق التي تستخدمه كمصدر حقيقي فعلي للعمليات. أتقن كليهما، أتمت كشف الانجراف من اليوم الأول، وستظل حالة بنيتك التحتية موثوقةً حتى في أعقد بيئات الإنتاج.