Introducción
La integración de Terraform con sistemas de Integración Continua y Entrega Continua (CI/CD) es uno de los pasos más importantes para llevar la gestión de infraestructura a un nivel profesional. En lugar de ejecutar comandos de Terraform manualmente desde tu máquina local, un pipeline CI/CD automatiza todo el proceso: valida el código, genera el plan de cambios, obtiene aprobaciones cuando son necesarias y aplica los cambios de forma controlada y reproducible.
Este módulo te enseñará los conceptos, herramientas y patrones necesarios para integrar Terraform en cualquier sistema CI/CD de forma segura y eficiente.
¿Por qué automatizar Terraform con CI/CD?
Ejecutar Terraform manualmente tiene varios problemas graves en entornos profesionales:
- Inconsistencia: Cada desarrollador puede tener una versión diferente de Terraform o de los providers instalados localmente.
- Falta de auditoría: No hay registro claro de quién ejecutó
terraform applyy cuándo. - Errores humanos: Es fácil olvidarse de hacer
terraform planantes deapply, o aplicar cambios en el ambiente equivocado. - Credenciales inseguras: Los desarrolladores necesitan tener credenciales de producción en sus máquinas locales.
- Sin proceso de revisión: Los cambios de infraestructura no pasan por revisión de código antes de aplicarse.
Los beneficios de automatizar con CI/CD son:
- Reproducibilidad: Siempre se usa la misma versión de Terraform y los mismos providers en un ambiente limpio.
- Trazabilidad: Cada ejecución queda registrada con su commit, autor y resultado.
- Seguridad: Las credenciales viven en el sistema CI/CD, no en las máquinas de los desarrolladores.
- Calidad: Se pueden añadir validaciones automáticas (formato, linting, tests) antes de aplicar cambios.
- Colaboración: Los cambios de infraestructura pasan por el mismo proceso de revisión que el código de aplicación.
El pipeline de Terraform típico
El patrón más común en equipos profesionales sigue este flujo:
Fase 1: Pull Request abierto
Cuando un desarrollador abre un Pull Request (PR) con cambios en el código Terraform:
- El CI ejecuta
terraform fmt -checkpara verificar el formato - El CI ejecuta
terraform validatepara verificar la sintaxis - El CI ejecuta
terraform plany publica el resultado como comentario en el PR - Los revisores pueden ver exactamente qué recursos se van a crear, modificar o destruir
- Se aprueba o rechaza el PR con base en el plan visible
Fase 2: Merge a la rama principal
Cuando el PR se aprueba y se hace merge a main (o master):
- El CI ejecuta nuevamente
terraform planpara confirmar que no hay cambios inesperados - (Opcional) Se solicita aprobación manual de una persona autorizada
- El CI ejecuta
terraform applyde forma automática - Se notifica el resultado (éxito o fallo) al equipo
Este flujo garantiza que nadie pueda aplicar cambios sin que hayan sido revisados y aprobados.
┌─────────────────────────────────────────────────────────────┐
│ FLUJO CI/CD TERRAFORM │
└─────────────────────────────────────────────────────────────┘
Developer GitHub/GitLab CI System AWS/Azure/GCP
│ │ │ │
│── git push ──────> │ │ │
│── Abre PR ───────> │ │ │
│ │── Trigger CI ───> │ │
│ │ │── terraform fmt │
│ │ │── terraform validate│
│ │ │── terraform plan │
│ │<── Comenta Plan ──│ │
│<── Notificación ───│ │ │
│ │ │ │
│── Aprueba PR ────> │ │ │
│── Merge a main ──> │ │ │
│ │── Trigger CI ───> │ │
│ │ │── terraform plan │
│ │ │── [Aprobación?] │
│ │ │── terraform apply │
│ │ │──────────────────> │
│ │ │<── Resultado ──────│
│<── Notificación ───│<── Resultado ─────│ │Herramientas CI/CD compatibles con Terraform
Terraform es agnóstico al sistema CI/CD: funciona con cualquier herramienta que pueda ejecutar comandos de shell. Las más populares son:
| Herramienta | Tipo | Fortalezas para Terraform | Consideraciones |
|---|---|---|---|
| GitHub Actions | SaaS | Integración nativa con GitHub, workflows en YAML, actions de la comunidad | Solo para repos en GitHub |
| GitLab CI | SaaS/Self-hosted | Integración con GitLab, runners propios, environments | Ecosistema GitLab |
| Jenkins | Self-hosted | Muy flexible, plugins para todo, control total | Requiere mantenimiento de infraestructura |
| CircleCI | SaaS | Configuración simple, orbs para Terraform | Costo basado en créditos |
| Azure DevOps | SaaS | Integración con Azure, pipelines gráficos | Mejor para equipos Microsoft |
| Atlantis | Self-hosted | Especializado para Terraform, comentarios en PR | Requiere servidor propio |
| Digger | SaaS/Self-hosted | GitOps para Terraform, fácil configuración | Más nuevo, menos maduro |
Consideraciones de seguridad en CI/CD
La seguridad es el aspecto más crítico al integrar Terraform con CI/CD. Los pipelines necesitan credenciales de nube con permisos elevados, lo que los convierte en un objetivo de alto valor.
Gestión de credenciales
Regla de oro: Las credenciales NUNCA deben estar en el código.
Las credenciales deben almacenarse como secretos del sistema CI/CD:
- En GitHub Actions:
Settings > Secrets and variables > Actions - En GitLab CI:
Settings > CI/CD > Variables - En Jenkins:
Manage Jenkins > Manage Credentials - En CircleCI:
Project Settings > Environment Variables
# MAL - Nunca hagas esto
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# BIEN - Usa secretos del CI que se inyectan como variables de entorno
# La variable $AWS_ACCESS_KEY_ID viene del sistema CI como secreto
terraform planNunca imprimir credenciales en logs
Los sistemas CI/CD modernos enmascaran automáticamente los secretos en los logs, pero hay que tener cuidado con:
# MAL - Esto puede exponer credenciales en logs
env | grep AWS
printenv
# BIEN - Solo ejecutar los comandos necesarios
terraform init
terraform plan -out=tfplan
terraform apply tfplanPrincipio de mínimo privilegio
El rol IAM o Service Principal usado por el CI/CD debe tener solo los permisos necesarios para crear los recursos que Terraform gestiona. No uses credenciales de administrador.
Rotación de credenciales
Prefiere métodos de autenticación sin credenciales estáticas:
- AWS: OIDC (OpenID Connect) - GitHub Actions puede asumir roles de IAM sin claves de acceso
- Azure: Workload Identity Federation - similar a OIDC
- GCP: Workload Identity - autenticación sin Service Account keys
Gestión del estado remoto en pipelines
Cuando Terraform se ejecuta en CI/CD, múltiples agentes pueden ejecutarse en paralelo. El estado debe almacenarse de forma remota y con bloqueo (locking) para evitar corrupción.
# backend.tf - Configuración de estado remoto para CI/CD
# Ejemplo con S3 (AWS)
terraform {
backend "s3" {
bucket = "mi-empresa-terraform-state"
key = "produccion/infraestructura/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock" # Para el bloqueo
}
}
# Ejemplo con Azure Blob Storage
# terraform {
# backend "azurerm" {
# resource_group_name = "rg-terraform-state"
# storage_account_name = "terrastateempresa"
# container_name = "tfstate"
# key = "produccion.terraform.tfstate"
# }
# }
# Ejemplo con GCS (Google Cloud)
# terraform {
# backend "gcs" {
# bucket = "mi-empresa-terraform-state"
# prefix = "produccion/infraestructura"
# }
# }El bloqueo automático del estado garantiza que si un pipeline está ejecutando terraform apply, otro pipeline que intente hacer lo mismo fallará en lugar de corromper el estado.
Aprobaciones manuales antes del apply
En entornos de producción, es una buena práctica requerir que un humano apruebe el plan antes de aplicarlo. Esto agrega una capa de control sobre los cambios automáticos.
Los sistemas CI/CD implementan esto de formas diferentes:
# GitHub Actions - Usando environments con required reviewers
jobs:
apply:
environment: produccion # Este environment requiere aprobación manual
runs-on: ubuntu-latest
steps:
- name: Terraform Apply
run: terraform apply -auto-approve
# El "environment" de GitHub Actions puede configurarse para requerir
# aprobación de revisores específicos antes de que el job continúe// Jenkins - Usando el paso "input"
stage('Aprobación Manual') {
steps {
input message: '¿Aprobar terraform apply en producción?',
ok: 'Aprobar',
submitter: 'admin,tech-lead'
}
}Herramientas especializadas para CI/CD con Terraform
Atlantis
Atlantis es una herramienta open-source diseñada específicamente para automatizar Terraform a través de pull requests. Se ejecuta como un servidor que escucha webhooks de GitHub/GitLab/Bitbucket.
Funcionamiento:
- Cuando abres un PR, comentas
/atlantis plany Atlantis ejecuta el plan - Cuando quieres aplicar, comentas
/atlantis apply - Atlantis controla los bloqueos para evitar conflictos
# atlantis.yaml - Configuración de Atlantis
version: 3
projects:
- name: produccion-vpc
dir: environments/produccion/vpc
workspace: default
autoplan:
when_modified:
- "**/*.tf"
enabled: true
- name: staging-app
dir: environments/staging/app
workspace: default
autoplan:
when_modified:
- "**/*.tf"
enabled: trueDigger
Digger es una alternativa más moderna a Atlantis, diseñada para funcionar directamente dentro de GitHub Actions/GitLab CI sin necesitar un servidor separado.
# .github/workflows/digger.yml
name: Digger
on:
pull_request:
types: [opened, synchronize]
issue_comment:
types: [created]
jobs:
digger:
runs-on: ubuntu-latest
steps:
- uses: diggerhq/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Flujo de trabajo GitOps con Terraform
GitOps es un paradigma donde Git es la única fuente de verdad para el estado deseado de la infraestructura. Los principios son:
- Todo el estado deseado vive en Git: Configuraciones, variables, políticas
- Los cambios se hacen solo a través de Git: Pull requests, no comandos manuales
- El sistema converge automáticamente: El CI/CD detecta divergencias y las corrige
┌──────────────────────────────────────────────────────┐
│ GITOPS CON TERRAFORM │
└──────────────────────────────────────────────────────┘
Git Repository CI/CD System Infraestructura
(Fuente de Verdad)
│ │ │
│ Estado Deseado ────> │ │
│ │── Compara con ──────> │
│ │<── Estado Actual ──────│
│ │ │
│ │── Si hay diferencia: │
│ │── terraform apply ──> │
│ │ │
│ │ Infraestructura │
│ │ converge al estado │
│ │ deseado en Git │Ejemplo de pipeline genérico con etapas
Aquí hay un pipeline genérico que ilustra las etapas típicas. Puede adaptarse a cualquier sistema CI/CD:
# pipeline-terraform-generico.yml
# Este es un pseudocódigo que ilustra las etapas
# La sintaxis real varía según el sistema CI/CD
pipeline:
# ETAPA 1: Preparación del entorno
setup:
- checkout del repositorio
- instalar Terraform versión X.Y.Z
- configurar credenciales de nube (desde secretos del CI)
- ejecutar: terraform init -backend-config="..."
# ETAPA 2: Validación de código
validate:
- ejecutar: terraform fmt -check -recursive
# Falla si el código no está formateado correctamente
- ejecutar: terraform validate
# Verifica la sintaxis y consistencia del código
- ejecutar: tflint (opcional)
# Linter adicional para mejores prácticas
- ejecutar: tfsec (opcional)
# Análisis de seguridad estático
# ETAPA 3: Plan
plan:
- ejecutar: terraform plan -out=tfplan -no-color
- guardar tfplan como artefacto
- si es un PR: publicar el output del plan como comentario
# ETAPA 4: Aprobación (solo en rama principal)
approval:
condicion: solo si es merge a main
- esperar aprobación manual de persona autorizada
timeout: 24 horas
# ETAPA 5: Apply (solo en rama principal)
apply:
condicion: solo si es merge a main Y aprobación recibida
- ejecutar: terraform apply tfplan
- publicar resultado (éxito/fallo)
- notificar al equipoEjemplo concreto en YAML (genérico)
# Este ejemplo muestra la estructura con pasos más concretos
stages:
- name: "Inicializar"
commands:
- |
# Instalar Terraform si no está disponible
terraform version || {
wget https://releases.hashicorp.com/terraform/1.9.0/terraform_1.9.0_linux_amd64.zip
unzip terraform_1.9.0_linux_amd64.zip
mv terraform /usr/local/bin/
}
- |
# Inicializar Terraform con backend remoto
terraform init \
-backend-config="bucket=${TF_STATE_BUCKET}" \
-backend-config="key=${TF_STATE_KEY}" \
-backend-config="region=${AWS_REGION}"
- name: "Validar"
commands:
- terraform fmt -check -recursive
- terraform validate
- name: "Plan"
commands:
- |
# Generar el plan y capturar el output
terraform plan \
-var-file="environments/${ENVIRONMENT}.tfvars" \
-out=tfplan \
-no-color 2>&1 | tee plan-output.txt
- |
# Publicar el plan como comentario en el PR
# (Esto varía según la plataforma)
post_pr_comment "$(cat plan-output.txt)"
- name: "Apply"
condicion: "rama == main"
commands:
- |
# Aplicar el plan guardado
terraform apply -auto-approve tfplan
- |
# Notificar resultado
if [ $? -eq 0 ]; then
notify_slack "✅ Terraform apply exitoso en ${ENVIRONMENT}"
else
notify_slack "❌ Terraform apply falló en ${ENVIRONMENT}"
exit 1
fiErrores comunes en CI/CD con Terraform
Error 1: Estado desactualizado
Problema: El pipeline falla con "Error locking state" o aplica sobre un estado desactualizado.
Causa: Otro pipeline está corriendo concurrentemente, o un pipeline anterior falló y no liberó el lock.
# Solución: Liberar el lock manualmente (con cuidado)
terraform force-unlock LOCK_ID
# Verificar el estado del lock en DynamoDB (AWS)
aws dynamodb get-item \
--table-name terraform-state-lock \
--key '{"LockID": {"S": "mi-empresa-terraform-state/produccion/terraform.tfstate"}}'Error 2: Credenciales expiradas o incorrectas
Problema: Error: No valid credential sources found
Solución: Verificar que los secretos del CI estén configurados correctamente y que el rol IAM tenga los permisos necesarios.
# Para debugging (temporal, nunca en producción)
aws sts get-caller-identity
# Muestra qué identidad está usando TerraformError 3: Versión de Terraform inconsistente
Problema: El plan funciona localmente pero falla en CI porque las versiones difieren.
Solución: Fijar la versión de Terraform en el código y en el pipeline.
# versions.tf
terraform {
required_version = "= 1.9.0" # Versión exacta, no rango
required_providers {
aws = {
source = "hashicorp/aws"
version = "= 5.50.0"
}
}
}Error 4: Variables de entorno vs variables de Terraform
Problema: Las variables no se pasan correctamente al pipeline.
# MAL - La variable no se pasa a Terraform
export DB_PASSWORD="secreto"
terraform apply
# BIEN - Usar el prefijo TF_VAR_ para variables de Terraform
export TF_VAR_db_password="secreto"
terraform apply
# Ahora Terraform puede usar la variable "db_password"Error 5: Aplicar sin plan guardado
Problema: Usando -auto-approve sin un plan previo puede aplicar cambios inesperados si el estado cambió entre el plan y el apply.
# MAL - Puede aplicar cambios diferentes a los planeados
terraform apply -auto-approve
# BIEN - Siempre usar el plan guardado
terraform plan -out=tfplan
terraform apply tfplan
# Esto garantiza que se aplica exactamente lo que se revisóEjercicio práctico
Enunciado
Diseña un pipeline CI/CD completo para un proyecto Terraform que gestiona infraestructura en AWS. El proyecto tiene dos ambientes: staging y produccion. Los requisitos son:
- En cualquier Pull Request: ejecutar
fmt,validateyplanpara el ambientestaging - En merge a
main: aplicar automáticamente enstaging - En creación de tag
v*.*.*: ejecutar plan enproducciony esperar aprobación manual antes de aplicar - Las credenciales de AWS deben ser secretos del CI
- El estado debe estar en S3 con bloqueo en DynamoDB
Solución
┌────────────────────────────────────────────────────────────────┐ │ DIAGRAMA DE FLUJO DEL PIPELINE │ └────────────────────────────────────────────────────────────────┘ Pull Request abierto/actualizado: ┌──────────────────────────────────────────────┐ │ 1. Checkout del código │ │ 2. Setup Terraform 1.9.0 │ │ 3. terraform init (backend staging) │ │ 4. terraform fmt -check │ │ 5. terraform validate │ │ 6. terraform plan -var-file=staging.tfvars │ │ 7. Comentar plan en el PR │ └──────────────────────────────────────────────┘ Merge a main: ┌──────────────────────────────────────────────┐ │ 1. Checkout del código │ │ 2. Setup Terraform 1.9.0 │ │ 3. terraform init (backend staging) │ │ 4. terraform plan -var-file=staging.tfvars │ │ 5. terraform apply (staging) │ └──────────────────────────────────────────────┘ Push de tag v*.*.*: ┌──────────────────────────────────────────────┐ │ 1. Checkout del código │ │ 2. Setup Terraform 1.9.0 │ │ 3. terraform init (backend produccion) │ │ 4. terraform plan -var-file=prod.tfvars │ │ 5. ⏸ ESPERAR APROBACIÓN MANUAL (24h) │ │ 6. terraform apply (produccion) │ └──────────────────────────────────────────────┘
# Configuración del pipeline (pseudocódigo estructurado)
secretos_requeridos:
- AWS_ACCESS_KEY_ID_STAGING
- AWS_SECRET_ACCESS_KEY_STAGING
- AWS_ACCESS_KEY_ID_PRODUCCION
- AWS_SECRET_ACCESS_KEY_PRODUCCION
- TF_STATE_BUCKET # Bucket S3 para el estado
- SLACK_WEBHOOK # Para notificaciones
variables_globales:
TF_VERSION: "1.9.0"
AWS_REGION: "us-east-1"
DYNAMODB_TABLE: "terraform-locks"
jobs:
# Job 1: Validación y plan para PRs
pr-check:
trigger: pull_request
ambiente: staging
pasos:
- checkout
- setup terraform ${TF_VERSION}
- configurar credenciales AWS staging
- terraform init -backend-config="key=staging/terraform.tfstate"
- terraform fmt -check
- terraform validate
- terraform plan -var-file=environments/staging.tfvars -out=tfplan
- publicar output del plan como comentario en PR
# Job 2: Apply automático en staging
staging-apply:
trigger: push a main
ambiente: staging
pasos:
- checkout
- setup terraform ${TF_VERSION}
- configurar credenciales AWS staging
- terraform init -backend-config="key=staging/terraform.tfstate"
- terraform plan -var-file=environments/staging.tfvars -out=tfplan
- terraform apply tfplan
- notificar en Slack resultado
# Job 3: Plan y apply en producción con aprobación
produccion-plan:
trigger: push de tag v*.*.*
ambiente: produccion
pasos:
- checkout
- setup terraform ${TF_VERSION}
- configurar credenciales AWS produccion
- terraform init -backend-config="key=produccion/terraform.tfstate"
- terraform plan -var-file=environments/produccion.tfvars -out=tfplan
- guardar tfplan como artefacto
- notificar en Slack: "Plan listo, pendiente aprobación"
produccion-apply:
trigger: aprobación manual después de produccion-plan
depende_de: produccion-plan
ambiente: produccion
timeout_aprobacion: 24 horas
aprobadores: ["tech-lead", "devops-manager"]
pasos:
- descargar tfplan del artefacto
- terraform apply tfplan
- notificar en Slack resultado
# Estructura de archivos del proyecto
estructura_proyecto:
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── backend.tf
└── environments/
├── staging.tfvars
└── produccion.tfvars# environments/staging.tfvars
environment = "staging"
instance_type = "t3.micro"
replica_count = 1
min_capacity = 1
max_capacity = 3
# environments/produccion.tfvars
environment = "produccion"
instance_type = "t3.large"
replica_count = 3
min_capacity = 3
max_capacity = 10Resumen y próximos pasos
En este módulo aprendiste:
- Por qué es esencial integrar Terraform con CI/CD: elimina inconsistencias, mejora la seguridad y agrega trazabilidad a los cambios de infraestructura.
- El patrón fundamental: plan en PR (para revisión), apply en merge (automatizado).
- Las herramientas disponibles: GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure DevOps, Atlantis y Digger.
- Seguridad en CI/CD: credenciales como secretos, principio de mínimo privilegio, OIDC para evitar claves estáticas.
- Estado remoto: imprescindible para pipelines con múltiples agentes, siempre con bloqueo habilitado.
- Aprobaciones manuales: para cambios en producción, siempre es mejor tener una revisión humana.
- Errores comunes: locks de estado, credenciales incorrectas, versiones inconsistentes.
En los próximos módulos veremos implementaciones concretas de estos conceptos:
- 08-02: Implementación completa con Jenkins y Jenkinsfile
- 08-03: Implementación completa con GitHub Actions
- 08-04: Terraform Cloud como alternativa gestionada que incluye CI/CD integrado
La automatización es el puente entre escribir código Terraform y operarlo de forma profesional y segura en una organización real.
Curso de Terraform
Módulo 1: Introducción a Terraform
- ¿Qué es Terraform?
- Instalando Terraform
- Conceptos Básicos de Terraform
- Primera Configuración de Terraform
Módulo 2: Lenguaje de Configuración de Terraform
Módulo 3: Gestión del Estado
Módulo 4: Módulos de Terraform
Módulo 5: Aprovisionamiento de Recursos
- Conceptos Básicos de Aprovisionamiento
- Aprovisionamiento de Recursos AWS
- Aprovisionamiento de Recursos Azure
- Aprovisionamiento de Recursos GCP
Módulo 6: Funcionalidades Avanzadas de Terraform
Módulo 7: Mejores Prácticas de Terraform
- Organización del Código
- Control de Versiones
- Pruebas del Código de Terraform
- Mejores Prácticas de Seguridad
Módulo 8: Terraform en CI/CD
- Integración de Terraform con CI/CD
- Automatización de Terraform con Jenkins
- Uso de Terraform con GitHub Actions
- Terraform Cloud y Enterprise
