Problema
En entornos donde ya se ejecuta Kubernetes, los equipos de desarrollo a menudo se topan con una fricción constante: cada nueva aplicación requiere escribir varios archivos YAML, crear Secrets, definir Services, Ingress y, en caso de bases de datos, montar operadores adicionales. Cuando el objetivo es ofrecer una experiencia similar a la de plataformas SaaS (Railway, Render) pero bajo control propio, esa complejidad se vuelve un obstáculo.
El patrón recurrente es una capa de orquestación que oculte la configuración de bajo nivel mientras mantiene la flexibilidad de Kubernetes. Los síntomas típicos incluyen:
- Los desarrolladores piden “un botón para crear una base de datos” y reciben una lista de CRDs que deben aplicar manualmente.
- Los pipelines de CI/CD se rompen al intentar sincronizar varios microservicios porque cada uno tiene su propio namespace y su propio chart.
- Los entornos de preview tardan minutos en estar listos, ya que se necesita crear recursos en varios clusters y esperar a que los volúmenes se aprovisionen.
En un homelab o en una VPS pequeña, el problema se agrava: el operador que gestiona la base de datos consume recursos que el nodo no puede soportar, y la gestión de múltiples clusters parece innecesaria pero a la vez inevitable cuando se escalan pruebas a producción.
Causa
-
Dependencia directa del YAML de Kubernetes
Los equipos que no son especialistas en k8s terminan editando manifiestos por copia‑pega, lo que genera inconsistencias y errores de sintaxis. -
Falta de un registro de imágenes interno
Cada despliegue construye la imagen en la máquina del desarrollador y la sube a Docker Hub. Cuando se necesita un entorno aislado, la latencia y los límites de cuota aparecen rápidamente. -
Operadores de bases de datos desplegados de forma ad‑hoc
Instalar PostgreSQL con Helm en cada namespace genera configuraciones dispares y dificulta la habilitación de WAL archiving, HA o PITR. -
Ausencia de una arquitectura hub‑spoke
Sin un punto central que distribuya workloads, escalar a varios clusters implica replicar la lógica de despliegue en cada uno, lo que aumenta la carga operativa. -
No hay un modelo declarativo de “aplicación completa”
Cada microservicio se gestiona por separado; no existe un artefacto que describa la topología completa (servicios, bases de datos, enlaces de red).
Solución
Construir una plataforma auto‑hosted de orquestación que se apoye en componentes nativos de la CNCF y ofrezca una UI ligera para crear y conectar servicios. Los bloques clave son:
| Bloque | Herramienta recomendada | Rol |
|---|---|---|
| Cluster base | k3s (para VPS) o cualquier distro k8s | Proveer el plano de control con bajo consumo. |
| Registro interno | Zot | Almacenar imágenes construidas por el CI sin salir del cluster. |
| Base de datos gestionada | CloudNativePG (CNPG) | Operador PostgreSQL con HA, WAL archiving y PITR. |
| Hub‑spoke | Cluster‑API + metallb (opcional) | Distribuir workloads entre varios clusters desde un punto central. |
| UI/CLI de aplicación | Un proyecto propio o una solución como Portainer extendida con plugins | Permitir al desarrollador crear “apps” en un canvas, definir enlaces y lanzar releases. |
| Declaración de stack | Stackfile (YAML propio) | Describir la topología completa y versionarla en Git. |
| Preview environments | Jobs de Kubernetes con TTL | Generar entornos efímeros que se destruyen automáticamente. |
Paso a paso genérico
-
Provisionar el cluster base
En una VPS ejecutak3scon la opción--disable=traefiksi vas a usar tu propio Ingress. -
Instalar Zot
Despliega el chart oficial y expón el Service comoClusterIP. Configura la autenticación con un Secret que el CI pueda leer. -
Desplegar CNPG
Aplica el manifiesto del operador y crea unClusterconreplicas: 2ywalArchive: true. UsastorageClassque apunte a un PVC de tipolocal-pathen entornos de prueba. -
Configurar hub‑spoke
Registra clusters secundarios en el hub medianteclusterctl init. UsaClusterSetpara agruparlos y define políticas de afinidad para que ciertos namespaces siempre se ejecuten en el cluster de mayor capacidad. -
Implementar la UI/CLI
- Despliega el backend (Go/Node) que exponga una API REST.
- Conecta la UI a la API y permite arrastrar servicios al canvas.
- Cada nodo del canvas genera un fragmento de Stackfile que se guarda en Git.
-
Pipeline CI
- Construye la imagen, empújala a Zot (
docker push <registry>/app:sha). - Ejecuta
stackdome-cli apply -f stackfile.yamlpara crear o actualizar la aplicación completa.
- Construye la imagen, empújala a Zot (
-
Entornos de preview
- En el Stackfile define un
previewflag. - El controlador crea un namespace temporal con TTL (por ejemplo, 2h) y replica la topología del stack.
- En el Stackfile define un
Cuándo aplicar esta solución
Aplica cuando:
- Ya tienes un cluster Kubernetes en producción o en pruebas y deseas ofrecer a los desarrolladores una experiencia “push‑button”.
- Necesitas gestionar bases de datos con alta disponibilidad sin que cada equipo configure su propio Helm chart.
- Tu organización usa varios clusters (VPS, bare‑metal, cloud) y quieres un punto único de control para distribuir workloads.
- Los tiempos de creación de entornos de preview son críticos para la velocidad del desarrollo.
No aplica si:
- No dispones de recursos para ejecutar un registro interno (Zot) y puedes usar Docker Hub sin restricciones.
- Solo manejas una única aplicación monolítica; la sobrecarga de la capa UI no se justifica.
- Tu equipo está cómodo trabajando directamente con Helm y Kustomize; la abstracción podría ser innecesaria.
Código
# 1. Instalar k3s (VPS)
curl -sfL https://get.k3s.io | sh -s - --disable=traefik
# 2. Instalar Zot (helm)
helm repo add zot https://helm.zotregistry.io
helm install zot zot/zot --namespace zot --create-namespace \
--set persistence.enabled=true \
--set persistence.size=10Gi
# 3. Instalar CloudNativePG
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm install cnpg cnpg/cloudnative-pg --namespace cnpg --create-namespace
# 4. Crear un Cluster PostgreSQL con WAL archiving
cat <<EOF | kubectl apply -f -
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-main
namespace: cnpg
spec:
instances: 2
storage:
size: 20Gi
storageClass: local-path
walStorage:
size: 5Gi
storageClass: local-path
backup:
retentionPolicy: "30d"
EOF
# 5. Deploy UI backend (ejemplo genérico)
kubectl apply -f https://raw.githubusercontent.com/example/stackdome/main/deploy/backend.yaml
# 6. Deploy UI frontend
kubectl apply -f https://raw.githubusercontent.com/example/stackdome/main/deploy/frontend.yaml
Verificación
-
Cluster operativo
kubectl get nodesdebe mostrar al menos un nodoReady. -
Registro interno
curl -s http://zot.zot.svc.cluster.local/v2/_catalogdebe listar los repositorios creados por el CI. -
PostgreSQL HA
kubectl -n cnpg get pods -l app=postgresqldebe mostrar dos pods en estadoRunning. -
UI accesible
Accede ahttp://<node-ip>:3000y verifica que el canvas permite arrastrar un servicio y conectar a la base de datos. -
Preview environment
Desde la UI crea un preview; luegokubectl get nsdebe mostrar un namespace con sufijo-preview-xxxxy, pasado el TTL, el namespace desaparece.
Notas adicionales
- En clusters con recursos limitados, configura
resourceQuotapor namespace para evitar que un preview consuma toda la memoria. - Zot permite habilitar autenticación mediante
htpasswd; guarda el archivo en un Secret y referencia el Secret en el chart. - CNPG expone métricas en Prometheus; si ya tienes un stack de observabilidad, agrega el
ServiceMonitorpara visualizar la latencia de replicación. - Cuando añades un nuevo cluster al hub, verifica la conectividad de la red entre los nodos del hub y el spoke; un firewall mal configurado suele romper la sincronización de
ClusterSet. - Mantén el Stackfile bajo control de versiones; cualquier cambio en la topología se vuelve trazable y permite revertir releases con un simple
git checkout.