Saltar al contenido

Infraestructura como código: principios que escalan

Trata la infraestructura como código de aplicación: versiónala, revísala, pruébala y despliégala por pipeline en lugar de a golpe de clic en consolas.

3 min de lectura
Flujo de trabajo de infraestructura como código, desde el control de versiones hasta el despliegue en la nube

Hacer clic en la consola de un proveedor cloud para crear recursos funciona exactamente una vez. La segunda vez que necesitas la misma configuración —en staging, en una nueva región, después de un incidente— la estás recreando de memoria. La infraestructura como código (IaC) resuelve esto definiendo la infraestructura en archivos declarativos que se versionan, se revisan y se despliegan mediante los mismos pipelines que el código de la aplicación.

Declarativo vs. imperativo

Las herramientas de IaC se dividen en dos categorías. Las herramientas declarativas (Terraform, CloudFormation, Pulumi) describen el estado final deseado. Las herramientas imperativas (Ansible, scripts de shell) describen los pasos necesarios para alcanzar ese estado.

hclhcl
# Declarative (Terraform): "I want this to exist"
resource "aws_s3_bucket" "assets" {
  bucket = "myapp-assets-production"
 
  versioning {
    enabled = true
  }
 
  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }
 
  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}
shbash
# Imperative (shell script): "Do these steps in order"
aws s3api create-bucket --bucket myapp-assets-production --region us-east-1
aws s3api put-bucket-versioning --bucket myapp-assets-production \
  --versioning-configuration Status=Enabled
aws s3api put-bucket-encryption --bucket myapp-assets-production \
  --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

El enfoque declarativo gana a gran escala porque Terraform hace seguimiento del estado. Si el bucket ya existe con una configuración distinta, Terraform lo actualiza. Si alguien cambia la configuración manualmente, terraform plan muestra esa desviación. El script imperativo simplemente falla o crea duplicados.

Gestión del estado

Terraform mantiene un archivo de estado que relaciona los recursos declarados con los recursos reales en la nube. Ese archivo es la fuente de verdad de todo lo que gestiona Terraform.

hclhcl
# ❌ Local state file — works for one person, breaks for teams
terraform {
  # Default: terraform.tfstate stored locally
}
 
# ✅ Remote state with locking — safe for team collaboration
terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "production/api/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-locks"  # Prevents concurrent modifications
  }
}

Estructura de módulos

A medida que la infraestructura crece, los archivos de configuración monolíticos se vuelven inmanejables. Los módulos encapsulan patrones de infraestructura reutilizables.

infrastructure/
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── rds/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── ecs-service/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── production/
│   │   └── main.tf
│   └── staging/
│       └── main.tf
└── global/
    └── iam/
        └── main.tf
hclhcl
# environments/production/main.tf — composes modules
module "vpc" {
  source = "../../modules/vpc"
 
  cidr_block          = "10.0.0.0/16"
  availability_zones  = ["us-east-1a", "us-east-1b", "us-east-1c"]
  environment         = "production"
}
 
module "database" {
  source = "../../modules/rds"
 
  vpc_id              = module.vpc.vpc_id
  subnet_ids          = module.vpc.private_subnet_ids
  instance_class      = "db.r6g.xlarge"
  engine_version      = "14.7"
  environment         = "production"
}
 
module "api_service" {
  source = "../../modules/ecs-service"
 
  vpc_id              = module.vpc.vpc_id
  subnet_ids          = module.vpc.private_subnet_ids
  container_image     = "registry.example.com/api:v1.2.0"
  desired_count       = 3
  environment         = "production"
}

Proceso de revisión de cambios

Los cambios de infraestructura deben pasar por el mismo proceso de revisión que el código de la aplicación. Ejecuta terraform plan en CI para mostrar exactamente qué va a cambiar antes de aplicarlo.

ymlyaml
# .github/workflows/terraform.yml
name: Terraform
on:
  pull_request:
    paths: ["infrastructure/**"]
 
jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
 
      - name: Terraform Init
        run: terraform init
        working-directory: infrastructure/environments/production
 
      - name: Terraform Plan
        run: terraform plan -no-color -out=tfplan
        working-directory: infrastructure/environments/production
 
      - name: Comment Plan on PR
        uses: actions/github-script@v7
        with:
          script: |
            const plan = require('fs').readFileSync('tfplan.txt', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## Terraform Plan\n\`\`\`\n${plan}\n\`\`\``
            });

Cómo evitar errores comunes

hclhcl
# ❌ Hardcoded values scattered across files
resource "aws_instance" "api" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"
  subnet_id     = "subnet-0bb1c79de3EXAMPLE"
}
 
# ✅ Variables with sensible defaults and validation
variable "instance_type" {
  description = "EC2 instance type for the API servers"
  type        = string
  default     = "t3.medium"
 
  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Only t3 instance types are allowed."
  }
}
 
resource "aws_instance" "api" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type
  subnet_id     = module.vpc.private_subnet_ids[0]
}

Puntos clave

  1. El IaC declarativo detecta el drift — Terraform muestra cuándo la realidad diverge del código
  2. El estado remoto con bloqueo es obligatorio para equipos — los archivos de estado locales provocan conflictos
  3. Los módulos crean patrones de infraestructura reutilizables — combínalos según cada entorno
  4. Ejecuta plan en CI en cada PR — los revisores deben ver exactamente qué va a cambiar
  5. Parametrízalo todo — los valores fijos en el código impiden la reutilización entre entornos
  6. Versiona tu código de infraestructura — con el mismo flujo de ramas, revisiones y CI/CD que el código de la aplicación
Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX