Zuletzt überprüft: Mai 2026
Erstellen Sie die AWS-Dienste der 004-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 den gesamten Core Terraform Workflow von Anfang bis Ende durchlaufen und jeden Prüfungsbereich des Associate-Zertifikats berührt – ohne Cloud-Konto oder einen einzigen Cent auszugeben. Wir verwenden die Provider random und local, die keine Anmeldeinformationen benötigen, sodass der Fokus auf dem liegt, was die Prüfung tatsächlich testet: wie sich Terraform selbst verhält.
Sie schreiben Ihre erste Ressource, parametrisieren sie mit Variablen und Ausgaben, generieren viele Ressourcen mit for_each, refaktorisieren die Wiederholung in ein wiederverwendbares Modul, inspizieren und migrieren den Zustand und richten die Konfiguration schließlich auf HCP Terraform für Remote-Ausführungen aus. Jedes Snippet ist reines Terraform – derselbe Code funktioniert unverändert auf OpenTofu. Legen Sie die Blöcke in eine einzelne main.tf (wir werden die wenigen, die in ihren eigenen Dateien leben, hervorheben), führen Sie einmal terraform init aus und dann Schritt für Schritt terraform apply.
>= 1.5 oder OpenTofu >= 1.6 in Ihrem PATH (terraform version). Wir verwenden konfigurationsgesteuerte import-Blöcke in Schritt 6, die 1.5+ benötigen.random und local laufen vollständig auf Ihrem Rechner.mkdir tf-associate-lab && cd tf-associate-lab).Dieses Lab ist komplett kostenlos. Die Provider random und local erstellen keine Cloud-Ressourcen – nur ein paar kleine Dateien auf Ihrer eigenen Festplatte und Einträge in einer lokalen terraform.tfstate. Schritt 7 (HCP Terraform) verwendet den kostenlosen Tarif, der für einen einzelnen Lab-Workspace ausreichend ist. Es fallen hierbei keine Kosten an, während es im Leerlauf ist.
Bevor irgendetwas ausgeführt wird, deklarieren wir, welche Terraform-Version wir erwarten und von welchen Providern wir abhängen. Die Festlegung ist ein Prüfungsliebling – die Domänen Terraform Basics und Core Workflow testen beide, ob Sie required_version, den required_providers-Block und die Rolle von terraform init beim Herunterladen von Provider-Plugins in .terraform/ verstehen.
Wir wählen bewusst random und local. Keiner benötigt einen Cloud-Login, sodass das gesamte Lab kostenlos und reproduzierbar bleibt, und die Prüfung fragt ohnehin nie nach einer bestimmten Cloud – sie fragt nach Terraform. Fügen Sie dies in eine neue main.tf ein und führen Sie terraform init aus; Sie werden sehen, wie Terraform beide Provider auflöst und in eine .terraform.lock.hcl-Datei sperrt, was selbst ein Diskussionspunkt im Basics-Domäne ist (committen Sie die Sperrdatei, damit jede Ausführung identische Plugin-Versionen verwendet).
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.Jetzt üben wir den Kern der Domäne Use the Core Terraform Workflow (18% der Prüfung). Wir deklarieren ein random_pet, das einen freundlichen Namen erfindet, und dann eine local_file, die diesen Namen auf die Festplatte schreibt. Die Ressource local_file.greeting referenziert random_pet.name.id, und diese Referenz sagt Terraform, dass die Datei vom Haustier abhängt – implizite Abhängigkeitsreihenfolge, kein depends_on erforderlich.
Führen Sie den Workflow der Reihe nach aus: terraform plan zeigt Ihnen einen Vergleich dessen, was sich ändern wird, bevor etwas passiert, und terraform apply macht es real und zeichnet das Ergebnis in terraform.tfstate auf. Führen Sie terraform apply ein zweites Mal aus, ohne etwas zu ändern, und Sie werden No changes sehen – das ist Idempotenz, und die Prüfung fragt gerne, warum ein zweites Apply ein No-Op ist. Mit einer funktionierenden Ressource und einer neuen Zustandsdatei in der Hand können wir beginnen, die Konfiguration flexibel zu gestalten.
resource "random_pet" "name" {
length = 2
separator = "-"
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}Festcodierte Werte sind für eine Demo in Ordnung, aber die Domäne Read, Generate, and Modify Configuration (19%) erwartet, dass Sie parametrisieren. Wir fügen eine Eingabe-variable mit einem type, einem default und einem validation-Block hinzu, der alles außerhalb unserer erlaubten Umgebungen ablehnt – die Validierung läuft zur Planungszeit und ist eine häufige Prüfungsfrage. Wir berechnen eine locals-Map einmal und verwenden sie wieder, und wir geben Ergebnisse mit output-Blöcken aus, damit andere Konfigurationen (und terraform output) sie lesen können.
Beachten Sie die Schichtung, die die Prüfung testet: variable ist Eingabe, locals ist ein abgeleiteter/Zwischenwert und output ist das veröffentlichte Ergebnis. Wenden Sie es erneut an und versuchen Sie terraform output pet_name, um einen einzelnen Wert zu lesen, oder terraform output -json, um damit zu skripten. Mit verbundenen Eingaben und Ausgaben sind wir bereit, nicht mehr eine Ressource nach der anderen zu schreiben, sondern einen ganzen Satz zu generieren.
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
}Echte Konfigurationen deklarieren selten alles einzeln. Hier vertiefen wir uns weiter in die Domäne Read, Generate, and Modify Configuration, indem wir die Ressourcenerstellung aus einer Sammlung steuern. Eine set(string)-Variable listet logische Dienste auf; for_each erstellt dann eine random_string und eine local_file pro Dienst, ansprechbar als random_string.suffix["api"] und so weiter.
Dieser Schritt zeigt auch Ausdrücke und eingebaute Funktionen, die Sie für die Prüfung kennen sollten: each.key für das aktuelle Element, String-Interpolation zum Erstellen eines Dienst-spezifischen Bucket-Namens und jsonencode() zum Umwandeln eines HCL-Objekts in eine JSON-Datei auf der Festplatte. Wir verwenden var.environment und local.common_tags aus Schritt 3 wieder, damit jede generierte Datei konsistente Metadaten enthält. Die offensichtliche nächste Frage – das wird repetitiv, wie verpacke ich es? – ist genau das, was Module beantworten.
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
})
}Die Domäne Interact with Terraform Modules verlangt von Ihnen, ein Modul zu erstellen, es aufzurufen und Werte hinein und heraus zu übergeben. Wir verschieben die dienstspezifische Logik in ein Kindermodul unter ./modules/service, geben ihm eigene variable-Eingaben und eine output-Ausgabe und rufen es dann aus dem Root mit for_each auf – eine Modulinstanz pro Dienst.
Die beiden untenstehenden Dateien zeigen die Grenze deutlich: Das Kindermodul weiß nichts darüber, welche Dienste existieren (das ist die Aufgabe des Aufrufers über var.name), und das Root weiß nichts darüber, wie ein Dienst aufgebaut ist (das ist im Modul gekapselt). Führen Sie terraform init erneut aus, nachdem Sie ein Modul hinzugefügt haben – die Prüfung testet, dass neue Modulquellen ein erneutes Initialisieren erfordern, um installiert zu werden. Mit unserer nun modularen Konfiguration bleibt ein letztes großes Associate-Thema zu behandeln: der Zustand, den Terraform die ganze Zeit über stillschweigend verfolgt hat.
# 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 }
}Die Domänen Implement and Maintain State (19%) und Use Terraform Outside the Core Workflow (9%) sind hier angesiedelt. Zustand ist das JSON-Hauptbuch, das Ihre Konfigurationsadressen auf reale Objekte abbildet; terraform state list listet es auf und terraform state show <addr> gibt einen Eintrag aus. Die Prüfung erwartet, dass Sie wissen, dass das Umbenennen einer Ressource in der Konfiguration sie normalerweise zerstören und neu erstellen würde – es sei denn, Sie teilen Terraform mit, dass die Adresse verschoben wurde.
Ein moved-Block tut genau das deklarativ: Benennen Sie local_file.greeting (aus Schritt 2) in local_file.welcome um, und der moved-Block migriert den Zustand vor Ort, sodass plan eine Verschiebung anzeigt, nicht ein Zerstören und Neuerstellen. (Das imperative Äquivalent ist terraform state mv local_file.greeting local_file.welcome.) Wir zeigen auch einen konfigurationsgesteuerten import-Block – die 1.5+-Methode, um ein bereits bestehendes Objekt in den Zustand zu übernehmen, ohne den älteren terraform import CLI-Befehl zu verwenden. Mit dem kontrollierten Zustand gibt es noch eine Funktion zu erfüllen: all dies remote auszuführen.
# 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" {}Die letzte Domäne, Understand HCP Terraform Capabilities (5%), rundet die Prüfung ab. Ein einziger cloud-Block innerhalb von terraform {} tauscht die lokale Ausführung gegen HCP Terraform aus: Der Zustand lebt remote und wird während der Ausführungen gesperrt, terraform plan/apply werden auf den HashiCorp-Runnern ausgeführt, und die Ausführungsausgabe (plus ein gespeicherter Plan) erscheint in der Web-Benutzeroberfläche. Hier vergleicht die Prüfung auch HCP Terraform-Funktionen – Remote-Zustand, Ausführungsverlauf, Richtliniendurchsetzung mit Sentinel/OPA und ein privates Modulregister – mit dem rein lokalen Workflow, den wir in den Schritten 1–6 verwendet haben.
Ersetzen Sie my-org durch Ihre eigene Organisation, führen Sie einmal terraform login aus, um ein API-Token zu speichern, dann terraform init, um den Zustand in den Remote-Workspace zu migrieren. Alles, was Sie in diesem Lab geschrieben haben, läuft jetzt unverändert – nur wo es läuft, hat sich geändert. Diese Rundreise, von einer leeren main.tf zu einem Remote-gestützten Workspace, ist der gesamte Associate-Bogen in einer Sitzung.
terraform {
cloud {
organization = "my-org"
workspaces {
name = "terraform-associate-lab"
}
}
}
# Run once to authenticate, then re-init to migrate state:
# terraform login
# terraform initAlles befindet sich auf Ihrem Rechner, daher ist die Bereinigung schnell erledigt:
terraform destroy aus, um die generierten Dateien zu entfernen und sie aus dem Zustand zu löschen.terraform-associate-lab aus der HCP Terraform UI (oder führen Sie zuerst terraform destroy dagegen aus) und entfernen Sie dann den cloud-Block.cd .. && rm -rf tf-associate-lab. Der lokale .terraform/-Plugin-Cache, .terraform.lock.hcl und terraform.tfstate verschwinden damit.Bei der Associate-Prüfung geht es um Terraform als Werkzeug, nicht um eine bestimmte Cloud, daher provisioniert dieses Lab absichtlich keine Cloud-Infrastruktur. Wir überspringen AWS / Azure / GCP-Ressourcen absichtlich: Sie erfordern Anmeldeinformationen, können Kosten verursachen und würden von den Mechanismen ablenken, die die Prüfung tatsächlich testet – den Workflow, den Zustand, die Konfigurationssprache und die Module.
Einige Associate-Themen lassen sich besser nachlesen als ausführen: Backends außer HCP Terraform (S3, Azure Blob, GCS, Consul), Provisionierer wie remote-exec (die HashiCorp als letzten Ausweg listet) und Workspaces zur Verwaltung mehrerer Zustandsinstanzen. Für diese bieten die Abschnitte Durchsuchen und Handbuch dieser Zertifizierungsseite die konzeptionelle Abdeckung. Der praktische Wert hier liegt im Muskelgedächtnis von init → plan → apply, dem Lesen einer Zustandsdatei und der sicheren Refaktorisierung mit moved.