נבדק לאחרונה: מאי 2026
בנו את שירותי AWS של בחינת 004 עם Terraform פשוט — בלוק אחד בכל פעם, כאשר כל אחד מהם מקושר בחזרה לתחום במבחן. אותו הקוד עובד גם ב-OpenTofu.
בסוף מעבדת זו תריצו את זרימת העבודה המלאה של Terraform מקצה לקצה ותגעו בכל תחום בבחינת ה-Associate — ללא חשבון ענן או הוצאה של סנט אחד. אנו משתמשים בספקי random ו-local, שאינם דורשים אישורים, כך שהמיקוד נשאר במה שהבחינה באמת בודקת: איך Terraform עצמו מתנהג.
תכתבו את המשאב הראשון שלכם, תפרמטרו אותו עם משתנים ותפוקות, תיצרו משאבים רבים עם for_each, תשכתבו את החזרתיות למודול לשימוש חוזר, תבדקו ותעבירו מצב, ולבסוף תפנו את התצורה ל-HCP Terraform עבור הפעלות מרחוק. כל קטע קוד הוא Terraform טהור — אותו קוד עובד ללא שינוי ב-OpenTofu. הטמיעו את הבלוקים לקובץ main.tf יחיד (אנו נציין את המעטים שחיים בקבצים משלהם), הריצו terraform init פעם אחת, ולאחר מכן terraform apply צעד אחר צעד.
>= 1.5 או OpenTofu >= 1.6 ב-PATH שלכם (terraform version). אנו משתמשים בבלוקי import מונחי תצורה בשלב 6, הדורשים גרסה 1.5+.random ו-local פועלים לחלוטין על המחשב שלכם.mkdir tf-associate-lab && cd tf-associate-lab).מעבדה זו חינמית לחלוטין. ספקי random ו-local אינם יוצרים משאבי ענן — רק כמה קבצים קטנים על הדיסק שלכם ורשומות בקובץ terraform.tfstate מקומי. שלב 7 (HCP Terraform) משתמש בשכבה החינמית, המספיקה בהחלט לסביבת עבודה יחידה במעבדה. אין כאן שום דבר שיחויב בזמן סרק.
לפני שכל דבר פועל, אנו מצהירים איזו גרסת Terraform אנו מצפים ועל אילו ספקים אנו תלויים. הצמדה היא נושא אהוב בבחינה — תחומי יסודות Terraform ו-זרימת עבודה מרכזית בודקים שאתם מבינים את required_version, את בלוק required_providers, ואת תפקידו של terraform init בהורדת תוספי ספק לתוך .terraform/.
אנו בוחרים בכוונה ב-random וב-local. אף אחד מהם אינו זקוק להתחברות לענן, כך שהמעבדה כולה נשארת חינמית וניתנת לשחזור, ובכל מקרה הבחינה לעולם אינה שואלת על ענן ספציפי — היא שואלת על Terraform. הטמיעו זאת לקובץ main.tf חדש והריצו terraform init; תראו כיצד Terraform פותר ונועל את שני הספקים לקובץ .terraform.lock.hcl, שהוא עצמו נקודת דיון בתחום היסודות (בצעו commit לקובץ הנעילה כך שכל הפעלה תשתמש בגרסאות תוספים זהות).
terraform {
required_version = ">= 1.5"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
# Both providers run locally and need no "provider" configuration
# block at all - a useful reminder that providers are just plugins.כעת אנו מתרגלים את ליבת תחום שימוש בזרימת העבודה המרכזית של Terraform (18% מהבחינה). אנו מצהירים על random_pet שממציא שם ידידותי, ולאחר מכן על local_file שכותב את השם הזה לדיסק. המשאב local_file.greeting מפנה אל random_pet.name.id, והפניה זו היא שאומרת ל-Terraform שהקובץ תלוי בחיה — סדר תלות מרומז, אין צורך ב-depends_on.
הריצו את זרימת העבודה לפי הסדר: terraform plan מראה לכם הבדל של מה שישתנה לפני שכל דבר קורה, ו-terraform apply הופך את זה למציאות ומתעד את התוצאה ב-terraform.tfstate. הריצו terraform apply פעם שנייה מבלי לשנות דבר ותראו No changes — זוהי אידמפוטנטיות, והבחינה אוהבת לשאול מדוע apply שני הוא פעולה ללא שינוי. עם משאב עובד וקובץ מצב טרי ביד, נוכל להתחיל להפוך את התצורה לגמישה.
resource "random_pet" "name" {
length = 2
separator = "-"
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}ערכים מקודדים קשיח מתאימים להדגמה, אך תחום קריאה, יצירה ושינוי תצורה (19%) מצפה מכם לפרמטריזציה. אנו מוסיפים variable קלט עם type, default, ובלוק validation שדוחה כל דבר מחוץ לסביבות המותרות שלנו — בדיקת תקינות פועלת בזמן התכנון והיא שאלה נפוצה בבחינה. אנו מחשבים מפת locals פעם אחת ומשתמשים בה שוב, ואנו חושפים תוצאות עם בלוקי output כך שתצורות אחרות (ו-terraform output) יוכלו לקרוא אותן.
שימו לב לשכבות שהבחינה בודקת: variable הוא קלט, locals הוא ערך נגזר/ביניים, ו-output הוא התוצאה המפורסמת. בצעו apply שוב ונסו terraform output pet_name כדי לקרוא ערך יחיד, או terraform output -json כדי לתסרטט מולו. כשהקלטים והפלטים מחוברים, אנו מוכנים להפסיק לכתוב משאב אחד בכל פעם ולייצר קבוצה שלמה.
variable "environment" {
description = "Deployment environment label."
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}
locals {
common_tags = {
environment = var.environment
managed_by = "terraform"
}
}
output "pet_name" {
description = "The generated pet name."
value = random_pet.name.id
}
output "tags" {
value = local.common_tags
}תצורות אמיתיות ממעטות להצהיר על כל דבר בנפרד. כאן אנו מתעמקים בתחום קריאה, יצירה ושינוי תצורה על ידי הנעת יצירת משאבים מאוסף. משתנה set(string) מפרט שירותים לוגיים; for_each מטביע אז random_string אחד ו-local_file אחד לכל שירות, הניתנים לגישה כ-random_string.suffix["api"] וכן הלאה.
שלב זה מציג גם ביטויים ופונקציות מובנות שהבחינה מצפה שתכירו: each.key עבור האלמנט הנוכחי, אינטרפולציית מחרוזת לבניית שם דלי לכל שירות, ו-jsonencode() להפיכת אובייקט HCL לקובץ JSON בדיסק. אנו משתמשים מחדש ב-var.environment וב-local.common_tags משלב 3 כך שכל קובץ שנוצר נושא מטא-נתונים עקביים. השאלה הבאה המתבקשת — זה נהיה חוזרני, איך אני אורז את זה? — היא בדיוק מה שמודולים עונים עליה.
variable "services" {
description = "Logical services to generate a config file for."
type = set(string)
default = ["api", "web", "worker"]
}
resource "random_string" "suffix" {
for_each = var.services
length = 6
special = false
upper = false
}
resource "local_file" "service_config" {
for_each = var.services
filename = "${path.module}/config/${each.key}.json"
content = jsonencode({
service = each.key
environment = var.environment
bucket = "${each.key}-${random_string.suffix[each.key].result}"
tags = local.common_tags
})
}תחום אינטראקציה עם מודולי Terraform רוצה שתחברו מודול, תקראו לו, ותעבירו ערכים פנימה והחוצה. אנו מעבירים את הלוגיקה הספציפית לשירות למודול ילד תחת ./modules/service, מעניקים לו תשומות variable משלו ותפוקה output, ואז קוראים לו מהשורש עם for_each — מופע מודול אחד לכל שירות.
שני הקבצים שלהלן מראים בבירור את הגבול: מודול הילד אינו יודע דבר על אילו שירותים קיימים (זו העבודה של הקורא באמצעות var.name), והשורש אינו יודע דבר על איך שירות נבנה (זה מקופסל במודול). הריצו terraform init שוב לאחר הוספת מודול — הבחינה בודקת שמקורות מודולים חדשים דורשים אתחול מחדש כדי להתקין אותם. כעת, כשהתצורה שלנו מודולרית, הנושא הגדול האחרון ב-Associate הוא מה ש-Terraform עוקבת אחריו בשקט כל הזמן: מצב (state).
# modules/service/main.tf
variable "name" {
type = string
}
variable "environment" {
type = string
}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
resource "local_file" "config" {
filename = "${path.root}/config/${var.name}.json"
content = jsonencode({
service = var.name
environment = var.environment
bucket = "${var.name}-${random_string.suffix.result}"
})
}
output "bucket_name" {
value = "${var.name}-${random_string.suffix.result}"
}
# main.tf (root) - call the module once per service
module "service" {
source = "./modules/service"
for_each = var.services
name = each.key
environment = var.environment
}
output "service_buckets" {
value = { for k, m in module.service : k => m.bucket_name }
}תחומי יישום ותחזוקת מצב (19%) ו-שימוש ב-Terraform מחוץ לזרימת העבודה המרכזית (9%) נמצאים שניהם כאן. מצב (State) הוא ספר החשבונות בפורמט JSON הממפה את כתובות התצורה שלכם לאובייקטים אמיתיים; terraform state list מפרט אותו ו-terraform state show <addr> מדפיס רשומה אחת. הבחינה מצפה מכם לדעת ששינוי שם של משאב בתצורה בדרך כלל יהרוס ויצור אותו מחדש — אלא אם תאמרו ל-Terraform שהכתובת זזה.
בלוק moved עושה בדיוק את זה באופן הצהרתי: שנה את השם של local_file.greeting (משלב 2) ל-local_file.welcome ובלוק moved מעביר את המצב במקום, כך ש-plan מראה העברה, לא הרס ויצירה מחדש. (המקבילה האימפרטיבית היא terraform state mv local_file.greeting local_file.welcome). אנו מציגים גם בלוק import מונחה תצורה — הדרך בגרסה 1.5+ לאמץ אובייקט קיים למצב ללא פקודת ה-CLI הישנה יותר terraform import. עם מצב בשליטה, נותרה יכולת אחת אחרונה לפגוש: הפעלת כל זה מרחוק.
# Renaming a resource? A "moved" block migrates state in place
# instead of destroying and recreating the object. Replace the
# Step 2 "greeting" resource with this renamed "welcome" one.
moved {
from = local_file.greeting
to = local_file.welcome
}
resource "local_file" "welcome" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}
# Config-driven import (Terraform 1.5+): adopt an object that
# already exists into state, no "terraform import" CLI command.
# terraform_data is a built-in resource - nothing to provision.
import {
to = terraform_data.tracked
id = "existing-id"
}
resource "terraform_data" "tracked" {}התחום האחרון, הבנת יכולות HCP Terraform (5%), משלים את הבחינה. בלוק cloud יחיד בתוך terraform {} מחליף ביצוע מקומי ב-HCP Terraform: המצב (state) חי מרחוק וננעל במהלך הפעלות, terraform plan/apply מתבצעים על רצי HashiCorp, ותפוקת ההרצה (בתוספת תוכנית שמורה) מופיעה בממשק המשתמש האינטרנטי. זהו גם המקום שבו הבחינה מציגה ניגוד בין תכונות HCP Terraform — מצב מרוחק, היסטוריית הרצות, אכיפת מדיניות עם Sentinel/OPA, ורישום מודולים פרטי — לבין זרימת העבודה המקומית הטהורה שהשתמשנו בה בשלבים 1–6.
החליפו את my-org בארגון שלכם, הריצו terraform login פעם אחת כדי לאחסן אסימון API, ולאחר מכן terraform init כדי להעביר מצב לסביבת העבודה המרוחקת. כל מה שכתבתם במעבדה זו פועל כעת ללא שינוי — רק היכן הוא פועל השתנה. נסיעה הלוך ושוב זו, מקובץ main.tf חשוף לסביבת עבודה מגובה מרחוק, היא כל מסלול ה-Associate בישיבה אחת.
terraform {
cloud {
organization = "my-org"
workspaces {
name = "terraform-associate-lab"
}
}
}
# Run once to authenticate, then re-init to migrate state:
# terraform login
# terraform initהכל חי על המכונה שלכם, כך שהניקוי מהיר:
terraform destroy כדי להסיר את הקבצים שנוצרו ולנקות אותם מהמצב (state).terraform-associate-lab מממשק המשתמש של HCP Terraform (או הריצו terraform destroy מולו קודם), ולאחר מכן הסירו את בלוק cloud.cd .. && rm -rf tf-associate-lab. מטמון התוספים המקומי .terraform/, הקובץ .terraform.lock.hcl ו-terraform.tfstate נמחקים יחד איתה.בחינת ה-Associate עוסקת ב-Terraform הכלי, לא בענן ספציפי כלשהו, ולכן מעבדה זו אינה מספקת תשתית ענן בכוונה. אנו מדלגים על משאבי AWS / Azure / GCP בכוונה: הם דורשים אישורים, עלולים לגרור עלויות, ויסיטו את תשומת הלב מהמכניקה שהבחינה בודקת בפועל — זרימת העבודה, המצב (state), שפת התצורה והמודולים.
כמה נושאי Associate עדיף לקרוא עליהם במקום להריץ אותם: backends שאינם HCP Terraform (S3, Azure Blob, GCS, Consul), provisioners כמו remote-exec (ש-HashiCorp מציינת כמוצא אחרון), ו-workspaces לניהול מופעי מצב מרובים. עבור אלה, מקטעי עיון ו-מדריך בדף אישור זה מכילים את הכיסוי המושגי. הערך המעשי כאן הוא זיכרון השריר של init → plan → apply, קריאת קובץ מצב, וביצוע רפקטורינג בטוח עם moved.