Saltar al contenido

Patrones de Terraform para la colaboración en equipo

Workspaces, estado remoto, registros de módulos y revisión de código: los patrones que hacen manejable Terraform cuando varios tocan la infraestructura.

3 min de lectura
Estructura de workspaces de Terraform con configuraciones para múltiples entornos

Terraform funciona bien para un desarrollador que gestiona un proyecto pequeño en solitario. Cuando un equipo de ingenieros modifica la infraestructura de forma concurrente, todo lo que funcionaba para una sola persona se convierte en una fuente de conflictos: corrupción del archivo de estado, entornos divergentes y cambios de recursos sin documentar. Estos patrones abordan los desafíos de colaboración que surgen cuando la infraestructura pasa a ser responsabilidad de todo un equipo.

Aislamiento del estado

La causa más común de desastres en equipos que usan Terraform es el estado compartido sin un aislamiento adecuado. Un ingeniero ejecuta terraform apply en producción mientras otro está probando cambios, y ambos terminan modificando el mismo archivo de estado.

hclhcl
# ❌ Single state file for all environments
terraform {
  backend "s3" {
    bucket = "terraform-state"
    key    = "terraform.tfstate"  # One file for everything
    region = "us-east-1"
  }
}
 
# ✅ Separate state per environment
# infrastructure/environments/production/backend.tf
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}
 
# infrastructure/environments/staging/backend.tf
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "staging/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

El bloqueo con DynamoDB evita aplicaciones concurrentes. Si alguien ya está modificando el estado de producción, el terraform apply del segundo ingeniero esperará o fallará con un error de bloqueo, en lugar de corromper el estado de forma silenciosa.

Versionado de modules

Los modules compartidos sin versionado crean un acoplamiento peligroso: actualizar un module para un servicio puede romper todos los demás servicios que lo utilizan.

hclhcl
# ❌ Referencing modules by local path — changes affect everyone immediately
module "ecs_service" {
  source = "../../modules/ecs-service"
  # If someone modifies this module, every consumer changes on next apply
}
 
# ✅ Versioned module references — consumers upgrade explicitly
module "ecs_service" {
  source  = "app.terraform.io/mycompany/ecs-service/aws"
  version = "~> 2.1.0"  # Accept 2.1.x patches, pin major.minor
 
  service_name   = "api"
  container_port = 3000
  desired_count  = 3
}

Cuando los equipos publican modules en un registro (Terraform Cloud, Artifactory o incluso etiquetas de Git), quienes los consumen controlan cuándo actualizar. La actualización de un module pasa por su propio ciclo de PR, pruebas y lanzamiento antes de que cualquier infraestructura la utilice.

Jerarquía de variables

Los equipos necesitan un patrón consistente para gestionar variables entre entornos sin duplicar la configuración.

hclhcl
# modules/ecs-service/variables.tf — module defines its interface
variable "service_name" {
  description = "Name of the ECS service"
  type        = string
}
 
variable "desired_count" {
  description = "Number of task replicas"
  type        = number
  default     = 2
}
 
variable "cpu" {
  description = "CPU units for the task (1024 = 1 vCPU)"
  type        = number
  default     = 256
}
 
variable "memory" {
  description = "Memory in MB for the task"
  type        = number
  default     = 512
}
hclhcl
# environments/production/terraform.tfvars
desired_count = 5
cpu           = 1024
memory        = 2048
 
# environments/staging/terraform.tfvars
desired_count = 2
cpu           = 256
memory        = 512

Cada directorio de entorno contiene su propio archivo terraform.tfvars. Los valores predeterminados del module sirven como una base razonable que cada entorno puede sobrescribir.

Flujo de revisión de código

Todo cambio de infraestructura debe generar un plan visible que los revisores puedan evaluar antes de fusionarlo.

ymlyaml
# .github/workflows/terraform-pr.yml
name: Terraform Plan
on:
  pull_request:
    paths: ["infrastructure/**"]
 
jobs:
  plan:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        environment: [staging, production]
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
 
      - name: Init
        run: terraform init
        working-directory: infrastructure/environments/${{ matrix.environment }}
 
      - name: Validate
        run: terraform validate
        working-directory: infrastructure/environments/${{ matrix.environment }}
 
      - name: Plan
        id: plan
        run: |
          terraform plan -no-color -input=false \
            -out=${{ matrix.environment }}.tfplan 2>&1 | tee plan_output.txt
        working-directory: infrastructure/environments/${{ matrix.environment }}

La salida del plan publicada en el PR muestra exactamente qué recursos se crearán, modificarán o destruirán. Los revisores pueden detectar cambios peligrosos (como la eliminación de una base de datos) antes de que lleguen a producción.

Convenciones de nombres

Una nomenclatura consistente evita la ambigüedad sobre qué gestiona Terraform y qué se creó manualmente.

hclhcl
# ❌ Arbitrary names — impossible to trace back to Terraform
resource "aws_security_group" "sg1" {
  name = "my-sg"
}
 
# ✅ Structured naming with standard tags
locals {
  name_prefix = "${var.project}-${var.environment}"
}
 
resource "aws_security_group" "api" {
  name        = "${local.name_prefix}-api-sg"
  description = "Security group for API servers"
  vpc_id      = var.vpc_id
 
  tags = {
    Name        = "${local.name_prefix}-api-sg"
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
    Module      = "ecs-service"
  }
}

La etiqueta ManagedBy = "terraform" deja claro de inmediato, desde la consola, qué recursos se gestionan mediante IaC y cuáles se crearon manualmente.

Importar y adoptar recursos existentes

Los equipos rara vez empiezan desde cero. Los recursos existentes creados manualmente deben importarse a la gestión de Terraform sin necesidad de recrearlos.

shbash
# Import an existing RDS instance into Terraform state
terraform import aws_db_instance.main myapp-production-db
 
# After import, write the matching configuration
# terraform plan should show no changes if config matches reality

Puntos clave

  1. Aísla el estado por entorno — archivos de estado separados con bloqueo mediante DynamoDB evitan desastres por modificaciones concurrentes
  2. Versiona tus modules — quienes los consumen deben controlar cuándo adoptar los cambios de un module
  3. Usa tfvars por entorno — mismos modules, distintos parámetros, estructura consistente
  4. Genera el plan en CI en cada PR — haz que los cambios de infraestructura sean visibles y revisables antes de aplicarlos
  5. Estandariza nombres y etiquetas — ManagedBy = "terraform" identifica al instante los recursos gestionados con IaC
  6. Importa antes de recrear — incorpora de forma segura los recursos existentes a la gestión de Terraform
Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX