Terraform conecta cuatro elementos:
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.
Terraform usa principalmente el HashiCorp Configuration Language, o HCL. El código se organiza en bloques.
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.
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
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.
Un recurso representa generalmente un objeto que Terraform crea o gestiona.
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.
data "aws_vpc" "default" {
default = true
}Luego puedes referenciar data.aws_vpc.default.id en un recurso.
Las variables son las entradas configurables:
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:
locals {
prefix = "application-${var.environment}"
}Los outputs exponen resultados útiles:
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.
Terraform detecta una dependencia cuando un recurso referencia a otro.
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.
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.
terraform initInitializing 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).
Escribes o modificas los archivos .tf, luego generalmente lanzas:
terraform fmt
terraform validateterraform 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:
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.
Terraform compara el estado deseado con lo que conoce de la infraestructura:
terraform planTerraform 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:
to add)to change)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.
Después de la revisión:
terraform applyTerraform vuelve a mostrar el plan y luego pide tu aprobación:
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:
No changes. Your infrastructure matches the configuration.Es la idempotencia de la lección 01, vista en la terminal.
terraform destroydestroy 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:
Destroy complete! Resources: 1 destroyed.Después de un destroy, terraform state list ya no muestra nada. En este curso, cada proyecto termina así.
| Símbolo | Acción | Frase en el plan | Qué cambia para ti |
|---|---|---|---|
+ | crear | will be created | Un objeto nuevo; nada existente se toca. |
~ | modificar en el lugar | will be updated in-place | El objeto permanece, un atributo cambia. |
-/+ | reemplazar | must be replaced | El objeto es destruido y luego recreado; su contenido y su identificador desaparecen. |
- | destruir | will be destroyed | El objeto desaparece. |
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:
# 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 decontentdice que el valor cambia. El-/+delante deresourcedice 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.
resource "tipo" "nombre" tiene una dirección, tipo.nombre, que encuentras en el plan y en el state.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.).+ crea, ~ modifica en el lugar, -/+ destruye y luego recrea, - destruye. El resumen Plan: N to add, N to change, N to destroy. los cuenta.~ o un -/+; el local_file del proyecto 01 se reemplaza en cuanto content cambia.terraform init, y qué archivos crea?content, ¿qué tres pistas indican que se trata de un reemplazo y no de una modificación en el lugar?y en lugar de yes?