يربط Terraform أربعة عناصر :
في صورة الدرس 01 : التكوين هو المخطط، وstate هو السجل، والمزوّدون هم الحرفيون، وCLI هو المهندس المعماري.
يستخدم Terraform أساسًا لغة HashiCorp Configuration Language، أو HCL. تُنظَّم الشيفرة في كتل (blocks).
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، وفي مراجع الكتل الأخرى.
المزوّد إضافة (plugin) تعرف API منصة وتوفّر أنواع موارد ومصادر بيانات. يثبّت Terraform المزوّدين المطلوبين أثناء terraform init. تجعل قيود الإصدار وملف القفل التنفيذات أكثر قابلية للتوقع. المرجع الرسمي — متطلبات المزوّدين
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 الثاني.
يمثّل المورد عمومًا كائنًا يُنشئه Terraform أو يديره.
resource "aws_security_group" "web" {
name = "web"
}يقرأ مصدر البيانات كائنًا أو معلومة موجودة أصلًا دون أن يصبح مالكها آليًا.
data "aws_vpc" "default" {
default = true
}يمكنك بعد ذلك الإشارة إلى data.aws_vpc.default.id في مورد.
المتغيرات هي المدخلات القابلة للتكوين :
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 قيمًا داخلية لتجنّب التكرار :
locals {
prefix = "application-${var.environment}"
}تعرض outputs نتائج مفيدة :
output "bucket_id" {
description = "Identifiant du bucket"
value = aws_s3_bucket.logs.id
}مشروع بلا كتلة output ليس لديه ما يعرضه : يجيب terraform output حينئذٍ بـ Warning: No outputs found. هذه حالة المشروع 01 ؛ تأتي outputs في المشروع 02.
يكشف Terraform تبعية عندما يشير مورد إلى مورد آخر.
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 على جهاز الدورة ؛ ستجدها كلمةً كلمة عندما تقوم بها بنفسك.
terraform initInitializing 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).
تكتب ملفات .tf أو تعدّلها، ثم تشغّل عمومًا :
terraform fmt
terraform validateيعيد terraform fmt محاذاة المسافات والإزاحات ؛ يعرض أسماء الملفات التي عدّلها، ولا شيء على الإطلاق إذا كان كل شيء نظيفًا أصلًا. يتحقق terraform validate من صياغة الكتل واتساقها دون أن يمسّ شيئًا، ويجيب :
Success! The configuration is valid.يلتقط الأخطاء المطبعية في أسماء الوسائط (An argument named "contenu" is not expected here. Did you mean "content"?) والأقواس المنسية (Error: Unclosed configuration block). ولا يتحقق من أن النتيجة ستكون معمارية جيدة.
يقارن Terraform الحالة المرغوبة بما يعرفه عن البنية التحتية :
terraform planTerraform 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 معرفتها قبل إنشاء الكائن : هنا معرّف الملف وبصمات محتواه.
بعد المراجعة :
terraform applyيعيد Terraform عرض الخطة ثم يطلب موافقتك :
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 دون تغيير أي شيء، يكون الكشف فارغًا :
No changes. Your infrastructure matches the configuration.هذا هو التكافؤ الذاتي من الدرس 01، مشاهَدًا في الطرفية.
terraform destroydestroy هو apply لا تحتوي خطته سوى على -. السؤال المطروح أكثر إلحاحًا (Do you really want to destroy all resources? … There is no undo.)، والإجابة المنتظرة دائمًا yes، والسطر الأخير هو :
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 لا يُنتج ~، بل استبدالًا -/+. إليك الخطة الحقيقية، المأخوذة بعد تعديل النص :
# 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، فهو الذي سبّب-/+للكتلة بأكملها.
resource "type" "nom" عنوان، type.nom، تجده في الخطة وفي state.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.).+ ينشئ، و~ يعدّل في المكان، و-/+ يدمّر ثم يعيد الإنشاء، و- يدمّر. يعدّها الملخص Plan: N to add, N to change, N to destroy..~ أو -/+ ؛ يُستبدل local_file في المشروع 01 بمجرد تغيير content.terraform init، وأي ملفات يُنشئ ؟content، أي مؤشرات ثلاثة تقول إن الأمر استبدال وليس تعديلًا في المكان ؟y بدلًا من yes ؟