Email copiado — support@tuurt.com
Cargando experiencia
devops · 07 de septiembre de 2026 · 7 min

Drift de infraestructura en Terraform: cómo detectamos cambios manuales antes del próximo apply

Un cambio hecho a mano en la consola de AWS deja el state de Terraform desincronizado, y el próximo apply lo revierte sin que nadie lo note. Vemos cómo detectamos ese drift antes de que duela.

Por Equipo Tuurt

Drift de infraestructura en Terraform: cómo detectamos cambios manuales antes del próximo apply

Terraform asume una cosa que rara vez se dice en voz alta: que el state es la única fuente de verdad sobre lo que existe en la nube, y que nada cambia esos recursos si no es a través de un apply. Esa asunción se sostiene mientras el equipo completo respeta el flujo declarado en el repositorio. Se rompe la primera vez que alguien entra a la consola de AWS, cambia el tamaño de una instancia, agrega una regla a un security group o sube el límite de un autoscaling group para apagar una alerta a las dos de la mañana. El recurso real ya no coincide con lo que el state cree que existe, y Terraform no tiene forma de enterarse de eso hasta que alguien corre un plan.

A ese desajuste lo llamamos drift: la diferencia entre lo que el código declara, lo que el state registró la última vez que corrió un apply, y lo que de verdad existe en la nube en este momento. El problema no es que el cambio manual haya sido necesariamente malo -a veces es exactamente lo correcto para resolver un incidente rápido-. El problema es que ese cambio queda invisible para el resto del equipo hasta que alguien vuelve a tocar ese mismo recurso desde Terraform, y en ese momento Terraform intenta revertir silenciosamente algo que un compañero cambió a propósito, sin que nadie se dé cuenta de que eso es lo que está pasando.

Por qué el drift no se nota hasta que ya duele

El escenario que más veces encontramos es el siguiente: un ingeniero de guardia responde a un incidente de capacidad subiendo a mano el desired_capacity de un autoscaling group desde la consola de AWS, porque es la vía más rápida para mitigar en el momento. El incidente se resuelve, la alerta se apaga, y nadie vuelve a tocar el tema. Semanas después, otra persona del equipo hace un cambio no relacionado en el mismo módulo de Terraform -por ejemplo, ajustar una política de escalado- y corre terraform apply. Terraform compara el código con el state, no con la realidad, así que no tiene ninguna razón para saber que el desired_capacity real es distinto al que el state registró la última vez. El apply revierte silenciosamente la capacidad al valor que estaba en el código antes del incidente, y el sistema vuelve a quedar bajo la misma presión que causó el problema original, solo que esta vez sin que nadie lo haya decidido a propósito.

Este tipo de incidente es difícil de diagnosticar porque no aparece como un error de Terraform. El apply corre limpio, sin advertencias, porque desde la perspectiva de Terraform no está revirtiendo nada: solo está aplicando el código sobre un state que, hasta ese momento, nadie había actualizado para reflejar el cambio manual.

terraform plan en CI como primera línea de defensa

La primera práctica que instalamos fue correr terraform plan en un pipeline de CI en cada pull request que toca el directorio de infraestructura, antes de fusionar. Esto no detecta drift por sí solo -compara código contra state, no contra la nube-, pero establece la disciplina de que ningún cambio de infraestructura llega a producción sin que alguien haya visto el plan completo, incluyendo los recursos que el propio autor del PR no tocó a propósito.

# .github/workflows/terraform-plan.yml
name: terraform-plan
on:
  pull_request:
    paths:
      - 'infra/**'

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
        working-directory: infra
      - run: terraform plan -detailed-exitcode -out=tfplan
        working-directory: infra

El flag -detailed-exitcode es el detalle que hace útil este paso: en vez del código de salida binario habitual (0 éxito, 1 error), Terraform devuelve 0 si no hay cambios, 1 si hubo un error real, y 2 si hay cambios pendientes de aplicar. Eso permite que el pipeline distinga entre "este PR no cambia infraestructura" y "este PR sí cambia algo", y publique el plan completo como comentario del PR para que el revisor lo lea antes de aprobar, en vez de confiar en que el autor describió bien el cambio en la descripción.

Detección de drift programada, sin esperar al próximo PR

terraform plan en CI resuelve el drift que se introduce después de un cambio manual, pero solo lo detecta cuando alguien vuelve a tocar ese módulo desde Terraform. Si nadie toca ese recurso en semanas, el drift queda invisible todo ese tiempo. Para eso agregamos un segundo mecanismo: un job programado que corre terraform plan contra cada módulo de infraestructura de forma periódica, sin ningún cambio de código de por medio, únicamente para comparar state contra la realidad.

# .github/workflows/terraform-drift-check.yml
name: terraform-drift-check
on:
  schedule:
    - cron: '0 12 * * 1-5'

jobs:
  drift:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        module: [networking, compute, data]
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
        working-directory: infra/${{ matrix.module }}
      - name: Check drift
        id: plan
        working-directory: infra/${{ matrix.module }}
        run: |
          terraform plan -detailed-exitcode -no-color -input=false
        continue-on-error: true
      - name: Notify on drift
        if: steps.plan.outputs.exitcode == '2'
        run: |
          echo "Drift detectado en ${{ matrix.module }}"
          # notificación al canal del equipo con el output del plan

Este job corre de lunes a viernes al mediodía, contra cada módulo por separado, y solo notifica cuando el código de salida es 2 -hay diferencias reales entre state y la nube-. La notificación incluye el output completo del plan, para que quien lo revise vea exactamente qué recurso cambió y en qué atributo, en vez de tener que correr el plan a mano para averiguarlo.

Lo que no funcionó: corregir el drift automáticamente

La primera versión de este job no solo detectaba drift, sino que intentaba corregirlo: si encontraba diferencias, corría terraform apply -auto-approve para devolver el recurso al estado declarado en el código, sin intervención humana. La idea era cerrar el ciclo completo -detectar y corregir- sin depender de que alguien viera la notificación y actuara. Revertimos esta parte a los pocos días, porque el mismo escenario del autoscaling group volvió a pasar, ahora de forma automatizada: un cambio manual legítimo, hecho para mitigar un incidente en curso, fue revertido por el job de drift mientras el incidente todavía estaba activo, sin que la persona que hizo el cambio manual se enterara de que algo lo había deshecho. Automatizar la corrección asume que todo drift es un error que hay que revertir, y esa asunción es falsa en el peor momento posible: durante un incidente, que es precisamente cuando más probable es que alguien haya cambiado algo a mano a propósito. El job ahora solo detecta y notifica; la decisión de si el drift hay que corregirlo con un apply o incorporarlo al código con un cambio deliberado la toma una persona, no el pipeline.

El problema de fondo es el acceso de consola

Ambos mecanismos -plan en CI y detección programada- tratan el síntoma. La causa de fondo es que hay suficiente gente con permisos de escritura en la consola de AWS sobre recursos que Terraform administra. Mientras ese acceso exista, va a seguir habiendo drift, porque en medio de un incidente nadie va a esperar a que un PR de Terraform pase por review antes de mitigar. La solución hacia la que estamos migrando, todavía en curso, es reducir los permisos de consola en las cuentas de producción a solo lectura para la mayoría del equipo, y dejar el acceso de escritura reservado a un rol de breakglass con sesión temporal y auditoría, que cualquiera puede activar durante un incidente pero que deja un registro explícito de que se usó. Eso no elimina el drift -seguirá pasando durante esos incidentes-, pero lo hace visible en el momento en que ocurre, en vez de dejarlo como una sorpresa que otra persona descubre semanas después en un plan que no debería tener cambios.

terraform aws devops infraestructura
← Volver al blog