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 ylifecycle { 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 importprohibido 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 reservado192.168.20.10-20). Gateway192.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
LoadBalancercon IPs LAN reales (rango pendiente: la red de nodos es192.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
Ingressclá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 clientecloudflaredcorrerá 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-homelabcomo 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 pooldata). No se crea storage dedicado: el VGpvesolo tiene ~16 GB de extensión física libre, insuficiente para un thin pool aparte.local-lvmtiene ~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-homelabpara 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.