נבדק לאחרונה: מאי 2026
בנו את שירותי AWS של בחינת TF-PRO עם Terraform פשוט — בלוק אחד בכל פעם, כאשר כל אחד מהם מקושר בחזרה לתחום במבחן. אותו הקוד עובד גם ב-OpenTofu.
עד סוף מעבדת למידה זו תתרגלו את ארבעת התחומים שהבחינה המקצועית של Terraform מדגישה ביותר: כתיבת HCL מתקדם, בנייה והרכבת מודולים, ביצוע 'ניתוחי' מצב (state surgery), ותפעול מול HCP Terraform — כל זאת עם ספקי ה-random וה-local ללא צורך באישורים, כך שאין מה לשלם עבורו ואין מה לנקות מלבד תיקייה.
תמצבו קלטים עם אילוצי טיפוס ואימות עשירים, תשנו אוספים באמצעות הביטויים והפונקציות שהבחינה מתמקדת בהם, תכתבו מודול לשימוש חוזר ותפרסו אותו באמצעות for_each, ולאחר מכן תארגנו מחדש מצב (state) חי עם בלוקים של import, moved, ו-removed. השלב האחרון מחבר את כל העניין ל-HCP Terraform לצורך ביצוע מרחוק. כל קטע קוד הוא Terraform טהור — זהה ב-OpenTofu. בנו אותו בספריית עבודה אחת; נציין את הקבצים הנמצאים תחת ./modules.
>= 1.9 או OpenTofu >= 1.8 (terraform version). אנו משתמשים בבלוקים removed (1.7+) ובלוקים import (1.5+), ולכן מהדורה עדכנית חשובה כאן יותר מאשר במעבדת ה-Associate.random ו-local רצים לחלוטין על המחשב שלך.mkdir tf-pro-lab && cd tf-pro-lab).terraform fmt ו-terraform validate לאחר כל עריכה — הבחינה המקצועית מניחה שאתם מתייחסים אליהם כחלק מתהליך הכתיבה, ולא כמחשבה שלאחר מכן.מעבדת למידה זו חינמית לחלוטין. ספקי ה-random וה-local יוצרים רק קבצים קטנים על הדיסק שלכם בתוספת קובץ terraform.tfstate מקומי; שום דבר לא מסופק בענן כלשהו ושום חיוב לא מתבצע בזמן סרק. שלב 7 (HCP Terraform) רץ על השכבה החינמית (free tier), המכסה סביבת עבודה אחת במעבדה ואת התכונות התפעוליות שהבחינה שואלת עליהן.
אנו מתחילים את תחום HCL ותצורה על ידי קיבוע גרסת Terraform עדכנית — הבחינה המקצועית מניחה תכונות שפה הקיימות רק במהדורות חדשות יותר (תכונות אובייקט optional(), בלוקים removed). הבלוק required_providers נועל את random ו-local, ו-terraform init כותב קובץ .terraform.lock.hcl שעליכם לבצע לו commit כדי שהתוכניות יהיו ניתנות לשחזור בין חברי צוות.
שני ההרגלים שיש לבנות כעת ילוו את כל המעבדה: terraform fmt מתקנן רווחים ויישור (הבחינה בודקת שהוא כותב קבצים במקומם ויוצא עם קוד שאינו אפס במצב -check), ו-terraform validate בודק שהתצורה שלכם עקבית פנימית לפני שנוצר קשר עם ספק כלשהו. כאשר ערכת הכלים מקובעת ושתי הפקודות הללו בהישג ידכם, נוכל לכתוב HCL ששווה לאמת.
terraform {
required_version = ">= 1.9"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
# Build the muscle memory:
# terraform fmt # rewrite files to canonical style
# terraform validate # check internal consistency, offlineהבחינה המקצועית חורגת הרבה מעבר ל-type = string. כאן אנו מצהירים על טיפוס object עם תכונות optional() המספקות ערכי ברירת מחדל כאשר קורא משמיט אותן, ולאחר מכן מצרפים בלוקי validation מרובים — הבחינה בודקת שמשתנה יכול להכיל יותר מאחד, כשכל אחד עם condition ו-error_message משלו.
הפונקציה try() ב-locals היא המהלך הנוסף ברמת מקצועית: היא מחזירה את הביטוי הראשון שמבוצע ללא שגיאה, כך שקריאת תכונה שעלולה להיות חסרה מתנהלת בחן במקום לעצור את התכנית. הפונקציה האחות שלה can() (המחזירה בוליאני) מופיעה מאוחר יותר באימות של שלב 4. משתנה ה-platform הזה הופך למקור האמת היחיד עבור שאר המעבדה — כל שלב מאוחר יותר קורא ממנו, כך שקבלת הטיפוס וההבטחות שלו כראוי כאן משתלמת בהמשך.
variable "platform" {
description = "Platform configuration."
type = object({
name = string
replicas = optional(number, 2)
features = optional(set(string), [])
owners = list(string)
})
validation {
condition = var.platform.replicas >= 1 && var.platform.replicas <= 10
error_message = "replicas must be between 1 and 10."
}
validation {
condition = length(var.platform.owners) > 0
error_message = "At least one owner is required."
}
default = {
name = "lab"
owners = ["platform@example.com"]
}
}
locals {
# try() returns the first error-free expression - here it shields
# against features being unset and classifies the tier.
tier = length(try(var.platform.features, [])) > 0 ? "enhanced" : "standard"
}לוליינות אוספים (Collection gymnastics) היא החלק הדחוס ביותר בתחום HCL ותצורה. אנו משתמשים ב-setproduct() כדי לבנות כל צמד אזור/שירות, ולאחר מכן בביטוי for כדי לעצב מחדש את הצמדים הללו למפה עם מפתחות — התבנית המדויקת שמזינה מאוחר יותר for_each. לצד זאת, flatten() + distinct() מקפלים רשימת-רשימות של בעלים למערך ייחודי ונקי.
אלו הן הפונקציות ששאלות בחינת ה-Pro חוזרות אליהן שוב ושוב: for עם => כדי לייצר מפה, setproduct עבור מכפלות קרטזיות, flatten כדי להוריד רמת קינון אחת, merge כדי לשלב מפות, ו-jsonencode כדי להפוך את התוצאה לפורמט JSON. אנו מדפיסים את מפת ה-deployments המחושבת לקובץ כך שתוכלו להריץ cat out/deployments.json ולראות את הצורה שהביטוי שלכם יצר. המפה עם המפתחות הזו היא בדיוק סוג הערך שנעביר ל-for_each של מודול בהמשך.
locals {
regions = ["us-east-1", "eu-west-1"]
services = ["api", "web"]
# setproduct builds every (region, service) pair; the for
# expression reshapes the pairs into a keyed map.
deployments = {
for pair in setproduct(local.regions, local.services) :
"${pair[0]}/${pair[1]}" => {
region = pair[0]
service = pair[1]
}
}
# flatten + distinct collapse nested owner lists into a unique set.
all_owners = distinct(flatten([
for owner in var.platform.owners : split(",", owner)
]))
}
resource "local_file" "matrix" {
filename = "${path.module}/out/deployments.json"
content = jsonencode(local.deployments)
}כעת תחום המודולים (25%). אנו מחלצים מודול צאצא workload תחת ./modules/workload עם required_providers משלו, קלטים מאומתים ופלטים מפורסמים. ה-can(regex(...)) באימות השם הוא האידיום המקצועי: regex היה מעלה שגיאה על אי-התאמה, ו-can הופך את השגיאה ל-false שבו ה-condition יכול להשתמש.
מודול בנוי היטב מסתיר את הפרטים הפנימיים שלו וחושף חוזה יציב — קוראים מעבירים name ו-replicas, ומקבלים בחזרה id ו-manifest_path, מבלי לגעת ב-random_string שבתוכו. הצהרת המודול על דרישות הספק שלו היא נקודה מכוונת לבחינת ה-Pro: תצורת הספק עוברת בירושה מהשורש כברירת מחדל, אך דרישות הספק מוצהרות לכל מודול בנפרד. עם מודול נקי ביד, השאלה המעניינת היא כיצד ליצור ממנו מופעים רבים ולחבר אותם יחד.
# modules/workload/main.tf
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
variable "name" {
type = string
validation {
# can() turns regex's raise-on-no-match into a usable bool.
condition = can(regex("^[a-z][a-z0-9-]{1,30}$", var.name))
error_message = "name must be 2-31 chars: lowercase letter, then letters/digits/hyphens."
}
}
variable "replicas" {
type = number
default = 1
}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
resource "local_file" "manifest" {
filename = "${path.root}/out/${var.name}.json"
content = jsonencode({
name = var.name
replicas = var.replicas
id = "${var.name}-${random_string.suffix.result}"
})
}
output "id" {
description = "Stable identifier for this workload."
value = "${var.name}-${random_string.suffix.result}"
}
output "manifest_path" {
value = local_file.manifest.filename
}עדיין בתחום המודולים, כעת אנו קוראים ל-module.workload פעם אחת לכל עומס עבודה (workload) עם for_each על פני מערך (set), מגדירים replicas באופן מותנה לכל מפתח, ולאחר מכן צורך את פלט ה-id של כל מופע לקובץ registry.json מצטבר יחיד. חזרה על module.workload עם ביטוי for היא הדרך הקנונית שבה הבחינה מחברת את הפלטים של מודול אחד לקלטים של משאב אחר.
ה-depends_on המפורש על הקובץ המצטבר הוא נקודת הוראה: תלויות מרומזות מההפניה ל-module.workload כבר מסדרות דברים נכון, אבל depends_on הופך את הכוונה לברורה ונדרש לעיתים כאשר תלות אינה מבוטאת באמצעות נתונים. הריצו terraform init לאחר הוספת מקור המודול, ולאחר מכן apply. עם מספר מופעי מודולים פעילים והפלטים שלהם מאוגדים, יש לנו מצב (state) אמיתי ששווה לתפעל — וזה התחום הבא.
# main.tf (root) - fan the module out, then aggregate its outputs
module "workload" {
source = "./modules/workload"
for_each = toset(["api", "web", "worker"])
name = each.key
replicas = each.key == "api" ? 3 : 1
}
resource "local_file" "registry" {
filename = "${path.module}/out/registry.json"
content = jsonencode({
for k, m in module.workload : k => m.id
})
depends_on = [module.workload]
}
output "workload_ids" {
value = { for k, m in module.workload : k => m.id }
}תחום CLI וניהול מצב (State Management) (25%) הוא המקום שבו בחינת ה-Pro נבדלת מזו של ה-Associate. אנו משתמשים בשלוש פעולות מצב המונעות על ידי תצורה. בלוק import מאמץ אובייקט שכבר קיים לתוך המצב (state) — אנו מכוונים ל-terraform_data מובנה כדי שהדוגמה תישאר ללא צורך באישורים. בלוק removed מסיר משאב מהמצב (state) מבלי להרוס את האובייקט האמיתי — בדיוק מה שאתם צריכים כשמעבירים בעלות לתצורה אחרת. ו-terraform_data עם replace_triggered_by כופה החלפה במורד הזרם בכל פעם שהרישום משלב 5 משתנה, ללא מעורבות של ספק.
הכירו גם את המקבילות האימפרטיביות של ה-CLI שהבחינה עדיין בודקת: terraform state list ו-state show לבדיקה, terraform state mv לשינוי שם, terraform state rm לשכחה, terraform plan -target=ADDR לצמצום הרצה, ו-terraform apply -replace=ADDR כדי לכפות יצירה מחדש של משאב אחד. כאשר המצב (state) בשליטתכם המלאה באופן מקומי, התחום האחרון הוא ביצוע כל זאת ב-HCP Terraform.
# 1) Adopt an existing object into state (Terraform 1.5+),
# replacing the older imperative "terraform import" command.
import {
to = terraform_data.legacy
id = "existing-id"
}
resource "terraform_data" "legacy" {}
# 2) Stop managing a resource WITHOUT destroying it (Terraform 1.7+).
removed {
from = random_string.deprecated
lifecycle {
destroy = false
}
}
# 3) terraform_data + replace_triggered_by recreates a marker
# whenever the registry file content changes.
resource "terraform_data" "deploy_marker" {
input = local_file.registry.content
lifecycle {
replace_triggered_by = [local_file.registry]
}
}תחום פעולות HCP Terraform (20%) סוגר את הבחינה. בלוק cloud — כאן באמצעות workspaces { tags = [...] } כך שתצורה אחת יכולה למפות למספר רב של סביבות עבודה (workspaces) התואמות לתגים — מעביר את הביצוע מהמחשב הנייד שלכם. ההרצות מתבצעות ב-HCP Terraform עם מצב (state) מרוחק ונעול; הבחינה משווה בין מצבי הביצוע מרוחק (remote), מקומי (local), וסוכן (agent) ובין זרימות העבודה של ההרצה מונעת-VCS, מונעת-CLI, ומונעת-API.
כאן גם מתכנסות התכונות התפעוליות שהבחינה המקצועית מציינת: ערכות משתנים (variable sets) לשיתוף קלטים בין סביבות עבודה, טריגרים של הרצה (run triggers) לשרשור apply של סביבת עבודה אחת ל-plan של אחרת, משימות הרצה (run tasks) לאינטגרציות חיצוניות, ערכות מדיניות (policy sets) (Sentinel או OPA) כדי לשלוט על apply, ורישום מודולים פרטי (private module registry) לפרסום סוג המודול שבניתם בשלב 4. החליפו את my-org בשם הארגון שלכם, הריצו terraform login, ולאחר מכן terraform init כדי להעביר מצב (state). התצורה שכתבתם לאורך שלבים 1–6 רצה ללא שינוי — רק המקום ומנגנוני ההגנה סביבה השתפרו, וזה כל סיפור ה-Pro.
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["terraform-pro-lab"]
}
}
}
# Authenticate once, then migrate state to the remote workspace:
# terraform login
# terraform initהכל מקומי, ולכן הפירוק מהיר:
terraform destroy כדי להסיר את הקבצים שנוצרו ולנקות אותם מהמצב (state). בגלל בלוק ה-removed שבשלב 6, random_string.deprecated (אם אי פעם היה קיים) נשכח במקום שנהרס — וזו הכוונה.terraform destroy מול סביבת העבודה המרוחקת, ולאחר מכן מחקו את סביבת העבודה terraform-pro-lab בממשק המשתמש והסירו את בלוק ה-cloud.cd .. && rm -rf tf-pro-lab. המטמון .terraform/, הקובץ .terraform.lock.hcl, וכל מצב (state) מקומי נמחקים איתה.בחינת ה-Pro בודקת כתיבת ותפעול Terraform, ולא ענן ספציפי, ולכן מעבדת למידה זו אינה מספקת תשתית ענן בכוונה — הדבר שומר עליה ללא צורך באישורים ומאפשר לכל שלב להתרכז ב-HCL, מודולים, מצב (state), ו-HCP Terraform.
קומץ נושאים רלוונטיים לבחינת ה-Pro עדיף ללמוד עליהם תיאורטית מאשר לספק אותם כאן: בלוקי dynamic (הדורשים טיפוס משאב עם בלוקים מקוננים הניתנים לחזרה — משאב ענן — כדי להיות משמעותיים), configuration_aliases של ספקים והעברת ספקים עם כינויים למודולים, כתיבת מדיניות Sentinel / OPA, ותהליך הפרסום של רישום מודולים פרטי (private module registry). הקטעים עיון ומדריך בדף אישור זה מכסים נושאים אלו באופן מושגי. הערך המעשי כאן הוא לולאת הכתיבה-והתפעול שהבחינה בנויה סביבה: קלטים מורכבים מאומתים, מודולים מורכבים, 'ניתוחי' מצב (state surgery) חסרי פחד, והרצות מרחוק.