Translate

miércoles, 7 de octubre de 2026

Terraform avanzado: count, for_each y expresiones


Hasta ahora vimos cómo describir infraestructura con Terraform.


Aprendimos a crear resources:


resource "local_file" "hello" {

  filename = "hello.txt"

  content  = "Hola!"

}


También vimos variables:


variable "environment" {

  type = string

}



Y módulos:


module "application" {

  source = "./modules/application"

}


Pero hay un problema.

En infraestructura real, muchas veces no queremos crear un único recurso.


Queremos crear:

  • 3 máquinas
  • 5 buckets
  • 10 reglas
  • 2 redes


Y probablemente tampoco queramos escribir diez veces el mismo bloque.

Ahí Terraform empieza a mostrar otra de sus características interesantes: podemos utilizar expresiones para construir infraestructura de forma dinámica.


Empecemos con algo sencillo. Supongamos que queremos crear tres archivos.

Sin count tendríamos que escribir:


resource "local_file" "file1" {

  filename = "file1.txt"

  content  = "Archivo 1"

}


resource "local_file" "file2" {

  filename = "file2.txt"

  content  = "Archivo 2"

}


resource "local_file" "file3" {

  filename = "file3.txt"

  content  = "Archivo 3"

}


Funciona.

Pero estamos repitiendo código.

Terraform nos permite utilizar count:


resource "local_file" "file" {

  count = 3


  filename = "file-${count.index}.txt"

  content  = "Archivo ${count.index}"

}


Ahora Terraform crea tres instancias del resource.


local_file.file[0]

local_file.file[1]

local_file.file[2]


El valor: count.index representa el índice de la instancia actual.


Por lo tanto:

count.index = 0


genera:

file-0.txt


Después:

count.index = 1


genera:

file-1.txt


Y finalmente:

count.index = 2


genera:

file-2.txt


Lo interesante es que count puede depender de una variable.


Por ejemplo:


variable "file_count" {

  type    = number

  default = 3

}


Nuestro resource:


resource "local_file" "file" {

  count = var.file_count


  filename = "file-${count.index}.txt"

  content  = "Archivo ${count.index}"

}


Ahora podemos cambiar:


file_count = 10


y Terraform creará diez recursos.


Tenemos entonces:

variable

    │

    ▼

file_count = 10

    │

    ▼

  count

    │

    ▼

10 resources


Esto ya resulta bastante útil para infraestructura real.


Pero count tiene una limitación


Supongamos que tenemos:

count = 3


Terraform identifica los recursos por posición:

resource[0]

resource[1]

resource[2]


Ahora imaginemos que queremos eliminar el segundo elemento.

Por ejemplo, teníamos:

[ "web", "api", "database" ]


y queremos:

[ "web", "database" ]


Si utilizamos count, las posiciones cambian:


Antes:

0 → web

1 → api

2 → database


Después:

0 → web

1 → database


Terraform puede interpretar que:

resource[1]


cambió de api a database.

Y eso puede provocar cambios que no esperábamos.

Cuando los elementos tienen una identidad propia, count muchas veces no es la mejor opción.

Ahí aparece: for_each


for_each permite crear múltiples instancias utilizando una colección.


Por ejemplo:


variable "files" {

  type = set(string)


  default = [

    "hello",

    "goodbye",

    "config"

  ]

}


Podemos hacer:


resource "local_file" "file" {

  for_each = var.files


  filename = "${each.key}.txt"

  content  = "Archivo ${each.key}"

}



Terraform creará:

hello.txt

goodbye.txt

config.txt


Pero ahora los recursos tienen una identidad basada en su clave:


local_file.file["hello"]

local_file.file["goodbye"]

local_file.file["config"]



Esto es diferente de:


local_file.file[0]

local_file.file[1]

local_file.file[2]


Podemos resumir la diferencia:

  • count : instancias identificadas por índice
  • for_each : instancias identificadas por clave

Por ejemplo:

count:

server[0]

server[1]

server[2]


Mientras que:

for_each:


server["web"]

server["api"]

server["database"]


Cuando cada elemento tiene una identidad propia, for_each suele expresar mejor nuestra intención.

for_each resulta especialmente interesante con mapas.


Por ejemplo:

variable "servers" {

  type = map(string)


  default = {

    web      = "small"

    api      = "medium"

    database = "large"

  }

}


Podemos escribir:


resource "local_file" "server" {

  for_each = var.servers


  filename = "${each.key}.txt"

  content  = "Server type: ${each.value}"

}


Ahora:

each.key


representa:

web

api

database


y:

each.value


representa:

small

medium

large


Por ejemplo, para web:

each.key   = "web"

each.value = "small"


Hasta ahora usamos for_each para crear recursos.

Pero Terraform también tiene expresiones for que permiten transformar colecciones.


Supongamos:


variable "names" {

  default = [

    "juan",

    "pedro",

    "ana"

  ]

}


Podemos crear una nueva lista:


locals {

  upper_names = [

    for name in var.names : upper(name)

  ]

}


El resultado será:

[

  "JUAN",

  "PEDRO",

  "ANA"

]


La sintaxis básica es:


[

  for elemento in coleccion : expresion

]


Es bastante parecida a un map en otros lenguajes o listas por comprensión.


Podemos pensarlo como:


colección

    │

    ▼

   for

    │

    ▼

transformación

    │

    ▼

nueva colección


Las expresiones for también pueden filtrar elementos.


Por ejemplo:


variable "numbers" {

  default = [1, 2, 3, 4, 5, 6]

}


Podemos quedarnos solamente con los números pares:


locals {

  even_numbers = [

    for number in var.numbers : number

    if number % 2 == 0

  ]

}


El resultado: [2, 4, 6]


La parte:

if number % 2 == 0

funciona como un filtro.


También podemos producir mapas.

Por ejemplo:


variable "names" {

  default = [

    "juan",

    "pedro",

    "ana"

  ]

}


Podemos construir:


locals {

  name_lengths = {

    for name in var.names :

    name => length(name)

  }

}


El resultado sería:

{

  juan  = 4

  pedro = 5

  ana   = 3

}



La expresión:

name => length(name)


define:

clave => valor


Esto es particularmente útil cuando necesitamos transformar datos antes de pasarlos a un resource o módulo.

Terraform también permite utilizar expresiones condicionales.

La sintaxis es:

condicion ? valor_si_true : valor_si_false


Por ejemplo:

variable "environment" {

  type = string

}


Podemos definir:

locals {

  instance_size = var.environment == "production"

    ? "large"

    : "small"

}


Esto permite adaptar nuestra infraestructura dependiendo del ambiente.


En los ejemplos anteriores apareció algo nuevo:


locals {

  instance_size = ...

}


Los locals permiten definir valores calculados que queremos reutilizar dentro de nuestra configuración.


Por ejemplo:


locals {

  environment = var.environment


  name_prefix = "myapp-${var.environment}"


  instance_size = var.environment == "production"

    ? "large"

    : "small"

}


Después podemos utilizarlos:


resource "..." "..." {

  name = local.name_prefix

}


La diferencia conceptual es:


variable

   ↓

valor que recibimos


local

   ↓

valor que calculamos


Las variables forman parte de la interfaz de nuestra configuración.

Los locals son principalmente una herramienta para organizar valores derivados.


Ahora podemos hacer algo un poco más interesante.

Supongamos que tenemos:


variable "environment" {

  type    = string

  default = "development"

}


variable "servers" {

  type = map(string)


  default = {

    web = "small"

    api = "small"

  }

}


Podemos definir:


locals {

  name_prefix = "myapp-${var.environment}"

}


Y utilizar for_each:


resource "local_file" "server" {

  for_each = var.servers


  filename = "${local.name_prefix}-${each.key}.txt"


  content = <<EOF

Environment: ${var.environment}

Server: ${each.key}

Size: ${each.value}

EOF

}


En development tendremos:

myapp-development-web.txt

myapp-development-api.txt


Y el contenido de cada archivo será diferente.


¿Cuándo utilizar count?

Una regla práctica puede ser:

Utilizá count cuando las instancias son esencialmente iguales y su identidad puede estar determinada por una posición o simplemente necesitás activar/desactivar un recurso.


Por ejemplo:

count = var.create_monitoring ? 1 : 0


Esto es muy común.


Si:

create_monitoring = true

tenemos:


monitoring[0]

Si es false:

ningún recurso


Este patrón es bastante útil para recursos opcionales.


¿Cuándo utilizar for_each?

Utilizá for_each cuando cada instancia tiene una identidad propia.


Por ejemplo:

  • web
  • api
  • database


es mucho más expresivo que:

  • 0
  • 1
  • 2


Con:

for_each = var.servers


podemos identificar los recursos mediante:

server["web"]

server["api"]

server["database"]


Esto además hace que los cambios en la colección sean más predecibles.


Cuando venimos de lenguajes como Java, C#, C++ o Python, es tentador pensar:

for_each es un for que ejecuta varias veces el resource.

No exactamente.

Terraform es declarativo.

No estamos escribiendo un programa que ejecuta:


for

    crear recurso

    crear recurso

    crear recurso


Estamos describiendo:"Quiero una instancia de este resource por cada elemento de esta colección."

Terraform después determina qué necesita hacer para conseguir ese resultado.

Esta diferencia de mentalidad se vuelve cada vez más importante a medida que las configuraciones crecen.


Entonces, tenemos varias herramientas para trabajar con colecciones:

count: Crea varias instancias indexadas:


resource "..." "server" {

  count = 3

}


Resultado:

server[0]

server[1]

server[2]


for_each: Crea instancias identificadas por claves:


resource "..." "server" {

  for_each = var.servers

}


Resultado:

server["web"]

server["api"]


for: Transforma colecciones:


[

  for item in var.items : transform(item)

]


condicional: Permite seleccionar valores:


condition ? value1 : value2


locals: Permite definir valores calculados reutilizables:


locals {

  name = "myapp-${var.environment}"

}


Hasta ahora utilizábamos Terraform principalmente para entender sus conceptos.

Con count, for_each, for, condicionales y locals empezamos a tener herramientas para construir configuraciones que se adapten a diferentes situaciones.



No hay comentarios.:

Publicar un comentario