En el post anterior conté la historia de montar la mininas. Aquí desgrano la arquitectura: qué atraviesa exactamente una petición desde tu navegador hasta el fichero en disco, y por qué cada pieza está donde está.

El mapa completo#

   [ navegador / app ]
           │  HTTPS :443
           ▼
   ┌─────────────────────────────┐
   │ Cloudflare (proxy + SSL)     │  nube naranja, SSL Full (strict)
   │ nextcloud/fotos/… (proxied)  │
   │   └─CNAME→ fl4t ─CNAME→ duckdns (DNS-only)
   └─────────────┬───────────────┘
                 │  origin pull HTTPS :443
                 ▼
        [ IP pública dinámica ]  85.85.x.x  (duckdns la mantiene al día)
                 │
                 ▼
   ┌─────────────────────────────┐
   │ Router doméstico — PAT        │  443→30443, 80→30080
   └─────────────┬───────────────┘
                 │  NodePort
                 ▼
   ┌──────────────────────────── mininas (k3s, 1 nodo) ───────────────────────┐
   │  Istio ingress gateway (Envoy)   ← NodePort 30080/30443                    │
   │        │  match por Host (SNI)                                            │
   │        ▼                                                                  │
   │  Gateway + VirtualService (uno por app)                                   │
   │        ▼                                                                  │
   │  Service → Pod de la app  ── conecta a ──►  su BD (Postgres/MariaDB)      │
   │                                                                           │
   │  cert-manager  ── emite TLS (Let's Encrypt DNS-01) ──► secret del Gateway │
   └──────────────┬─────────────────────────────────┬──────────────────────────┘
                  │ ficheros pesados                 │ datos de BD
                  ▼                                  ▼
         [ HDD btrfs /mnt/data ]           [ SSD ext4 /mnt/pg_data · /mnt/mysql_data ]

Capa por capa#

1. Cloudflare — el borde#

Los dominios (nextcloud., fotos., receptes., …) son registros proxied (nube naranja). Cloudflare termina el TLS del cliente, aporta caché/protección y vuelve a cifrar hacia el origen en modo Full (strict) (funciona porque el origen presenta un certificado Let’s Encrypt válido).

⚠️ Cloudflare solo proxya HTTP/HTTPS. Para SSH u otros puertos hay que apuntar a un registro DNS-only (gris) o a la IP directa.

2. La cadena DNS#

nextcloud.cortinasval.cat  ─CNAME→ fl4t.cortinasval.cat ─CNAME→ c0rt1n4s.duckdns.org
   (proxied, naranja)          (proxied)                     (DNS-only, gris → IP real)

fl4t es DNS-only y resuelve a la IP pública real, que un cron de duckdns mantiene actualizada. Así, si el ISP cambia la IP, el dominio sigue apuntando bien.

3. Router — PAT#

El router traduce los puertos públicos a la mininas: 443 → 192.168.1.201:30443 y 80 → :30080. Esos son los NodePort que expone Istio. (Detalle vivido: esa .201 debe ser una IP persistente del host, o tras un reinicio el PAT se queda sin destino.)

4. Istio ingress — un Gateway por host#

Istio (Envoy) recibe en el NodePort y enruta por Host/SNI. El patrón por app es:

RecursoRol
GatewayEscucha 80/443 para un host, con el credentialName del cert TLS
VirtualServiceEnruta ese host → el Service de la app
ServiceBalancea al Pod

🔑 Un solo Gateway por host/puerto. Dos Gateways con el mismo host provocan 404 intermitentes (Istio no sabe con cuál quedarse).

5. cert-manager — TLS automático#

Un ClusterIssuer (Let’s Encrypt) resuelve el reto DNS-01 vía la API de Cloudflare y genera un Certificate con todos los subdominios como SAN. El secret resultante lo consume el Gateway. Renovación automática, cero intervención.

6. Almacenamiento — el reparto que importa#

Cada aplicación tiene su propia base de datos en su namespace, y separamos por tipo de dato:

DatoDiscoPor qué
Bases de datos (Postgres/MariaDB)SSD ext4 (/mnt/pg_data, /mnt/mysql_data)IOPS: consultas y búsquedas vuelan
Ficheros pesados (fotos, adjuntos, media)HDD btrfs (/mnt/data)Volumen grande, no necesita IOPS

Los volúmenes son PersistentVolume de tipo hostPath con política Retain: los datos viven en rutas concretas del disco y sobreviven a que se borre un pod o incluso el PVC.

El principio que lo sostiene todo#

Declarativo o no existe. Un reinicio recrea el cluster entero desde el estado de k3s… pero solo lo que está descrito de forma persistente: manifiestos aplicados, PVs Retain, discos en fstab, IPs en netplan. Cualquier cosa “añadida a mano” desaparece. Por eso el siguiente paso natural es GitOps con FluxCD: que todo esto viva en un repositorio y el cluster se reconcilie solo.

Este blog, por cierto, es un sitio estático servido por un contenedor de ~5 MB detrás de este mismo Istio. La coherencia también es arquitectura.