---
title: قفل حالة Terraform S3 بدون
description: فعّل خاصية قفل الحالة (state locking) الأصلية في Terraform S3 باستخدام use_lockfile - بدون الحاجة لجدول DynamoDB. قم بإعداد الـ backend، وترحيل مشروع موجود، وتأمين صلاحيات الـ IAM.
url: https://nerdleveltech.com/ar/terraform-s3-native-state-locking-tutorial
language: ar
date: '2026-05-25'
category: cloud-devops
tags:
  - terraform
  - s3
  - state locking
  - dynamodb
  - aws
  - infrastructure as code
  - devops
  - backend configuration
markdown_url: https://nerdleveltech.com/ar/terraform-s3-native-state-locking-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/terraform-s3-native-state-locking-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# قفل حالة Terraform S3 بدون

يمكن لـ Terraform S3 backend قفل الحالة (state) من تلقاء نفسه من خلال وسيط `use_lockfile`، دون الحاجة إلى جدول DynamoDB. تتوفر هذه الميزة بشكل عام منذ إصدار Terraform 1.11، وهي تقوم بكتابة كائن `.tflock` بجوار ملف الحالة الخاص بك باستخدام عمليات الكتابة الشرطية (conditional writes) في S3. يوضح هذا البرنامج التعليمي كيفية إعداد ذلك من البداية إلى النهاية.

## ملخص

ستقوم بإنشاء دلو (bucket) S3 مشفر وذو إصدارات (versioned)، وتوجيه Terraform S3 backend إليه باستخدام `use_lockfile = true`، والتأكد من أن التشغيل المتزامن الثاني يتم حظره بواسطة كائن قفل أصلي بدلاً من جدول DynamoDB. ستقوم أيضاً بترحيل مشروع موجود حالياً ومقفل بواسطة DynamoDB دون أي توقف عن العمل، وكتابة سياسة IAM بأقل الامتيازات. تستغرق العملية حوالي 20 دقيقة. تحتاج إلى حساب AWS وإصدار Terraform 1.11 أو أحدث.

لسنوات، كان قفل S3 backend يعني إنشاء جدول DynamoDB منفصل: خدمة AWS أخرى لتهيئتها، وواجهة IAM أخرى لإدارتها، وبند آخر للدفع، ومورد آخر للاحتفاظ به في كود Terraform الخاص بك. قدم إصدار Terraform 1.10 قفل S3 الأصلي كخيار تجريبي، وقام إصدار Terraform 1.11 بترقيته ليصبح متاحاً بشكل عام مع وضع علامة "مهجور" (deprecated) على وسائط DynamoDB.[^1][^2] لذا فإن الإجابة المختصرة على سؤال "هل لا يزال Terraform بحاجة إلى DynamoDB لقفل الحالة؟" هي لا. دلو واحد الآن يقوم بالمهمتين.

## ما ستتعلمه

- إنشاء دلو S3 مشفر وذو إصدارات للاحتفاظ بحالة Terraform
- تكوين S3 backend باستخدام وسيط `use_lockfile` للقفل الأصلي
- تهيئة الـ backend والتأكد من وجود كائن القفل `.tflock`
- إعادة إنتاج تعارض في قفل الحالة ومسح قفل قديم باستخدام `force-unlock`
- ترحيل مشروع موجود مقفل بـ DynamoDB إلى قفل S3 دون توقف عن العمل
- كتابة سياسة IAM بأقل الامتيازات لـ S3 backend وملف القفل

## المتطلبات الأساسية

- **Terraform 1.11 أو أحدث.** ميزة `use_lockfile` متاحة بشكل عام من الإصدار 1.11؛ تم شحنها تجريبياً في 1.10، لذا فإن 1.11+ هو الإصدار الذي تريده في بيئة الإنتاج. تم التحقق من هذا البرنامج التعليمي مقابل Terraform 1.15.2. تحقق من إصدارك باستخدام `terraform version`.
- **حساب AWS و AWS CLI v2**، مهيأ ببيانات الاعتماد (`aws configure`، أو متغيرات البيئة، أو AWS IAM Identity Center). تحقق باستخدام `aws sts get-caller-identity`.
- **أذونات IAM** لإنشاء دلو S3 ووضع الكائنات فيه.
- إلمام أساسي بـ `terraform init`، و `plan`، و `apply`. إذا كانت الحالة عن بُعد (remote state) جديدة بالنسبة لك، فإن [دليل أساسيات البنية التحتية ككود](https://nerdleveltech.com/infrastructure-as-code-iac-fundamentals-a-complete-2025-guide) يغطي "لماذا" قبل أن يغطي هذا الدليل "كيف".

## الخطوة 1: إنشاء دلو S3 للحالة

هناك مشكلة "البيضة والدجاجة" مع الحالة عن بُعد: يجب أن يكون الدلو موجوداً قبل أن يتمكن Terraform من تخزين الحالة فيه. الحل الأنظف هو إنشاء الدلو مرة واحدة باستخدام AWS CLI، ثم ترك Terraform يستخدمه من ذلك الحين فصاعداً.

اختر منطقة (region) واسماً فريداً عالمياً للدلو. أسماء الدلاء مشتركة عبر كل حسابات AWS على الأرض، لذا ألحق اسمك بمعرف حسابك:

```bash
export AWS_REGION="us-east-1"
export TF_STATE_BUCKET="tf-state-acme-$(aws sts get-caller-identity --query Account --output text)"

aws s3api create-bucket \
  --bucket "$TF_STATE_BUCKET" \
  --region "$AWS_REGION"
```

لأي منطقة أخرى غير `us-east-1`، يجب عليك أيضاً تمرير `--create-bucket-configuration LocationConstraint="$AWS_REGION"` — حيث أن `us-east-1` هي المنطقة الوحيدة التي يكون فيها حذف هذا الخيار صالحاً.

بعد ذلك، قم بتشغيل إصدارات الدلو (bucket versioning). توصي وثائق S3 backend صراحةً بذلك حتى تتمكن من استعادة نسخة سابقة من الحالة بعد حذف غير مقصود أو تطبيق خاطئ:[^3]

```bash
aws s3api put-bucket-versioning \
  --bucket "$TF_STATE_BUCKET" \
  --versioning-configuration Status=Enabled
```

قم بتمكين التشفير الافتراضي من جانب الخادم (server-side encryption) بحيث يتم تشفير كل من ملف الحالة وملف القفل عند التخزين:

```bash
aws s3api put-bucket-encryption \
  --bucket "$TF_STATE_BUCKET" \
  --server-side-encryption-configuration \
  '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
```

أخيراً، احظر جميع عمليات الوصول العام. تحتوي ملفات الحالة بشكل روتيني على أسرار، لذا فإن الدلو الخاص أمر غير قابل للتفاوض:

```bash
aws s3api put-public-access-block \
  --bucket "$TF_STATE_BUCKET" \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
```

الدلو جاهز الآن للاحتفاظ بالحالة والأقفال.

## الخطوة 2: تكوين S3 backend باستخدام use\_lockfile

قم بإنشاء دليل عمل وملف `main.tf`. يخبر بلوك `backend "s3"` برنامج Terraform بمكان وجود الحالة؛ والوسيط الوحيد `use_lockfile = true` هو ميزة القفل الأصلي بالكامل.[^3]

```hcl
terraform {
  required_version = ">= 1.11"

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.9"
    }
  }

  backend "s3" {
    bucket       = "tf-state-acme-REPLACE-WITH-YOURS"
    key          = "global/s3-locking-demo/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
}

resource "random_pet" "server" {
  length = 2
}

output "server_name" {
  value = random_pet.server.id
}
```

استبدل قيمة `bucket` بالاسم الذي أنشأته في الخطوة 1. ملاحظات قليلة حول بلوك الـ backend:

- `key` هو مسار الكائن داخل الدلو. يتم اشتقاق ملف القفل منه: يكتب Terraform ‏`global/s3-locking-demo/terraform.tfstate.tflock` — نفس المسار مع لاحقة `.tflock`.[^3]
- `encrypt = true` يطلب من Terraform تشفير الحالة وكائنات القفل التي يرفعها، بالإضافة إلى التشفير الافتراضي على مستوى الدلو من الخطوة 1.
- `use_lockfile = true` يفعل القفل الأصلي. لا يوجد وسيط `dynamodb_table` في أي مكان في هذا التكوين.
- لا يمكن لبلوكات الـ Backend استخدام المتغيرات أو الإدراج (interpolation)، لذا فإن القيم حرفية. للقيم الخاصة بالبيئة، استخدم [التكوين الجزئي](https://nerdleveltech.com/infrastructure-as-code-iac-fundamentals-a-complete-2025-guide) ومرر `-backend-config` في وقت التهيئة (init).

مورد `random_pet` هو عرض توضيحي بسيط ومقصود: يقوم بإنشاء اسم ودود ويخزنه في الحالة دون تهيئة أو فوترة أي بنية تحتية حقيقية.[^4] إنه موجود حتى يكون لديك حالة لتقفلها.

## الخطوة 3: تهيئة الـ backend والتأكد من كائن القفل

قم بتهيئة دليل العمل. يقوم Terraform بتكوين S3 backend وتنزيل موفر (provider) ‏`random`:

```bash
terraform init
```

```text
Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Installing hashicorp/random v3.9.0...

Terraform has been successfully initialized!
```

الآن قم بالتطبيق. اكتب `yes` عند المطالبة:

```bash
terraform apply
```

```text
Terraform will perform the following actions:

  # random_pet.server will be created
  + resource "random_pet" "server" {
      + id        = (known after apply)
      + length    = 2
      + separator = "-"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

random_pet.server: Creating...
random_pet.server: Creation complete after 0s [id=stirring-mongoose]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

server_name = "stirring-mongoose"
```

انظر إلى ما استقر في الدلو:

```bash
aws s3 ls "s3://$TF_STATE_BUCKET/global/s3-locking-demo/"
```

```text
2026-05-25 09:14:02       1180 terraform.tfstate
```

سترى `terraform.tfstate` ولكن لن ترى ملف `.tflock`. هذا متوقع وصحيح: كائن القفل موجود فقط *أثناء* العملية. يقوم Terraform بإنشائه في بداية `plan` أو `apply` ويحذفه في اللحظة التي تنتهي فيها العملية. بين عمليات التشغيل، لا يوجد قفل لتراه. تحت الغطاء، يقوم Terraform بإنشاء هذا الكائن باستخدام كتابة شرطية في S3 — وهي عملية `PutObject` تحمل ترويسة `If-None-Match`، وهي ميزة جعلتها S3 متاحة بشكل عام في أغسطس 2024 — لذا تنجح الكتابة فقط إذا لم يكن كائن القفل موجوداً بالفعل.[^5]

## الخطوة 4: مشاهدة تعارض القفل أثناء العمل

لرؤية القفل وهو يحظر فعلياً تشغيلاً متزامناً، تحتاج إلى نافذتي تيرمينال (terminal) في نفس دليل المشروع.

**التيرمينال 1** — ابدأ عملية apply تحتوي على عمل حقيقي، ثم توقف عند مطالبة الموافقة. يجبر علم `-replace` برنامج Terraform على التخطيط لاستبدال المورد التجريبي، بحيث يكون لعملية apply دائماً شيء لتأكيده. يحصل Terraform على قفل الحالة في *بداية* العملية ويحتفظ به حتى تكتمل العملية، بما في ذلك أثناء انتظاره لإجابتك على المطالبة:

```bash
terraform apply -replace="random_pet.server"
```

```text
  # random_pet.server will be replaced, as requested
-/+ resource "random_pet" "server" {
      ~ id     = "stirring-mongoose" -> (known after apply)
        length = 2
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Enter a value:
```

اترك تلك المطالبة كما هي. القفل الآن ممسوك.

**التيرمينال 2** — بينما ينتظر التيرمينال 1، حاول إجراء أي عملية حالة:

هما جوهر الأمر. فشلت عملية `PutObject` المشروطة في Terminal 2 لأن كائن `.tflock` موجود بالفعل — وهذا هو بالضبط نوع الحماية الذي تريده. عد إلى Terminal 1، وأجب بـ `yes` (أو `no`)، وسيتم تحرير القفل؛ أعد تشغيل Terminal 2 وسينجح الأمر.

إذا تعطلت عملية Terraform في منتصف التشغيل، فسيتم ترك كائن `.tflock` خلفها، لأن كائنات S3 لا تنتهي صلاحيتها تلقائيًا. هذا ما يسمى بالقفل الراكد (stale lock). تأكد من عدم وجود أي شخص آخر يقوم بتشغيل Terraform فعليًا، ثم قم بتحريره باستخدام الـ `ID` الوارد في رسالة الخطأ:

```bash
terraform force-unlock 4d1f0e87-3b6a-2c9d-a5e1-77c0b9f4e220
```

في أنابيب CI، حيث يكون التنافس القصير بين الوظائف المتتالية أمرًا طبيعيًا، يفضل استخدام `terraform plan -lock-timeout=120s`. سيقوم Terraform حينها بإعادة محاولة الحصول على القفل لمدة دقيقتين قبل الاستسلام، بدلاً من الفشل عند أول تعارض.

## الخطوة 5: ترحيل مشروع موجود بعيدًا عن DynamoDB

إذا كنت تقوم بالفعل بتشغيل خلفية S3 مع `dynamodb_table`، فيمكنك الانتقال إلى القفل الأصلي (native locking) دون أي توقف، لأن وسيطات S3 و DynamoDB مسموح لها بالتعايش معًا. عندما يتم تعيين كليهما، يحصل Terraform على قفل من *كلا* النظامين في كل عملية تشغيل.[^3] هذا التداخل هو ما يجعل الترحيل آمنًا.

افترض أن خلفيتك الحالية تبدو هكذا:

```hcl
  backend "s3" {
    bucket         = "tf-state-acme-REPLACE-WITH-YOURS"
    key            = "global/s3-locking-demo/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
```

**المرحلة 1 — تشغيل كليهما.** أضف `use_lockfile = true` مع الاحتفاظ بـ `dynamodb_table`:

```hcl
  backend "s3" {
    bucket         = "tf-state-acme-REPLACE-WITH-YOURS"
    key            = "global/s3-locking-demo/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
    use_lockfile   = true
  }
```

أعد التهيئة حتى يلتقط Terraform تكوين الخلفية المتغير:

```bash
terraform init -reconfigure
```

قم بتشغيل دورة `plan`/`apply` عادية. سيتم الآن إنشاء وحذف كل من عنصر DynamoDB وكائن S3 `.tflock` في كل عملية تشغيل. اترك هذه المرحلة تعمل لبضعة أيام عبر فريقك بالكامل و CI.

**المرحلة 2 — التخلي عن DynamoDB.** بمجرد أن تثق في القفل الأصلي، قم بإزالة وسيطات DynamoDB:

```hcl
  backend "s3" {
    bucket       = "tf-state-acme-REPLACE-WITH-YOURS"
    key          = "global/s3-locking-demo/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
```

```bash
terraform init -reconfigure
```

**المرحلة 3 — التنظيف.** مع ترحيل كل مشروع، احذف الجدول غير المستخدم الآن وأذونات IAM التي كانت تشير إليه:

```bash
aws dynamodb delete-table --table-name terraform-locks --region us-east-1
```

هذه خدمة AWS واحدة أقل في حسابك، وشيء واحد أقل لتدفع ثمنه، ومورد واحد أقل للاحتفاظ به في كود Terraform الخاص بك.

## الخطوة 6: تأمين الخلفية بسياسة IAM ذات الحد الأدنى من الصلاحيات

تمتلك أوراق اعتماد المسؤول الخاصة بك بالفعل وصولاً كافيًا لتشغيل كل ما سبق. بالنسبة لمشغل CI أو دور أتمتة مخصص، حدد الأذونات بدقة. تحدد وثائق خلفية S3 بالضبط ما هو مطلوب، ويستحق ملف القفل الاهتمام لأن مجموعة أذوناته تختلف عن ملف الحالة:[^3]

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListStatePrefix",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::tf-state-acme-REPLACE-WITH-YOURS",
      "Condition": {
        "StringEquals": {
          "s3:prefix": "global/s3-locking-demo/terraform.tfstate"
        }
      }
    },
    {
      "Sid": "ReadWriteState",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::tf-state-acme-REPLACE-WITH-YOURS/global/s3-locking-demo/terraform.tfstate"
    },
    {
      "Sid": "ManageLockFile",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::tf-state-acme-REPLACE-WITH-YOURS/global/s3-locking-demo/terraform.tfstate.tflock"
    }
  ]
}
```

التفصيل الذي يربك الناس: يحتاج مورد `.tflock` إلى `s3:DeleteObject` لأن Terraform يحذف كائن القفل عند انتهاء العملية، لكن ملف الحالة *لا* يُحذف — Terraform لا يحذف الحالة أبدًا، لذا فإن منح `s3:DeleteObject` على `terraform.tfstate` سيكون بمثابة صلاحية زائدة.[^3] إذا وجهت `kms_key_id` إلى مفتاح KMS يديره العميل، فامنح الدور أيضًا `kms:Encrypt` و `kms:Decrypt` و `kms:GenerateDataKey` على ذلك المفتاح.

## التحقق

تأكد من ثلاثة أشياء قبل اعتبار المهمة منتهية.

الحالة مشفرة أثناء السكون:

```bash
aws s3api head-object \
  --bucket "$TF_STATE_BUCKET" \
  --key "global/s3-locking-demo/terraform.tfstate" \
  --query 'ServerSideEncryption' --output text
```

```text
AES256
```

القفل يمنع التزامن فعليًا: أعد تشغيل اختبار المحطتين (two-terminal test) من الخطوة 4 وتأكد من فشل Terminal 2 بـ `Error acquiring the state lock`.

لا يوجد قفل راكد متبقي: مع عدم تشغيل أي عملية Terraform، قم بسرد بادئة الحالة وتأكد من رؤية `terraform.tfstate` ولكن بدون `terraform.tfstate.tflock`.

```bash
aws s3 ls "s3://$TF_STATE_BUCKET/global/s3-locking-demo/"
```

وجود كائن `.tflock` بينما لا يوجد شيء قيد التشغيل يعني وجود قفل راكد — قم بمسحه باستخدام `terraform force-unlock` ومعرف القفل من الخطأ، كما هو موضح في الخطوة 4.

## الأخطاء الشائعة

**`Error acquiring the state lock` في عملية تشغيل عادية ومنفردة.** هذا قفل راكد تركه عملية متعطلة. تأكد من عدم وجود زميل أو وظيفة CI في منتصف التشغيل، ثم استخدم `terraform force-unlock <ID>` باستخدام المعرف الموجود في الخطأ. كحل أخير، احذف كائن `.tflock` من الحاوية (bucket) مباشرة.

**`terraform init` يبلغ عن تغيير تكوين الخلفية.** متوقع في أي وقت تقوم فيه بتحرير كتلة `backend "s3"`، بما في ذلك عند إضافة `use_lockfile`. قم بتشغيل `terraform init -reconfigure`. استخدم `terraform init -migrate-state` فقط عندما تتغير الـ `bucket` أو الـ `key` نفسها.

**`AccessDenied` عند كتابة ملف القفل.** تفتقر سياسة IAM إلى `s3:PutObject` أو `s3:DeleteObject` على مورد `*.tflock`. أضف بيان `ManageLockFile` من الخطوة 6 — السياسة التي تغطي `terraform.tfstate` فقط ستقرأ وتكتب الحالة بشكل جيد ولكنها ستفشل في اللحظة التي يبدأ فيها القفل بالعمل.

**`IllegalLocationConstraintException` من `create-bucket`.** أنت تقوم بإنشاء الحاوية في منطقة أخرى غير `us-east-1` بدون `--create-bucket-configuration LocationConstraint="$AWS_REGION"`. أضف هذا العلم لكل منطقة باستثناء `us-east-1`.

**الدروس القديمة لا تزال تخبرك بإنشاء جدول DynamoDB.** إنها تسبق إصدار Terraform 1.10. بالنسبة للإصدار 1.11 والأحدث، فإن `use_lockfile` هو المسار المدعوم ووسيطة `dynamodb_table` مهجورة ومن المقرر إزالتها في إصدار فرعي مستقبلي.[^2][^3]

## الخطوات التالية

لديك الآن خلفية S3 بحاوية واحدة تقوم بتخزين وقفل الحالة دون الاعتماد على DynamoDB. من هنا:

- قم بتقسيم الحالة لكل بيئة باستخدام `workspace_key_prefix` ومساحة عمل Terraform لكل مرحلة.
- انقل إعداد الخلفية (backend bootstrap) إلى الأتمتة بحيث تكون الحاوية نفسها قابلة لإعادة الإنتاج.
- اربط `terraform plan`/`apply` بـ CI مع `-lock-timeout` بحيث تصطف الوظائف المتتالية بسلاسة. الأنماط الموجودة في [نشر تطبيق إنتاجي على Fly.io](https://nerdleveltech.com/deploy-bun-hono-fly-io-production-tutorial) و [ترحيل Kubernetes ingress إلى Gateway API](https://nerdleveltech.com/migrate-ingress-to-Kubernetes-gateway-API-tutorial) تتماشى جيدًا مع الحالة البعيدة المقفلة.

يعد قفل S3 الأصلي أحد تلك التغييرات النادرة التي تزيل جزءًا متحركًا بدلاً من إضافة جزء جديد. استمتع بهذا الفوز.

[^1]: إصدار Terraform v1.10.0 (2024-11-27) — قدم قفل الحالة الأصلي التجريبي لـ S3. <https://GitHub.com/hashicorp/terraform/releases/tag/v1.10.0>

[^2]: إصدار Terraform v1.11.0 (2025-02-27) — أصبح `use_lockfile` متاحًا بشكل عام؛ وتم إهمال القفل المستند إلى DynamoDB. <https://GitHub.com/hashicorp/terraform/releases/tag/v1.11.0>

[^3]: "Backend Type: s3" — وثائق HashiCorp Developer (قفل الحالة، `use_lockfile`، `.tflock`، أذونات IAM، الهجرة). <https://developer.hashicorp.com/terraform/language/backend/s3>

[^4]: hashicorp/random provider — Terraform Registry. <https://registry.terraform.io/providers/hashicorp/random/latest>

[^5]: "Amazon S3 يدعم الآن عمليات الكتابة المشروطة" — AWS What's New، 2024-08-20. <https://aws.amazon.com/about-aws/whats-new/2024/08/amazon-s3-conditional-writes/>
