Última revisión: mayo de 2026
Crea los servicios de AWS del examen 004 con Terraform puro: bloque a bloque, cada uno vinculado a un dominio del examen. El mismo código funciona en OpenTofu.
Al finalizar este laboratorio, habrá ejecutado el flujo de trabajo principal completo de Terraform de principio a fin y habrá abordado todos los dominios del examen de Asociado, sin una cuenta en la nube ni un solo céntimo de gasto. Utilizamos los proveedores random y local, que no necesitan credenciales, por lo que el enfoque se mantiene en lo que realmente evalúa el examen: cómo se comporta Terraform.
Escribirá su primer recurso, lo parametrizará con variables y salidas, generará muchos recursos con for_each, refactorizará la repetición en un módulo reutilizable, inspeccionará y migrará el estado, y finalmente dirigirá la configuración a HCP Terraform para ejecuciones remotas. Cada fragmento es Terraform puro; el mismo código funciona sin modificar en OpenTofu. Coloque los bloques en un único main.tf (mencionaremos los pocos que viven en sus propios archivos), ejecute terraform init una vez y luego terraform apply paso a paso.
>= 1.5 o OpenTofu >= 1.6 en su PATH (terraform version). Usamos bloques import controlados por configuración en el Paso 6, que requieren 1.5+.random y local se ejecutan completamente en su máquina.mkdir tf-associate-lab && cd tf-associate-lab).Este laboratorio es completamente gratuito. Los proveedores random y local no crean recursos en la nube, solo unos pocos archivos pequeños en su propio disco y entradas en un terraform.tfstate local. El Paso 7 (HCP Terraform) utiliza el nivel gratuito, que es suficiente para un único espacio de trabajo de laboratorio. No hay nada aquí que genere cargos mientras esté inactivo.
Antes de que se ejecute nada, declaramos qué versión de Terraform esperamos y de qué proveedores dependemos. Fijar la versión es uno de los temas favoritos del examen: los dominios Conceptos básicos de Terraform y Flujo de trabajo principal evalúan que comprende required_version, el bloque required_providers y el papel de terraform init en la descarga de complementos de proveedores en .terraform/.
Elegimos deliberadamente random y local. Ninguno necesita un inicio de sesión en la nube, por lo que todo el laboratorio sigue siendo gratuito y reproducible, y el examen nunca pregunta sobre una nube específica de todos modos, sino sobre Terraform. Coloque esto en un main.tf nuevo y ejecute terraform init; verá cómo Terraform resuelve y bloquea ambos proveedores en un archivo .terraform.lock.hcl, que es en sí mismo un punto clave del dominio de conceptos básicos (confirme el archivo de bloqueo para que cada ejecución utilice versiones de complemento idénticas).
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.Ahora ejercitamos el corazón del dominio Usar el flujo de trabajo principal de Terraform (18% del examen). Declaramos un random_pet que inventa un nombre amigable, luego un local_file que escribe ese nombre en el disco. El recurso local_file.greeting hace referencia a random_pet.name.id, y esa referencia es lo que le dice a Terraform que el archivo depende de la mascota — orden de dependencia implícito, no se necesita depends_on.
Ejecute el flujo de trabajo en orden: terraform plan le muestra una diferencia de lo que cambiará antes de que suceda algo, y terraform apply lo hace realidad y registra el resultado en terraform.tfstate. Ejecute terraform apply una segunda vez sin cambiar nada y verá No changes (Sin cambios) — eso es idempotencia, y al examen le encanta preguntar por qué una segunda aplicación no hace nada. Con un recurso funcional y un archivo de estado nuevo en mano, podemos comenzar a flexibilizar la configuración.
resource "random_pet" "name" {
length = 2
separator = "-"
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}Los valores codificados son útiles para una demostración, pero el dominio Leer, generar y modificar la configuración (19%) espera que usted los parametrice. Agregamos una variable de entrada con un type, un default y un bloque validation que rechaza cualquier cosa fuera de nuestros entornos permitidos — la validación se ejecuta en tiempo de planificación y es una pregunta frecuente en el examen. Calculamos un mapa locals una vez y lo reutilizamos, y exponemos los resultados con bloques output para que otras configuraciones (y terraform output) puedan leerlos.
Observe la estratificación que evalúa el examen: variable es la entrada, locals es un valor derivado/intermedio y output es el resultado publicado. Aplique de nuevo e intente terraform output pet_name para leer un solo valor, o terraform output -json para usarlo en un script. Con las entradas y salidas conectadas, estamos listos para dejar de escribir un recurso a la vez y generar un conjunto completo.
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
}Las configuraciones reales rara vez declaran una instancia de cada cosa. Aquí nos adentramos más en el dominio Leer, generar y modificar la configuración al impulsar la creación de recursos a partir de una colección. Una variable set(string) enumera servicios lógicos; for_each luego genera un random_string y un local_file por cada servicio, accesibles como random_string.suffix["api"] y así sucesivamente.
Este paso también muestra expresiones y funciones integradas que el examen espera que reconozca: each.key para el elemento actual, interpolación de cadenas para construir un nombre de bucket por servicio, y jsonencode() para convertir un objeto HCL en un archivo JSON en disco. Reutilizamos var.environment y local.common_tags del Paso 3 para que cada archivo generado contenga metadatos coherentes. La siguiente pregunta obvia — esto se está volviendo repetitivo, ¿cómo lo empaqueto? — es exactamente lo que responden los módulos.
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
})
}El dominio Interactuar con módulos de Terraform requiere que usted cree un módulo, lo llame y pase valores de entrada y salida. Movemos la lógica por servicio a un módulo hijo bajo ./modules/service, le damos sus propias entradas variable y una output, y luego lo llamamos desde la raíz con for_each — una instancia de módulo por servicio.
Los dos archivos a continuación muestran claramente el límite: el módulo hijo no sabe nada sobre qué servicios existen (esa es la tarea del llamador a través de var.name), y la raíz no sabe nada sobre cómo se construye un servicio (eso está encapsulado en el módulo). Ejecute terraform init nuevamente después de agregar un módulo — el examen evalúa que las nuevas fuentes de módulos requieren una reinicialización para ser instaladas. Con nuestra configuración ahora modular, el último gran tema del examen de Asociado es lo que Terraform ha estado rastreando discretamente todo el tiempo: el estado.
# 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 }
}Los dominios Implementar y mantener el estado (19%) y Usar Terraform fuera del flujo de trabajo principal (9%) residen aquí. El estado es el registro JSON que mapea las direcciones de su configuración a objetos reales; terraform state list lo enumera y terraform state show <addr> imprime una entrada. El examen espera que sepa que renombrar un recurso en la configuración normalmente lo destruiría y recrearía — a menos que le diga a Terraform que la dirección se movió.
Un bloque moved hace exactamente eso de forma declarativa: cambie el nombre de local_file.greeting (del Paso 2) a local_file.welcome y el bloque moved migrará el estado en su lugar, por lo que plan mostrará un movimiento, no una destrucción y recreación. (El equivalente imperativo es terraform state mv local_file.greeting local_file.welcome.) También mostramos un bloque import controlado por configuración — la forma para 1.5+ de adoptar un objeto preexistente al estado sin el comando CLI terraform import más antiguo. Con el estado bajo control, queda una capacidad por cubrir: ejecutar todo esto de forma remota.
# 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" {}El dominio final, Comprender las capacidades de HCP Terraform (5%), completa el examen. Un solo bloque cloud dentro de terraform {} cambia la ejecución local por HCP Terraform: el estado reside de forma remota y se bloquea durante las ejecuciones, terraform plan/apply se ejecutan en los runners de HashiCorp, y la salida de la ejecución (más un plan almacenado) aparece en la interfaz de usuario web. Aquí es también donde el examen contrasta las características de HCP Terraform — estado remoto, historial de ejecución, aplicación de políticas con Sentinel/OPA y un registro de módulos privados — frente al flujo de trabajo puramente local que usamos en los Pasos 1-6.
Reemplace my-org con su propia organización, ejecute terraform login una vez para almacenar un token de API, luego terraform init para migrar el estado al espacio de trabajo remoto. Todo lo que escribió en este laboratorio ahora se ejecuta sin cambios — solo se ha movido dónde se ejecuta. Ese recorrido, desde un simple main.tf hasta un espacio de trabajo respaldado remotamente, es todo el arco del examen de Asociado en una sola sesión.
terraform {
cloud {
organization = "my-org"
workspaces {
name = "terraform-associate-lab"
}
}
}
# Run once to authenticate, then re-init to migrate state:
# terraform login
# terraform initTodo reside en su máquina, por lo que la limpieza es rápida:
terraform destroy para eliminar los archivos generados y borrarlos del estado.terraform-associate-lab de la interfaz de usuario de HCP Terraform (o ejecute terraform destroy primero), luego elimine el bloque cloud.cd .. && rm -rf tf-associate-lab. La caché de complementos local .terraform/, .terraform.lock.hcl y terraform.tfstate se eliminan con él.El examen de Asociado trata sobre Terraform como herramienta, no sobre una nube en particular, por lo que este laboratorio intencionalmente no aprovisiona infraestructura en la nube. Omitimos los recursos de AWS / Azure / GCP a propósito: requieren credenciales, pueden generar costos y desviarían la atención de los mecanismos que realmente evalúa el examen — el flujo de trabajo, el estado, el lenguaje de configuración y los módulos.
Algunos temas del examen de Asociado es mejor leerlos que ejecutarlos: backends distintos de HCP Terraform (S3, Azure Blob, GCS, Consul), provisioners como remote-exec (que HashiCorp enumera como último recurso) y workspaces para gestionar múltiples instancias de estado. Para esos temas, las secciones Buscar y Manual de esta página de certificación tienen la cobertura conceptual. El valor práctico aquí es la memoria muscular de init → plan → apply, la lectura de un archivo de estado y la refactorización segura con moved.