Cómo funciona Terraform

8 min
Público
principiante, lección 01 leída
Duración
30 a 40 min
Módulo
1/7
Competencia buscada
nombrar las cuatro piezas de Terraform, leer un bloque HCL palabra por palabra, y reconocer en la terminal las frases que muestran init, plan, apply y destroy cuando todo va bien

Una visión de conjunto

Terraform conecta cuatro elementos:

  • La configuración describe el resultado que quieres.
  • Terraform lee esta configuración y construye un grafo de dependencias.
  • Los providers traducen las solicitudes de Terraform en llamadas a las API de las plataformas.
  • El state asocia los bloques de tu código con los objetos remotos reales.

En la imagen de la lección 01: la configuración es el plano, el state es el registro, los providers son los gremios, y el CLI es el arquitecto.

El lenguaje HCL

Terraform usa principalmente el HashiCorp Configuration Language, o HCL. El código se organiza en bloques.

hcl
resource "aws_s3_bucket" "logs" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

En este ejemplo:

  • resource anuncia un objeto que Terraform debe gestionar;
  • aws_s3_bucket es el tipo proporcionado por el provider de AWS;
  • logs es el nombre local usado en este módulo;
  • bucket y tags son argumentos;
  • var.bucket_name y var.environment provienen de variables.

El tipo y el nombre local forman juntos la dirección del recurso: aws_s3_bucket.logs. Bajo este nombre lo encontrarás en un plan, en terraform state list y en las referencias de otros bloques.

Provider

Un provider es un plugin que conoce la API de una plataforma y proporciona tipos de recursos y de fuentes de datos. Terraform instala los providers requeridos durante terraform init. Las restricciones de versión y el archivo de bloqueo hacen las ejecuciones más predecibles. Referencia oficial — Requisitos de los providers

hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ca-central-1"
}

La versión del CLI de Terraform y la versión de un provider son dos cosas diferentes. Las controlas por separado: terraform version muestra la primera (Terraform v1.12.2 en la máquina del curso), .terraform.lock.hcl fija la segunda.

Recurso y data source

Un recurso representa generalmente un objeto que Terraform crea o gestiona.

hcl
resource "aws_security_group" "web" {
  name = "web"
}

Una data source lee un objeto o una información que ya existe sin convertirse automáticamente en su propietaria.

hcl
data "aws_vpc" "default" {
  default = true
}

Luego puedes referenciar data.aws_vpc.default.id en un recurso.

Variables, valores locales y outputs

Las variables son las entradas configurables:

hcl
variable "environment" {
  type        = string
  description = "Nom de l'environnement"

  validation {
    condition     = contains(["dev", "test", "prod"], var.environment)
    error_message = "L'environnement doit être dev, test ou prod."
  }
}

Los locals calculan valores internos para evitar repeticiones:

hcl
locals {
  prefix = "application-${var.environment}"
}

Los outputs exponen resultados útiles:

hcl
output "bucket_id" {
  description = "Identifiant du bucket"
  value       = aws_s3_bucket.logs.id
}

Un proyecto sin bloque output no tiene nada que mostrar: terraform output responde entonces Warning: No outputs found. Es el caso del proyecto 01; los outputs llegan en el proyecto 02.

El grafo de dependencias

Terraform detecta una dependencia cuando un recurso referencia a otro.

hcl
resource "aws_s3_object" "journal" {
  bucket = aws_s3_bucket.logs.id
  key    = "journal.txt"
  source = "journal.txt"
}

El objeto depende del bucket. Terraform debe entonces crear el bucket antes de enviar el archivo. Los recursos sin dependencia entre ellos pueden procesarse en paralelo. El orden de los bloques en el archivo no tiene ninguna importancia: solo cuenta la referencia.

El flujo de trabajo central

HashiCorp resume el flujo de trabajo de Terraform en tres etapas: Write, Plan, Apply. Referencia oficial — Core workflow

Antes de la primera de estas etapas, hay un gesto único por carpeta: terraform init. Las salidas a continuación son las del proyecto 01 de este módulo, capturadas con Terraform 1.12.2 en la máquina del curso; las encontrarás palabra por palabra cuando lo hagas tú mismo.

Init — preparar la carpeta

powershell
terraform init
text
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. …

Terraform has been successfully initialized!

Ocurren tres cosas: Terraform descarga el provider (Installing hashicorp/local v2.9.1), lo guarda en la carpeta oculta .terraform/, y escribe .terraform.lock.hcl para retener la versión elegida. La frase que hay que esperar es Terraform has been successfully initialized!. Sin init, cualquier otro comando se detiene: Error: Inconsistent dependency lock file para plan, Error: Missing required provider para validate, y ambos te dicen qué hacer (run: terraform init).

Write — escribir

Escribes o modificas los archivos .tf, luego generalmente lanzas:

powershell
terraform fmt
terraform validate

terraform fmt realinea los espacios y la indentación; muestra el nombre de los archivos que modificó, y nada en absoluto si todo ya estaba limpio. terraform validate verifica la sintaxis y la coherencia de los bloques sin tocar nada, y responde:

text
Success! The configuration is valid.

Detecta los errores de tipeo en los nombres de argumentos (An argument named "contenu" is not expected here. Did you mean "content"?) y las llaves olvidadas (Error: Unclosed configuration block). No verifica que el resultado sea una buena arquitectura.

Plan — prever

Terraform compara el estado deseado con lo que conoce de la infraestructura:

powershell
terraform plan
text
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.message will be created
  + resource "local_file" "message" {
      + content              = "Bonjour, ce fichier a été créé avec Terraform."
      + content_md5          = (known after apply)
      + filename             = "./message.txt"
      + id                   = (known after apply)

    }

Plan: 1 to add, 0 to change, 0 to destroy.

El plan responde a tres preguntas, y la última línea las resume en tres números:

  • ¿Qué se va a crear? (to add)
  • ¿Qué se va a modificar? (to change)
  • ¿Qué se va a destruir o reemplazar? (to destroy)

Cada línea de atributo lleva el símbolo de la acción. (known after apply) señala un valor que Terraform no puede conocer antes de haber creado el objeto: aquí, el identificador del archivo y las huellas de su contenido.

Apply — aplicar

Después de la revisión:

powershell
terraform apply

Terraform vuelve a mostrar el plan y luego pide tu aprobación:

text
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.message: Creating...
local_file.message: Creation complete after 0s [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Solo se acepta yes completo; y, Y o una línea vacía dan Apply cancelled. y nada se toca. Terraform luego llama a los providers en el orden dictado por las dependencias, y actualiza el state. La última línea repite los tres números del plan: Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Si relanzas terraform plan sin haber cambiado nada, el presupuesto está vacío:

text
No changes. Your infrastructure matches the configuration.

Es la idempotencia de la lección 01, vista en la terminal.

Destroy — deshacer todo

powershell
terraform destroy

destroy es un apply cuyo plan solo contiene -. La pregunta planteada es más insistente (Do you really want to destroy all resources? … There is no undo.), la respuesta esperada sigue siendo yes, y la última línea es:

text
Destroy complete! Resources: 1 destroyed.

Después de un destroy, terraform state list ya no muestra nada. En este curso, cada proyecto termina así.

Ciclo completo

Los cuatro símbolos de un plan

SímboloAcciónFrase en el planQué cambia para ti
+crearwill be createdUn objeto nuevo; nada existente se toca.
~modificar en el lugarwill be updated in-placeEl objeto permanece, un atributo cambia.
-/+reemplazarmust be replacedEl objeto es destruido y luego recreado; su contenido y su identificador desaparecen.
-destruirwill be destroyedEl objeto desaparece.

Declarativo no quiere decir mágico

Terraform determina cómo alcanzar el estado solicitado, pero el comportamiento exacto depende de cada provider. Algunos argumentos pueden modificarse en el lugar; otros obligan a reemplazar el recurso. El provider local del proyecto 01 da un ejemplo claro de esto: cambiar la línea content de un local_file no produce un ~, sino un reemplazo -/+. Aquí está el plan real, capturado después de modificar el texto:

text
  # local_file.message must be replaced
-/+ resource "local_file" "message" {
      ~ content              = "Bonjour, ce fichier a été créé avec Terraform." -> "Deuxième version du fichier créée avec Terraform." # forces replacement
      ~ content_md5          = "6daf774f0eb6d3da439c871afec7cf90" -> (known after apply)
      ~ id                   = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Lee las tres pistas: must be replaced en el título, # forces replacement al final de la línea content, y 1 to add, 0 to change, 1 to destroy en el resumen. Para un archivo de texto, el matiz no tiene consecuencias. Para una base de datos, -/+ significa pérdida de datos: por eso el plan debe leerse, incluso cuando el cambio de código parece pequeño.

La diferencia esencial entre ~ y -/+. El ~ delante de content dice que el valor cambia. El -/+ delante de resource dice cómo lo va a hacer Terraform: destruir, y luego recrear. Cuando una línea ~ lleva # forces replacement, es ella la que provocó el -/+ del bloque completo.

Lo esencial

  1. Cuatro piezas: la configuración describe, el CLI compara, los providers llaman a las API, el state memoriza.
  2. Un bloque resource "tipo" "nombre" tiene una dirección, tipo.nombre, que encuentras en el plan y en el state.
  3. init una vez por carpeta (Terraform has been successfully initialized!), luego fmt, validate (Success! The configuration is valid.), plan (Plan: 1 to add, 0 to change, 0 to destroy.), apply con yes (Apply complete! Resources: 1 added, 0 changed, 0 destroyed.), y destroy al final de la sesión (Destroy complete! Resources: 1 destroyed.).
  4. Cuatro símbolos: + crea, ~ modifica en el lugar, -/+ destruye y luego recrea, - destruye. El resumen Plan: N to add, N to change, N to destroy. los cuenta.
  5. El provider decide si un cambio es un ~ o un -/+; el local_file del proyecto 01 se reemplaza en cuanto content cambia.

Preguntas de comprensión

  1. ¿Qué componente se comunica directamente con la API de AWS o de GitHub?
  2. ¿Por qué debe ejecutarse terraform init, y qué archivos crea?
  3. ¿Qué diferencia existe entre un recurso y una data source?
  4. ¿Cómo sabe Terraform que un objeto S3 depende de un bucket?
  5. En el plan del proyecto 01 después de modificar content, ¿qué tres pistas indican que se trata de un reemplazo y no de una modificación en el lugar?
  6. ¿Qué responde Terraform si escribes y en lugar de yes?