Problema

En entornos homelab o pequeñas infraestructuras con Proxmox, es frecuente que cada máquina virtual o contenedor necesite información adicional: rutas de reverse‑proxy, definición de stacks Docker, asignación de GPUs, o cualquier otro dato de “intención” que no pertenece al propio hypervisor. La práctica habitual es guardar esa información en notas, etiquetas o archivos externos. Cada alternativa tiene limitaciones claras:

  • Notas son texto libre, difíciles de validar y no se versionan de forma estructurada.
  • Etiquetas son planas, no permiten jerarquías ni valores complejos.
  • Repositorios de configuración externos obligan a mantener una sincronía manual entre Proxmox y el origen de datos.

El resultado es un “spaghetti” de scripts y archivos que se rompen al mover máquinas, al escalar nodos o al cambiar la topología de red. Lo que falta es un punto de almacenamiento que sea nativo a Proxmox, versionado con el resto del cluster y accesible tanto desde la UI como desde la línea de comandos.

Causa

Los síntomas aparecen cuando:

  1. Se necesita un punto único de verdad para datos que no son parte del modelo de VM/LXC (por ejemplo, puertos que expone Traefik).
  2. Los scripts de arranque (hook scripts) deben leer esa información antes de iniciar la VM.
  3. Se quiere reutilizar la metadata en varios sistemas (Traefik, Docker‑Compose, gestores de GPU) sin duplicar archivos.

En la práctica, la raíz del problema es la ausencia de un mecanismo de metadata estructurada integrado en Proxmox. Sin él, los administradores improvisan con notas o con archivos en directorios arbitrarios, lo que genera:

  • Inconsistencias entre nodos cuando el clúster replica solo /etc/pve.
  • Falta de validación de esquema, lo que permite errores de sintaxis que sólo aparecen en tiempo de ejecución.
  • Dificultad para exponer la metadata a APIs externas sin escribir parsers ad‑hoc.

Solución

Implementar un repositorio de archivos YAML uno por VM/LXC, almacenado bajo /etc/pve y gestionado por una API nativa. Cada archivo sigue un JSON Schema que define sub‑árboles para los distintos consumidores (por ejemplo, traefik, compose, nvidia). Con este enfoque se consigue:

  • Persistencia automática: los archivos se replican con el resto de la configuración del clúster.
  • Validación temprana: cualquier error de sintaxis es rechazado por el esquema antes de que la VM arranque.
  • Interoperabilidad: scripts de hook, servicios externos y la UI pueden leer el mismo documento sin ambigüedades.

Paso a paso

  1. Crear el esquema JSON que describa los bloques esperados. Un ejemplo mínimo incluye traefik, compose y nvidia.
  2. Instalar el paquete que expone la API (pve-meta) y añade una pestaña de edición en la UI.
  3. Definir la convención de nombres: vm-101.yaml para la VM con ID 101, lxc-202.yaml para el contenedor 202.
  4. Poblar los archivos con la información requerida.
  5. Configurar los loops (scripts o systemd timers) que lean los bloques y generen los artefactos finales:
    • Renderizar composedocker-compose.yml dentro del nodo y ejecutar docker compose up -d.
    • Convertir traefik → archivo dinámico consumido por Traefik HTTP provider.
    • Actualizar la configuración de passthrough GPU en /etc/pve/lxc/202.conf a partir del bloque nvidia.

Este modelo es extensible: cualquier nuevo servicio solo necesita añadir su sub‑árbol al esquema y un consumidor que lo interprete.

Cuándo aplicar esta solución

  • Entornos con múltiples VMs/LXCs que comparten configuraciones de red, proxy o hardware.
  • Automatizaciones basadas en metadata (por ejemplo, despliegues de Docker‑Compose por VM).
  • Necesidad de mantener la configuración bajo control de versiones del clúster sin repositorios externos.

No es recomendable cuando:

  • Sólo se gestionan unas pocas máquinas y la sobrecarga de crear esquemas resulta innecesaria.
  • Se requiere un control de acceso granular a la metadata; la solución actual carece de permisos por‑campo.

Código

# 1. Instalar pve-meta (asumiendo repositorio configurado)
apt-get update && apt-get install -y pve-meta

# 2. Crear esquema JSON (guardado en /etc/pve/meta-schema.json)
cat > /etc/pve/meta-schema.json <<'EOF'
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "traefik": {
      "type": "object",
      "properties": {
        "router": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "rule": { "type": "string" },
              "service": { "type": "string" },
              "middlewares": {
                "type": "array",
                "items": { "type": "string" }
              }
            },
            "required": ["rule", "service"]
          }
        }
      }
    },
    "compose": {
      "type": "object"
    },
    "nvidia": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "additionalProperties": false
}
EOF

# 3. Crear metadata para la VM 101
cat > /etc/pve/vm-101.yaml <<'EOF'
traefik:
  router:
    web:
      rule: "Host(`app.example.com`)"
      service: "app-service"
compose:
  version: "3.9"
  services:
    app:
      image: "my/app:latest"
      ports:
        - "8080:80"
nvidia:
  - "GPU-1234abcd"
EOF

# 4. Validar (pve-meta validate) y aplicar cambios
pve-meta validate /etc/pve/vm-101.yaml
pve-meta apply /etc/pve/vm-101.yaml

Verificación

  1. Validación del esquema: el comando pve-meta validate debe devolver OK. Cualquier error aparecerá con línea y campo.
  2. Traefik: abrir http://<node-ip>:8080/api/http/routers y confirmar que el router web está presente.
  3. Docker‑Compose: ejecutar docker compose ps dentro del nodo donde se renderizó el stack; los contenedores declarados deben estar en estado running.
  4. GPU passthrough: inspeccionar la configuración del contenedor (cat /etc/pve/lxc/101.conf) y buscar la línea lxc.cgroup2.devices.allow = c 195:* rwm. El UUID listado en nvidia debe coincidir con la tarjeta asignada.

Si alguno de los pasos falla, revisar el archivo YAML en busca de errores de sangrado o claves no definidas en el esquema.

Notas adicionales

  • Backup: los archivos YAML forman parte de /etc/pve, por lo que los snapshots del clúster ya los incluyen. Sin embargo, es buena práctica versionar los cambios en un repositorio Git externo para auditoría.
  • Extensión de permisos: aunque la versión actual no permite control granular, se puede envolver la API con sudo -u o crear un wrapper que verifique el usuario antes de ejecutar pve-meta apply.
  • Escalado: en clusters con decenas de nodos, considera un loop centralizado (por ejemplo, un timer en cada nodo) que lea solo los archivos locales, evitando tráfico innecesario entre nodos.
  • Depuración: habilita log_level = debug en /etc/pve/pve-meta.conf para obtener trazas detalladas cuando los consumidores (Traefik, Docker) no reflejen los cambios esperados.

Con esta arquitectura, la metadata deja de ser un parche y se convierte en una capa de intención declarativa que cualquier herramienta del ecosistema puede consumir de forma fiable.