Taller fundamental 2 — Terraform: modificar, reemplazar, destruir

Práctica guiada15 min
Duración
20 min
Módulo
1/7
Prerrequisitos
el taller fundamental 1 terminado, sin destroy: la carpeta travail/atelier-1 contiene main.tf, bonjour.txt y terraform.tfstate, y terraform state list responde ahí local_file.bonjour
Vas a construir
nada nuevo; vas a hacer vivir lo que creaste: romperlo a mano, verlo reparado, cambiar su contenido, verlo reemplazado, y luego destruirlo todo hasta un state vacío
Entregable
la salida del terraform plan del paso 5 (la que contiene must be replaced) y la de terraform state list del paso 9 (vacía)

Cómo leer esta página. Diez pasos, un comando a la vez. Para cada uno: el comando, la salida exacta de la máquina del curso (Terraform 1.12.2), y qué hay que mirar. Escribe tú mismo cada comando. Los bloques «Para entender bien» son opcionales. Los comandos terraform … son idénticos en todas partes; cuando un comando depende del sistema, se dan las dos versiones, una debajo de la otra. Si la carpeta del taller 1 ya no existe o si state list no responde local_file.bonjour, rehaz primero el taller fundamental 1 (kit: https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr).

Objetivo

El taller 1 te hizo crear un recurso y leer el state. Pero una infraestructura no se crea de una vez para siempre: se modifica, alguien la rompe por descuido, y un día se desmonta. Estos son los tres gestos de este taller. Primero vas a eliminar bonjour.txt a mano, como un colega que «limpia» una carpeta, y vas a ver a Terraform darse cuenta y luego recrearlo: es la deriva de la lección 01. Luego vas a cambiar una línea de main.tf y descubrir que, para el provider local, cambiar el contenido de un archivo no lo modifica: lo reemplaza, con un símbolo que debes saber reconocer antes de decir yes. Finalmente vas a destruir todo, y verificar que el registro está vacío y que el kit cuenta cero.

El vocabulario en una imagen

Siempre el arquitecto. Una deriva (drift) es un obrero que desplazó un muro sin decirlo: el edificio ya no corresponde al registro; el arquitecto se da cuenta cuando va a ver el lugar (Refreshing state...). Modificar en el lugar (~) es repintar un muro: el muro sigue siendo el mismo. Reemplazar (-/+) es demoler el muro y reconstruir uno en el mismo lugar: tiene un nuevo número en el registro. Destruir (-) es demoler sin reconstruir. El presupuesto siempre dice cuál de los tres prevé; tú lo lees antes de firmar.

Símbolo en el planFrase en el planQué hace TerraformEn este taller
+will be createdcrea un objeto nuevopaso 3: el archivo eliminado a mano
~will be updated in-placecambia un atributo, el objeto permanecenunca para content de un local_file
-/+must be replaceddestruye el objeto, luego crea otropaso 5: content cambia
-will be destroyedelimina el objetopaso 8: destroy

Dónde escribir

La misma terminal que para el taller 1, en la carpeta travail/atelier-1 del kit lab-terraform. Y VS Code para modificar main.tf. Los comandos terraform … son idénticos en todas partes; los comandos para eliminar, leer o listar un archivo se dan en las dos versiones.

Paso 1 — Volver al punto de partida

Windows (PowerShell), desde la raíz del kit:

powershell
cd travail\atelier-1

Linux, macOS, WSL 2, Git Bash:

bash
cd travail/atelier-1

Luego:

text
terraform state list
text
local_file.bonjour

Lo que pide el comando: «¿Qué gestionas en esta carpeta?»

Qué mirar: una línea, local_file.bonjour. Es el estado en el que te dejó el taller 1. Si lees No state file was found! o nada en absoluto, la carpeta no es la correcta o el recurso fue destruido: rehaz el taller 1 antes de continuar.

Paso 2 — Romper a mano

Windows (PowerShell):

powershell
Remove-Item .\bonjour.txt
Get-ChildItem

Linux, macOS, WSL 2, Git Bash:

bash
rm bonjour.txt
ls

Lo que piden los comandos: «Elimina bonjour.txt, luego muéstrame la carpeta.»

Qué mirar: bonjour.txt ya no está; main.tf y terraform.tfstate siguen ahí. Acabas de hacer lo que un colega hace en la consola AWS cuando elimina un bucket «que no servía para nada»: lo real cambió, pero ni el código ni el registro lo saben todavía.

Paso 3 — Ver la deriva

text
terraform plan
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]

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.bonjour will be created
  + resource "local_file" "bonjour" {
      + content              = "Bonjour Terraform"
      + content_base64sha256 = (known after apply)

      + filename             = "./bonjour.txt"
      + id                   = (known after apply)
    }

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

Lo que pide el comando: «Compara el código, el registro y lo real, y dime qué falta.»

Qué mirar: Refreshing state... [id=fd9aee55…]: Terraform fue a ver el archivo que su registro menciona, y no lo encontró. Resultado: lo retira de su registro y el presupuesto dice will be created, Plan: 1 to add. El mismo plan que en el taller 1, paso 5, aunque no tocaste el código. Eso es una deriva: lo real se movió sin pasar por Terraform, y Terraform lo detectó en el momento del plan, no antes.

Para entender bien
  • Terraform no vigila nada de forma continua. Entre dos comandos, no sabe qué pasa. Es el Refreshing state... del plan (y del apply) el que relee lo real. Un archivo eliminado hace tres días solo se descubre en el próximo plan.
  • ¿Por qué + y no -/+? El objeto ya no existe en absoluto: no hay nada que destruir, solo que crear. -/+ (paso 5) es para un objeto que todavía existe pero que hay que rehacer.
  • En el cloud, es la situación más frecuente: alguien modificó una regla de firewall en la consola, y el plan siguiente propone restablecerla como dice el código. El plan te lo muestra; a ti te toca decidir si es el código o la consola el que tenía razón.

Paso 4 — Reparar

text
terraform apply
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]



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

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.bonjour: Creating...
local_file.bonjour: Creation complete after 0s [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]

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

Lo que pide el comando: «Recalcula el presupuesto, espera mi yes, luego hazlo.»

Qué mirar: bonjour.txt volvió, y su id es el mismo que antes (fd9aee55…): el identificador de un local_file es la huella de su contenido, y el contenido no cambió. Verifica con Get-Content .\bonjour.txt o cat bonjour.txt: Bonjour Terraform. Un script no habría sabido que había que reparar; Terraform lo supo porque tiene un registro y un código para comparar.

Paso 5 — Cambiar el contenido, leer el presupuesto de reemplazo

En VS Code, modifica la línea content de main.tf para obtener:

hcl
resource "local_file" "bonjour" {
  filename = "${path.module}/bonjour.txt"
  content  = "Bonjour Terraform, deuxième version"
}

Guarda, luego:

text
terraform plan
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # local_file.bonjour must be replaced
-/+ resource "local_file" "bonjour" {
      ~ content              = "Bonjour Terraform" -> "Bonjour Terraform, deuxième version" # forces replacement
      ~ content_base64sha256 = "SvXkP5Iqo1rJKHgTVJnjanxfsNVGhb+r52mzUqZJYyg=" -> (known after apply)
      ~ content_base64sha512 = "r3+PevYNJRFsHMz4H4U1DK+lg89nhO9SLJZMAqqtzPKiIv3ilrmbx5sYh2JCW9o5EF0NXanYmuxMvI2QN4Z1Ag==" -> (known after apply)
      ~ content_md5          = "ff3a967c227a58691a3d34a931d3eeb5" -> (known after apply)
      ~ content_sha1         = "fd9aee5556589b4d797e6d49f8ec894e29e57713" -> (known after apply)
      ~ content_sha256       = "4af5e43f922aa35ac92878135499e36a7c5fb0d54685bfabe769b352a6496328" -> (known after apply)
      ~ content_sha512       = "af7f8f7af60d25116c1cccf81f85350cafa583cf6784ef522c964c02aaadccf2a222fde296b99bc79b188762425bda39105d0d5da9d89aec4cbc8d9037867502" -> (known after apply)
      ~ id                   = "fd9aee5556589b4d797e6d49f8ec894e29e57713" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

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

Lo que pide el comando: lo mismo que en el paso 3. Pero esta vez es el código el que cambió, no lo real.

Qué mirar, línea por línea. Es el plan más importante del módulo:

LíneaQué dice
-/+ destroy and then create replacementLa leyenda: el símbolo usado es -/+, «destruir y luego crear un reemplazo».
# local_file.bonjour must be replacedNo will be updated in-place: el recurso debe ser reemplazado.
-/+ resource "local_file" "bonjour" {El bloque entero lleva el -/+.
~ content = "Bonjour Terraform" -> "Bonjour Terraform, deuxième version" # forces replacementEl ~ dice que el valor cambia, del antiguo (->) al nuevo. El comentario # forces replacement dice que es este atributo el que obliga a reemplazar.
~ content_md5 = "ff3a967c…" -> (known after apply)Las huellas cambiarán; Terraform todavía no conoce las nuevas.
~ id = "fd9aee55…" -> (known after apply)El identificador va a cambiar. Será otro objeto.
# (3 unchanged attributes hidden)filename, directory_permission, file_permission no se mueven; Terraform los oculta por legibilidad.
Plan: 1 to add, 0 to change, 1 to destroy.Una destrucción y una creación, cero modificación.

Esta es tu primera respuesta-entregable: guarda esta salida.

La diferencia esencial entre ~ y -/+. El ~ delante de content dice qué cambia. El -/+ delante de resource dice cómo lo va a hacer Terraform: demoler, y luego reconstruir. Para un archivo de texto, no verás la diferencia. Para una base de datos, -/+ significa que los datos desaparecen con el objeto antiguo. Es el provider quien decide, atributo por atributo; el provider local decidió que content fuerza el reemplazo. Tú lees las tres pistas (must be replaced, # forces replacement, 1 to destroy) antes de escribir yes.

Para entender bien
  • ¿Por qué el provider local reemplaza en lugar de modificar? Porque no sabe «corregir» un archivo existente: sabe escribirlo o borrarlo. El id de un local_file es además la huella SHA-1 de su contenido; cambiar el contenido cambia el identificador, y un objeto cuyo identificador cambia es, para Terraform, otro objeto. En la máquina del curso, file_permission también fuerza el reemplazo. Otros providers actúan de otra manera: cambiar una etiqueta (tag) en un recurso AWS es un ~, cambiar su nombre suele ser un -/+.
  • ¿Dónde está escrito esto? En la documentación de cada recurso en el Registry, ciertos argumentos están marcados forces new resource (o ForceNew). Antes de un apply en producción, es lo que hay que leer.
  • ¿Se puede forzar un reemplazo voluntariamente? Sí: terraform apply -replace=local_file.bonjour reemplaza el recurso incluso sin cambio de código. Útil cuando un objeto está corrupto. Lo verás en el proyecto 08.

Paso 6 — Aplicar el reemplazo

text
terraform apply
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]



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

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.bonjour: Destroying... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
local_file.bonjour: Destruction complete after 0s
local_file.bonjour: Creating...
local_file.bonjour: Creation complete after 0s [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]

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

Lo que pide el comando: lo mismo que en el paso 4.

Qué mirar: cuatro líneas de acción en lugar de dos, en el orden del símbolo -/+: Destroying... luego Destruction complete (el antiguo, fd9aee55…), luego Creating... luego Creation complete (el nuevo, bd2b5f8c…). Y la última línea: Apply complete! Resources: 1 added, 0 changed, 1 destroyed. No 1 changed: nada se modificó, un objeto fue destruido y otro creado.

Paso 7 — Verificar el archivo y el nuevo identificador

Windows (PowerShell):

powershell
Get-Content .\bonjour.txt

Linux, macOS, WSL 2, Git Bash:

bash
cat bonjour.txt
text
Bonjour Terraform, deuxième version

Luego:

text
terraform state show local_file.bonjour
text
# local_file.bonjour:
resource "local_file" "bonjour" {
    content              = "Bonjour Terraform, deuxième version"
    content_base64sha256 = "hg9L75JxH+5SDk+ZOADZinGONkirydetS0TFjN3uZ3o="
    content_base64sha512 = "EjT8mRVQU2JQ+Em/rnbRKYfe7E2UqIWw8yQMMtaatimH5XCKg4ZomW4PI5yO5jhujdSrU04vX17HE2kaqEVSkQ=="
    content_md5          = "9a1a1355b48e28ad727200e450b851e1"
    content_sha1         = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092"
    content_sha256       = "860f4bef92711fee520e4f993800d98a718e3648abc9d7ad4b44c58cddee677a"
    content_sha512       = "1234fc991550536250f849bfae76d12987deec4d94a885b0f3240c32d69ab62987e5708a838668996e0f239c8ee6386e8dd4ab534e2f5f5ec713691aa8455291"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "./bonjour.txt"
    id                   = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092"
}

Lo que piden los comandos: «Muéstrame el archivo real, luego lo que dice el registro sobre él.»

Qué mirar: el archivo contiene la segunda versión. En el registro, content es el nuevo valor, todas las huellas cambiaron, y el id es bd2b5f8c…, el mostrado por Creation complete en el paso 6. Compara con el state show del taller 1, paso 9: misma dirección local_file.bonjour, pero otro objeto. El código, el registro y lo real vuelven a estar de acuerdo: un terraform plan ahora diría No changes.

Paso 8 — Destruir todo

text
terraform destroy
text
local_file.bonjour: Refreshing state... [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # local_file.bonjour will be destroyed
  - resource "local_file" "bonjour" {
      - content              = "Bonjour Terraform, deuxième version" -> null
      - content_base64sha256 = "hg9L75JxH+5SDk+ZOADZinGONkirydetS0TFjN3uZ3o=" -> null

      - filename             = "./bonjour.txt" -> null
      - id                   = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092" -> null
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

local_file.bonjour: Destroying... [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]
local_file.bonjour: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.

Lo que pide el comando: «Destruye todo lo que gestionas aquí; muéstrame el presupuesto y espera mi yes

Qué mirar: el tercer símbolo, - solo, y cada atributo que pasa a -> null («ya nada»). La pregunta es más grave que en el apply: Do you really want to destroy all resources? y There is no undo. Escribe yes. La frase que hay que esperar: Destroy complete! Resources: 1 destroyed. El archivo desapareció de la carpeta.

Paso 9 — Leer un registro vacío

text
terraform state list
text

Luego, para ver qué responde Terraform cuando se le pide un recurso que ya no gestiona:

text
terraform state show local_file.bonjour
text
No instance found for the given address!

This command requires that the address references one specific instance.
To view the available instances, use "terraform state list". Please modify
the address to reference a specific instance.

Lo que piden los comandos: «Lista lo que gestionas», luego «muéstrame local_file.bonjour».

Qué mirar: state list no responde nada: ni una línea, ni un error, un registro vacío. Es distinto del No state file was found! del taller 1 antes del apply: aquí el archivo terraform.tfstate existe, pero su lista de recursos está vacía. Y state show responde No instance found for the given address!: la dirección es correcta, pero ya nada corresponde a ella, y Terraform te remite a state list. Esta es tu segunda respuesta-entregable: la salida vacía de terraform state list.

Paso 10 — Listar la carpeta después

Windows (PowerShell):

powershell
Get-ChildItem -Force

Linux, macOS, WSL 2, Git Bash:

bash
ls -la
text
total 17
drwxr-xr-x 1 rehou 197609    0 Sep 15 14:57 .
drwxr-xr-x 1 rehou 197609    0 Sep 15 14:57 ..
drwxr-xr-x 1 rehou 197609    0 Sep 15 14:57 .terraform
-rw-r--r-- 1 rehou 197609 1228 Sep 15 14:57 .terraform.lock.hcl
-rw-r--r-- 1 rehou 197609  130 Sep 15 14:57 main.tf
-rw-r--r-- 1 rehou 197609  181 Sep 15 14:57 terraform.tfstate
-rw-r--r-- 1 rehou 197609 1653 Sep 15 14:57 terraform.tfstate.backup

(Listado ls -la de la máquina del curso; Get-ChildItem -Force muestra los mismos cinco nombres: .terraform, .terraform.lock.hcl, main.tf, terraform.tfstate, terraform.tfstate.backup.)

Lo que pide el comando: «Muéstrame toda la carpeta, incluidos los archivos ocultos.»

Qué mirar: bonjour.txt ya no está. main.tf está intacto: destruir los recursos nunca toca el código. terraform.tfstate todavía está ahí pero minúsculo (181 bytes), y apareció un terraform.tfstate.backup: la copia del registro de antes del destroy, que Terraform guarda por precaución. .terraform/ y .terraform.lock.hcl también permanecen: si relanzas apply, no hace falta rehacer init.

Abre el state para verlo vacío, sin modificarlo (Get-Content .\terraform.tfstate o cat terraform.tfstate):

json
{
  "version": 4,
  "terraform_version": "1.12.2",
  "serial": 6,
  "lineage": "69fb219e-a88c-a97f-5aed-a33e7c34ff69",
  "outputs": {},
  "resources": [],
  "check_results": null
}

"resources": []: nada. El "serial" cuenta las escrituras en el state (seis aquí: dos apply en el taller 1, luego reparación, reemplazo y destrucción); el "lineage" es el identificador de este state, distinto en tu caso.

Verificación final

Vuelve a la raíz del kit y lanza el contador:

Windows (PowerShell):

powershell
cd ..\..
.\labo.ps1 etat

Linux, macOS, WSL 2, Git Bash:

bash
cd ../..
./labo.sh etat
text
travail atelier-1 : aucune ressource
Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

Respuesta esperada: aucune ressource y Ressources encore gérées : 0. Es el final de sesión limpio que cada proyecto del curso exigirá, cuando los recursos cuesten dinero.

  • Eliminaste bonjour.txt a mano y viste a plan proponer will be created sin que el código hubiera cambiado: una deriva, detectada en el Refreshing state....
  • apply recreó el archivo con el mismo id.
  • Cambiaste content y leíste las tres pistas del reemplazo: must be replaced, # forces replacement, Plan: 1 to add, 0 to change, 1 to destroy.
  • apply mostró Destroying... luego Creating..., y Resources: 1 added, 0 changed, 1 destroyed.; el id cambió.
  • destroy pidió yes con There is no undo. y respondió Destroy complete! Resources: 1 destroyed.
  • state list no devuelve nada; state show devuelve No instance found for the given address!; terraform.tfstate contiene "resources": [].
  • etat devuelve Ressources encore gérées : 0.
  • Guardaste el plan del paso 5 y la salida vacía del paso 9 como entregables.

Si algo se traba

Mostrar los casos frecuentes
  • En el paso 1, state list responde No state file was found! → No estás en travail/atelier-1, o el taller 1 no se hizo hasta el apply. Verifica la carpeta (Get-ChildItem -Force o ls -la debe mostrar terraform.tfstate), si no, rehaz el taller 1.
  • En el paso 1, state list no responde nada → El recurso ya fue destruido (un destroy al final del taller 1). Vuelve a lanzar terraform apply, yes, luego retoma en el paso 2.
  • En el paso 3, el plan dice No changes. → El archivo no fue eliminado (error de tipeo en Remove-Item o rm, o carpeta equivocada). Verifica con Get-ChildItem o ls, elimínalo, vuelve a lanzar plan.
  • En el paso 5, el plan dice No changes.main.tf no se guardó después de la modificación (VS Code: punto blanco en la pestaña). Ctrl+S y vuelve a lanzar.
  • En el paso 5, Error: Unterminated template string → La comilla de cierre del nuevo valor content se perdió durante la edición. Vuelve a ponerla.
  • En el paso 5, la línea marcada # forces replacement no es content → Modificaste otro argumento al mismo tiempo (por ejemplo file_permission = "0644"). Para un local_file, ese también fuerza el reemplazo (verificado: ~ file_permission = "0777" -> "0644" # forces replacement). Vuelve a poner el archivo como en el paso 5, solo la línea content debe cambiar.
  • Apply cancelled. → Se escribió algo distinto de yes. Vuelve a lanzar, escribe yes completo.
  • En el paso 8, destroy dice No changes. No objects need to be destroyed. y luego Destroy complete! Resources: 0 destroyed. → Ya no hay nada en el state (Either you have not created any objects yet or the existing objects were already deleted outside of Terraform.). Pasa al paso 9.
  • Después de destroy, crees que el state no se limpió porque terraform.tfstate todavía existe → Es normal. Ábrelo: "resources": []. El archivo permanece, vacío, con un .backup al lado.
  • etat responde Ressources encore gérées : 1 → El destroy se canceló o se hizo en otra carpeta. Vuelve a travail/atelier-1, terraform destroy, yes, vuelve a lanzar etat.