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

جراحة حالة Terraform

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

جراحة حالة Terraform

تُعدّ حالة Terraform المصدرَ الوحيد للحقيقة الذي يربط إعدادات HCL بالبنية التحتية الفعلية في السحابة. في عالم مثالي لن تضطر إلى لمسها مباشرةً أبدًا — فكل مورد يُولَد عبر terraform apply ويُزال عبر terraform destroy. لكن الإنتاج ليس مثاليًا؛ تواجه الفرق موارد قديمة أُنشئت قبل وجود Terraform، وعمليات إعادة هيكلة تُغيّر أسماء الموارد، وتلفًا محتملًا في ملف الحالة، والحاجة إلى تقسيم ملفات الحالة الضخمة. إتقان جراحة الحالة — أي التعامل مع الحالة بأمان دون تدمير البنية التحتية الفعلية — مهارة لا غنى عنها في أي مؤسسة تُشغّل Terraform على نطاق واسع.

الحالة تُضاعف نطاق الضرر. خطأ في terraform destroy يطال موردًا واحدًا. أما عملية جراحة حالة خاطئة فقد تُفقد المزامنة بين عشرات الموارد دفعةً واحدة، فيحاول Terraform إعادة إنشاء كل شيء في التطبيق التالي. احرص دائمًا على نسخ احتياطي من الحالة قبل أي عملية جراحية، ثم نفّذ terraform plan بعدها للتحقق من أن نية Terraform تطابق ما تقصده.

نسخ الحالة احتياطيًا قبل الجراحة

سواء كانت الواجهة الخلفية S3 أو GCS أو Terraform Cloud، التقط نسخة محلية قبل أن تبدأ:

# سحب الحالة الحالية إلى ملف احتياطي محلي terraform state pull > state-backup-$(date +%Y%m%d-%H%M%S).tfstate # التحقق من أن الملف الاحتياطي JSON صالح python3 -m json.tool state-backup-*.tfstate > /dev/null && echo "Backup OK"

في واجهات S3 مع تفعيل الإصدار، تُحفظ نسخة الحالة السابقة تلقائيًا — لكن السحب الصريح يمنحك نسخة محلية يمكنك فحصها والاستعادة منها دون الحاجة إلى الوصول السحابي.

أمر terraform import

استخدم import حين يكون مورد موجودًا في السحابة فعليًا لكن لا يوجد له سجل مقابل في حالة Terraform. أبرز السيناريوهات: مدير قاعدة بيانات أنشأ نسخة RDS يدويًا لأسباب طارئة، أو مهندس أضاف قاعدة في مجموعة أمان عبر وحدة التحكم، أو تبنّيت حسابًا AWS قديمًا لم يُدار بـ IaC قط.

المسار الصحيح هو: اكتب إعداد HCL أولًا، ثم استورد المورد الفعلي إلى الحالة. لا يولّد Terraform الكود نيابةً عنك عبر terraform import بمفرده — وإن كانت ميزة terraform plan -generate-config-out المُضافة في Terraform 1.5 تستطيع توليد هيكل أولي للإعداد كنقطة انطلاق.

# 1. اكتب (أو ولّد هيكل) HCL للمورد الموجود # main.tf resource "aws_security_group" "app_sg" { name = "app-prod-sg" description = "Application security group" vpc_id = var.vpc_id } # 2. استورد المورد الفعلي في AWS إلى حالة Terraform # الصيغة: terraform import <عنوان_المورد> <معرّف_المزوّد> terraform import aws_security_group.app_sg sg-0abc123def456789a # 3. نفّذ plan فورًا بعد الاستيراد — يجب أن يتطابق الإعداد مع الواقع terraform plan # الهدف: "No changes. Your infrastructure matches the configuration." # أي فرق يعني أن HCL لا يصف المورد الفعلي بالكامل.

في Terraform 1.5 وما بعده، يمكنك أيضًا الإعلان عن عمليات الاستيراد داخل HCL عبر كتلة import، وهي مثالية وآمنة للتنفيذ في خطوط الأنابيب:

# import.tf — موجود في Git، مراجعته عبر PR، يُطبَّق مرة واحدة import { to = aws_security_group.app_sg id = "sg-0abc123def456789a" } resource "aws_security_group" "app_sg" { name = "app-prod-sg" description = "Application security group" vpc_id = var.vpc_id }
في سياق العمل الجماعي، فضّل كتل import على أمر CLI. أمر terraform import من سطر الأوامر عملية آنية لا تترك أثرًا في Git. أما كتل import فهي تصريحية، تُراجَع في طلبات السحب، ويمكن تطبيقها عبر خط أنابيب CI/CD الاعتيادي. بعد تطبيق الاستيراد، احذف الكتلة من الكود (المورد بات تحت إدارة Terraform والكتلة لن تحدث شيئًا لو بقيت، لكنها تُشوّش القراءة).

كتلة moved — إعادة التسمية بلا تدمير

حين تُعيد هيكلة HCL — إعادة تسمية مورد، نقله إلى وحدة، أو تغيير مفتاح for_each — يرى Terraform أن العنوان القديم اختفى والجديد ظهر. في غياب توجيه صريح، سيُخطط لحذف المورد القديم وإنشاء مورد جديد، مما يعني انقطاعًا في الإنتاج. كتلة moved (مُقدَّمة في Terraform 1.1) تُخبر Terraform أن العنوانين القديم والجديد يشيران إلى الكائن الفعلي ذاته، فلا تحدث دورة حذف/إنشاء.

# قبل إعادة الهيكلة — المورد في المستوى الجذر # resource "aws_instance" "web" { ... } # بعد إعادة الهيكلة — نُقل إلى وحدة module "web" { source = "./modules/ec2" instance_type = "t3.medium" } # moved.tf — يُوضّح لـ Terraform معنى إعادة التسمية moved { from = aws_instance.web to = module.web.aws_instance.instance }

كتل moved هي سجلات تاريخية دائمة — احتفظ بها في الكود حتى يُطبّقها جميع المهندسون وخطوط الأنابيب، ثم احذفها بعد فترة استقرار (عادةً سبرينت واحد). إن حذفتها مبكرًا، سيرى المهندس الذي لم يُطبّق بعدُ دورة حذف/إنشاء في تطبيقه التالي.

تُعالج الكتلة أيضًا إعادة تسمية مفاتيح for_each، وهي من أكثر نقاط الألم في عمليات إعادة الهيكلة:

# قبل: مفاتيح باسم البيئة فقط resource "aws_s3_bucket" "data" { for_each = toset(["prod", "staging"]) bucket = "mycompany-data-${each.key}" } # بعد: مفاتيح تشمل المنطقة لمزيد من الوضوح resource "aws_s3_bucket" "data" { for_each = toset(["prod-us-east-1", "staging-us-east-1"]) bucket = "mycompany-data-${each.key}" } # كتل moved لكل مفتاح أُعيدت تسميته moved { from = aws_s3_bucket.data["prod"] to = aws_s3_bucket.data["prod-us-east-1"] } moved { from = aws_s3_bucket.data["staging"] to = aws_s3_bucket.data["staging-us-east-1"] }

أوامر CLI للحالة: mv وrm وlist

أوامر terraform state الفرعية هي أدوات جراحية للحالات التي لا تكفي فيها كتل HCL — كالنقل عبر مساحات العمل أو الواجهات الخلفية المختلفة.

terraform state list — يسرد جميع عناوين الموارد المُتتبَّعة حاليًا في الحالة. أساسي قبل أي عملية جراحية لفهم ما بين يديك:

terraform state list # module.network.aws_vpc.main # module.network.aws_subnet.private[0] # module.network.aws_subnet.private[1] # aws_iam_role.eks_node # aws_eks_cluster.main # التصفية حسب الوحدة أو نوع المورد terraform state list module.network terraform state list 'aws_iam_role.*'

terraform state mv — ينقل موردًا من عنوان إلى آخر داخل ملف الحالة ذاته، أو بين ملفي حالة مختلفين. استخدمه حين لا تصلح كتل moved (كالنقل عبر مساحات العمل):

# إعادة تسمية داخل الحالة نفسها terraform state mv aws_instance.web aws_instance.web_server # النقل من الجذر إلى وحدة (حين لا تُجدي كتلة moved) terraform state mv aws_security_group.app_sg module.app.aws_security_group.sg # النقل عبر ملفَي حالة مختلفين terraform state pull > old-workspace.tfstate # في مساحة العمل المستهدفة: terraform state mv -state=old-workspace.tfstate \ -state-out=new-workspace.tfstate \ aws_s3_bucket.data aws_s3_bucket.data terraform state push new-workspace.tfstate

terraform state rm — يحذف موردًا من الحالة دون تدمير البنية التحتية الفعلية. استخدمه حين تريد إيقاف إدارة Terraform لمورد ما (تسليمه لإدارة يدوية أو ترحيله لأداة أخرى) مع إبقاء المورد السحابي سليمًا:

# حذف مورد واحد من الحالة (نسخة EC2 الفعلية لا تُوقَف) terraform state rm aws_instance.legacy_app # حذف وحدة كاملة من الحالة terraform state rm module.legacy_network # تشغيل جاف مع خيار -dry-run (Terraform 1.6+) terraform state rm -dry-run module.legacy_network
بعد state rm، سيُظهر terraform plan التالي المورد كـ "سيُنشأ" — لأن Terraform لم يعد يعلم بوجود المورد الفعلي. أضف إما lifecycle { ignore_changes = all }، أو احذف المورد من HCL كليًا، أو أعد استيراده. تأكد دائمًا من وجود نية واضحة قبل الحذف من الحالة.

إعادة الهيكلة بلا تدمير: النمط الآمن

أفضل طريقة لإعادة هيكلة كود Terraform الكبير تتبع هذا التسلسل:

  1. نسخ احتياطي للحالةterraform state pull > backup.tfstate
  2. كتابة HCL الجديد — إعادة تسمية الموارد، استخراج الوحدات، إعادة الهيكلة
  3. إضافة كتل moved — واحدة لكل عنوان أُعيدت تسميته أو نُقل
  4. تشغيل terraform plan — يجب أن يُظهر صفر إضافات وصفر حذوفات؛ فقط ملاحظات النقل تظهر
  5. التطبيق على بيئة غير إنتاجية أولًا — للتحقق من نظافة عملية إعادة الهيكلة
  6. التطبيق على الإنتاج — مع مراجعة مهندس آخر لناتج plan قبل التأكيد
  7. حذف كتل moved — بعد تطبيق جميع البيئات بنجاح
State Surgery Safe Refactor Flow 1. Backup State state pull > backup 2. Rewrite HCL rename / modularize 3. Add moved{} one per rename 4. terraform plan 0 destroy = safe 5. Apply Staging validate clean 6. Apply Prod peer review plan 7. Remove moved{} after all envs apply تأكيد صفر عمليات حذف إن ظهرت عمليات حذف → استعد النسخة الاحتياطية وحقّق
تدفق جراحة الحالة الآمنة: ابدأ بالنسخ الاحتياطي، وتحقق من أن plan يُظهر صفر عمليات حذف قبل لمس أي بيئة.

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

أخطر أخطاء جراحة الحالة على نطاق واسع:

  • التطبيق بلا plan بعد الاستيراد — إذا لم يتطابق إعداد المورد المستورَد مع الواقع، سيُعدّل Terraform خصائصه أو يحذفها. نفّذ plan فورًا بعد كل استيراد وحلّ جميع الفوارق قبل المتابعة.
  • حذف كتل moved مبكرًا — المهندسون الذين لم يُطبّقوا بعد سيواجهون دورات حذف/إنشاء في تطبيقهم التالي. احتفظ بكتل moved طوال دورة إصدار كاملة على الأقل.
  • دفع الحالة بلا قفل — دفع ملف حالة معدَّل يدويًا عبر terraform state push يتجاوز القفل الموزّع. إن كان خط أنابيب يُطبّق في الوقت ذاته، سيُتلف الدفع الحالة. استخدم -lock=false كملاذ أخير حصرًا حين تتيقن من عدم وجود تطبيق نشط.
  • الجمع بين state mv وكتل moved للمورد ذاته — تطبيق كتلة moved بعد تشغيل state mv على المورد نفسه سيُسبّب خطأ في Terraform. اختر مسارًا واحدًا والتزم به.
على نطاق Google، جراحة الحالة محمية بإجراء طارئ خاص. دفع terraform state push مباشرةً يستلزم موافقة مهندس ثانٍ، وتذكرة حادثة مرتبطة، ويُطلق تنبيهًا تلقائيًا لفريق المنصة. حتى في المؤسسات الأصغر، تعامل مع أي تعديل يدوي للحالة باعتباره حدثًا بمستوى حادثة: وثّق ما فعلته، ولماذا، وماذا أظهر plan بعدها.

ES
Edrees Salih
منذ ساعة

We are still cooking the magic in the way!