Saltar a contenido

Arquitectura

Documento de referencia de la arquitectura completa del homelab. Recoge las decisiones tomadas y los puntos aún pendientes (marcados como tal).

1. Visión general

Un único nodo Proxmox (NUC11TNKi7 "Luka") aloja un cluster Kubernetes k3s compuesto por 3 VMs, desplegado con Terraform (provider bpg/proxmox) y gestionado con Argo CD (GitOps).

flowchart TD
    GH[GitHub Repo<br/>homelab-infra] -->|plan/apply vía Actions| TF[Terraform<br/>provider bpg/proxmox]
    GH -->|autosync| ACD[Argo CD]

    TF -->|crea VMs en pool k8s-homelab| PVE[Proxmox NUC11TNKi7<br/>"Luka"]

    subgraph Cluster["Cluster k3s (VMIDs 9000-9099)"]
        CP[kobe<br/>control-plane]
        W1[kyrie<br/>worker-1]
        W2[kawhi<br/>worker-2]
    end

    PVE --> Cluster
    ACD --> Cluster

    subgraph Net["Networking"]
        ML[MetalLB<br/>LoadBalancer LAN]
        TR[Traefik + Gateway API]
        CF[Cloudflare Tunnel<br/>exposición externa]
    end

    Cluster --> Net

2. Capas y flujo de cambios

La gestión se separa en capas con distinto nivel de riesgo y, por tanto, distinto grado de automatización:

Capa Riesgo Autosync Flujo
VMs / Proxmox (terraform/) Alto Sí (GitHub Actions) PR con terraform plan → merge → apply auto
Plataforma (clusters/luka/argocd/) Medio Sí (Argo CD) merge a main → autosync
Apps (clusters/luka/apps/) Bajo-Medio Sí (Argo CD) merge a main → autosync

El autosync de la capa Terraform es seguro porque no puede tocar las VMs existentes: token limitado a /pool/k8s-homelab, rango VMID 9000-9099 y lifecycle { prevent_destroy = true }. El plan del PR actúa de review gate.

3. Aislamiento de Proxmox (crítico)

El mismo Proxmox aloja VMs de producción ajenas a este repo (lebron, steph, shai, butler, gestionadas con Docker Compose/Portainer). Para no interferir:

  • Resource Pool dedicado: k8s-homelab.
  • Rango de VMID reservado: 9000-9099.
  • Token de API de Proxmox con permisos limitados al pool k8s-homelab (privsep ON; ver §9 para el procedimiento completo).
  • Storage: se reutiliza local-lvm (sin storage dedicado; ver §9).
  • terraform import prohibido sobre cualquier VM no creada por este repo.
  • lifecycle { prevent_destroy = true } en VMs y discos críticos.

4. Nodos del cluster

Host Rol VMID vCPU RAM Disco
kobe control-plane 9001 2 2 GB 20 GB
kyrie worker-1 9002 1 1 GB 20 GB
kawhi worker-2 9003 1 1 GB 20 GB
  • Template base: Ubuntu Server 24.04 LTS (cloud image, VMID 9000).
  • Storage: local-lvm. Pool: k8s-homelab. Node: luka.
  • IPs estáticas (cloud-init): kobe 192.168.20.10, kyrie .11, kawhi .12 (rango reservado 192.168.20.10-20). Gateway 192.168.20.1.
  • Arranque: se crean apagadas (started = false) hasta disponer de RAM.

Restricción de RAM (importante): el host tiene 16 GB y ya está saturado (lebron usa ~10 GB, steph 1 GB, ~710 MB disponibles, con swap activo). Este sizing (4 GB total de cluster) no cabe hasta instalar la ampliación a 32 GB (ya pedida). Ver §10.

5. Networking

  • MetalLB: servicios LoadBalancer con IPs LAN reales (rango pendiente: la red de nodos es 192.168.20.x; confirmar el rango de MetalLB en esa red). Para tráfico no-HTTP (p. ej. DNS/PiHole).
  • Traefik + Gateway API: tráfico HTTP/HTTPS interno, sustituye a Nginx Proxy Manager. Se usa Gateway API (estándar de futuro), no Ingress clásico; requiere activar el provider de Traefik e instalar los CRDs en k3s.
  • Cloudflare Tunnel: exposición externa segura, sin abrir puertos en el router. La parte de Cloudflare (objeto tunnel + registros DNS) se gestionará con Terraform (provider cloudflare) en la Fase 5 (al final de la migración); el cliente cloudflared correrá dentro del cluster. El bucket R2 del state queda como bootstrap manual (fuera de TF, por el ciclo state-dentro-del-bucket).

6. Repositorio GitOps

  • Argo CD instalado y gestionado desde clusters/luka/argocd/.
  • Cada servicio futuro vive en clusters/luka/apps/<servicio>/ como una Application de Argo CD.
  • Autosync completo: apps vía Argo CD, y VMs vía GitHub Actions (merge a main).

7. Estado actual

  • [ ] Fase 1 — Todo lo nuevo, sin datos a migrar: Terraform (VMs) → k3s → Argo CD → Gateway API + Traefik → MetalLB → cert-manager → Sealed Secrets → Longhorn → CloudNativePG → Kured → MkDocs → Heimdall → Authentik → Vaultwarden + External Secrets → kube-prometheus-stack + Loki + Alloy → ARC.
  • [ ] Fase 2 — Servicios simples, sin estado complejo que migrar: Grocy.
  • [ ] Fase 3 — El grueso, con BD/estado real: n8n, Grafana, InfluxDB (+ Goldilocks temporal).
  • [ ] Fase 4 — Complicados: hardware físico + críticos sin HA: Home Assistant, Zigbee2MQTT (USB → kawhi), Frigate (iGPU → kyrie).
  • [ ] Fase 5 (final) — Cloudflare como IaC (DNS, Tunnel, reglas).

8. Pendiente de definir

  • Rango de MetalLB en la red 192.168.20.x.
  • k3s bootstrap: scripts vs. Ansible (ver k3s/).
  • Versiones concretas (k3s, provider proxmox, Argo CD).
  • Si Terraform gestiona el pool k8s-homelab como recurso o solo lo referencia (el pool ya existe, creado a mano).

9. Setup de Proxmox (pool, rol, token y ACLs)

Procedimiento reproducible para dar acceso a Terraform dejándolo aislado del resto de VMs del host.

9.1 Pool y storage

  • Pool dedicado k8s-homelab (creado a mano, vacío).
  • Storage: se reutiliza local-lvm (thin pool data). No se crea storage dedicado: el VG pve solo tiene ~16 GB de extensión física libre, insuficiente para un thin pool aparte. local-lvm tiene ~590 GB libres (25 % usado), de sobra para el cluster.
  • El aislamiento real lo dan las ACLs de VM (pool) + rango VMID 9000-9099 + prevent_destroy, no el storage.
  • El template (VMID 9000) debe estar en el pool k8s-homelab para que el token pueda clonarlo (VM.Clone). Si no, el apply falla con 403: qm set 9000 --pool k8s-homelab.
  • Vigilar el thin pool data (~25 % usado): si se llena por aprovisionamiento fino, afecta a todas las VMs del host, incluidas las de producción.

9.2 Rol

Rol k8s-homelab con los privilegios mínimos que necesita el provider bpg/proxmox:

pveum role modify k8s-homelab -privs "Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Pool.Allocate Pool.Audit Sys.Audit Sys.Console VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.PowerMgmt"

9.3 Usuario y token

pveum user add terraform@pam
pveum user token add terraform@pam terraform -privsep 1

9.4 ACLs (usuario Y token)

Importante: con privsep 1, los permisos efectivos del token son la intersección entre los del usuario y los del token. Hay que dar las ACLs a ambos o el token queda sin permisos efectivos.

pveum acl modify /pool/k8s-homelab -role k8s-homelab -user terraform@pam
pveum acl modify /storage/local-lvm -role k8s-homelab -user terraform@pam
pveum acl modify /pool/k8s-homelab -role k8s-homelab -token terraform@pam!terraform
pveum acl modify /storage/local-lvm -role k8s-homelab -token terraform@pam!terraform

9.5 Verificación

pveum acl list

# Listar el pool por id (debe devolver k8s-homelab):
curl -sk -H 'Authorization: PVEAPIToken=terraform@pam!terraform=<SECRET>' \
  'https://<HOST>:8006/api2/json/pools/k8s-homelab'

# Listar VMs (debe devolver lista vacía: no ve las de producción):
curl -sk -H 'Authorization: PVEAPIToken=terraform@pam!terraform=<SECRET>' \
  'https://<HOST>:8006/api2/json/nodes/luka/qemu'
  • pools/k8s-homelab{"data":{"poolid":"k8s-homelab","members":[]}}
  • nodes/luka/qemu{"data":[]} ✓ (no ve las VMs de producción)

10. Restricción de RAM del host

El NUC tiene 16 GB y ya está saturado por las VMs de producción. Medidas reales (agosto 2026):

VM RAM asignada Uso real Nota
lebron 12 GB pico ~8 GB no se puede bajar de ~10 GB sin riesgo
steph 1 GB ~550 MB no se puede bajar a 512 MB (provocó panic/OOM)
shai 4 GB stopped (no consume)
butler 2 GB stopped (no consume)

El host queda con ~710 MB disponibles y swap activo. El cluster de 3 nodos (4 GB) no cabe hasta instalar la ampliación.

Decisión tomada: ampliar a 32 GB (kit Crucial CT2K16G4SFRA32A 2×16 GB DDR4-3200 SODIMM), ya pedido, llega ~30 agosto 2026. Mientras tanto, la config de Terraform deja los nodos definidos (started = false) para desplegar en cuanto haya RAM.