Problema

Mantener un homelab pequeño pero funcional suele colapsar cuando la configuración se vuelve manual. Cada vez que se añade una nueva VM, un contenedor LXC o un servicio (por ejemplo, AdGuard o un clúster k3s), el proceso implica varios pasos repetitivos: crear discos, asignar redes, instalar paquetes y ajustar configuraciones de arranque. Con el tiempo, la documentación se dispersa, los cambios se pierden y la recuperación tras un fallo de hardware se vuelve un dolor de cabeza. El desafío es conseguir que la infraestructura sea reproducible, versionable y desplegable con un solo comando, sin depender de ajustes manuales en cada nodo.

Causa

  1. Ausencia de IaC (Infrastructure as Code). Sin una capa declarativa, los recursos se crean de forma ad‑hoc y el estado real del entorno no se refleja en ningún repositorio.
  2. Separación de herramientas. Usar Terraform para la provisión de VMs y Ansible para la configuración es una práctica recomendada, pero cuando cada herramienta tiene su propio flujo de trabajo, el proceso se vuelve fragmentado.
  3. Falta de orquestador de alto nivel. Ejecutar varios scripts por separado aumenta la probabilidad de errores humanos y dificulta la automatización completa.
  4. Gestión de secretos y estado dispersa. Guardar credenciales en archivos locales o en el código fuente expone el entorno a riesgos y complica la migración a otro nodo.
  5. Redes caseras sin DHCP centralizado. Cuando el único servidor que ofrece DNS/DHCP cae, toda la red pierde conectividad, lo que evidencia la necesidad de alta disponibilidad o al menos de una estrategia de fallback.

Solución

Una arquitectura basada en Terraform + Ansible + Make permite describir la infraestructura, aplicar configuraciones y encadenar los pasos en una única orden (make all). El flujo típico es:

  1. Terraform declara los recursos de Proxmox (VMs, contenedores LXC, discos, interfaces de red). Usa el provider oficial de Proxmox y cloud‑init para inyectar usuarios y claves SSH.
  2. Ansible consume el inventario generado por Terraform y ejecuta playbooks que instalan Docker, despliegan AdGuard, configuran k3s o cualquier otro servicio.
  3. Make actúa como “orquestador de comandos”, definiendo targets (plan, apply, provision, deploy) que llaman a Terraform y Ansible en el orden correcto. Con variables de entorno se pueden pasar secretos sin escribirlos en disco.
  4. Vault o SOPS protege los archivos .tfvars y los ansible_vault.yml. El estado de Terraform se guarda en un backend remoto (por ejemplo, HashiCorp Cloud) para que cualquier máquina pueda continuar el trabajo.
  5. Redundancia ligera: se configura una segunda VM mínima que actúe como DNS/DHCP de reserva usando dnsmasq. En caso de caída del nodo principal, el router puede apuntar al backup mediante una regla estática.

Paso a paso resumido

  1. Inicializar Terraform
    terraform init -backend-config="bucket=homelab-tfstate"
    
  2. Planificar cambios
    terraform plan -var-file=secrets.tfvars
    
  3. Aplicar infraestructura
    terraform apply -auto-approve -var-file=secrets.tfvars
    
  4. Generar inventario dinámico
    Terraform exporta un archivo inventory.ini con IPs y nombres de host.
  5. Ejecutar Ansible
    ansible-playbook -i inventory.ini site.yml
    
  6. Orquestar todo con Make
    make all
    

Cuándo aplicar esta solución

  • Escala pequeña‑media (1‑5 nodos) donde la complejidad de la red justifica IaC pero no se necesita un orquestador de clúster completo.
  • Entornos de pruebas que deben ser recreados frecuentemente (p.ej., para validar actualizaciones de k3s o de contenedores).
  • Equipos con conocimientos básicos de Terraform y Ansible; la curva de aprendizaje se amortiza rápidamente al ganar reproducibilidad.
  • Situaciones donde el tiempo de inactividad es crítico: al tener el estado en la nube y los playbooks versionados, una reinstalación del hardware se reduce a “clonar el repo y ejecutar make all”.

No es adecuado cuando:

  • Se requiere alta disponibilidad a nivel de hipervisor (cluster Proxmox con Ceph, etc.).
  • La infraestructura supera varios decenas de nodos; en ese caso conviene migrar a herramientas como Pulumi o Terraform Cloud con workspaces más avanzados.
  • No se dispone de una máquina de gestión que pueda ejecutar Make/Ansible de forma continua.

Código

# Makefile (fragmento esencial)
.PHONY: all plan apply provision deploy

all: plan apply provision

plan:
	terraform plan -var-file=secrets.tfvars

apply:
	terraform apply -auto-approve -var-file=secrets.tfvars

provision:
	ansible-playbook -i inventory.ini site.yml

deploy:
	# Aquí irían los playbooks de apps (k3s, etc.)
	@echo "Despliegue de aplicaciones pendiente"
# terraform/main.tf (ejemplo de VM con cloud‑init)
provider "proxmox" {
  endpoint = var.proxmox_endpoint
  api_token_id = var.api_token_id
  api_token_secret = var.api_token_secret
  insecure_skip_verify = true
}

resource "proxmox_vm_qemu" "adguard" {
  name        = "adguard"
  target_node = "pve"
  vmid        = 101
  memory      = 2048
  cores       = 2
  scsihw      = "virtio-scsi-pci"
  bootdisk    = "scsi0"
  disk {
    slot    = 0
    size    = "20G"
    type    = "scsi"
    storage = "local-lvm"
  }
  network {
    model  = "virtio"
    bridge = "vmbr0"
  }
  cloudinit {
    user = "admin"
    password = var.vm_password
    ssh_keys = file(var.ssh_pub_key)
  }
}
# ansible/playbooks/site.yml (snippet)
- hosts: all
  become: true
  vars_files:
    - vault.yml
  tasks:
    - name: Instalar paquetes base
      apt:
        name: [docker.io, curl, gnupg2]
        state: present
        update_cache: yes

    - name: Desplegar AdGuard Home (Docker)
      docker_container:
        name: adguard
        image: adguard/adguardhome:latest
        restart_policy: unless-stopped
        ports:
          - "53:53/tcp"
          - "53:53/udp"
          - "80:80/tcp"
          - "443:443/tcp"
        volumes:
          - "/opt/adguard/work:/opt/adguard/work"
          - "/opt/adguard/conf:/opt/adguard/conf"

Verificación

  1. Estado de Terraform

    terraform show
    

    Confirma que los recursos aparecen con los IDs esperados y que las IP asignadas coinciden con el inventario.

  2. Conectividad de Ansible

    ansible -m ping all -i inventory.ini
    

    Todos los hosts deben responder pong.

  3. Servicios críticos

    • Acceder a http://<IP_ADGUARD>:80 y comprobar la UI de AdGuard.
    • Ejecutar docker ps dentro de la VM para validar que el contenedor está corriendo.
    • Si se configuró k3s, kubectl get nodes debe listar el nodo.
  4. Resiliencia de red

    • Apagar la VM principal y verificar que el backup DNS/DHCP responde a consultas (dig @<IP_BACKUP> example.com).

Notas adicionales

  • Manejo de secretos: Usa ansible-vault para contraseñas y sops para cifrar los .tfvars. Evita variables de entorno en texto plano.
  • Idempotencia: Tanto Terraform como Ansible son declarativos; sin embargo, revisa que los playbooks no incluyan tareas que dependan de estado externo (p.ej., shell: docker pull … sin creates).
  • Versionado: Mantén el repositorio en Git con ramas dev y prod. Los cambios en main pueden disparar pipelines CI que ejecuten terraform fmt y ansible-lint.
  • Backup del estado: Si el backend remoto falla, Terraform puede restaurarse desde un snapshot del bucket de almacenamiento. Configura rotación de snapshots.
  • Escalado futuro: Cuando necesites añadir un nodo Proxmox, simplemente extiende el bloque proxmox_vm_qemu o crea un módulo reutilizable. El resto del flujo (make all) sigue siendo el mismo.

Con este enfoque, cualquier homelab basado en Proxmox pasa de ser una colección de máquinas configuradas a mano a una plataforma reproducible, versionable y fácil de mantener. La inversión inicial en IaC paga rápidamente en tiempo de recuperación, consistencia y capacidad de experimentar sin temor a romper el entorno.