كيف يعمل Terraform

7 دقيقة
الجمهور
مبتدئ، قرأ الدرس 01
المدة
30 إلى 40 دقيقة
الوحدة
1/7
الكفاءة المستهدفة
تسمية القطع الأربع لـ Terraform، وقراءة كتلة HCL كلمةً كلمة، والتعرّف في الطرفية على الجمل التي يعرضها init وplan وapply وdestroy عندما يسير كل شيء على ما يرام

نظرة شاملة

يربط Terraform أربعة عناصر :

  • يصف التكوين النتيجة التي تريدها.
  • يقرأ Terraform هذا التكوين ويبني رسم تبعيات.
  • يترجم المزوّدون طلبات Terraform إلى استدعاءات لواجهات API الخاصة بالمنصات.
  • يربط state كتل شيفرتك بالكائنات البعيدة الحقيقية.

في صورة الدرس 01 : التكوين هو المخطط، وstate هو السجل، والمزوّدون هم الحرفيون، وCLI هو المهندس المعماري.

لغة HCL

يستخدم Terraform أساسًا لغة HashiCorp Configuration Language، أو HCL. تُنظَّم الشيفرة في كتل (blocks).

hcl
resource "aws_s3_bucket" "logs" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

في هذا المثال :

  • resource يعلن كائنًا يجب على Terraform إدارته ؛
  • aws_s3_bucket هو النوع الذي يوفّره مزوّد AWS ؛
  • logs هو الاسم المحلي المستخدم في هذه الوحدة ؛
  • bucket وtags وسيطان (arguments) ؛
  • var.bucket_name وvar.environment يأتيان من متغيرات.

يشكّل النوع والاسم المحلي معًا عنوان المورد : aws_s3_bucket.logs. تحت هذا الاسم ستجده في خطة، وفي terraform state list، وفي مراجع الكتل الأخرى.

المزوّد (provider)

المزوّد إضافة (plugin) تعرف API منصة وتوفّر أنواع موارد ومصادر بيانات. يثبّت Terraform المزوّدين المطلوبين أثناء terraform init. تجعل قيود الإصدار وملف القفل التنفيذات أكثر قابلية للتوقع. المرجع الرسمي — متطلبات المزوّدين

hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ca-central-1"
}

إصدار Terraform CLI وإصدار المزوّد شيئان مختلفان. تتحكم فيهما بشكل منفصل : يعرض terraform version الأول (Terraform v1.12.2 على جهاز الدورة)، ويثبّت .terraform.lock.hcl الثاني.

المورد ومصدر البيانات (data source)

يمثّل المورد عمومًا كائنًا يُنشئه Terraform أو يديره.

hcl
resource "aws_security_group" "web" {
  name = "web"
}

يقرأ مصدر البيانات كائنًا أو معلومة موجودة أصلًا دون أن يصبح مالكها آليًا.

hcl
data "aws_vpc" "default" {
  default = true
}

يمكنك بعد ذلك الإشارة إلى data.aws_vpc.default.id في مورد.

المتغيرات والقيم المحلية وoutputs

المتغيرات هي المدخلات القابلة للتكوين :

hcl
variable "environment" {
  type        = string
  description = "Nom de l'environnement"

  validation {
    condition     = contains(["dev", "test", "prod"], var.environment)
    error_message = "L'environnement doit être dev, test ou prod."
  }
}

تحسب locals قيمًا داخلية لتجنّب التكرار :

hcl
locals {
  prefix = "application-${var.environment}"
}

تعرض outputs نتائج مفيدة :

hcl
output "bucket_id" {
  description = "Identifiant du bucket"
  value       = aws_s3_bucket.logs.id
}

مشروع بلا كتلة output ليس لديه ما يعرضه : يجيب terraform output حينئذٍ بـ Warning: No outputs found. هذه حالة المشروع 01 ؛ تأتي outputs في المشروع 02.

رسم التبعيات

يكشف Terraform تبعية عندما يشير مورد إلى مورد آخر.

hcl
resource "aws_s3_object" "journal" {
  bucket = aws_s3_bucket.logs.id
  key    = "journal.txt"
  source = "journal.txt"
}

يعتمد الكائن على الـ bucket. لذلك يجب على Terraform إنشاء الـ bucket قبل إرسال الملف. يمكن معالجة الموارد التي لا تبعية بينها بالتوازي. لا أهمية لترتيب الكتل في الملف : المرجع وحده هو ما يُحتسب.

سير العمل المركزي

تلخّص HashiCorp سير عمل Terraform في ثلاث خطوات : Write, Plan, Apply. المرجع الرسمي — Core workflow

قبل أولى هذه الخطوات، توجد حركة واحدة لكل مجلد : terraform init. المخرجات أدناه هي مخرجات المشروع 01 من هذه الوحدة، مأخوذة بـ Terraform 1.12.2 على جهاز الدورة ؛ ستجدها كلمةً كلمة عندما تقوم بها بنفسك.

Init — تحضير المجلد

powershell
terraform init
text
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. …

Terraform has been successfully initialized!

تحدث ثلاثة أشياء : ينزّل Terraform المزوّد (Installing hashicorp/local v2.9.1)، ويضعه في المجلد المخفي .terraform/، ويكتب .terraform.lock.hcl لتذكّر الإصدار المختار. الجملة المنتظرة هي Terraform has been successfully initialized!. بدون init، يتوقف أي أمر آخر : Error: Inconsistent dependency lock file لـ plan، وError: Missing required provider لـ validate، وكلاهما يقول لك ماذا تفعل (run: terraform init).

Write — الكتابة

تكتب ملفات .tf أو تعدّلها، ثم تشغّل عمومًا :

powershell
terraform fmt
terraform validate

يعيد terraform fmt محاذاة المسافات والإزاحات ؛ يعرض أسماء الملفات التي عدّلها، ولا شيء على الإطلاق إذا كان كل شيء نظيفًا أصلًا. يتحقق terraform validate من صياغة الكتل واتساقها دون أن يمسّ شيئًا، ويجيب :

text
Success! The configuration is valid.

يلتقط الأخطاء المطبعية في أسماء الوسائط (An argument named "contenu" is not expected here. Did you mean "content"?) والأقواس المنسية (Error: Unclosed configuration block). ولا يتحقق من أن النتيجة ستكون معمارية جيدة.

Plan — التوقع

يقارن Terraform الحالة المرغوبة بما يعرفه عن البنية التحتية :

powershell
terraform plan
text
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.message will be created
  + resource "local_file" "message" {
      + content              = "Bonjour, ce fichier a été créé avec Terraform."
      + content_md5          = (known after apply)
      + filename             = "./message.txt"
      + id                   = (known after apply)

    }

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

تجيب الخطة على ثلاثة أسئلة، ويلخّصها السطر الأخير في ثلاثة أعداد :

  • ماذا سننشئ ؟ (to add)
  • ماذا سنعدّل ؟ (to change)
  • ماذا سندمّر أو نستبدل ؟ (to destroy)

يحمل كل سطر خاصية رمز الإجراء. يشير (known after apply) إلى قيمة لا يستطيع Terraform معرفتها قبل إنشاء الكائن : هنا معرّف الملف وبصمات محتواه.

Apply — التطبيق

بعد المراجعة :

powershell
terraform apply

يعيد Terraform عرض الخطة ثم يطلب موافقتك :

text
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.message: Creating...
local_file.message: Creation complete after 0s [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]

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

لا يُقبل سوى yes كاملة ؛ y أو Y أو سطر فارغ يعطي Apply cancelled. ولا يُمسّ شيء. ثم يستدعي Terraform المزوّدين بالترتيب الذي تفرضه التبعيات، ويحدّث state. يعيد السطر الأخير الأعداد الثلاثة للخطة : Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

إذا أعدت تشغيل terraform plan دون تغيير أي شيء، يكون الكشف فارغًا :

text
No changes. Your infrastructure matches the configuration.

هذا هو التكافؤ الذاتي من الدرس 01، مشاهَدًا في الطرفية.

Destroy — فكّ كل شيء

powershell
terraform destroy

destroy هو apply لا تحتوي خطته سوى على -. السؤال المطروح أكثر إلحاحًا (Do you really want to destroy all resources? … There is no undo.)، والإجابة المنتظرة دائمًا yes، والسطر الأخير هو :

text
Destroy complete! Resources: 1 destroyed.

بعد destroy، لا يعرض terraform state list شيئًا بعد الآن. في هذه الدورة، ينتهي كل مشروع هكذا.

الدورة الكاملة

الرموز الأربعة في خطة

الرمزالإجراءالجملة في الخطةما يغيّره ذلك لك
+إنشاءwill be createdكائن جديد ؛ لا يُمسّ أي شيء موجود.
~تعديل في المكانwill be updated in-placeيبقى الكائن، وتتغيّر خاصية.
-/+استبدالmust be replacedيُدمَّر الكائن ثم يُعاد إنشاؤه ؛ يختفي محتواه ومعرّفه.
-تدميرwill be destroyedيختفي الكائن.

تصريحي لا يعني سحريًا

يحدّد Terraform كيفية الوصول إلى الحالة المطلوبة، لكن السلوك الدقيق يعتمد على كل مزوّد. يمكن تعديل بعض الوسائط في المكان ؛ وتفرض أخرى استبدال المورد. يعطي المزوّد local في المشروع 01 مثالًا واضحًا : تغيير سطر content في local_file لا يُنتج ~، بل استبدالًا -/+. إليك الخطة الحقيقية، المأخوذة بعد تعديل النص :

text
  # local_file.message must be replaced
-/+ resource "local_file" "message" {
      ~ content              = "Bonjour, ce fichier a été créé avec Terraform." -> "Deuxième version du fichier créée avec Terraform." # forces replacement
      ~ content_md5          = "6daf774f0eb6d3da439c871afec7cf90" -> (known after apply)
      ~ id                   = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

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

اقرأ المؤشرات الثلاثة : must be replaced في العنوان، و# forces replacement في نهاية سطر content، و1 to add, 0 to change, 1 to destroy في الملخص. بالنسبة لملف نصي، الفارق بلا عواقب. أما بالنسبة لقاعدة بيانات، فـ -/+ يعني فقدان البيانات : لذلك يجب قراءة الخطة، حتى عندما يبدو تغيير الشيفرة صغيرًا.

الفرق الجوهري بين ~ و-/+. يقول ~ أمام content إن القيمة تتغيّر. ويقول -/+ أمام resource كيف سيتصرف Terraform : تدمير، ثم إعادة إنشاء. عندما يحمل سطر ~ علامة # forces replacement، فهو الذي سبّب -/+ للكتلة بأكملها.

الخلاصة

  1. أربع قطع : التكوين يصف، وCLI يقارن، والمزوّدون يستدعون واجهات API، وstate يتذكّر.
  2. لكتلة resource "type" "nom" عنوان، type.nom، تجده في الخطة وفي state.
  3. init مرة واحدة لكل مجلد (Terraform has been successfully initialized!)، ثم fmt، وvalidate (Success! The configuration is valid.)، وplan (Plan: 1 to add, 0 to change, 0 to destroy.)، وapply مع yes (Apply complete! Resources: 1 added, 0 changed, 0 destroyed.)، وdestroy في نهاية الجلسة (Destroy complete! Resources: 1 destroyed.).
  4. أربعة رموز : + ينشئ، و~ يعدّل في المكان، و-/+ يدمّر ثم يعيد الإنشاء، و- يدمّر. يعدّها الملخص Plan: N to add, N to change, N to destroy..
  5. يقرّر المزوّد ما إذا كان التغيير ~ أو -/+ ؛ يُستبدل local_file في المشروع 01 بمجرد تغيير content.

أسئلة الفهم

  1. أي مكوّن يتواصل مباشرة مع API الخاص بـ AWS أو GitHub ؟
  2. لماذا يجب تنفيذ terraform init، وأي ملفات يُنشئ ؟
  3. ما الفرق بين مورد ومصدر بيانات ؟
  4. كيف يعرف Terraform أن كائن S3 يعتمد على bucket ؟
  5. في خطة المشروع 01 بعد تعديل content، أي مؤشرات ثلاثة تقول إن الأمر استبدال وليس تعديلًا في المكان ؟
  6. بماذا يجيب Terraform إذا كتبت y بدلًا من yes ؟