Última revisión: mayo de 2026
Crea los servicios de AWS del examen TF-PRO 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á practicado los cuatro dominios que el examen Terraform Pro valora más: la creación de HCL avanzado, la construcción y composición de módulos, la manipulación de estados (state surgery) y la operación contra HCP Terraform, todo ello con los proveedores random y local libres de credenciales, por lo que no hay nada que pagar ni nada que limpiar más allá de una carpeta.
Modelará entradas con ricas restricciones de tipo y validación, transformará colecciones con las expresiones y funciones en las que se basa el examen, creará un módulo reutilizable y lo expandirá con for_each, y luego refactorizará el estado activo con los bloques import, moved y removed. El paso final conecta todo a HCP Terraform para la ejecución remota. Cada fragmento es Terraform puro, idéntico en OpenTofu. Constrúyalo en un único directorio de trabajo; destacamos los archivos que se encuentran bajo ./modules.
>= 1.9 o OpenTofu >= 1.8 (terraform version). Usamos bloques removed (1.7+) y import (1.5+), por lo que una versión actual es más importante aquí que en el laboratorio de Asociado.random y local se ejecutan completamente en su máquina.mkdir tf-pro-lab && cd tf-pro-lab).terraform fmt y terraform validate después de cada edición — el examen Pro asume que los trata como parte de la autoría, no como una ocurrencia tardía.Este laboratorio es completamente gratuito. Los proveedores random y local solo crean pequeños archivos en su propio disco más un terraform.tfstate local; no se provisiona nada en ninguna nube y no se cobra nada mientras está inactivo. El Paso 7 (HCP Terraform) se ejecuta en la capa gratuita, que cubre un solo espacio de trabajo de laboratorio y las características operativas que el examen pregunta.
Comenzamos el dominio de HCL y Configuración fijando una versión reciente de Terraform — el examen Pro asume características del lenguaje que solo existen en versiones más nuevas (atributos de objeto optional(), bloques removed). El bloque required_providers bloquea random y local, y terraform init escribe un archivo .terraform.lock.hcl que debe confirmar para que los planes sean reproducibles en un equipo.
Los dos hábitos que debe adquirir ahora se mantienen durante todo el laboratorio: terraform fmt canoniza el espaciado y la alineación (el examen comprueba que reescribe los archivos en su lugar y sale con un valor distinto de cero en el modo -check), y terraform validate comprueba que su configuración es internamente consistente antes de que se contacte con cualquier proveedor. Con la cadena de herramientas fijada y estos dos comandos a su disposición, podemos escribir HCL que valga la pena validar.
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, offlineEl examen Pro va mucho más allá de type = string. Aquí declaramos un tipo object con atributos optional() que proporcionan valores predeterminados cuando un llamador los omite, luego adjuntamos múltiples bloques validation — el examen comprueba que una variable puede contener más de uno, cada uno con su propia condition y error_message.
La función try() en locals es otro movimiento a nivel Pro: devuelve la primera expresión que se evalúa sin error, por lo que la lectura de un atributo que quizás no esté presente se degrada de forma elegante en lugar de detener el plan. Su función hermana can() (que devuelve un booleano) aparece más adelante en la validación del Paso 4. Esta variable platform se convierte en la única fuente de verdad para el resto del laboratorio — cada paso posterior la lee, por lo que acertar con el tipo y sus garantías aquí se amortiza más adelante.
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"
}La manipulación de colecciones es la parte más densa del dominio HCL y Configuración. Usamos setproduct() para construir cada par región/servicio, luego una expresión for para transformar esos pares en un mapa con claves — el patrón exacto que luego alimentará un for_each. Junto a esto, flatten() + distinct() colapsan una lista de listas de propietarios en un conjunto único y limpio.
Estas son las funciones a las que las preguntas Pro vuelven una y otra vez: for con un => para producir un mapa, setproduct para productos cartesianos, flatten para eliminar un nivel de anidamiento, merge para combinar mapas y jsonencode para serializar el resultado. Renderizamos el mapa deployments calculado a un archivo para que pueda ejecutar cat out/deployments.json y ver la forma que produjo su expresión. Ese mapa con claves es precisamente el tipo de valor que pasaremos al for_each de un módulo a continuación.
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)
}Ahora el dominio de Módulos (25%). Extraemos un submódulo workload bajo ./modules/workload con sus propios required_providers, entradas validadas y salidas publicadas. La expresión can(regex(...)) en la validación del nombre es el modismo Pro: regex generaría un error en caso de no coincidencia, y can convierte ese error en un false que la condition puede usar.
Un módulo bien construido oculta sus componentes internos y expone un contrato estable: los llamadores pasan name y replicas, y reciben un id y una manifest_path, sin tocar nunca el random_string interno. El módulo que declara sus propios requisitos de proveedor es un punto Pro deliberado: la configuración del proveedor se hereda de la raíz por defecto, pero los requisitos del proveedor se declaran por módulo. Con un módulo limpio en mano, la pregunta interesante es cómo instanciarlo muchas veces y conectar las instancias.
# 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
}Todavía en el dominio de Módulos, ahora llamamos a module.workload una vez por carga de trabajo con for_each sobre un conjunto, establecemos replicas condicionalmente por clave, y luego consumimos la salida id de cada instancia en un único registry.json agregado. Iterar sobre module.workload con una expresión for es la forma canónica en que el examen conecta las salidas de un módulo con las entradas de otro recurso.
El depends_on explícito en el archivo agregado es un punto de aprendizaje: las dependencias implícitas de la referencia module.workload ya ordenan las cosas correctamente, pero depends_on hace que la intención sea inconfundible y ocasionalmente es necesario cuando una dependencia no se expresa a través de datos. Ejecute terraform init después de agregar la fuente del módulo, luego apply. Con varias instancias de módulo activas y sus salidas agregadas, tenemos un estado real sobre el cual operar, que es el siguiente dominio.
# 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 }
}El dominio de CLI y Gestión de Estado (25%) es donde el examen Pro se diferencia del de Asociado. Usamos tres operaciones de estado controladas por configuración. Un bloque import adopta un objeto que ya existe en el estado — nos dirigimos a un terraform_data incorporado para que el ejemplo permanezca libre de credenciales. Un bloque removed elimina un recurso del estado sin destruir el objeto real — exactamente lo que se busca al entregar la propiedad a otra configuración. Y terraform_data con replace_triggered_by fuerza un reemplazo posterior cada vez que el registro del Paso 5 cambia, sin involucrar a ningún proveedor.
Conozca también los equivalentes imperativos de la CLI que el examen sigue evaluando: terraform state list y state show para inspeccionar, terraform state mv para renombrar, terraform state rm para olvidar, terraform plan -target=ADDR para acotar una ejecución, y terraform apply -replace=ADDR para forzar la recreación de un recurso. Con el estado firmemente bajo su control localmente, el último dominio es hacer todo esto en 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]
}
}El dominio de Operaciones de HCP Terraform (20%) cierra el examen. Un bloque cloud — aquí usando workspaces { tags = [...] } para que una configuración pueda mapearse a múltiples espacios de trabajo coincidentes por etiqueta — traslada la ejecución fuera de su portátil. Las ejecuciones tienen lugar en HCP Terraform con estado remoto y bloqueado; el examen contrasta los modos de ejecución remoto, local y por agente, y los flujos de trabajo de ejecución impulsados por VCS, impulsados por CLI e impulsados por API.
Aquí es también donde se unen las características operativas que menciona el examen Pro: conjuntos de variables para compartir entradas entre espacios de trabajo, disparadores de ejecución para encadenar el apply de un espacio de trabajo con el plan de otro, tareas de ejecución para integraciones externas, conjuntos de políticas (Sentinel u OPA) para controlar los apply, y un registro de módulos privado para publicar el tipo de módulo que construyó en el Paso 4. Cambie my-org por su organización, ejecute terraform login, luego terraform init para migrar el estado. La configuración que escribió en los Pasos 1 a 6 se ejecuta sin cambios — solo el lugar y las barreras de protección a su alrededor han subido de nivel, que es toda la historia Pro.
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["terraform-pro-lab"]
}
}
}
# Authenticate once, then migrate state to the remote workspace:
# terraform login
# terraform initTodo es local, por lo que el desmantelamiento es rápido:
terraform destroy para eliminar los archivos generados y borrarlos del estado. Debido al bloque removed en el Paso 6, random_string.deprecated (si alguna vez existió) se olvida en lugar de destruirse — esto es intencionado.terraform destroy contra el espacio de trabajo remoto, luego elimine el espacio de trabajo terraform-pro-lab en la interfaz de usuario y elimine el bloque cloud.cd .. && rm -rf tf-pro-lab. La caché .terraform/, el archivo .terraform.lock.hcl y cualquier estado local se eliminan con él.El examen Pro evalúa la autoría y las operaciones de Terraform, no una nube en particular, por lo que este laboratorio no provisiona infraestructura en la nube a propósito — esto lo mantiene libre de credenciales y permite que cada paso se concentre en HCL, módulos, estado y HCP Terraform.
Un puñado de temas relevantes para el examen Pro se estudian mejor que se provisionan aquí: los bloques dynamic (que necesitan un tipo de recurso con bloques anidados repetibles — un recurso en la nube — para ser significativos), los configuration_aliases del proveedor y el paso de proveedores con alias a módulos, la autoría de políticas Sentinel / OPA, y el flujo de publicación del registro de módulos privado. Las secciones de Buscar y Manual de esta página de certificación cubren esos temas conceptualmente. El valor práctico aquí es el ciclo de autoría y operaciones en torno al cual se construye el examen: entradas complejas validadas, módulos compuestos, manipulación de estado sin temor y ejecuciones remotas.