Saltar a contenido

Infrastructure as Code (IaC)


Proposito

Guia y ejemplos de como gestionar la infraestructura del sistema como codigo. Todo cambio de infra se versiona, se revisa, y se aplica de forma reproducible.


1. Principios de IaC

  1. Todo en version control. La infraestructura se define en archivos, no en consolas web.
  2. Reproducible. El mismo codigo produce el mismo resultado, siempre.
  3. Inmutable preferido. En vez de modificar un servidor, crea uno nuevo y reemplaza.
  4. Documentacion viva. El codigo ES la documentacion de la infraestructura.

2. Stack de IaC del Proyecto

Componente Herramienta Alternativas
Infraestructura cloud Terraform Pulumi, CloudFormation
Configuracion de servidores Ansible Chef, Puppet
Contenedores (futuro) Docker + Docker Compose Podman
Orquestacion (futuro) Kubernetes (si escala) ECS, Docker Swarm

3. Ejemplo: Terraform para DigitalOcean

Estructura de archivos

infrastructure/
  terraform/
    main.tf
    variables.tf
    outputs.tf
    environments/
      staging.tfvars
      production.tfvars

main.tf - Recursos principales

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

# Base de datos PostgreSQL
resource "digitalocean_database_cluster" "postgres" {
  name       = "${var.project_name}-db-${var.environment}"
  engine     = "pg"
  version    = "17"
  size       = var.db_size
  region     = var.region
  node_count = var.db_node_count

  maintenance_window {
    day  = "sunday"
    hour = "04:00:00"
  }
}

# Firewall de BD: solo acceso desde la app
resource "digitalocean_database_firewall" "postgres_fw" {
  cluster_id = digitalocean_database_cluster.postgres.id

  rule {
    type  = "droplet"
    value = digitalocean_droplet.app_server.id
  }
}

# Servidor de aplicacion
resource "digitalocean_droplet" "app_server" {
  name     = "${var.project_name}-app-${var.environment}"
  size     = var.app_size
  image    = "ubuntu-24-04-x64"
  region   = var.region
  ssh_keys = [var.ssh_key_id]

  tags = ["${var.project_name}", var.environment]
}

# DNS
resource "digitalocean_record" "api" {
  domain = var.domain
  type   = "A"
  name   = var.environment == "production" ? "api" : "staging-api"
  value  = digitalocean_droplet.app_server.ipv4_address
}

variables.tf

variable "project_name" {
  description = "Nombre del proyecto"
  type        = string
  default     = "turnos-medicos"
}

variable "environment" {
  description = "Ambiente: staging o production"
  type        = string
}

variable "region" {
  description = "Region de DigitalOcean"
  type        = string
  default     = "nyc3"
}

variable "db_size" {
  description = "Tamano de la instancia de BD"
  type        = string
  default     = "db-s-1vcpu-1gb"
}

variable "db_node_count" {
  description = "Numero de nodos de BD"
  type        = number
  default     = 1
}

variable "app_size" {
  description = "Tamano del droplet de la app"
  type        = string
  default     = "s-2vcpu-4gb"
}

variable "domain" {
  description = "Dominio principal"
  type        = string
}

variable "ssh_key_id" {
  description = "ID de la SSH key en DigitalOcean"
  type        = string
}

environments/production.tfvars

environment    = "production"
db_size        = "db-s-1vcpu-1gb"
db_node_count  = 1
app_size       = "s-4vcpu-8gb"
domain         = "turnosmedicos.com"

4. Ejemplo: Ansible para Configuracion del Servidor

playbook.yml

---
- name: Configurar servidor de aplicacion
  hosts: app_servers
  become: yes

  vars:
    node_version: "20"
    app_dir: /opt/turnos-medicos
    pm2_instances: 3

  tasks:
    - name: Instalar Node.js
      shell: |
        curl -fsSL https://deb.nodesource.com/setup_{{ node_version }}.x | bash -
        apt-get install -y nodejs

    - name: Instalar PM2
      npm:
        name: pm2
        global: yes

    - name: Crear directorio de la app
      file:
        path: "{{ app_dir }}"
        state: directory
        owner: deploy
        group: deploy

    - name: Configurar PM2 ecosystem
      template:
        src: templates/ecosystem.config.js.j2
        dest: "{{ app_dir }}/ecosystem.config.js"

    - name: Configurar Nginx como reverse proxy
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/turnos-medicos
      notify: Reload Nginx

    - name: Habilitar sitio en Nginx
      file:
        src: /etc/nginx/sites-available/turnos-medicos
        dest: /etc/nginx/sites-enabled/turnos-medicos
        state: link
      notify: Reload Nginx

    - name: Configurar firewall (UFW)
      ufw:
        rule: allow
        name: "{{ item }}"
      loop:
        - 'Nginx Full'
        - 'OpenSSH'

    - name: Habilitar firewall
      ufw:
        state: enabled

  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

5. Workflow de IaC

1. Cambiar archivos de infra en una branch
2. PR con terraform plan como comentario
3. Review por otro ingeniero
4. Merge a main
5. terraform apply (manual o via CI)
6. Verificar que la infra esta correcta

Comandos clave

# Ver que va a cambiar (SEGURO, no modifica nada)
terraform plan -var-file=environments/staging.tfvars

# Aplicar cambios (CUIDADO, modifica infraestructura real)
terraform apply -var-file=environments/staging.tfvars

# Destruir infraestructura (MUY PELIGROSO)
terraform destroy -var-file=environments/staging.tfvars

6. Seguridad en IaC

Practica Implementacion
No hardcodear secrets Usar variables de ambiente o vault
State file seguro Terraform state en backend remoto (S3/DO Spaces) con encriptacion
Acceso minimo Service accounts con permisos especificos
Audit trail Todo cambio via PR, nunca directo en consola
Rotacion de credentials Automatizar rotacion de API keys cada 90 dias

Checklist de Completitud

  • Principios de IaC documentados
  • Terraform ejemplo con DigitalOcean
  • Ansible ejemplo para configuracion
  • Workflow de IaC definido
  • Seguridad en IaC
  • Revisada por otro ingeniero

Archivos relacionados