Terraform S3 Lockfile: قفل الـ state بدون DynamoDB
مستوى القارئ: متوسط
هتطلع من المقال بإعداد عملي يخلي Terraform يمنع تشغيلين apply في نفس الوقت على نفس الـ state، من غير ما تضيف DynamoDB table جديدة.
المشكلة باختصار
لو عندك فريق صغير أو متوسط بيستخدم Terraform مع AWS، غالبًا الـ state موجود على S3. المشكلة مش في التخزين. المشكلة في اللحظة اللي CI job وواحد من الفريق يشغلوا terraform apply مع بعض.
اللي بيحصل فعلاً إن كل عملية ممكن تقرأ نفس النسخة من terraform.tfstate، وبعدها تكتب تغييرات مختلفة. النتيجة: drift، موارد orphaned، أو apply ناجح شكله طبيعي لكنه كسر الواقع. Terraform عنده state locking عشان يمنع ده، ولو الـ backend بيدعم القفل فالأوامر اللي ممكن تكتب في state بتقف لو القفل مش متاح.
الافتراض هنا إنك شغال على Terraform مع S3 backend، وعندك بيئة AWS واحدة أو أكثر، وعدد التشغيلات المتزامنة قليل نسبيًا: مثل 5 إلى 30 pipeline في اليوم. لو عندك platform team كبير ومئات التشغيلات اليومية، اقرأ قسم متى لا تستخدم قبل التنفيذ.
الفكرة: ملف .tflock جنب الـ state
قبل فترة طويلة، الإعداد الشائع كان S3 لتخزين الـ state وDynamoDB للقفل. الطريقة دي شغالة، لكنها بتضيف مورد إضافي، IAM permissions إضافية، وشرح أطول لأي مهندس جديد يدخل المشروع.
في S3 backend الحالي، HashiCorp بتوثق خيار use_lockfile = true لتفعيل قفل مبني على ملف داخل نفس bucket. كمان توثيق S3 backend بيقول إن DynamoDB-based locking deprecated وسيتم إزالته في إصدار minor مستقبلي. ركز: deprecated لا تعني إنه اتكسر النهارده، لكنها إشارة واضحة إنك ما تبدأش مشروع جديد عليه.
مثال بسيط: بدل ما تعمل table اسمها terraform-locks وفيها partition key باسم LockID، Terraform يكتب lock file بجانب مسار الـ state. لو Runner A ماسك القفل، Runner B هيستنى أو يفشل حسب إعدادات الأمر.
الإعداد العملي
أفضل طريقة للمشاريع الجديدة على AWS هي S3 backend مع versioning وuse_lockfile. versioning مهم لأن ملف الـ state حساس. AWS بتوضح إن S3 Versioning بيساعدك ترجع نسخة سابقة لو حصل overwrite أو delete غير مقصود.
terraform {
required_version = ">= 1.10.0"
backend "s3" {
bucket = "acme-prod-terraform-state"
key = "prod/network/terraform.tfstate"
region = "eu-central-1"
encrypt = true
use_lockfile = true
}
}
بعد تعديل ملف الـ backend، شغل:
terraform init -reconfigure
terraform plan -lock-timeout=3m
terraform apply -lock-timeout=3m
استخدام -lock-timeout=3m عملي في CI. بدل ما job يفشل بعد ثواني بسبب apply تاني شغال، يستنى 3 دقائق. الرقم ده مناسب لو متوسط apply عندك بين 45 و120 ثانية. لو الـ apply بياخد 20 دقيقة، متخليش timeout كبير وخلاص؛ قسم الـ state أفضل.
IAM المطلوب باختصار
لو فعلت use_lockfile، Terraform محتاج صلاحيات على ملف القفل أيضًا. توثيق S3 backend يذكر احتياج s3:GetObject وs3:PutObject وs3:DeleteObject على مسار .tflock. مثال policy مختصر:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::acme-prod-terraform-state/prod/network/terraform.tfstate"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::acme-prod-terraform-state/prod/network/terraform.tfstate.tflock"
}
]
}
الـ trade-off هنا واضح. بتكسب إعداد أقل من DynamoDB، لكن لازم تسمح بحذف ملف القفل. ده طبيعي لأن Terraform لازم يشيل القفل بعد انتهاء العملية. لو منعت DeleteObject على .tflock، هتدخل نفسك في locks عالقة.
سيناريو واقعي بأرقام
خلينا نقول عندك موقع SaaS صغير: 50K زائر يوميًا، وبيئتين staging وproduction. الفريق عنده 6 مهندسين، وكل Pull Request ممكن يشغل terraform plan، والدمج على main يشغل apply.
قبل القفل، احتمال التشغيل المتزامن مش كبير، لكنه مؤلم. مرة واحدة في الشهر كفاية تعمل ساعتين debugging. لو تكلفة ساعة المهندس 40 دولار، حادث واحد يساوي 80 دولار وقت ضائع، غير خطر تعطيل deploy. بعد القفل، Runner B هيستنى 3 دقائق بدل ما يكتب state فوق Runner A. بتكسب حماية سلوكية، وتخسر انتظار بسيط عند التزامن.
لو حصل lock عالق بسبب انقطاع runner، استخدم terraform force-unlock بحذر شديد. HashiCorp بتوصي إن force unlock يستخدم فقط لقفلك أنت عندما يفشل unlock التلقائي، لأن فتح قفل شخص آخر يرجعك لمشكلة multiple writers.
متى لا تستخدم هذه الطريقة
لا تستخدمها كحل وحيد لو عندك تنظيم كبير يحتاج audit مركزي لكل عمليات Terraform. في الحالة دي، Terraform Cloud أو منصة CI فيها approvals وrun history أوضح.
كمان لا تبدأ migrate عشوائي من DynamoDB لو عندك إصدارات Terraform قديمة في بعض الـ runners. الافتراض الآمن إنك توحد Terraform version أولًا، وتشغل تجربة على state غير حرج، ثم تنقل بيئة production.
ولو الـ state كبير جدًا وapply بياخد أكثر من 15 دقيقة بشكل متكرر، المشكلة غالبًا في تقسيم الـ state أو تصميم modules، مش في طريقة القفل. القفل يمنع الفساد، لكنه مش هيصلح بطء التصميم.
مصادر راجعتها
- HashiCorp Terraform S3 backend documentation
- HashiCorp Terraform state locking documentation
- AWS S3 Versioning documentation
الخطوة التالية
افتح backend الخاص بأقل بيئة خطورة عندك، غالبًا staging، وضيف use_lockfile = true. بعدها شغل terraform init -reconfigure ثم جرّب تشغيل plan من مكانين في نفس الوقت وتأكد إن واحد منهم يستنى القفل.