From n00b to ZeroCool / Profesionalización

Infrastructure as Code: servidores que se escriben (y no se rezan)

Guía práctica de IaC con Terraform: módulos, state, CI y rollback. Menos click-ops, más control para deploys cloud-native sin drama.

Lo que vale la pena leer aquí

Al día siguiente, nadie podía entrar al sistema. El balanceador “misteriosamente” apuntaba a un target group viejo, el security group tenía un puerto abierto “temporal” y el único que sabía qué se cambió… iba en el Uber, sin batería y con la laptop viejita apagada en la mochila.

Intro con gancho

El outage no empezó con un hackeo épico ni con un meteorito. Empezó con un “nomás le moví tantito” en la consola del cloud, un martes 6:40 pm, cuando ya estabas pensando en los tacos.

Al día siguiente, nadie podía entrar al sistema. El balanceador “misteriosamente” apuntaba a un target group viejo, el security group tenía un puerto abierto “temporal” y el único que sabía qué se cambió… iba en el Uber, sin batería y con la laptop viejita apagada en la mochila.

Infrastructure as Code (IaC) es la vacuna contra ese tipo de tragedias. No te quita el estrés, pero lo vuelve trazable, reproducible y reversible.

Qué vas a aprender

  • Qué problema real resuelve IaC y cuándo vale la pena (y cuándo no).
  • Una guía paso a paso para levantar infraestructura con Terraform de forma profesional (sin magia negra).
  • Cómo organizar repos, módulos y ambientes (dev/stage/prod) sin que se vuelva una telenovela.
  • Cómo manejar el state, secretos, cambios peligrosos y deploys con CI.
  • Errores comunes que ya nos cobraron horas nalga (para que no te cobren a ti).

Contexto práctico

Piensa en IaC como tratar tu infraestructura igual que tu código:

  • Git como fuente de verdad (quién cambió qué y cuándo).
  • Pull requests para revisar cambios antes de romper production.
  • Environments (dev/stage/prod) con diferencias controladas.
  • Automatización: menos click-ops, más pipeline.

En México y LATAM esto pega doble:

  • Si tu internet se pone punk y se cae a mitad de un cambio manual, IaC te salva porque puedes reintentar y dejar evidencia.
  • Cuando el presupuesto está apretado (sí, AWS en dólares), IaC te ayuda a ver y auditar qué está encendido y por qué. Ese RDS “de prueba” que lleva 4 meses corriendo ya no se esconde tan fácil.

Tradeoff honesto: IaC mete disciplina. Al inicio se siente más lento que “nomás crear el recurso en la consola”. Pero a la tercera vez que lo replicas, o al primer rollback de verdad, se paga solo.

Paso a paso: tu primera base seria con Terraform

La idea es armar una infraestructura pequeña pero realista. Por ejemplo, en AWS:

  • VPC + subnets
  • Security group
  • Un EC2 (o lo que uses) como placeholder

Si usas GCP/Azure aplica igual. Cambian providers y nombres, no el patrón.

1) Instala y prepara el repo

Requisitos:

  • Terraform (ideal: tfenv para versionar)
  • Credenciales del cloud (AWS CLI con perfil, o variables)
  • Git

Estructura recomendada (sencilla pero escalable):

infra/
  modules/
    network/
    compute/
  envs/
    dev/
      main.tf
      providers.tf
      variables.tf
      outputs.tf
      terraform.tfvars
    prod/
      ...
  .terraform.lock.hcl
  README.md

Reglas de oro:

  • Módulos para cosas repetibles (network, compute, db).
  • envs para valores por ambiente (tamaños, CIDR, tags, regiones, etc.).

2) Configura provider y versiones

envs/dev/providers.tf

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

envs/dev/variables.tf

variable "region" {
  type    = string
  default = "us-east-1"
}

variable "project" {
  type    = string
  default = "byteit"
}

variable "env" {
  type    = string
  default = "dev"
}

3) Crea un módulo de red (VPC + subnets)

modules/network/main.tf

resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true

  tags = {
    Name = "${var.project}-${var.env}-vpc"
  }
}

resource "aws_subnet" "public_a" {
  vpc_id                  = aws_vpc.this.id
  cidr_block              = var.public_subnet_cidr
  map_public_ip_on_launch = true
  availability_zone       = var.az

  tags = {
    Name = "${var.project}-${var.env}-public-a"
  }
}

modules/network/variables.tf

variable "project" { type = string }
variable "env"     { type = string }
variable "vpc_cidr" { type = string }
variable "public_subnet_cidr" { type = string }
variable "az" { type = string }

modules/network/outputs.tf

output "vpc_id" {
  value = aws_vpc.this.id
}

output "public_subnet_id" {
  value = aws_subnet.public_a.id
}

4) Crea un módulo de compute (EC2 simple)

modules/compute/main.tf

resource "aws_security_group" "this" {
  name   = "${var.project}-${var.env}-sg"
  vpc_id = var.vpc_id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = var.ssh_cidrs
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project}-${var.env}-sg"
  }
}

resource "aws_instance" "this" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [aws_security_group.this.id]

  tags = {
    Name = "${var.project}-${var.env}-ec2"
  }
}

modules/compute/variables.tf

variable "project" { type = string }
variable "env"     { type = string }

variable "vpc_id"   { type = string }
variable "subnet_id"{ type = string }

variable "ami_id" {
  type = string
}

variable "instance_type" {
  type    = string
  default = "t3.micro"
}

variable "ssh_cidrs" {
  type    = list(string)
  default = ["0.0.0.0/0"]
}

5) Une todo desde el environment

envs/dev/main.tf

module "network" {
  source             = "../../modules/network"
  project            = var.project
  env                = var.env
  vpc_cidr           = "10.10.0.0/16"
  public_subnet_cidr  = "10.10.1.0/24"
  az                 = "us-east-1a"
}

module "compute" {
  source        = "../../modules/compute"
  project       = var.project
  env           = var.env
  vpc_id        = module.network.vpc_id
  subnet_id     = module.network.public_subnet_id

  # Nota: define un AMI real para tu región
  ami_id        = "ami-0c02fb55956c7d316"
  instance_type = "t3.micro"

  # Neta: no dejes 0.0.0.0/0 en prod. En dev quizá, pero que sea consciente.
  ssh_cidrs     = ["YOUR_PUBLIC_IP/32"]
}

6) Plan y apply (sin volarte un pie)

Desde infra/envs/dev:

terraform init
terraform fmt -recursive
terraform validate
terraform plan -out tfplan
terraform apply tfplan

Tips de batalla:

  • plan -out te deja un artefacto. Si alguien corre apply con ese plan, aplica exactamente lo revisado.
  • Si el plan está gigante y no lo entiendes: no lo apliques. Algo está raro (provider upgrade, drift, o módulo mal amarrado).
Infrastructure as Code: servidores que se escriben (y no se rezan) - visual explicativa 1
Visual de apoyo: Intro con gancho

7) Profesionaliza: state remoto + locking

El state local (terraform.tfstate) en tu laptop es como llevar la contabilidad en servilletas. Funciona… hasta que alguien más corre apply, o hasta que tu disco muere el día que el jefe te pide “un cambio rápido” antes del deadline.

Lo sano:

  • State remoto (por ejemplo S3)
  • Locking (DynamoDB) para evitar cambios concurrentes

Ejemplo backend S3 (en cada env):
envs/dev/backend.tf

terraform {
  backend "s3" {
    bucket         = "byteit-terraform-state"
    key            = "dev/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "byteit-terraform-locks"
    encrypt        = true
  }
}

Realidad: el bucket/tabla para el backend se vuelve “infra de la infra”. Puedes crearlo manualmente una vez (bien documentado) o con un bootstrap separado. Lo que no quieres es que cada quien lo invente a su modo.

8) Secretos: no los metas al repo (aunque “nadie lo vea”)

Terraform va a guardar valores en el state. Si metes passwords literal, terminas con secretos regados en:

  • el state
  • logs del CI
  • PRs

Estrategias:

  • Usa un secret manager (AWS Secrets Manager, SSM Parameter Store, Vault).
  • Pasa referencias, no el secreto crudo.
  • Marca variables como sensitive = true para reducir exposición en outputs.

Ejemplo (variable sensible):

variable "db_password" {
  type      = string
  sensitive = true
}

9) Mete CI: plan en PR, apply con control

Un workflow típico (y bien aterrizado):

  • PR: terraform fmt, validate, plan y publica el plan como comentario/artefacto.
  • Merge a main: apply (ideal: con aprobación manual para prod).

Si tu equipo es chico y traen mil pendientes (clásico): mínimo

  • PR obligatorio
  • plan visible
  • apply solo desde CI

Porque el “lo corrí en mi máquina” en infraestructura se traduce a “lo rompí en production”. Y luego viene el rollback a mano, con sudor.

Screenshots sugeridos

  • Captura del output de terraform plan mostrando cambios claros (add/change/destroy).
  • Vista del state remoto en S3 y el lock en DynamoDB.
  • Ejemplo de PR con el plan pegado (GitHub/GitLab) y un approve.
  • Un diff de módulo (cambio de variable/tag) para mostrar trazabilidad.

Errores comunes + solución

1) Drift: alguien cambió cosas en consola

Síntoma: plan muestra cambios que nadie “hizo en código”.

Solución:

  • Acepta la realidad: la consola existe. Define una regla y respétala.
  • Corre terraform refresh (o plan normal) para ver drift.
  • Decide: o regresas el cambio a lo que dice el código, o actualizas el código para que represente el nuevo estado.
  • En prod, reduce click-ops con permisos (IAM) y separa roles.

2) Destroy accidental por cambios de nombre o count

Síntoma: Terraform quiere destruir y recrear recursos porque cambió un name, un count o un índice.

Solución:

  • Usa for_each con llaves estables en lugar de count cuando puedas.
  • Revisa terraform plan buscando -/+ (recreate).
  • Para recursos críticos, considera lifecycle { prevent_destroy = true }.

3) State perdido o mezclado entre ambientes

Síntoma: aplicas dev y te cambia prod (terror real).

Solución:

  • Backend remoto separado por key/workspace.
  • Variables por environment bien definidas.
  • Bloquea el apply de prod con aprobaciones.

4) Módulos sin versión: el “ya cambió algo” fantasma

Síntoma: un módulo cambia y rompe ambientes sin aviso.

Solución:

  • Versiona módulos (tags/commits) si están en repo aparte.
  • Si están en monorepo, usa PRs y releases internos; evita cambios sin revisión.

5) Plan enorme por upgrade del provider

Síntoma: actualizas provider y el plan se vuelve kilométrico.

Solución:

  • Actualiza provider con intención (PR dedicado).
  • Lee changelog, prueba en dev, luego stage, luego prod.
  • Si es demasiado, congela versión y agenda la migración. Mejor un upgrade bien hecho que un deploy tarde con rollback improvisado.
Infrastructure as Code: servidores que se escriben (y no se rezan) - visual explicativa 2
Visual de apoyo: Qué vas a aprender

Checklist final

  • Infra en Git con estructura modules/ + envs/.
  • terraform fmt, validate pasan local y en CI.
  • State remoto con locking (S3 + DynamoDB o equivalente).
  • Secrets fuera del repo; variables sensibles marcadas.
  • PR con plan visible y apply controlado (idealmente desde CI).
  • Regla anti-click-ops definida (y permisos alineados).
  • Estrategia para cambios peligrosos (for_each, prevent_destroy, revisiones).

FAQ

1) ¿Terraform es lo mismo que Ansible?

No. Terraform es más para provisioning (crear/gestionar recursos). Ansible es más de config management (configurar cosas dentro del servidor). Se complementan, no compiten.

2) ¿IaC solo sirve en cloud?

No. Puedes usar IaC para on-prem (VMs, redes) dependiendo de tooling/proveedores. Pero donde más brilla es en cloud porque todo se mueve por API.

3) ¿Qué tan “cloud-native” es IaC?

Mucho. Cloud-native no es solo Kubernetes: es operar con automatización, reproducibilidad y cambios auditables. IaC es base para pipelines, clusters, observabilidad y escalamiento.

4) ¿Qué hago si ya tengo infraestructura hecha a mano?

Empieza por lo más doloroso o repetible. Puedes:

  • Importar recursos (terraform import) y luego limpiar el código.
  • Recrear por etapas (blue/green) si importar es un infierno.
  • Hacer migración gradual: primero redes, luego compute, luego datos (con cuidado).

5) ¿Cómo evito que Terraform borre cosas críticas?

  • Revisión humana del plan.
  • prevent_destroy en recursos sensibles.
  • Separar stacks (por ejemplo, datos en un stack diferente al de compute).
  • Backups y runbooks de rollback (porque la vida pasa).

Siguiente episodio: teaser

Ya escribes servidores. Ahora toca que se desplieguen solos, sin que alguien esté “picándole” a mano.

Lo que sigue: pipelines CI/CD para infraestructura y apps, con guardrails para no quemar production por accidente.