Zuletzt überprüft: Mai 2026
Erstellen Sie die AWS-Dienste der TF-PRO-Prüfung mit reinem Terraform — ein Block nach dem anderen, jeweils abgestimmt auf eine Prüfungsdomäne. Derselbe Code funktioniert auch mit OpenTofu.
Am Ende dieses Labs haben Sie die vier Bereiche trainiert, die bei der Terraform Pro-Prüfung am stärksten gewichtet werden: das Erstellen von fortgeschrittenem HCL, das Erstellen und Zusammensetzen von Modulen, das Ausführen von Zustandsoperationen und das Betreiben gegen HCP Terraform – alles mit den anmeldeinformationsfreien random- und local-Providern, sodass nichts bezahlt und außer einem Ordner nichts bereinigt werden muss.
Sie modellieren Eingaben mit reichen Typbeschränkungen und Validierungen, transformieren Sammlungen mit den Ausdrücken und Funktionen, auf die sich die Prüfung stützt, erstellen ein wiederverwendbares Modul und fächern es mit for_each auf, und refaktorisieren dann den Live-Zustand mit import, moved und removed Blöcken. Der letzte Schritt verbindet das Ganze mit HCP Terraform für die Remote-Ausführung. Jeder Schnipsel ist reines Terraform – identisch auf OpenTofu. Bauen Sie es in einem einzigen Arbeitsverzeichnis auf; wir kennzeichnen die Dateien, die unter ./modules liegen.
>= 1.9 oder OpenTofu >= 1.8 (terraform version). Wir verwenden removed-Blöcke (1.7+) und import-Blöcke (1.5+), daher ist eine aktuelle Version hier wichtiger als im Associate-Lab.random und local laufen vollständig auf Ihrem Computer.mkdir tf-pro-lab && cd tf-pro-lab).terraform fmt und terraform validate nach jeder Bearbeitung auszuführen – die Pro-Prüfung geht davon aus, dass Sie diese als Teil des Erstellungsprozesses und nicht als nachträglichen Gedanken behandeln.Dieses Lab ist komplett kostenlos. Die random- und local-Provider erstellen nur kleine Dateien auf Ihrer eigenen Festplatte sowie eine lokale terraform.tfstate; es wird nichts in einer Cloud bereitgestellt und im Leerlauf fallen keine Kosten an. Schritt 7 (HCP Terraform) läuft auf dem kostenlosen Tarif, der einen einzelnen Lab-Workspace und die betrieblichen Funktionen abdeckt, nach denen die Prüfung fragt.
Wir beginnen den Bereich HCL und Konfiguration, indem wir eine aktuelle Terraform-Version festlegen – die Pro-Prüfung setzt Sprachfunktionen voraus, die nur in neueren Releases existieren (optional()-Objektattribute, removed-Blöcke). Der required_providers-Block sperrt random und local, und terraform init schreibt eine .terraform.lock.hcl, die Sie committen sollten, damit Pläne über ein Team hinweg reproduzierbar sind.
Die zwei Gewohnheiten, die Sie jetzt aufbauen, ziehen sich durch das gesamte Lab: terraform fmt kanonisiert Abstände und Ausrichtung (die Prüfung testet, dass es Dateien vor Ort umschreibt und im -check-Modus mit einem Nicht-Null-Wert beendet wird), und terraform validate überprüft die interne Konsistenz Ihrer Konfiguration, bevor ein Provider kontaktiert wird. Mit der festgelegten Toolchain und diesen beiden Befehlen zur Hand können wir HCL schreiben, das es wert ist, validiert zu werden.
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, offlineDie Pro-Prüfung geht weit über type = string hinaus. Hier deklarieren wir einen object-Typ mit optional()-Attributen, die Standardwerte liefern, wenn ein Aufrufer sie weglässt, und fügen dann mehrere validation-Blöcke an – die Prüfung testet, dass eine Variable mehr als einen tragen kann, jeder mit seiner eigenen condition und error_message.
Das try() in locals ist der andere Pro-Level-Schachzug: Es gibt den ersten Ausdruck zurück, der fehlerfrei ausgewertet wird, sodass das Lesen eines möglicherweise fehlenden Attributs elegant abfällt, anstatt den Plan zu stoppen. Sein Geschwister can() (das einen Bool-Wert zurückgibt) erscheint später in der Validierung von Schritt 4. Diese platform-Variable wird zur einzigen Quelle der Wahrheit für den Rest des Labs – jeder spätere Schritt liest daraus, sodass die korrekte Angabe des Typs und seiner Garantien hier nachgelagert Früchte trägt.
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"
}Sammlungs-Gymnastik ist der dichteste Teil des Bereichs HCL und Konfiguration. Wir verwenden setproduct(), um jedes Regions-/Dienstpaar zu erstellen, dann einen for-Ausdruck, um diese Paare in eine schlüsselbasierte Karte umzuformen – genau das Muster, das später einen for_each speist. Daneben reduzieren flatten() + distinct() eine Liste von Listen von Eigentümern zu einer sauberen, eindeutigen Menge.
Dies sind die Funktionen, auf die Pro-Fragen immer wieder zurückkommen: for mit =>, um eine Karte zu erstellen, setproduct für Kreuzprodukte, flatten, um eine Verschachtelungsebene zu entfernen, merge, um Karten zu kombinieren, und jsonencode, um das Ergebnis zu serialisieren. Wir rendern die berechnete deployments-Karte in eine Datei, damit Sie cat out/deployments.json ausführen und die Form sehen können, die Ihr Ausdruck erzeugt hat. Diese schlüsselbasierte Karte ist genau die Art von Wert, die wir als Nächstes dem for_each eines Moduls übergeben werden.
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)
}Jetzt kommt der Bereich Module (25%). Wir extrahieren ein workload-Kindmodul unter ./modules/workload mit eigenen required_providers, validierten Eingaben und veröffentlichten Ausgaben. Das can(regex(...)) in der Namensvalidierung ist die Pro-Idiom: regex würde bei einer Nichtübereinstimmung einen Fehler auslösen, und can wandelt diesen Fehler in ein false um, das die condition verwenden kann.
Ein gut gebautes Modul verbirgt seine Interna und bietet einen stabilen Vertrag – Aufrufer übergeben name und replicas und erhalten eine id und einen manifest_path zurück, ohne jemals den random_string im Inneren zu berühren. Dass das Modul seine eigenen Provider-Anforderungen deklariert, ist ein bewusster Pro-Punkt: Die Provider-Konfiguration wird standardmäßig vom Root geerbt, aber die Provider-Anforderungen werden pro Modul deklariert. Mit einem sauberen Modul in der Hand ist die interessante Frage, wie man es viele Male instanziiert und die Instanzen miteinander verbindet.
# 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
}Immer noch im Bereich Module rufen wir nun module.workload einmal pro Workload mit for_each über eine Menge auf, setzen replicas bedingt pro Schlüssel und konsumieren dann die id-Ausgabe jeder Instanz in einer einzigen aggregierten registry.json. Die Iteration über module.workload mit einem for-Ausdruck ist die kanonische Art und Weise, wie die Prüfung die Ausgaben eines Moduls mit den Eingaben einer anderen Ressource verbindet.
Die explizite depends_on auf der aggregierten Datei ist ein Lehrpunkt: Implizite Abhängigkeiten von der module.workload-Referenz ordnen die Dinge bereits korrekt an, aber depends_on macht die Absicht unübersehbar und ist gelegentlich erforderlich, wenn eine Abhängigkeit nicht über Daten ausgedrückt wird. Führen Sie terraform init aus, nachdem Sie die Modulquelle hinzugefügt haben, dann apply. Mit mehreren Live-Modulinstanzen und deren aggregierten Ausgaben haben wir einen echten Zustand, der bearbeitet werden kann – was der nächste Bereich ist.
# 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 }
}Der Bereich CLI und Zustandsverwaltung (25%) unterscheidet die Pro-Prüfung von der Associate-Prüfung. Wir verwenden drei konfigurationsgesteuerte Zustandsoperationen. Ein import-Block übernimmt ein bereits existierendes Objekt in den Zustand – wir zielen auf ein integriertes terraform_data ab, damit das Beispiel anmeldeinformationsfrei bleibt. Ein removed-Block entfernt eine Ressource aus dem Zustand, ohne das reale Objekt zu zerstören – genau das, wonach Sie greifen, wenn Sie die Eigentümerschaft an eine andere Konfiguration übergeben. Und terraform_data mit replace_triggered_by erzwingt einen nachgelagerten Ersatz, wann immer sich die Registrierung aus Schritt 5 ändert, ohne dass ein Provider beteiligt ist.
Kennen Sie auch die imperativen CLI-Äquivalente, die die Prüfung immer noch testet: terraform state list und state show zum Inspizieren, terraform state mv zum Umbenennen, terraform state rm zum Vergessen, terraform plan -target=ADDR zum Eingrenzen eines Laufs und terraform apply -replace=ADDR zum Erzwingen der Neuerstellung einer Ressource. Mit dem Zustand fest unter Ihrer lokalen Kontrolle ist der letzte Bereich, all dies auf HCP Terraform zu tun.
# 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]
}
}Der Bereich HCP Terraform Operations (20%) schließt die Prüfung ab. Ein cloud-Block – hier mit workspaces { tags = [...] } verwendet, damit eine Konfiguration mehreren tag-übereinstimmenden Workspaces zugeordnet werden kann – verlagert die Ausführung von Ihrem Laptop. Ausführungen erfolgen auf HCP Terraform mit Remote-, gesperrtem Zustand; die Prüfung kontrastiert die Remote-, lokalen und Agenten-Ausführungsmodi sowie die VCS-gesteuerten, CLI-gesteuerten und API-gesteuerten Ausführungs-Workflows.
Hier kommen auch die betrieblichen Funktionen zusammen, die die Pro-Prüfung nennt: Variablensätze, um Eingaben über Workspaces hinweg zu teilen, Run-Trigger, um den Apply eines Workspaces mit dem Plan eines anderen zu verketten, Run-Tasks für externe Integrationen, Policy Sets (Sentinel oder OPA), um Applies zu steuern, und ein privates Modulregister, um die Art von Modul zu veröffentlichen, die Sie in Schritt 4 erstellt haben. Ersetzen Sie my-org durch Ihre Organisation, führen Sie terraform login aus, dann terraform init, um den Zustand zu migrieren. Die Konfiguration, die Sie in den Schritten 1–6 geschrieben haben, läuft unverändert – nur der Ort und die Schutzmaßnahmen darum herum wurden hochgestuft, was die gesamte Pro-Story ist.
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["terraform-pro-lab"]
}
}
}
# Authenticate once, then migrate state to the remote workspace:
# terraform login
# terraform initAlles ist lokal, daher ist der Abbau schnell:
terraform destroy aus, um die generierten Dateien zu entfernen und aus dem Zustand zu löschen. Aufgrund des removed-Blocks in Schritt 6 wird random_string.deprecated (falls es jemals existierte) vergessen, anstatt zerstört zu werden – das ist beabsichtigt.terraform destroy gegen den Remote-Workspace aus, löschen Sie dann den terraform-pro-lab-Workspace in der Benutzeroberfläche und entfernen Sie den cloud-Block.cd .. && rm -rf tf-pro-lab. Der .terraform/-Cache, .terraform.lock.hcl und alle lokalen Zustände werden damit ebenfalls entfernt.Die Pro-Prüfung testet Terraform-Autorisierung und -Operationen, nicht eine bestimmte Cloud, daher stellt dieses Lab absichtlich keine Cloud-Infrastruktur bereit – das hält es anmeldeinformationsfrei und lässt jeden Schritt sich auf HCL, Module, Zustand und HCP Terraform konzentrieren.
Eine Handvoll Pro-relevanter Themen lässt sich besser studieren als hier bereitzustellen: dynamic-Blöcke (die einen Ressourcentyp mit wiederholbaren verschachtelten Blöcken – einer Cloud-Ressource – benötigen, um sinnvoll zu sein), Provider configuration_aliases und die Weitergabe von aliasierten Providern an Module, Sentinel / OPA-Richtlinienautorisierung und der Veröffentlichungsworkflow des privaten Modulregisters. Die Bereiche Durchsuchen und Handbuch dieser Zertifizierungsseite behandeln diese konzeptionell. Der praktische Wert hier liegt in der Autor- und Operationsschleife, um die die Prüfung aufgebaut ist: validierte komplexe Eingaben, zusammengesetzte Module, furchtlose Zustandsoperationen und Remote-Ausführungen.