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