Introducción
Hasta ahora hemos visto cómo usar count y for_each para crear múltiples recursos del mismo tipo. Pero, ¿qué ocurre cuando necesitamos repetir bloques de configuración anidados dentro de un único recurso? Por ejemplo, un Security Group de AWS puede tener múltiples reglas de entrada (ingress) y salida (egress), y escribir cada una como un bloque estático se vuelve inmanejable cuando las reglas provienen de una variable.
Los bloques dinámicos (dynamic) resuelven exactamente este problema: permiten generar bloques de configuración repetitivos de forma programática, manteniendo el código limpio y flexible.
- ¿Qué son los Bloques Dinámicos?
Un bloque dinámico es una forma de iterar sobre una colección (lista o mapa) y generar múltiples bloques de configuración anidados dentro de un recurso. Son la solución de Terraform para evitar la repetición manual de bloques de configuración similares.
El problema que resuelven
Sin bloques dinámicos, tendrías que escribir así un Security Group con 4 reglas:
# SIN bloques dinámicos - Repetitivo y difícil de mantener
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
# Si mañana necesitas añadir otra regla, debes editar el recurso
# Si las reglas vienen de una variable externa, esto no es posible
}Con bloques dinámicos, las reglas se generan automáticamente desde una variable:
# CON bloques dinámicos - Limpio y flexible
variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
}))
}
resource "aws_security_group" "web" {
name = "web-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
- Sintaxis del Bloque
dynamic
dynamicLa sintaxis completa de un bloque dinámico es:
dynamic "nombre_del_bloque" {
# for_each es el iterador: puede ser una lista, set o mapa
for_each = coleccion
# iterator (opcional): renombra la variable de iteración
# Por defecto, tiene el mismo nombre que el bloque dinámico
iterator = alias_personalizado
# content define qué contiene cada bloque generado
content {
# Aquí se accede a los valores mediante nombre_del_bloque.value
# o mediante alias_personalizado.value si se especificó iterator
atributo = nombre_del_bloque.value.campo
}
}Variables disponibles dentro del bloque content
| Variable | Descripción |
|---|---|
nombre_del_bloque.key |
La clave del elemento actual (índice si es lista, clave si es mapa) |
nombre_del_bloque.value |
El valor completo del elemento actual |
nombre_del_bloque.value.campo |
Un campo específico del elemento actual |
- El Iterador y Cómo Personalizar su Nombre
Por defecto, la variable de iteración tiene el mismo nombre que el bloque dinámico. Esto puede ser confuso cuando el nombre del bloque es largo o cuando hay bloques dinámicos anidados. El atributo iterator te permite renombrarlo.
# Sin personalizar el iterador
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port # La variable se llama "ingress"
to_port = ingress.value.port
protocol = ingress.value.protocol
}
}
# Con iterador personalizado (más legible)
dynamic "ingress" {
for_each = var.ingress_rules
iterator = rule # Renombramos "ingress" a "rule"
content {
from_port = rule.value.port # Ahora se llama "rule"
to_port = rule.value.port
protocol = rule.value.protocol
# Más claro semánticamente: estamos iterando sobre "rules"
}
}
# El iterador personalizado es especialmente útil con bloques anidados
dynamic "statement" {
for_each = var.policy_statements
iterator = stmt # "stmt" en lugar de "statement"
content {
effect = stmt.value.effect
actions = stmt.value.actions
# "stmt" es más conciso que "statement"
}
}
- Cuándo Usar
dynamic vs for_each en Recursos
dynamic vs for_each en RecursosEs importante distinguir cuándo usar bloques dinámicos y cuándo usar for_each directamente en recursos:
| Escenario | Usar for_each en recurso |
Usar dynamic |
|---|---|---|
| Crear múltiples instancias del mismo tipo de recurso | Si | No |
| Crear múltiples configuraciones anidadas dentro de UN recurso | No | Si |
| Las reglas de un Security Group | No | Si |
| Múltiples Security Groups | Si | No |
Múltiples bloques ingress en un SG |
No | Si |
| Múltiples buckets S3 | Si | No |
| Múltiples reglas de lifecycle en un bucket | No | Si |
- Ejemplo 1: Security Group con Reglas Dinámicas
# variables.tf
variable "project_name" {
type = string
default = "myapp"
}
variable "environment" {
type = string
default = "prod"
}
variable "ingress_rules" {
description = "Lista de reglas de entrada para el security group"
type = list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{
description = "HTTP desde internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "HTTPS desde internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "SSH solo desde red interna"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
# Solo desde la red privada, no desde internet
},
{
description = "API interna"
from_port = 8080
to_port = 8090
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
]
}
variable "egress_rules" {
description = "Lista de reglas de salida para el security group"
type = list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{
description = "Todo el tráfico de salida"
from_port = 0
to_port = 0
protocol = "-1"
# "-1" significa "todos los protocolos" en AWS
cidr_blocks = ["0.0.0.0/0"]
}
]
}# main.tf
resource "aws_security_group" "web" {
name = "${var.project_name}-${var.environment}-web-sg"
description = "Security group para servidores web en ${var.environment}"
vpc_id = aws_vpc.main.id
# Bloque dinámico para reglas de ENTRADA
dynamic "ingress" {
for_each = var.ingress_rules
# Iteramos sobre la lista de reglas de entrada
iterator = rule
# Renombramos el iterador a "rule" para mayor claridad
content {
description = rule.value.description
# rule.value es el objeto completo de la regla actual
from_port = rule.value.from_port
to_port = rule.value.to_port
protocol = rule.value.protocol
cidr_blocks = rule.value.cidr_blocks
}
}
# Bloque dinámico para reglas de SALIDA
dynamic "egress" {
for_each = var.egress_rules
iterator = rule
content {
description = rule.value.description
from_port = rule.value.from_port
to_port = rule.value.to_port
protocol = rule.value.protocol
cidr_blocks = rule.value.cidr_blocks
}
}
tags = {
Name = "${var.project_name}-${var.environment}-web-sg"
Environment = var.environment
ManagedBy = "Terraform"
}
lifecycle {
create_before_destroy = true
# Crea el nuevo SG antes de destruir el viejo para evitar downtime
}
}Resultado: ¿Qué genera Terraform?
Terraform generará automáticamente uno de estos bloques por cada elemento en var.ingress_rules:
# Terraform genera internamente algo equivalente a:
ingress {
description = "HTTP desde internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS desde internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# ... y así para cada regla en la lista
- Ejemplo 2: IAM Policy con Statements Dinámicos
# iam.tf
variable "s3_policy_statements" {
description = "Lista de statements para la política IAM de S3"
type = list(object({
sid = string
effect = string
actions = list(string)
resources = list(string)
conditions = optional(list(object({
test = string
variable = string
values = list(string)
})), [])
# conditions es opcional y por defecto una lista vacía
}))
default = [
{
sid = "AllowReadPublicBucket"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:ListBucket"
]
resources = [
"arn:aws:s3:::my-public-bucket",
"arn:aws:s3:::my-public-bucket/*"
]
conditions = []
},
{
sid = "AllowWritePrivateBucket"
effect = "Allow"
actions = [
"s3:PutObject",
"s3:DeleteObject"
]
resources = [
"arn:aws:s3:::my-private-bucket/*"
]
conditions = [
{
test = "StringEquals"
variable = "s3:prefix"
values = ["uploads/"]
# Solo permite escribir en el prefijo "uploads/"
}
]
}
]
}
resource "aws_iam_policy" "s3_access" {
name = "s3-access-policy"
description = "Política de acceso a buckets S3"
policy = jsonencode({
Version = "2012-10-17"
# jsonencode convierte el objeto HCL a JSON para la API de IAM
Statement = [
# Usamos una for expression en lugar de dynamic aquí porque
# el bloque Statement es un array JSON, no bloques HCL anidados
for stmt in var.s3_policy_statements : {
Sid = stmt.sid
Effect = stmt.effect
Action = stmt.actions
Resource = stmt.resources
# Usamos el operador ternario para condiciones opcionales
Condition = length(stmt.conditions) > 0 ? {
# Si hay condiciones, las procesamos
for cond in stmt.conditions : cond.test => {
(cond.variable) = cond.values
}
} : null
# Si no hay condiciones, el campo Condition es null (se omite)
}
]
})
}
- Bloques Dinámicos Anidados
Los bloques dinámicos pueden anidarse para manejar estructuras más complejas. Hay que tener cuidado con los nombres de los iteradores para evitar confusiones.
# Ejemplo: ECS Task Definition con múltiples contenedores y múltiples variables de entorno
variable "containers" {
description = "Lista de contenedores para la task definition"
type = list(object({
name = string
image = string
cpu = number
memory = number
port_mappings = list(object({
container_port = number
host_port = number
protocol = string
}))
environment_vars = list(object({
name = string
value = string
}))
}))
}
resource "aws_ecs_task_definition" "app" {
family = "my-app"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "512"
memory = "1024"
container_definitions = jsonencode([
for container in var.containers : {
name = container.name
image = container.image
cpu = container.cpu
memory = container.memory
portMappings = [
for pm in container.port_mappings : {
containerPort = pm.container_port
hostPort = pm.host_port
protocol = pm.protocol
}
]
environment = [
for env in container.environment_vars : {
name = env.name
value = env.value
}
]
}
])
# Aquí usamos for expressions dentro de jsonencode en lugar de
# dynamic blocks porque estamos generando JSON, no bloques HCL
}Cuándo los bloques dinámicos anidados son necesarios
# Caso donde SÍ necesitamos dynamic anidados: atributos HCL anidados
# Ejemplo: AWS WAF con reglas y condiciones anidadas
resource "aws_wafv2_web_acl" "main" {
name = "web-acl"
scope = "REGIONAL"
default_action {
allow {}
}
# Bloque dinámico de PRIMER NIVEL: reglas
dynamic "rule" {
for_each = var.waf_rules
iterator = waf_rule
content {
name = waf_rule.value.name
priority = waf_rule.key
# waf_rule.key es el índice en la lista (0, 1, 2...)
action {
dynamic "allow" {
for_each = waf_rule.value.action == "allow" ? [1] : []
# [1] genera un bloque, [] no genera ninguno
# Esto es un patrón común: usar [1] o [] como condición booleana
content {}
}
dynamic "block" {
for_each = waf_rule.value.action == "block" ? [1] : []
content {}
}
}
statement {
# Bloque dinámico de SEGUNDO NIVEL: condiciones dentro de cada regla
dynamic "ip_set_reference_statement" {
for_each = waf_rule.value.ip_set_arn != null ? [waf_rule.value.ip_set_arn] : []
iterator = ip_ref
content {
arn = ip_ref.value
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = waf_rule.value.name
sampled_requests_enabled = true
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "web-acl"
sampled_requests_enabled = true
}
}
- La Expresión
for: Transformar Listas y Mapas
for: Transformar Listas y MapasLa expresión for (sin ser un bloque dinámico) permite transformar colecciones dentro de expresiones. Es complementaria a los bloques dinámicos.
Sintaxis
# Para generar una lista:
[for elemento in coleccion : expresion_de_transformacion]
[for elemento in coleccion : expresion IF condicion_filtro]
# Para generar un mapa:
{for clave, valor in mapa : nueva_clave => nuevo_valor}
{for elemento in lista : elemento.clave => elemento.valor}Ejemplos prácticos
variable "instances" {
type = list(object({
name = string
az = string
type = string
}))
default = [
{ name = "web-01", az = "us-east-1a", type = "t3.micro" },
{ name = "web-02", az = "us-east-1b", type = "t3.small" },
{ name = "db-01", az = "us-east-1a", type = "t3.medium" }
]
}
locals {
# Extraer solo los nombres de las instancias
instance_names = [for inst in var.instances : inst.name]
# → ["web-01", "web-02", "db-01"]
# Filtrar solo instancias web
web_instances = [for inst in var.instances : inst if startswith(inst.name, "web")]
# → [{name="web-01",...}, {name="web-02",...}]
# Convertir lista a mapa (nombre → tipo)
instance_type_map = {for inst in var.instances : inst.name => inst.type}
# → {"web-01" = "t3.micro", "web-02" = "t3.small", "db-01" = "t3.medium"}
# Transformar valores de un mapa existente
instance_names_upper = {
for name, type in local.instance_type_map : upper(name) => type
}
# → {"WEB-01" = "t3.micro", "WEB-02" = "t3.small", "DB-01" = "t3.medium"}
# Agrupar instancias por zona de disponibilidad
instances_by_az = {
for az in distinct([for inst in var.instances : inst.az]) :
az => [for inst in var.instances : inst.name if inst.az == az]
}
# → {"us-east-1a" = ["web-01", "db-01"], "us-east-1b" = ["web-02"]}
}
- Diferencia entre
for Expressions y for_each
for Expressions y for_each| Característica | for expression |
for_each en recurso/bloque dinámico |
|---|---|---|
| Propósito | Transformar datos en locals/variables | Crear múltiples recursos o bloques |
| Resultado | Una lista o mapa de valores | Múltiples instancias de un recurso o bloque |
| Referenciable como recurso | No | Si (como aws_instance.web["key"]) |
| Aparece en el plan | No (solo datos internos) | Si (como recursos a crear/modificar) |
| Dónde se usa | Dentro de locals, variable, argumentos |
En el bloque de un recurso, dynamic |
- Tabla: Comparativa de Técnicas de Iteración en Terraform
| Técnica | Escenario ideal | Sintaxis básica | Acceso a elementos |
|---|---|---|---|
count |
N recursos idénticos o casi idénticos | count = 3 |
aws_instance.web[0] |
for_each en recurso |
N recursos con configuración variada por clave | for_each = var.instances |
aws_instance.web["web-01"] |
dynamic |
Múltiples bloques anidados en UN recurso | dynamic "ingress" { for_each = ... } |
Solo dentro del recurso |
for expression |
Transformar datos (listas/mapas) | [for x in list : x.campo] |
Como cualquier local |
for en jsonencode |
Generar JSON con arrays dinámicos | Dentro de jsonencode(...) |
Solo como JSON |
- Ejercicio Práctico: Security Group con Reglas Totalmente Dinámicas
Objetivo
Crear un módulo de Security Group que acepte las reglas como variables y las aplique dinámicamente. La solución debe manejar tanto reglas por puerto único como por rangos de puertos.
Instrucciones
- Crea un directorio
sg-module/ - Define una variable
ingress_rulescon tipo flexible - Usa bloques dinámicos para generar las reglas
- Incluye un output con información del SG creado
Solución Detallada
# sg-module/variables.tf
variable "name" {
description = "Nombre del security group"
type = string
}
variable "description" {
description = "Descripción del security group"
type = string
default = "Managed by Terraform"
}
variable "vpc_id" {
description = "ID de la VPC donde crear el security group"
type = string
}
variable "ingress_rules" {
description = "Reglas de entrada. Cada regla puede ser por puerto único o rango."
type = list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = optional(list(string), [])
# optional() permite omitir el campo; usa [] por defecto
ipv6_cidr_blocks = optional(list(string), [])
self = optional(bool, false)
# self = true permite tráfico entre instancias del mismo SG
}))
default = []
}
variable "egress_allow_all" {
description = "Si true, añade una regla que permite todo el tráfico de salida"
type = bool
default = true
}
variable "tags" {
description = "Tags adicionales para el security group"
type = map(string)
default = {}
}# sg-module/main.tf
locals {
# Preparamos las reglas de egress
# Si egress_allow_all = true, creamos la regla "allow all"
# Si egress_allow_all = false, no generamos ninguna regla de egress
egress_rules = var.egress_allow_all ? [
{
description = "Permite todo el tráfico de salida"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
self = false
}
] : []
# Lista vacía = no se generarán bloques egress
}
resource "aws_security_group" "this" {
# "this" es una convención en módulos de Terraform: el recurso principal
name = var.name
description = var.description
vpc_id = var.vpc_id
# Bloque dinámico para reglas de ENTRADA
dynamic "ingress" {
for_each = var.ingress_rules
# Terraform asigna automáticamente un índice (0, 1, 2...) como clave
content {
description = ingress.value.description
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
ipv6_cidr_blocks = ingress.value.ipv6_cidr_blocks
self = ingress.value.self
}
}
# Bloque dinámico para reglas de SALIDA
dynamic "egress" {
for_each = local.egress_rules
# Será vacío si egress_allow_all = false
content {
description = egress.value.description
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
cidr_blocks = egress.value.cidr_blocks
ipv6_cidr_blocks = egress.value.ipv6_cidr_blocks
self = egress.value.self
}
}
tags = merge(var.tags, {
Name = var.name
ManagedBy = "Terraform"
})
lifecycle {
create_before_destroy = true
# Siempre crear el nuevo SG antes de destruir el viejo
# Esto evita que las instancias queden sin SG durante la actualización
}
}# sg-module/outputs.tf
output "security_group_id" {
description = "ID del security group creado"
value = aws_security_group.this.id
}
output "security_group_name" {
description = "Nombre del security group"
value = aws_security_group.this.name
}
output "ingress_rules_count" {
description = "Número de reglas de entrada aplicadas"
value = length(var.ingress_rules)
}# Uso del módulo desde el proyecto principal (main.tf)
module "web_sg" {
source = "./sg-module"
name = "web-server-sg"
vpc_id = aws_vpc.main.id
ingress_rules = [
{
description = "HTTP público"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "HTTPS público"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "SSH solo desde VPN"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["203.0.113.0/24"]
# Solo desde la IP de la VPN corporativa
},
{
description = "Monitoreo interno"
from_port = 9100
to_port = 9100
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
# Puerto del Node Exporter de Prometheus
}
]
egress_allow_all = true
tags = {
Environment = "production"
Team = "platform"
}
}
module "db_sg" {
source = "./sg-module"
name = "database-sg"
vpc_id = aws_vpc.main.id
ingress_rules = [
{
description = "PostgreSQL desde servidores web"
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["10.0.0.0/24"]
# Solo desde la subred de los servidores web
},
{
description = "PostgreSQL entre réplicas"
from_port = 5432
to_port = 5432
protocol = "tcp"
self = true
# Tráfico entre instancias del mismo SG (réplicas de BD)
}
]
egress_allow_all = false
# La base de datos no necesita acceso saliente irrestricto
tags = {
Environment = "production"
Team = "data"
}
}Verificar el resultado
# Inicializar y planificar
terraform init
terraform plan
# El plan mostrará algo como:
# + resource "aws_security_group" "this" { (módulo web_sg)
# + ingress {
# + from_port = 80
# ...
# }
# + ingress {
# + from_port = 443
# ...
# }
# + ingress {
# + from_port = 22
# ...
# }
# + ingress {
# + from_port = 9100
# ...
# }
# + egress {
# + from_port = 0
# ...
# }
# }
- Errores Comunes con Bloques Dinámicos
Error 1: Usar dynamic con bloques que no lo soportan
# Problema: No todos los bloques dentro de un recurso pueden ser dinámicos
resource "aws_s3_bucket" "example" {
bucket = "my-bucket"
# CORRECTO: lifecycle_rule es un bloque repetible → soporta dynamic
dynamic "lifecycle_rule" {
for_each = var.lifecycle_rules
content {
id = lifecycle_rule.value.id
enabled = lifecycle_rule.value.enabled
# ...
}
}
# INCORRECTO: El bloque "website" solo puede aparecer UNA vez
# No tendría sentido hacerlo dynamic
# dynamic "website" { ... } ← Error o resultado inesperado
}Error 2: Confundir for_each de recurso con for_each de dynamic
# Ambos usan for_each pero tienen propósitos totalmente diferentes
# for_each EN EL RECURSO: Crea 3 security groups distintos
resource "aws_security_group" "per_env" {
for_each = toset(["dev", "staging", "prod"])
name = "sg-${each.key}"
}
# dynamic con for_each: Crea UN security group con 3 reglas
resource "aws_security_group" "with_rules" {
name = "sg-with-rules"
dynamic "ingress" {
for_each = toset(["80", "443", "22"])
content {
from_port = tonumber(ingress.value)
to_port = tonumber(ingress.value)
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}Error 3: Acceder a campos que no existen en value
variable "rules" {
type = list(object({
port = number
protocol = string
# "description" NO está definido en el objeto
}))
}
resource "aws_security_group" "example" {
dynamic "ingress" {
for_each = var.rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
description = ingress.value.description # ERROR: campo no definido en el tipo
}
}
}
# Solución 1: Añadir el campo al tipo
variable "rules" {
type = list(object({
port = number
protocol = string
description = optional(string, "") # optional con valor por defecto
}))
}
# Solución 2: Usar un valor por defecto con try()
content {
description = try(ingress.value.description, "")
# try() devuelve el primer valor que no cause error
}Error 4: Olvidar que for_each en dynamic NO acepta listas directamente cuando son de objetos complejos
# A veces necesitas convertir la lista a un mapa para mejor rastreabilidad
variable "rules" {
type = list(object({
name = string
port = number
}))
}
resource "aws_security_group" "example" {
# Opción 1: Usar la lista directamente (índice numérico como key)
dynamic "ingress" {
for_each = var.rules # El key será 0, 1, 2...
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = ingress.value.name
}
}
# Opción 2: Convertir a mapa por nombre para mejor identificación en el estado
# (más útil cuando las reglas pueden cambiar de orden)
dynamic "ingress" {
for_each = {for rule in var.rules : rule.name => rule}
# Ahora el key es rule.name en lugar de un índice numérico
iterator = rule_entry
content {
from_port = rule_entry.value.port
to_port = rule_entry.value.port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = rule_entry.key # El key es el nombre de la regla
}
}
}Resumen
En esta lección hemos dominado los bloques dinámicos de Terraform:
- Los bloques
dynamicpermiten generar bloques de configuración anidados de forma programática, evitando la repetición manual. - La sintaxis básica es
dynamic "nombre_bloque" { for_each = coleccion content { ... } }. - El iterador (
iterator) permite renombrar la variable de acceso para mayor claridad, especialmente en bloques anidados. - Las for expressions complementan a los bloques dinámicos para transformar datos antes de usarlos.
- El patrón
for_each = condicion ? [1] : []permite hacer bloques opcionales de forma elegante. - Es fundamental distinguir entre
for_eachen recursos (crea múltiples recursos) yfor_eachendynamic(crea múltiples bloques dentro de un recurso).
En la siguiente y última lección del módulo 6 aprenderemos sobre terraform import, una herramienta esencial para adoptar Terraform en proyectos con infraestructura existente.
Curso de Terraform
Módulo 1: Introducción a Terraform
- ¿Qué es Terraform?
- Instalando Terraform
- Conceptos Básicos de Terraform
- Primera Configuración de Terraform
Módulo 2: Lenguaje de Configuración de Terraform
Módulo 3: Gestión del Estado
Módulo 4: Módulos de Terraform
Módulo 5: Aprovisionamiento de Recursos
- Conceptos Básicos de Aprovisionamiento
- Aprovisionamiento de Recursos AWS
- Aprovisionamiento de Recursos Azure
- Aprovisionamiento de Recursos GCP
Módulo 6: Funcionalidades Avanzadas de Terraform
Módulo 7: Mejores Prácticas de Terraform
- Organización del Código
- Control de Versiones
- Pruebas del Código de Terraform
- Mejores Prácticas de Seguridad
Módulo 8: Terraform en CI/CD
- Integración de Terraform con CI/CD
- Automatización de Terraform con Jenkins
- Uso de Terraform con GitHub Actions
- Terraform Cloud y Enterprise
