<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Plataforma on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/plataforma/</link><description>Recent content in Plataforma on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Sun, 31 May 2026 08:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/plataforma/index.xml" rel="self" type="application/rss+xml"/><item><title>Siete fases de despliegue greenfield de una plataforma LLM on-premise: del hardware en la sala al primer token productivo</title><link>https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/</link><pubDate>Sun, 31 May 2026 08:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Los dos posts anteriores de esta trilogía arquitectónica fijaron las piezas: &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">las siete capas del stack de inferencia LLM&lt;/a> describen los componentes encima del cluster, y &lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">los cinco niveles de madurez de la plataforma&lt;/a> describen los estratos por debajo. Este post fija el &lt;strong>cuándo&lt;/strong>: en qué orden se despliega cada cosa cuando se parte de cero —hardware comprado, racks instalados, cableado físico hecho— y se quiere llegar a un cluster sirviendo el primer token productivo a un cliente. Siete fases nominales &lt;strong>F0 a F6&lt;/strong> sin compromisos de calendario, organizadas por &lt;strong>dependencias técnicas&lt;/strong> (no se entra en F3 sin gate de F2) y con un &lt;strong>camino crítico&lt;/strong> identificable. F0 inventario hardware y conectividad eléctrica/red. F1 OS bare metal + drivers + container runtime. F2 cluster Kubernetes con CNI y storage Ceph operativos. F3 GitOps y observabilidad de infraestructura. F4 identidad, TLS, secretos y políticas. F5 plataforma GPU con observabilidad LLM-aware. F6 stack LLM operativo y abierto a tráfico productivo. Para cada fase: qué se monta, qué tiene que estar listo antes (dependencias entre fases), &lt;strong>gate&lt;/strong> que valida el cierre, y la trampa típica que retrasa el camino crítico. La tesis: una plataforma LLM on-premise se hunde mucho más a menudo por &lt;strong>secuenciar mal&lt;/strong> que por &lt;strong>elegir mal&lt;/strong>. Las herramientas están todas inventadas; el orden es lo único que cada equipo redescubre.&lt;/p>
&lt;h2 id="estás-aquí-las-siete-fases-y-sus-dependencias">Estás aquí: las siete fases y sus dependencias&lt;/h2>
&lt;p>Las fases no se ejecutan en serie pura. F2 y F3 pueden empezarse a la vez para acelerar (instalar Kubernetes y preparar el repo GitOps en paralelo). F4 puede solaparse con la parte final de F3. F5 espera a que F4 cierre porque los pods GPU exigen NetworkPolicy y RBAC desde el primer día. F6 es &lt;strong>un único paso atómico&lt;/strong>: el cluster entra en producción o no.&lt;/p>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="dag de fases F0 a F6 con dependencias y camino crítico">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.f0{fill:#f6e2e2;stroke:#a33}.f1{fill:#f4e3cf;stroke:#a63}.f2{fill:#eef0d0;stroke:#7a3}.f3{fill:#dfe9f5;stroke:#356}.f4{fill:#d8eecf;stroke:#373}.f5{fill:#f5e3d8;stroke:#763}.f6{fill:#ead8f5;stroke:#634}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#222}.tiny{font:600 10px sans-serif;fill:#222}.note{font:italic 10px sans-serif;fill:#555}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#a)}.crit{stroke:#c33;stroke-width:2.4;fill:none;marker-end:url(#ac)}&lt;/style>
&lt;defs>&lt;marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;marker id="ac" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#c33"/>&lt;/marker>&lt;/defs>
&lt;text x="410" y="22" text-anchor="middle" class="lbl">DAG de fases · camino crítico marcado en rojo&lt;/text>
&lt;rect x="40" y="50" width="110" height="48" class="b f0"/>&lt;text x="95" y="68" text-anchor="middle" class="tiny">F0&lt;/text>&lt;text x="95" y="84" text-anchor="middle" class="sm">Hardware&lt;/text>&lt;text x="95" y="98" text-anchor="middle" class="note">Inventario · red&lt;/text>
&lt;rect x="180" y="50" width="110" height="48" class="b f1"/>&lt;text x="235" y="68" text-anchor="middle" class="tiny">F1&lt;/text>&lt;text x="235" y="84" text-anchor="middle" class="sm">Bare metal&lt;/text>&lt;text x="235" y="98" text-anchor="middle" class="note">OS · drivers&lt;/text>
&lt;rect x="320" y="50" width="110" height="48" class="b f2"/>&lt;text x="375" y="68" text-anchor="middle" class="tiny">F2&lt;/text>&lt;text x="375" y="84" text-anchor="middle" class="sm">Cluster k8s&lt;/text>&lt;text x="375" y="98" text-anchor="middle" class="note">Cilium · Ceph&lt;/text>
&lt;rect x="460" y="50" width="110" height="48" class="b f3"/>&lt;text x="515" y="68" text-anchor="middle" class="tiny">F3&lt;/text>&lt;text x="515" y="84" text-anchor="middle" class="sm">GitOps + obs&lt;/text>&lt;text x="515" y="98" text-anchor="middle" class="note">Flux · VM/Loki&lt;/text>
&lt;rect x="600" y="50" width="110" height="48" class="b f4"/>&lt;text x="655" y="68" text-anchor="middle" class="tiny">F4&lt;/text>&lt;text x="655" y="84" text-anchor="middle" class="sm">Identidad&lt;/text>&lt;text x="655" y="98" text-anchor="middle" class="note">OIDC · Kyverno&lt;/text>
&lt;rect x="320" y="170" width="110" height="48" class="b f5"/>&lt;text x="375" y="188" text-anchor="middle" class="tiny">F5&lt;/text>&lt;text x="375" y="204" text-anchor="middle" class="sm">GPU plane&lt;/text>&lt;text x="375" y="218" text-anchor="middle" class="note">NVIDIA op · DCGM&lt;/text>
&lt;rect x="600" y="170" width="110" height="48" class="b f6"/>&lt;text x="655" y="188" text-anchor="middle" class="tiny">F6&lt;/text>&lt;text x="655" y="204" text-anchor="middle" class="sm">Stack LLM live&lt;/text>&lt;text x="655" y="218" text-anchor="middle" class="note">7 capas activas&lt;/text>
&lt;path class="crit" d="M150,74 L180,74"/>
&lt;path class="crit" d="M290,74 L320,74"/>
&lt;path class="crit" d="M430,74 L460,74"/>
&lt;path class="crit" d="M570,74 L600,74"/>
&lt;path class="arr" d="M655,98 L655,170"/>
&lt;path class="arr" d="M375,98 L375,170"/>
&lt;path class="crit" d="M430,194 L600,194"/>
&lt;text x="410" y="262" text-anchor="middle" class="sm" fill="#c33">Camino crítico: F0 → F1 → F2 → F3 → F4 → F5 → F6&lt;/text>
&lt;text x="410" y="282" text-anchor="middle" class="note">Solapes posibles: F2 ↔ F3 (preparar repo mientras se monta cluster) · F3 ↔ F4 (políticas en audit antes de enforce)&lt;/text>
&lt;text x="410" y="304" text-anchor="middle" class="note">No solapables: F4 antes de F5 (GPU sin RBAC = bomba) · F5 antes de F6 (stack LLM sin GPU plane no arranca)&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Las flechas rojas son el &lt;strong>camino crítico&lt;/strong>: el cuello de botella secuencial que ningún paralelismo puede acortar. Las flechas grises son dependencias que admiten solape parcial. Reconocer dónde solapar y dónde no es la diferencia entre un despliegue de tres meses y uno de seis para el mismo perímetro.&lt;/p>
&lt;h2 id="la-analogía-la-expedición-a-una-cumbre-de-ocho-mil">La analogía: la expedición a una cumbre de ocho mil&lt;/h2>
&lt;p>Una expedición a una cumbre alpina alta no es un trekking largo. Es una serie de &lt;strong>campamentos&lt;/strong> que se montan en orden, cada uno con su altura, su función y su gate de validación: si no se aclimata bien en el campo base, no se puede subir al C1 sin riesgo; si el C2 no tiene su cocina y su radio en marcha, no se puede mandar gente arriba; si el ataque a cumbre se intenta sin los porteadores en los campamentos altos, no hay descenso seguro.&lt;/p>
&lt;p>El despliegue greenfield de una plataforma LLM funciona idéntico. &lt;strong>F0&lt;/strong> es la llegada del material al campamento base — cajas, sponsors, permisos, primera revisión. &lt;strong>F1&lt;/strong> es montar el campo base operativo: cocina, tiendas, generador. &lt;strong>F2&lt;/strong> es la subida al C1: ya hay altitud real (cluster k8s en marcha) y se respira distinto. &lt;strong>F3&lt;/strong> es C2: añade comunicaciones, planificación y aclimatación operativa. &lt;strong>F4&lt;/strong> es C3, la última noche antes del ataque: equipo cordado, oxígeno listo, todos los protocolos verificados. &lt;strong>F5&lt;/strong> es el día del ataque a cumbre — esfuerzo intenso, márgenes finos. &lt;strong>F6&lt;/strong> es la cumbre y el inicio del descenso seguro: a partir de aquí la expedición está en operación día a día, ya no en construcción.&lt;/p>
&lt;p>La analogía aguanta dos lecciones útiles: &lt;strong>no se salta un campo&lt;/strong> (subir directo del campo base a la cumbre mata al equipo), y &lt;strong>los gates son técnicos, no anímicos&lt;/strong> (si el barómetro pone tormenta, no se sale, aunque haya entusiasmo). El equipo de plataforma que sigue esas dos reglas llega a la cumbre. El que las negocia, no.&lt;/p>
&lt;h2 id="f0--hardware-en-la-sala-el-campamento-base">F0 — Hardware en la sala: el campamento base&lt;/h2>
&lt;p>&lt;strong>Lo que se monta en esta fase.&lt;/strong> Inventario del hardware recibido (servidores, switches, PDUs, BMC), racks montados, cableado eléctrico y de datos terminado, etiquetado físico de cada equipo (rack/U/función), conectividad a la red corporativa, IPs de gestión asignadas, BMC accesible vía VPN con MFA, primer ping de cada nodo desde el bastion.&lt;/p>
&lt;p>&lt;strong>Dependencias.&lt;/strong> Cero técnicas — esta es la fase &lt;strong>previa al software&lt;/strong>. Sí dependencias de procurement (servidores comprados, switches comprados), de obra civil (sala con climatización suficiente, suelo técnico) y administrativas (acceso al CPD para los técnicos).&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F0.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Cada nodo aparece en el inventario con &lt;code>(hostname, MAC, IP gestión, IP datos, rack, U, función, owner)&lt;/code>.&lt;/li>
&lt;li>BMC de cada nodo responde a &lt;code>ipmitool power status&lt;/code> y a la UI HTTPS desde la VPN de gestión.&lt;/li>
&lt;li>El switch de top-of-rack tiene su configuración versionada en git (incluso si todavía no hay GitOps de cluster, los configs de switch sí).&lt;/li>
&lt;li>Un comando &lt;code>for h in $(cat hosts); do ping -c1 -W1 $h.mgmt; done&lt;/code> devuelve 100% de éxito.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Cablear &amp;ldquo;como salga&amp;rdquo; sin etiquetado físico ni esquema. Cuando llega F4 y hay que troubleshootear una NetworkPolicy, no saber qué interfaz física lleva qué VLAN duplica el tiempo de diagnóstico de cada incidente para siempre.&lt;/p>
&lt;p>&lt;strong>Por qué F0 no se solapa con F1.&lt;/strong> Hasta que cada servidor tiene IP de gestión y BMC vivo, no se puede automatizar el bootstrap del OS. Toda hora invertida en F0 ahorra horas en cada fase posterior — es la fase con mejor ROI del proyecto y la única que no admite atajos.&lt;/p>
&lt;h2 id="f1--bare-metal-el-campamento-base-operativo">F1 — Bare metal: el campamento base operativo&lt;/h2>
&lt;p>&lt;strong>Lo que se monta.&lt;/strong> Imagen del sistema operativo (Debian estable o Ubuntu LTS) provisionada vía PXE o cloud-init con el &lt;code>cloud-config&lt;/code> versionado en git. Cada nodo tiene: hostname coherente, particiones LVM, kernel ≥ 6.6, container runtime &lt;code>containerd&lt;/code>, drivers NVIDIA para los nodos GPU, &lt;code>chrony&lt;/code> sincronizando contra servidores propios, SSH key del operador como única vía de acceso, &lt;code>nvidia-smi&lt;/code> pasando smoke test en los nodos GPU.&lt;/p>
&lt;p>&lt;strong>Dependencias.&lt;/strong> F0 cerrada. Necesita la red de gestión funcionando para que el PXE responda y para que el bastion alcance cada nodo.&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F1.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>ansible -i inventory all -m ping&lt;/code> devuelve 100% de éxito (o equivalente con Salt / Pulumi / etc).&lt;/li>
&lt;li>Cada nodo GPU pasa &lt;code>nvidia-smi&lt;/code> mostrando las GPUs esperadas con driver consistente entre nodos.&lt;/li>
&lt;li>Reloj de cada nodo desviado &amp;lt; 50 ms del NTP de referencia.&lt;/li>
&lt;li>Reinicio físico de un nodo lo deja exactamente en el mismo estado tras boot (idempotencia).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Drivers NVIDIA instalados manualmente con &lt;code>apt install&lt;/code> o con el script &lt;code>.run&lt;/code> de NVIDIA. Funciona el día uno y se rompe el día de la primera actualización de kernel. La regla operativa que ya quedó establecida en &lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">el post de los cinco niveles&lt;/a>: los drivers acaban siendo gestionados por el GPU Operator en F5; lo que se haga ahora es solo para que &lt;code>nvidia-smi&lt;/code> pase el smoke test, no para producción.&lt;/p>
&lt;p>&lt;strong>Solape posible.&lt;/strong> F1 puede empezar para algunos nodos mientras todavía se finaliza F0 en otros (greenfield real raramente entrega todos los servidores el mismo día). El gate de F1 es por cluster, no por nodo individual.&lt;/p>
&lt;h2 id="f2--cluster-kubernetes-operativo">F2 — Cluster Kubernetes operativo&lt;/h2>
&lt;p>&lt;strong>Lo que se monta.&lt;/strong> RKE2 instalado con tres nodos de control plane HA, joining de todos los workers (CPU y GPU), Cilium como CNI con &lt;code>kubeProxyReplacement&lt;/code> habilitado y BGP control plane apuntando a los switches ToR del F0, Rook-Ceph desplegado en los nodos de storage para cubrir block (RBD), filesystem (CephFS) y object (RGW S3-compatible), &lt;code>kubectl get nodes&lt;/code> devolviendo todos los nodos &lt;code>Ready&lt;/code>, primer pod de prueba con PVC montando y datos persistiendo tras restart del pod.&lt;/p>
&lt;p>&lt;strong>Dependencias.&lt;/strong> F1 cerrada (drivers + container runtime). Switches con BGP configurado (lo cerrado en F0). Discos NVMe particionados o disponibles raw para Ceph OSDs.&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F2.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>kubectl get nodes -o wide&lt;/code> muestra todos los nodos &lt;code>Ready&lt;/code> con la versión esperada de Kubernetes.&lt;/li>
&lt;li>Un Deployment con replicas=3 y antiAffinity por nodo arranca y los pods caen en nodos distintos.&lt;/li>
&lt;li>Una PVC RWO (RBD) crea un volumen, el pod escribe datos, el pod se borra, otro pod la monta y lee los datos.&lt;/li>
&lt;li>Una PVC RWX (CephFS) hace lo mismo con dos pods escribiendo simultáneamente.&lt;/li>
&lt;li>Un bucket RGW vía &lt;code>s3cmd&lt;/code> o &lt;code>mc&lt;/code> acepta &lt;code>put&lt;/code> y &lt;code>get&lt;/code> con TLS.&lt;/li>
&lt;li>Hubble (lado lectura del CNI) muestra flow logs entre dos pods de namespaces distintos.&lt;/li>
&lt;li>Test de chaos: drain de un nodo worker no GPU; las cargas se reschedulean automáticamente.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Empezar a &lt;code>kubectl apply&lt;/code> cargas reales en F2 sin GitOps. El backlog de cosas-aplicadas-a-mano crece más rápido que la capacidad de migrarlo a git después. La regla: en F2 sólo se aplican los &lt;strong>prerrequisitos&lt;/strong> del cluster (CNI, CSI, storage class por defecto). Cualquier carga de aplicación espera a F3.&lt;/p>
&lt;p>&lt;strong>Solape posible.&lt;/strong> F2 ↔ F3. Mientras se monta el cluster, se prepara en paralelo el repo GitOps (estructura de directorios, primeras Helm releases). Cuando F2 cierra, Flux se enchufa al repo y todo lo que iba a ser &lt;code>kubectl apply&lt;/code> ya está como manifest reconciliado.&lt;/p>
&lt;h2 id="f3--gitops-y-observabilidad-de-infraestructura">F3 — GitOps y observabilidad de infraestructura&lt;/h2>
&lt;p>&lt;strong>Lo que se monta.&lt;/strong> Forgejo desplegado primero (es prerrequisito de todo lo que viene). Repo &lt;code>gitops-infra&lt;/code> con la estructura inicial (&lt;code>apps/&lt;/code>, &lt;code>infrastructure/&lt;/code>, &lt;code>tenants/&lt;/code>, &lt;code>clusters/&lt;/code>). Flux instalado y reconciliando ese repo. Las cargas de prerequisito que se aplicaron a mano en F2 se mueven al repo y se reconcilian (deja de haber &lt;code>kubectl apply&lt;/code> operativo). VictoriaMetrics + vmagent scrapeando métricas. Grafana con dashboards iniciales (USE/RED + cluster + Ceph + Cilium). Loki recibiendo logs vía vector/fluent-bit. Alertmanager + Keep enrutando alertas a un canal de chat. Backups Barman Cloud para Postgres (futuro CNPG) y snapshots Ceph programados.&lt;/p>
&lt;p>&lt;strong>Dependencias.&lt;/strong> F2 cerrada. Bucket RGW para almacenar backups (lo cubre Ceph del F2).&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F3.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Cambio aplicado al repo se refleja en el cluster en &amp;lt; 5 minutos sin intervención manual.&lt;/li>
&lt;li>Un cambio aplicado con &lt;code>kubectl edit&lt;/code> directamente al cluster es detectado por Flux y revertido (drift detection vinculante, no sólo observacional).&lt;/li>
&lt;li>Grafana muestra dashboards de cluster, Ceph, Cilium y nodos GPU (DCGM no llega hasta F5, pero las métricas básicas del nodo sí).&lt;/li>
&lt;li>Un alert de prueba enviado a Alertmanager llega al canal de chat en &amp;lt; 1 minuto.&lt;/li>
&lt;li>Restore de un backup Postgres en un cluster temporal devuelve datos coherentes (la prueba define el RPO real).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Tener Helm charts en git pero seguir aplicando con &lt;code>helm install&lt;/code> desde la terminal. Eso es nivel 1 con disfraz de nivel 2. F3 sólo se cierra cuando Flux es &lt;strong>la única autoridad&lt;/strong> que aplica cambios y los humanos editan repo, no cluster.&lt;/p>
&lt;p>&lt;strong>Solape posible.&lt;/strong> F3 ↔ F4. Mientras se cierra F3, se puede preparar el manifest de Defguard y cert-manager en el repo. Cuando se reconcilien tienen donde aterrizar.&lt;/p>
&lt;h2 id="f4--identidad-certificados-secretos-políticas">F4 — Identidad, certificados, secretos, políticas&lt;/h2>
&lt;p>&lt;strong>Lo que se monta.&lt;/strong> Defguard desplegado con su Postgres dedicado (CNPG). Realm inicial con los operadores de plataforma enrolados con MFA y WireGuard. OIDC integrado en kube-apiserver (&lt;code>--oidc-issuer-url&lt;/code>, &lt;code>--oidc-client-id&lt;/code>, &lt;code>--oidc-username-claim&lt;/code>), en Forgejo, en Grafana, en Alertmanager — un solo SSO. cert-manager instalado con CA interna emitiendo certs internos para mTLS y con Let&amp;rsquo;s Encrypt ACME para certs de borde. SOPS configurado con KMS (puede ser un HSM físico, una clave age en un cofre, o un Vault externo) y External Secrets Operator sincronizando secretos al cluster. Kyverno desplegado con políticas iniciales en modo &lt;code>audit&lt;/code> durante una semana, después promovidas a &lt;code>enforce&lt;/code>. NetworkPolicy default-deny aplicada a cada namespace existente. Tetragon habilitado para runtime security. Audit log de kube-apiserver enviado a Loki con retención larga.&lt;/p>
&lt;p>&lt;strong>Dependencias.&lt;/strong> F3 cerrada (Flux aplica los manifests, VM/Loki ingieren métricas y logs).&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F4.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>kubectl&lt;/code> con &lt;code>kubeconfig&lt;/code> admin compartido &lt;strong>deja de funcionar&lt;/strong>; cada operador usa su propio token OIDC con MFA.&lt;/li>
&lt;li>Un secret en &lt;code>data:&lt;/code> plano en un commit es rechazado por el pre-commit hook (o por Kyverno admission).&lt;/li>
&lt;li>Un pod sin &lt;code>securityContext.runAsNonRoot=true&lt;/code> es rechazado por Kyverno en admission.&lt;/li>
&lt;li>Una NetworkPolicy intencionalmente errónea (allow-all) en un namespace de tenant es rechazada.&lt;/li>
&lt;li>Un audit del último día devuelve la lista completa de actores y cambios (huella regulatoria mínima).&lt;/li>
&lt;li>Pen-test interno básico: un atacante con &lt;code>kubeconfig&lt;/code> falsificado falla en MFA y queda registrado.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Kyverno en modo &lt;code>audit&lt;/code> permanente porque &amp;ldquo;no queremos romper cargas en producción&amp;rdquo;. F4 se cierra cuando las políticas están en &lt;code>enforce&lt;/code>. Hasta entonces, sigues en F3 con cara de F4.&lt;/p>
&lt;p>&lt;strong>Por qué F4 no se solapa con F5.&lt;/strong> F5 introduce pods GPU que mueven mucha VRAM y mucho cómputo. Sin NetworkPolicy default-deny, sin RBAC OIDC, sin Kyverno bloqueando configuraciones inseguras, los pods GPU son la superficie de ataque más jugosa del cluster. Cualquier compromiso en F5 sin F4 cerrada es un acceso casi-total al hardware caro.&lt;/p>
&lt;h2 id="f5--plataforma-gpu-con-observabilidad-llm-aware">F5 — Plataforma GPU con observabilidad LLM-aware&lt;/h2>
&lt;p>&lt;strong>Lo que se monta.&lt;/strong> NVIDIA GPU Operator vía Flux con la versión de driver decidida en F1 (ahora ya no se manipula a mano). DCGM Exporter expone métricas GPU a VictoriaMetrics. MIG manager configurado para los nodos donde tenga sentido (por ejemplo, en un cluster 4×H100 SXM: dos GPUs con passthrough completo para el LLM general TP=4, dos GPUs particionadas en 2×3g.40gb cada una para LLMs pequeños y embeddings). Topology Manager con política &lt;code>single-numa-node&lt;/code>. KEDA con Prometheus scaler instalado y un ScaledObject de ejemplo apuntando a una métrica vLLM (&lt;code>vllm:num_requests_running&lt;/code>). OpenTelemetry Collector con receivers OTLP, processors &lt;code>attributes&lt;/code> (enriquecen spans con &lt;code>tenant_id&lt;/code>, &lt;code>priority_tier&lt;/code>), exporters a Langfuse y a Tempo. LeaderWorkerSet API habilitada para topologías tensor parallel. OME (Operator Model Engine) o vLLM Production Stack desplegado como controller — todavía sin modelos cargados.&lt;/p>
&lt;p>&lt;strong>Dependencias.&lt;/strong> F4 cerrada (los pods GPU heredan NetworkPolicy default-deny, RBAC OIDC y políticas Kyverno).&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F5.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Un pod de prueba pidiendo &lt;code>nvidia.com/gpu: 1&lt;/code> se programa en el nodo correcto y &lt;code>nvidia-smi&lt;/code> desde dentro del contenedor ve la GPU correcta (entera o un slice MIG).&lt;/li>
&lt;li>DCGM Exporter expone métricas en Grafana (utilization, VRAM, temperatura, NVLink bandwidth) para cada GPU.&lt;/li>
&lt;li>Un Deployment de vLLM de prueba arranca con un modelo pequeño (por ejemplo, un 7B FP16) cargado desde Ceph RGW.&lt;/li>
&lt;li>Un span OpenTelemetry generado por ese vLLM llega a Langfuse con atributos &lt;code>gen_ai.*&lt;/code> correctos.&lt;/li>
&lt;li>KEDA escala el Deployment de prueba de 1 a N réplicas bajo carga sintética y vuelve a 1 cuando cesa.&lt;/li>
&lt;li>Un upgrade del GPU Operator a una nueva versión drena y reprograma los pods GPU sin pérdida de servicio.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Cargar el modelo grande &amp;ldquo;para probar&amp;rdquo; antes de que DCGM y OTel estén verdes. Cuando algo falle, no habrá métricas que distingan entre OOM, throttling térmico, mismatch de driver o problema de red — se diagnostica a ciegas. La regla: &lt;strong>modelo pequeño primero&lt;/strong>, golden path verde, &lt;strong>después&lt;/strong> modelo grande.&lt;/p>
&lt;p>&lt;strong>Solape posible.&lt;/strong> Ninguno con F6. F6 es atómico.&lt;/p>
&lt;h2 id="f6--stack-llm-en-producción">F6 — Stack LLM en producción&lt;/h2>
&lt;p>&lt;strong>Lo que se monta.&lt;/strong> Las &lt;strong>siete capas del stack de inferencia&lt;/strong> descritas en el &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">post correspondiente&lt;/a>, desplegadas en este orden:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Vector store + datos relacionales&lt;/strong> (Qdrant, PostgreSQL CNPG, Ceph RGW para pesos y adapters, CephFS para datasets). Algunos componentes ya existían de F3 como datos; aquí se especializan para RAG con sus colecciones y schemas iniciales.&lt;/li>
&lt;li>&lt;strong>Embeddings + reranker&lt;/strong> (Infinity con &lt;code>multilingual-e5-large&lt;/code>, TEI con &lt;code>bge-reranker-v2-m3&lt;/code>). Es la capa que debe estar verde antes de cualquier modelo grande, porque el RAG depende de ella.&lt;/li>
&lt;li>&lt;strong>Inferencia LLM&lt;/strong> (vLLM Production Stack con el LLM general y el LLM código). Carga modelos desde Ceph RGW. Multi-LoRA pool inicial vacío.&lt;/li>
&lt;li>&lt;strong>Gateway&lt;/strong> (Envoy AI Gateway) con OAuth Defguard, routing por &lt;code>body.model&lt;/code>, rate-limit por tenant. Este es el punto que &lt;strong>abre tráfico al exterior&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Observabilidad LLM-aware&lt;/strong> (Langfuse enchufado al OTel del F5).&lt;/li>
&lt;li>&lt;strong>Control plane GitOps&lt;/strong> y &lt;strong>dependency tracking&lt;/strong> ya estaban activos desde F3 y F4 respectivamente; aquí simplemente se les añade el catálogo de los nuevos servicios LLM.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Dependencias.&lt;/strong> Todas las anteriores cerradas.&lt;/p>
&lt;p>&lt;strong>Gate de validación que cierra F6.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Curl al endpoint público con bearer token Defguard recibe respuesta de chat completion en castellano técnico correcta, con &lt;code>trace_id&lt;/code> propagado.&lt;/li>
&lt;li>La traza aparece en Langfuse con atributos &lt;code>gen_ai.*&lt;/code> completos, latencia desglosada y &lt;code>tenant_id&lt;/code> propio.&lt;/li>
&lt;li>Un canary 5% de tráfico al nuevo modelo durante 24 h no degrada métricas de calidad ni de latencia.&lt;/li>
&lt;li>Un golpe de tráfico controlado dispara KEDA, las réplicas escalan, la latencia P95 se mantiene dentro de presupuesto.&lt;/li>
&lt;li>Un fallo intencional de un pod vLLM no afecta a la disponibilidad del endpoint (réplicas + reschedule).&lt;/li>
&lt;li>El operador interno demuestra el camino completo de revocación de acceso a un tenant en &amp;lt; 5 minutos (Defguard → Kyverno → cierre de NetworkPolicy).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Trampa típica.&lt;/strong> Abrir tráfico de cliente real antes de tener el runbook de incidentes firmado, el SLO negociado y el plan de continuidad probado. F6 técnicamente está cerrada; operativamente, la plataforma sigue siendo experimento hasta que el primer postmortem real demuestre que el equipo sabe responder.&lt;/p>
&lt;h2 id="las-matemáticas-que-importan-peso-relativo-del-esfuerzo-por-fase">Las matemáticas que importan: peso relativo del esfuerzo por fase&lt;/h2>
&lt;p>Sin comprometernos con semanas calendario, sí podemos cuantificar el &lt;strong>peso relativo&lt;/strong> del esfuerzo de ingeniería por fase en un greenfield típico. La curva no es uniforme:&lt;/p>
&lt;p>$$
\text{esfuerzo}_{F_i} \approx \text{base}_i \cdot (1 + \epsilon_i)
$$&lt;/p>
&lt;p>donde $\text{base}_i$ es el esfuerzo nominal y $\epsilon_i$ es el factor de &lt;strong>sorpresas&lt;/strong> (cabling errado, drivers incompatibles, certificados mal emitidos, conflictos de versiones). La tabla siguiente da el peso relativo nominal y el factor típico de sorpresa observado:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Fase&lt;/th>
&lt;th>Peso nominal&lt;/th>
&lt;th>Factor sorpresa típico ε&lt;/th>
&lt;th>Peso efectivo medio&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>F0 — Hardware&lt;/td>
&lt;td>8 %&lt;/td>
&lt;td>0.5 (1× a 2×)&lt;/td>
&lt;td>&lt;strong>12 %&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F1 — Bare metal&lt;/td>
&lt;td>6 %&lt;/td>
&lt;td>0.3&lt;/td>
&lt;td>8 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F2 — Cluster k8s&lt;/td>
&lt;td>12 %&lt;/td>
&lt;td>0.4&lt;/td>
&lt;td>17 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F3 — GitOps + obs&lt;/td>
&lt;td>14 %&lt;/td>
&lt;td>0.5&lt;/td>
&lt;td>21 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F4 — Identidad + políticas&lt;/td>
&lt;td>18 %&lt;/td>
&lt;td>0.7&lt;/td>
&lt;td>&lt;strong>31 %&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F5 — GPU plane&lt;/td>
&lt;td>10 %&lt;/td>
&lt;td>0.4&lt;/td>
&lt;td>14 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>F6 — Stack LLM live&lt;/td>
&lt;td>8 %&lt;/td>
&lt;td>0.3&lt;/td>
&lt;td>10 %&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Buffer / integración&lt;/td>
&lt;td>24 %&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Dos observaciones operativas. &lt;strong>F4 concentra más sorpresas que ninguna otra&lt;/strong> (federación OIDC entre cuatro o cinco apps con configuraciones distintas, políticas Kyverno que tumban cargas legítimas, secretos rotos por encriptación mal probada). &lt;strong>F0 tiene un coeficiente de sorpresa alto en relación a su tamaño&lt;/strong> porque cualquier error de cableado o etiquetado se descubre tarde y se paga caro. Las dos consecuencias prácticas: planificar &lt;strong>F4 con margen generoso&lt;/strong> y no escatimar tiempo en &lt;strong>F0&lt;/strong> porque cada hora ahorrada ahí cuesta cinco después.&lt;/p>
&lt;p>&lt;strong>Camino crítico y holguras.&lt;/strong> El camino crítico es lineal F0 → F1 → F2 → F3 → F4 → F5 → F6. Las únicas holguras reales son los solapes ya identificados:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>F2 ↔ F3 (holgura ~30 %)&lt;/strong>: preparar repo y dashboards iniciales mientras se monta cluster.&lt;/li>
&lt;li>&lt;strong>F3 ↔ F4 (holgura ~20 %)&lt;/strong>: manifests de identidad listos al cerrar F3, aplicación inmediata.&lt;/li>
&lt;li>&lt;strong>Dentro de F4&lt;/strong>: políticas en modo &lt;code>audit&lt;/code> corriendo en paralelo con setup de Defguard.&lt;/li>
&lt;/ul>
&lt;p>Nada acorta el camino crítico más de un ~15 % del total. Quien promete un greenfield productivo en la mitad del tiempo razonable está vendiendo otra cosa: probablemente saltarse F4 o cargar F6 con F5 verde-pero-no-validado.&lt;/p>
&lt;h2 id="diagrama-final-el-cronograma-de-despliegue-completo">Diagrama final: el cronograma de despliegue completo&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cronograma completo del despliegue por fases con piezas y gates">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.bg{fill:#fafafa;stroke:#bbb;rx:8}.f0{fill:#f6e2e2;stroke:#a33}.f1{fill:#f4e3cf;stroke:#a63}.f2{fill:#eef0d0;stroke:#7a3}.f3{fill:#dfe9f5;stroke:#356}.f4{fill:#d8eecf;stroke:#373}.f5{fill:#f5e3d8;stroke:#763}.f6{fill:#ead8f5;stroke:#634}.gate{fill:#fffbe0;stroke:#a90}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#222}.tiny{font:600 10px sans-serif;fill:#222}.note{font:italic 10px sans-serif;fill:#555}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#a)}&lt;/style>
&lt;defs>&lt;marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="410" y="22" text-anchor="middle" class="lbl">Cronograma completo: piezas por fase y gates de validación&lt;/text>
&lt;rect x="40" y="40" width="740" height="68" class="b f0"/>&lt;text x="60" y="58" class="tiny">F0 · HARDWARE EN LA SALA&lt;/text>
&lt;text x="60" y="76" class="sm">Inventario · cableado · BMC TLS+MFA · IPs gestión · switches BGP versionados&lt;/text>
&lt;text x="60" y="92" class="note">Gate: `for h in hosts; ping $h.mgmt` 100% éxito · inventario completo&lt;/text>
&lt;rect x="40" y="116" width="740" height="68" class="b f1"/>&lt;text x="60" y="134" class="tiny">F1 · BARE METAL&lt;/text>
&lt;text x="60" y="152" class="sm">PXE/cloud-init · OS LTS · kernel ≥6.6 · containerd · drivers NVIDIA · chrony · LVM&lt;/text>
&lt;text x="60" y="168" class="note">Gate: `ansible all -m ping` 100% · `nvidia-smi` smoke OK · reboot idempotente&lt;/text>
&lt;rect x="40" y="192" width="740" height="68" class="b f2"/>&lt;text x="60" y="210" class="tiny">F2 · CLUSTER KUBERNETES&lt;/text>
&lt;text x="60" y="228" class="sm">RKE2 HA · Cilium (kube-proxy replacement + BGP) · Rook-Ceph (RBD + CephFS + RGW)&lt;/text>
&lt;text x="60" y="244" class="note">Gate: PVCs RWO/RWX OK · bucket RGW OK · drain node sin downtime&lt;/text>
&lt;rect x="40" y="268" width="740" height="68" class="b f3"/>&lt;text x="60" y="286" class="tiny">F3 · GITOPS + OBSERVABILIDAD INFRA&lt;/text>
&lt;text x="60" y="304" class="sm">Forgejo · Flux · VictoriaMetrics + Grafana + Loki · Alertmanager + Keep · backups&lt;/text>
&lt;text x="60" y="320" class="note">Gate: cambio en repo → cluster en &amp;lt;5min · drift revertido · restore backup OK&lt;/text>
&lt;rect x="40" y="344" width="740" height="68" class="b f4"/>&lt;text x="60" y="362" class="tiny">F4 · IDENTIDAD + POLÍTICAS&lt;/text>
&lt;text x="60" y="380" class="sm">Defguard OIDC+MFA+WG · cert-manager · SOPS+ESO · Kyverno enforce · NP default deny · Tetragon&lt;/text>
&lt;text x="60" y="396" class="note">Gate: kubeconfig admin compartido no funciona · políticas en enforce · audit log completo&lt;/text>
&lt;rect x="40" y="420" width="740" height="68" class="b f5"/>&lt;text x="60" y="438" class="tiny">F5 · PLATAFORMA GPU + OBSERVABILIDAD LLM-AWARE&lt;/text>
&lt;text x="60" y="456" class="sm">NVIDIA GPU Operator · DCGM · MIG manager · KEDA con métricas vLLM · OTel gen_ai.* · OME&lt;/text>
&lt;text x="60" y="472" class="note">Gate: pod GPU programado · DCGM verde · vLLM smoke con modelo pequeño · KEDA escala&lt;/text>
&lt;rect x="40" y="496" width="740" height="38" class="b f6"/>&lt;text x="60" y="514" class="tiny">F6 · STACK LLM LIVE&lt;/text>
&lt;text x="60" y="528" class="sm">7 capas activas · primer modelo verde · canary OK · runbook firmado · primer cliente con SLA&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>El cronograma no es decorativo: cada fila define lo que se monta en su fase &lt;strong>y el gate que la cierra&lt;/strong>. Una fase no se da por terminada hasta que su gate está verde. Una fase con gate amarillo arrastra todas las posteriores; intentar saltar a la siguiente con un gate parcialmente cumplido es lo que produce, varias semanas después, el incidente que obliga a &amp;ldquo;volver a F4 con producción rodando&amp;rdquo; — la situación más cara de toda la matriz de costes del &lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">post de los cinco niveles&lt;/a>.&lt;/p>
&lt;h2 id="errores-típicos-de-planificación">Errores típicos de planificación&lt;/h2>
&lt;p>Patrones que retrasan o hunden el despliegue greenfield, independientemente de las herramientas elegidas:&lt;/p>
&lt;p>&lt;strong>1. Comprar el LLM antes que el cluster.&lt;/strong> Empezar el proyecto por &amp;ldquo;qué modelo vamos a servir&amp;rdquo; en vez de por &amp;ldquo;qué plataforma puede sostener cualquier modelo razonable&amp;rdquo;. El modelo es un parámetro intercambiable; la plataforma no.&lt;/p>
&lt;p>&lt;strong>2. Subestimar F0.&lt;/strong> &amp;ldquo;Eso lo hace el equipo de redes&amp;rdquo;. Sí, pero el resultado de F0 lo consumen todas las fases posteriores. Si el equipo de redes entrega tarde, el proyecto entero llega tarde — y nadie lo había marcado como camino crítico.&lt;/p>
&lt;p>&lt;strong>3. Solapar F4 con F5 &amp;ldquo;para ganar tiempo&amp;rdquo;.&lt;/strong> Es la única dependencia donde no hay holgura. Si se intenta solapar, F5 acaba operando con políticas en &lt;code>audit&lt;/code> permanente (no estás en F4) o sin OIDC integrado (operadores con kubeconfig compartido tocando GPU). Ambos antipatrones se quedan en producción.&lt;/p>
&lt;p>&lt;strong>4. Saltar el smoke test del modelo pequeño en F5.&lt;/strong> &amp;ldquo;Vamos a por el 70B directamente&amp;rdquo;. Cuando algo falle (y algo fallará), no habrá baseline contra el que diagnosticar.&lt;/p>
&lt;p>&lt;strong>5. Tratar F6 como &amp;ldquo;encender vLLM&amp;rdquo;.&lt;/strong> F6 incluye gateway, observabilidad LLM-aware, runbook, SLO, plan de continuidad. Encender vLLM es cinco minutos; cerrar F6 es semanas de validación y firma.&lt;/p>
&lt;p>&lt;strong>6. No definir gates por escrito.&lt;/strong> Si los gates no están escritos, son negociables a posteriori. &amp;ldquo;Esto ya cuenta como F4&amp;rdquo; es la frase que precede a los seis meses siguientes de retrofit.&lt;/p>
&lt;p>&lt;strong>7. Asignar la fase a un único responsable.&lt;/strong> Cada fase necesita al menos dos personas que la entiendan. La rotación de personal en proyectos largos destruye el conocimiento; los gates por escrito + revisión cruzada lo preservan.&lt;/p>
&lt;p>&lt;strong>8. Olvidar el camino de descenso.&lt;/strong> El post se centra en subir. La operación día a día (descenso, en la analogía) es otra historia que también merece planificación — runbooks, on-call, capacidad de upgrade, plan de fin de vida. Los equipos que sólo planifican la subida llegan a la cumbre y se quedan ahí sin oxígeno.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico-4h100-sxm">Aplicado a hardware on-premise típico: 4×H100 SXM&lt;/h2>
&lt;p>Sobre el cluster genérico de referencia (4×H100 SXM 80 GB, NVLink, 640 GB RAM por nodo GPU, 3 nodos control plane, 3-5 nodos worker CPU, 2 nodos worker GPU), el reparto &lt;strong>temporal&lt;/strong> del trabajo se distribuye así:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">F0 (hardware)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ 8 servidores físicos racks + switches + BMC + IPs gestión
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 3 nodos cp-01..03 — control plane (sin GPU)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 3 nodos worker-cpu-01..03 — CPU plane (Forgejo, Ceph, observabilidad)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─ 2 nodos worker-gpu-01..02 — GPU plane (4×H100 SXM cada uno)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F1 (bare metal)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ OS + drivers + containerd en los 8 nodos por igual
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (los drivers NVIDIA solo en los 2 nodos GPU, smoke `nvidia-smi`)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F2 (cluster k8s)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ RKE2 control plane en cp-01..03 (HA con etcd embebido)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> workers joining: 3 CPU + 2 GPU
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ceph OSDs en los 3 nodos worker CPU
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pools por defecto: RBD-replicated-3, CephFS-replicated-3, RGW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F3 (GitOps + obs)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ Forgejo + Flux + VM/Grafana/Loki + Keep en CPU plane
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> primer repo `gitops-infra` reconcilia lo de F2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F4 (identidad)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ Defguard en CPU plane (StatefulSet con Postgres CNPG)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> OIDC en kube-apiserver, Forgejo, Grafana, Alertmanager
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Kyverno como Deployment en control plane
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F5 (GPU plane)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ NVIDIA GPU Operator targetea workers GPU
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> MIG manager: 1ª GPU MIG 7g.80gb (= passthrough), 2ª 2×3g.40gb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> OTel Collector como DaemonSet en GPU plane + CPU plane
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> primer vLLM con modelo 7B FP16 verde
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">F6 (stack LLM)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─ Las 7 capas se reconcilian vía Flux desde un segundo repo `gitops-llm`
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> primer endpoint público con OAuth Defguard
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> primer cliente productivo enrolado bajo SLA
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>La distribución física del cluster aprovecha el aislamiento entre planos definido en F0: el plano de control no toca GPU, el plano CPU concentra estado relevante (Forgejo, Ceph, Postgres CNPG, Langfuse, Defguard) y el plano GPU se especializa al máximo. Esa separación, decidida en F0 antes de instalar el primer servidor, condiciona el éxito del resto de fases — es otro recordatorio de por qué F0 importa más de lo que parece.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-posts">Lo que no hemos cubierto (próximos posts)&lt;/h2>
&lt;p>Este post recorre el &lt;strong>camino de subida&lt;/strong> a la cumbre. Quedan piezas que merecen su propio artículo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>El descenso seguro&lt;/strong>: operación día a día, runbooks por componente, on-call, capacity planning continuo, ciclo de upgrades del cluster sin downtime.&lt;/li>
&lt;li>&lt;strong>Multi-site (segunda cumbre)&lt;/strong>: cómo se federan dos clusters con Cilium Cluster Mesh y qué fases extra introduce. F3.5 (Cluster Mesh) y F4.5 (replicación cross-site) son las fases que faltan.&lt;/li>
&lt;li>&lt;strong>El camino brownfield&lt;/strong>: lo que cambia cuando ya hay un cluster con cargas. Las fases siguen siendo las mismas, pero los gates se aplican retroactivamente y cada paso requiere planning de migración.&lt;/li>
&lt;li>&lt;strong>El coste calendario real&lt;/strong>: rangos típicos en semanas para un equipo de plataforma de 2-3 personas, separado por fase, con bandas de incertidumbre.&lt;/li>
&lt;li>&lt;strong>El handoff a operación&lt;/strong>: cómo se entrega la plataforma del equipo de despliegue al equipo de operación, qué documentos firman, qué se hereda y qué se renegocia.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Anatomía de un stack de inferencia LLM on-premise&lt;/a> — las siete capas que se montan en F6. Los componentes, no el cronograma.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/">Cinco niveles de madurez de la plataforma debajo del LLM&lt;/a> — los niveles correspondientes a las fases F1→F5. Los estratos, no la secuencia.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/catalogo-herramientas-oss-llmops/">El catálogo OSS para LLMOps en seis etapas&lt;/a> — fichas individuales de las piezas citadas aquí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> — el OTel del F5 con detalle de las semantic conventions &lt;code>gen_ai.*&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>RKE2 Documentation — &lt;a href="https://docs.rke2.io/">docs.rke2.io&lt;/a>&lt;/li>
&lt;li>Cilium documentation — &lt;a href="https://docs.cilium.io/">docs.cilium.io&lt;/a>&lt;/li>
&lt;li>Rook documentation — &lt;a href="https://rook.io/">rook.io/docs&lt;/a>&lt;/li>
&lt;li>Flux GitOps toolkit — &lt;a href="https://fluxcd.io/">fluxcd.io&lt;/a>&lt;/li>
&lt;li>Forgejo — &lt;a href="https://forgejo.org/">forgejo.org&lt;/a>&lt;/li>
&lt;li>cert-manager — &lt;a href="https://cert-manager.io/">cert-manager.io&lt;/a>&lt;/li>
&lt;li>External Secrets Operator — &lt;a href="https://external-secrets.io/">external-secrets.io&lt;/a>&lt;/li>
&lt;li>Kyverno — &lt;a href="https://kyverno.io/">kyverno.io&lt;/a>&lt;/li>
&lt;li>NVIDIA GPU Operator — &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/">docs.nvidia.com/datacenter/cloud-native/gpu-operator&lt;/a>&lt;/li>
&lt;li>DCGM Exporter — &lt;a href="https://github.com/NVIDIA/dcgm-exporter">github.com/NVIDIA/dcgm-exporter&lt;/a>&lt;/li>
&lt;li>KEDA — &lt;a href="https://keda.sh/">keda.sh&lt;/a>&lt;/li>
&lt;li>LeaderWorkerSet API — &lt;a href="https://github.com/kubernetes-sigs/lws">github.com/kubernetes-sigs/lws&lt;/a>&lt;/li>
&lt;li>OpenTelemetry Semantic Conventions for GenAI — &lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">opentelemetry.io/docs/specs/semconv/gen-ai&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Cinco niveles de madurez de la plataforma debajo del LLM: del servidor con Linux al cluster listo para vLLM</title><link>https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/</link><pubDate>Sun, 31 May 2026 07:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/cinco-niveles-madurez-plataforma-llm-on-premise/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El post de &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">las siete capas del stack de inferencia LLM&lt;/a> daba por supuestas muchas piezas: un cluster Kubernetes operativo, GitOps reconciliando, identidades resueltas, GPUs visibles para el scheduler, observabilidad capaz de transportar &lt;code>gen_ai.*&lt;/code>. Antes de que vLLM tenga sentido, &lt;strong>hay que llegar a ese punto de partida&lt;/strong>, y se llega por niveles. Este post define &lt;strong>cinco niveles de madurez&lt;/strong> de la plataforma que vive debajo del LLM, desde un servidor bare metal con Linux instalado (nivel 0) hasta un cluster listo para correr la capa de inferencia (nivel 4) y el handoff al post anterior (nivel 5). Cada nivel &lt;strong>desbloquea una capacidad concreta&lt;/strong> —ejecutar contenedores con reproducibilidad, reconstruir el cluster desde git, autenticar humanos vía OIDC, programar GPUs con MIG y métricas DCGM, demostrar compliance sin intervención manual— y cada uno tiene un &lt;strong>test de validación&lt;/strong> que decide si estás de verdad ahí o solo te lo cuentas. Para cada nivel: qué piezas OSS lo cubren en 2026 (Cilium, RKE2, Flux, cert-manager, Defguard, NVIDIA GPU Operator, KEDA, Trivy, Kyverno…), &lt;strong>el orden de despliegue&lt;/strong> dentro del nivel, las decisiones que cuesta caro saltarse, y los antipatrones que te bajan de nivel cuando creías estar arriba. La tesis: &lt;strong>subir de nivel cuesta poco esfuerzo si lo haces a tiempo, y mucho refactor si pretendes saltártelo&lt;/strong>. La inferencia LLM exige al menos nivel 4; quien intenta servir LLMs desde un nivel 1 o 2 acaba pagando con incidentes nocturnos lo que se ahorró en plataforma.&lt;/p>
&lt;h2 id="estás-aquí-los-cinco-niveles-de-un-vistazo">Estás aquí: los cinco niveles de un vistazo&lt;/h2>
&lt;p>Antes del detalle, la escalera. Cada peldaño añade una capacidad ausente en el anterior. El test del nivel es la pregunta cuya respuesta honesta dice si ya estás en él.&lt;/p>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cinco niveles de madurez de la plataforma debajo del LLM">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.l0{fill:#f6e2e2;stroke:#a33}.l1{fill:#f4e3cf;stroke:#a63}.l2{fill:#eef0d0;stroke:#7a3}.l3{fill:#d8eecf;stroke:#373}.l4{fill:#dfe9f5;stroke:#356}.l5{fill:#ead8f5;stroke:#634}.title{font:600 13px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#222}.tiny{font:600 10px sans-serif;fill:#222}.note{font:italic 10px sans-serif;fill:#555}&lt;/style>
&lt;text x="410" y="20" text-anchor="middle" class="title">Cinco niveles de madurez (más el handoff al stack LLM en el nivel 5)&lt;/text>
&lt;rect x="40" y="38" width="740" height="44" class="b l0"/>&lt;text x="60" y="56" class="tiny">NIVEL 0 · CAÓTICO&lt;/text>&lt;text x="60" y="74" class="sm">Bare metal con Linux · docker / podman ad-hoc · sin orquestador · cambios manuales con SSH&lt;/text>
&lt;rect x="40" y="90" width="740" height="44" class="b l1"/>&lt;text x="60" y="108" class="tiny">NIVEL 1 · REPETIBLE&lt;/text>&lt;text x="60" y="126" class="sm">Cluster k8s instalado (RKE2 / kubeadm) · CNI · CSI · kubectl apply / Helm desde terminal · pods rodando&lt;/text>
&lt;rect x="40" y="142" width="740" height="44" class="b l2"/>&lt;text x="60" y="160" class="tiny">NIVEL 2 · DEFINIDO&lt;/text>&lt;text x="60" y="178" class="sm">GitOps (Flux) · registry interno · observabilidad infra · backups · el cluster se reconstruye desde el repo&lt;/text>
&lt;rect x="40" y="194" width="740" height="44" class="b l3"/>&lt;text x="60" y="212" class="tiny">NIVEL 3 · GESTIONADO&lt;/text>&lt;text x="60" y="230" class="sm">OIDC + RBAC · cert-manager · External Secrets · Kyverno · NetworkPolicy default deny · auditoría&lt;/text>
&lt;rect x="40" y="246" width="740" height="44" class="b l4"/>&lt;text x="60" y="264" class="tiny">NIVEL 4 · OPTIMIZADO PARA GPU&lt;/text>&lt;text x="60" y="282" class="sm">NVIDIA GPU Operator · DCGM Exporter · MIG / time-slicing · KEDA con métricas LLM · OTel listo para gen_ai.*&lt;/text>
&lt;rect x="40" y="298" width="740" height="32" class="b l5"/>&lt;text x="60" y="318" class="sm">&lt;tspan font-weight="700">NIVEL 5 · HANDOFF&lt;/tspan> — el cluster está preparado para que el stack LLM (las 7 capas) tenga sentido&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Los niveles &lt;strong>no son intercambiables&lt;/strong>. Un cluster en nivel 2 no puede correr LLMs en producción con garantías: técnicamente carga el pod de vLLM, pero al primer incidente nocturno se descubre que no hay TLS, ni identidades, ni alerting, ni métricas GPU, ni forma de saber quién cambió qué. Subir un nivel después de tener LLMs ya en producción cuesta &lt;strong>órdenes de magnitud&lt;/strong> más que subirlo cuando el cluster aún está vacío.&lt;/p>
&lt;h2 id="la-analogía-del-puesto-callejero-al-restaurante-con-estrella">La analogía: del puesto callejero al restaurante con estrella&lt;/h2>
&lt;p>Imagina la escala de un negocio de hostelería. &lt;strong>Nivel 0&lt;/strong> es el puesto callejero: una plancha, una bombona, un cocinero que improvisa. Puede vender comida — funciona — pero cualquier cosa que se desvíe del día normal (una inspección sanitaria, un cliente alérgico, un pedido de 200 raciones) le tira el negocio. &lt;strong>Nivel 1&lt;/strong> es el bar de tapas: cocina dimensionada, carta corta repetible, varios turnos. El cocinero ya no improvisa cada día; trabaja sobre un menú escrito, aunque las recetas viven en la cabeza del jefe. &lt;strong>Nivel 2&lt;/strong> es el restaurante con menú del día: hay procedimientos escritos, proveedores fijos, control de stock, libro de incidencias. Si el cocinero principal se cae enfermo, el segundo puede sacar el servicio sin estragos. &lt;strong>Nivel 3&lt;/strong> es el restaurante con carta y servicio formal: trazabilidad de cada ingrediente, alérgenos en la carta, certificación sanitaria, contrato con los proveedores, formación obligatoria del personal. &lt;strong>Nivel 4&lt;/strong> es la cocina especializada en un producto complejo (sushi, alta cocina, panadería artesanal): herramientas específicas que el restaurante normal no necesita (horno de leña, cuchillos especiales, cámara de fermentación), procesos calibrados, métricas de calidad. &lt;strong>Nivel 5&lt;/strong> es el restaurante con estrella Michelin: el sistema entero funciona, &lt;strong>el plato es el resultado de la organización, no del talento de una persona&lt;/strong>.&lt;/p>
&lt;p>La analogía aguanta hasta el final, incluido el detalle más interesante: &lt;strong>se puede operar a cualquier nivel&lt;/strong>, pero las promesas que se pueden cumplir son distintas. El puesto callejero no puede prometer una experiencia consistente a 80 comensales con reserva. El cluster en nivel 1 no puede prometer servicio LLM productivo multi-tenant con SLA. En ambos casos el problema no es de &lt;strong>capacidad técnica del último componente&lt;/strong> (la plancha cocina; el pod arranca); es de &lt;strong>capacidad organizativa del sistema entero&lt;/strong>.&lt;/p>
&lt;p>Vamos nivel por nivel.&lt;/p>
&lt;h2 id="nivel-0--caótico-el-servidor-con-linux-y-nada-más">Nivel 0 — Caótico: el servidor con Linux y nada más&lt;/h2>
&lt;p>&lt;strong>La capacidad que da.&lt;/strong> Ejecutar contenedores con &lt;code>docker&lt;/code>/&lt;code>podman&lt;/code>, ejecutar binarios, conectar el servidor a la red. El operador puede entrar por SSH, hacer cosas, y ver resultados.&lt;/p>
&lt;p>&lt;strong>El test del nivel.&lt;/strong> &lt;em>&amp;ldquo;Si reinstalo el servidor desde cero, ¿puedo dejarlo idéntico a como estaba en una tarde, usando sólo notas guardadas?&amp;rdquo;&lt;/em>. Si la respuesta es no (porque los pasos están en la cabeza del que lo montó, en &lt;code>.bash_history&lt;/code>, en un wiki desactualizado), estás en nivel 0.&lt;/p>
&lt;p>&lt;strong>Piezas mínimas que dejar resueltas antes de subir a nivel 1.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Decisión sugerida en 2026&lt;/th>
&lt;th>Por qué importa al subir&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Distribución Linux&lt;/td>
&lt;td>Debian estable u Ubuntu LTS&lt;/td>
&lt;td>Soporte largo, predecible, kernel reciente disponible&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Kernel&lt;/td>
&lt;td>LTS reciente (≥ 6.6) con BPF y schedulers modernos&lt;/td>
&lt;td>Cilium/eBPF, drivers NVIDIA recientes lo exigen&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Drivers NVIDIA&lt;/td>
&lt;td>Versión que casa con la CUDA del motor LLM que vas a servir&lt;/td>
&lt;td>Mismatch driver/CUDA bloquea vLLM antes de empezar&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Container runtime&lt;/td>
&lt;td>&lt;code>containerd&lt;/code>&lt;/td>
&lt;td>Estándar CNCF, integrado con RKE2/kubeadm&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Filesystem raíz&lt;/td>
&lt;td>XFS o ext4 + LVM thin pools&lt;/td>
&lt;td>Snapshots, ampliación en caliente&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sincronización horaria&lt;/td>
&lt;td>&lt;code>chrony&lt;/code> con servidores propios&lt;/td>
&lt;td>TLS, logs correlados, certificados cortos lo exigen&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Red de gestión&lt;/td>
&lt;td>VLAN dedicada, ACLs en switch&lt;/td>
&lt;td>Aislar plano de control del tráfico de carga&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Red de cluster&lt;/td>
&lt;td>LACP + jumbo frames + BGP (si vas a Cilium)&lt;/td>
&lt;td>NVLink intra-nodo no salva la red de servicio&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BMC / IPMI&lt;/td>
&lt;td>Acceso fuera de banda con TLS y MFA&lt;/td>
&lt;td>Recuperación cuando el sistema operativo no arranca&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Antipatrones que te dejan clavado en nivel 0.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Servidores &lt;strong>mascota&lt;/strong> (con nombre propio, configurados a mano, no reemplazables).&lt;/li>
&lt;li>Cambios aplicados con &lt;code>vi&lt;/code> directo sobre &lt;code>/etc/...&lt;/code> sin commit a un repo.&lt;/li>
&lt;li>Despliegue con &lt;code>docker-compose&lt;/code> sin healthchecks ni reinicio automático.&lt;/li>
&lt;li>Inventario que vive en una hoja Excel que nadie actualiza.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Orden de despliegue dentro del nivel.&lt;/strong> Imagen del sistema desde PXE/cloud-init con configuración inicial (LVM, hostname, red, SSH key, chrony) → bootstrap de bastion/jump host → inventario en Ansible (o equivalente declarativo aunque luego se reemplace) → drivers NVIDIA + container runtime → smoke test (un contenedor CUDA pasa &lt;code>nvidia-smi&lt;/code>). En este punto, el servidor está listo para que entre Kubernetes.&lt;/p>
&lt;h2 id="nivel-1--repetible-cluster-kubernetes-operativo">Nivel 1 — Repetible: cluster Kubernetes operativo&lt;/h2>
&lt;p>&lt;strong>La capacidad que da.&lt;/strong> Programar contenedores con scheduler, abstracción de red entre pods, volúmenes persistentes, lifecycle de cargas, escalado horizontal manual.&lt;/p>
&lt;p>&lt;strong>El test del nivel.&lt;/strong> &lt;em>&amp;quot;¿Puedo perder un nodo y que las cargas se reprogramen sin intervención humana?&amp;quot;&lt;/em>. Si sí, estás en nivel 1. Si no — porque los pods están pinneados a nodos, porque no hay réplicas, porque las PVCs no se reattachean — sigues en 0 con Kubernetes encima.&lt;/p>
&lt;p>&lt;strong>Piezas mínimas del nivel.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Decisión sugerida en 2026&lt;/th>
&lt;th>Alternativa principal&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Distribución k8s&lt;/td>
&lt;td>&lt;strong>RKE2&lt;/strong> (CIS-hardened por defecto, sin sobrecosto comercial)&lt;/td>
&lt;td>k3s para edge muy pequeño, kubeadm puro para casos custom&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CNI&lt;/td>
&lt;td>&lt;strong>Cilium&lt;/strong> con kube-proxy replacement, BGP, Gateway API&lt;/td>
&lt;td>Calico (sin BGP no compite contra Cilium en 2026)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CSI block + filesystem + object&lt;/td>
&lt;td>&lt;strong>Rook-Ceph&lt;/strong> (RBD + CephFS + RGW S3-compatible)&lt;/td>
&lt;td>OpenEBS Mayastor + Garage para deployments pequeños&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ingress&lt;/td>
&lt;td>Cilium Gateway API (mejor unificar con CNI)&lt;/td>
&lt;td>NGINX Ingress, Traefik&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cert básico&lt;/td>
&lt;td>Self-signed bootstrap&lt;/td>
&lt;td>(cert-manager entra en nivel 3)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Manejo de cargas&lt;/td>
&lt;td>&lt;code>kubectl apply&lt;/code> + Helm desde terminal&lt;/td>
&lt;td>Sin GitOps todavía&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Container registry&lt;/td>
&lt;td>Cualquier registry interno (o externo de confianza) con TLS&lt;/td>
&lt;td>(registry interno gestionado entra en nivel 2)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Antipatrones que te bajan a nivel 0.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Servicios desplegados con &lt;code>kubectl apply&lt;/code> desde la terminal de una persona y &lt;strong>sin guardar el YAML en ninguna parte&lt;/strong>.&lt;/li>
&lt;li>Volúmenes persistentes sin política de backup.&lt;/li>
&lt;li>&amp;ldquo;Cluster de un nodo&amp;rdquo; como producción permanente — un solo punto de fallo arquitectónico.&lt;/li>
&lt;li>CNI sin NetworkPolicy disponible o sin BGP cuando la red lo requiere.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Orden de despliegue dentro del nivel.&lt;/strong> RKE2 instalado en al menos tres nodos para control plane HA → Cilium instalado en modo kube-proxy replacement + BGP control plane → Rook-Ceph en al menos tres nodos cubriendo block (RBD) + filesystem (CephFS) + object (RGW S3-compatible) con replicación 3× o Erasure Coding según pool → smoke test (un Deployment con PVC arranca, los pods se reschedulean al cordon de un nodo, los datos persisten).&lt;/p>
&lt;h2 id="nivel-2--definido-el-cluster-se-reconstruye-desde-git">Nivel 2 — Definido: el cluster se reconstruye desde git&lt;/h2>
&lt;p>&lt;strong>La capacidad que da.&lt;/strong> El estado del cluster vive en un repositorio. Cualquier cambio pasa por commit. Cualquier persona puede reconstruir el cluster (o uno equivalente) desde el repo y los backups. La observabilidad básica avisa cuando algo se rompe.&lt;/p>
&lt;p>&lt;strong>El test del nivel.&lt;/strong> &lt;em>&amp;ldquo;Si pierdo el cluster entero, ¿puedo recrearlo en X horas desde el repo + los backups, sin intervención manual fuera del bootstrap?&amp;rdquo;&lt;/em>. Las dos horas son negociables; lo que define el nivel es que &lt;strong>el repo + los backups bastan&lt;/strong>, no que la persona-que-sabe esté disponible.&lt;/p>
&lt;p>&lt;strong>Piezas mínimas del nivel.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Decisión sugerida en 2026&lt;/th>
&lt;th>Por qué&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Forge&lt;/td>
&lt;td>&lt;strong>Forgejo&lt;/strong> (o Gitea, GitLab CE)&lt;/td>
&lt;td>OSS auto-alojado, fork comunitario de Gitea, gobernanza abierta&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reconciliador GitOps&lt;/td>
&lt;td>&lt;strong>Flux&lt;/strong>&lt;/td>
&lt;td>CNCF graduado, multi-tenancy nativo, lightweight&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Registry de imágenes&lt;/td>
&lt;td>&lt;strong>Forgejo Container Registry&lt;/strong>&lt;/td>
&lt;td>Junto al código, sin pieza extra&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TSDB métricas&lt;/td>
&lt;td>&lt;strong>VictoriaMetrics&lt;/strong> + vmagent&lt;/td>
&lt;td>Throughput superior a Prometheus puro, retención larga, compatible PromQL&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Visualización&lt;/td>
&lt;td>&lt;strong>Grafana&lt;/strong>&lt;/td>
&lt;td>Estándar de facto&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Logs&lt;/td>
&lt;td>&lt;strong>Loki&lt;/strong> o &lt;strong>Vector&lt;/strong>&lt;/td>
&lt;td>OSS, integrado con Grafana&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Alerting&lt;/td>
&lt;td>&lt;strong>Alertmanager&lt;/strong> + &lt;strong>Keep&lt;/strong> (orquestador OSS)&lt;/td>
&lt;td>Keep añade enrutamiento multi-canal sin lock-in&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Backups DB&lt;/td>
&lt;td>&lt;strong>Barman Cloud&lt;/strong> (Postgres)&lt;/td>
&lt;td>Estándar para CNPG&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Backups objeto / dataset&lt;/td>
&lt;td>&lt;strong>Ceph RGW multisite&lt;/strong> + snapshots CephFS&lt;/td>
&lt;td>Cross-pool y cross-site&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Antipatrones que te bajan a nivel 1.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>kubectl apply&lt;/code> aplicado en producción &lt;strong>fuera&lt;/strong> del repo (drift no detectado).&lt;/li>
&lt;li>Branches &lt;code>main&lt;/code> con permisos de escritura para humanos sin revisión.&lt;/li>
&lt;li>Repo monolítico sin separación tenant/infra/apps (cambios cruzados no auditables).&lt;/li>
&lt;li>Métricas que no se conservan más de 7 días (sin SLO observable a un mes vista).&lt;/li>
&lt;li>Alerting que dispara para todo (fatiga) o para nada (silencio).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Orden de despliegue dentro del nivel.&lt;/strong> Forgejo desplegado primero (es prerrequisito de todo lo demás) → Flux instalado y apuntando al repo de manifests → repositorio inicial con Helm releases de Cilium y Rook-Ceph reconciliados por Flux (sustituyendo los &lt;code>kubectl apply&lt;/code> del nivel 1) → VictoriaMetrics + Grafana + Loki vía Helm/Flux → backups Postgres y snapshots Ceph programados → smoke test (tira el cluster, restaura desde repo + backup, los servicios vuelven).&lt;/p>
&lt;h2 id="nivel-3--gestionado-identidades-certificados-secretos-y-políticas">Nivel 3 — Gestionado: identidades, certificados, secretos y políticas&lt;/h2>
&lt;p>&lt;strong>La capacidad que da.&lt;/strong> Cualquier humano que opera el cluster lo hace con identidad propia (no &lt;code>kubeconfig&lt;/code> compartido), con MFA y con permisos limitados. TLS interno automático. Secretos versionados encriptados. Políticas que &lt;strong>rechazan&lt;/strong> configuraciones inseguras antes de que entren al cluster. Auditoría completa de quién hizo qué.&lt;/p>
&lt;p>&lt;strong>El test del nivel.&lt;/strong> &lt;em>&amp;ldquo;Si un atacante consigue el portátil de un administrador, ¿qué puede hacer en producción?&amp;rdquo;&lt;/em>. En nivel 3 la respuesta es &lt;em>&amp;ldquo;poco&amp;rdquo;&lt;/em>: MFA bloquea el segundo factor, las políticas Kyverno bloquean cambios destructivos sin aprobación, las NetworkPolicies impiden lateral movement, los secretos están encriptados con KMS externo, el audit log queda. En nivel 2, &lt;em>&amp;ldquo;todo&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>&lt;strong>Piezas mínimas del nivel.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Decisión sugerida en 2026&lt;/th>
&lt;th>Por qué&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>IdP / OIDC&lt;/td>
&lt;td>&lt;strong>Defguard&lt;/strong>&lt;/td>
&lt;td>OSS español, WireGuard + OIDC + 2FA, multi-org&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Federación con cluster&lt;/td>
&lt;td>OIDC en kube-apiserver, OIDC en Forgejo, OIDC en Grafana&lt;/td>
&lt;td>SSO consistente&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PKI interna&lt;/td>
&lt;td>&lt;strong>cert-manager&lt;/strong> + Trust Manager&lt;/td>
&lt;td>Estándar de facto, ACME y CA interna&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ACME externo&lt;/td>
&lt;td>Let&amp;rsquo;s Encrypt para certs de borde&lt;/td>
&lt;td>Sin pago, automatizado&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Secretos en git&lt;/td>
&lt;td>&lt;strong>SOPS&lt;/strong> + age o KMS externo&lt;/td>
&lt;td>Versionable, encriptado en repo&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sync de secretos&lt;/td>
&lt;td>&lt;strong>External Secrets Operator&lt;/strong>&lt;/td>
&lt;td>Pull desde KMS / Vault al cluster&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Policy as code&lt;/td>
&lt;td>&lt;strong>Kyverno&lt;/strong> (o OPA Gatekeeper)&lt;/td>
&lt;td>Kyverno tiene menos curva de aprendizaje&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NetworkPolicy&lt;/td>
&lt;td>&lt;strong>Cilium NetworkPolicy&lt;/strong> + L7&lt;/td>
&lt;td>Default deny per namespace&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Runtime security&lt;/td>
&lt;td>&lt;strong>Tetragon&lt;/strong> (Cilium)&lt;/td>
&lt;td>eBPF, complementa NetworkPolicy con detección&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vulnerability scanning&lt;/td>
&lt;td>&lt;strong>Trivy&lt;/strong> en pipeline CI + admission&lt;/td>
&lt;td>SBOM por imagen, bloqueo de CVE críticas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Audit log&lt;/td>
&lt;td>kube-apiserver con &lt;code>--audit-policy-file&lt;/code> enviado a Loki&lt;/td>
&lt;td>Trazabilidad regulatoria&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Políticas Kyverno mínimas a tener vivas.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Deny de imágenes &lt;code>:latest&lt;/code> o sin sha digest.&lt;/li>
&lt;li>Deny de pods sin &lt;code>securityContext.runAsNonRoot=true&lt;/code>.&lt;/li>
&lt;li>Deny de pods sin &lt;code>resources.limits&lt;/code> (CPU + memoria).&lt;/li>
&lt;li>Deny de Services sin label &lt;code>owner=&amp;lt;equipo&amp;gt;&lt;/code>.&lt;/li>
&lt;li>Deny de cambios en namespaces críticos (&lt;code>kube-system&lt;/code>, &lt;code>flux-system&lt;/code>) sin label de aprobación.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Antipatrones que te bajan a nivel 2.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>kubeconfig&lt;/code> compartido entre administradores.&lt;/li>
&lt;li>Secretos en &lt;code>data:&lt;/code> plano del manifest commiteado al repo.&lt;/li>
&lt;li>NetworkPolicy ausente en namespaces nuevos por defecto (allow-all implícito).&lt;/li>
&lt;li>&lt;code>kubectl edit&lt;/code> o &lt;code>kubectl patch&lt;/code> en producción sin pasar por el repo.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Orden de despliegue dentro del nivel.&lt;/strong> Defguard desplegado y enrolado con WireGuard / OIDC → integración OIDC con kube-apiserver, Forgejo y Grafana → cert-manager instalado y emitiendo certificados internos (CA propia para mTLS, Let&amp;rsquo;s Encrypt para borde) → SOPS configurado y External Secrets Operator instalado → migración de secretos plano → encriptado → Kyverno con políticas iniciales y modo &lt;em>audit&lt;/em>, después &lt;em>enforce&lt;/em> → NetworkPolicy default-deny por namespace → Tetragon habilitado → smoke test (intentar saltarse cada política y comprobar que las admisiones rechazan).&lt;/p>
&lt;h2 id="nivel-4--optimizado-para-gpu-el-cluster-ya-sabe-lo-que-es-una-h100">Nivel 4 — Optimizado para GPU: el cluster ya sabe lo que es una H100&lt;/h2>
&lt;p>&lt;strong>La capacidad que da.&lt;/strong> El scheduler de Kubernetes ve las GPUs, las distingue, las puede particionar (MIG) o multiplexar (time-slicing), exponer métricas DCGM, autoescalar con KEDA usando métricas de la propia carga LLM (&lt;code>vllm:num_requests_running&lt;/code>, &lt;code>vllm:gpu_cache_usage_perc&lt;/code>), transportar trazas con semantic conventions GenAI. Todo lo necesario para que el stack de inferencia LLM se apoye en una plataforma que entiende su naturaleza.&lt;/p>
&lt;p>&lt;strong>El test del nivel.&lt;/strong> &lt;em>&amp;ldquo;Si pongo un pod que pide &lt;code>nvidia.com/gpu: 1&lt;/code>, ¿se programa en la GPU correcta, con el slice correcto, con métricas DCGM expuestas, con observabilidad GenAI lista para recibir spans?&amp;rdquo;&lt;/em>. Si sí, estás en nivel 4. Si la respuesta requiere &amp;ldquo;depende de qué nodo y quién lo despliegue&amp;rdquo;, todavía no.&lt;/p>
&lt;p>&lt;strong>Piezas mínimas del nivel.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Pieza&lt;/th>
&lt;th>Decisión sugerida en 2026&lt;/th>
&lt;th>Por qué&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>GPU device plugin&lt;/td>
&lt;td>&lt;strong>NVIDIA GPU Operator&lt;/strong>&lt;/td>
&lt;td>Despliega drivers, container toolkit, DCGM y MIG manager con un operator&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Particionamiento HW&lt;/td>
&lt;td>&lt;strong>MIG&lt;/strong> (Multi-Instance GPU) en H100 cuando aplique&lt;/td>
&lt;td>Aislamiento hardware real, no time-slicing&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Métricas GPU&lt;/td>
&lt;td>&lt;strong>DCGM Exporter&lt;/strong>&lt;/td>
&lt;td>SM utilization, VRAM, temperatura, throttling, NVLink bandwidth&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Métricas LLM&lt;/td>
&lt;td>&lt;strong>vLLM Prometheus&lt;/strong> endpoint + scrape&lt;/td>
&lt;td>TTFT, TPOT, KV cache, prefix hit rate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Autoscaling&lt;/td>
&lt;td>&lt;strong>KEDA&lt;/strong> con ScaledObject Prometheus&lt;/td>
&lt;td>Escala por métricas LLM, no por CPU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Operadores LLM&lt;/td>
&lt;td>&lt;strong>vLLM Production Stack&lt;/strong> / &lt;strong>OME&lt;/strong> (Operator Model Engine)&lt;/td>
&lt;td>Manejo declarativo de modelos / adapters&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Trazas&lt;/td>
&lt;td>&lt;strong>OpenTelemetry Collector&lt;/strong> con receivers OTLP + processors + exporters&lt;/td>
&lt;td>Semantic conventions &lt;code>gen_ai.*&lt;/code> (&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">post&lt;/a>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LeaderWorkerSet&lt;/td>
&lt;td>API LeaderWorkerSet (k8s 1.30+)&lt;/td>
&lt;td>Topología tensor parallel coherente con NVLink&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Topology Manager&lt;/td>
&lt;td>habilitado con &lt;code>single-numa-node&lt;/code>&lt;/td>
&lt;td>Pin de pods GPU a NUMA correcta&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Decisión clave: MIG, time-slicing o pasthrough.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MIG&lt;/strong> divide una H100 en 1g.10gb, 2g.20gb, 3g.40gb, 7g.80gb (slices con aislamiento HW real). Útil para servir varios modelos pequeños o reservar capacidad por tenant con garantía. Limitación: hasta 7 instancias por GPU, perfiles predefinidos.&lt;/li>
&lt;li>&lt;strong>Time-slicing&lt;/strong> comparte una GPU entre varios pods sin aislamiento HW. Útil para dev/test, no para producción multi-tenant con SLA.&lt;/li>
&lt;li>&lt;strong>Passthrough&lt;/strong> asigna la GPU entera a un pod. Útil para tensor parallel sobre múltiples GPUs del mismo nodo (LLM grande con TP=4).&lt;/li>
&lt;/ul>
&lt;p>Para una plataforma LLM productiva, la regla práctica: &lt;strong>passthrough para los modelos grandes con TP&lt;/strong>, &lt;strong>MIG para embeddings y modelos pequeños que cohabitan&lt;/strong>, &lt;strong>nunca time-slicing en producción&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Antipatrones que te bajan a nivel 3.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Instalar drivers NVIDIA a mano fuera del GPU Operator (rotura silenciosa al actualizar Kubernetes).&lt;/li>
&lt;li>Servir un LLM con &lt;code>requests.gpu: 1&lt;/code> sin haber decidido MIG / passthrough (terminas con GPUs idle por fragmentación o pods que se pisan).&lt;/li>
&lt;li>KEDA autoscalando por CPU (&lt;code>HorizontalPodAutoscaler&lt;/code> clásico) en pods que están casi siempre al 10% de CPU pero al 95% de KV cache.&lt;/li>
&lt;li>OpenTelemetry desplegado pero sin semantic conventions &lt;code>gen_ai.*&lt;/code> (las trazas no son LLM-aware).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Orden de despliegue dentro del nivel.&lt;/strong> NVIDIA GPU Operator instalado vía Helm/Flux con la versión de driver que case con el motor LLM elegido → DCGM Exporter habilitado y métricas visibles en Grafana (dashboards NVIDIA importados) → MIG manager configurado para los nodos donde tenga sentido (mezcla typical en cluster 4×H100 SXM: dos GPUs con passthrough completo para el LLM general TP=4, dos GPUs particionadas en 2×3g.40gb cada una para LLMs pequeños + embeddings) → OpenTelemetry Collector con processors &lt;code>attributes&lt;/code> para enriquecer spans con etiquetas propias (&lt;code>tenant_id&lt;/code>, &lt;code>priority_tier&lt;/code>) + exporters a Langfuse y a Tempo → KEDA instalado con ScaledObject de ejemplo apuntando a &lt;code>vllm:num_requests_running&lt;/code> → vLLM Production Stack o OME para declarar modelos como CRD → smoke test (un Deployment de vLLM declarado vía CRD arranca, sirve un token, expone métricas, la traza llega a Langfuse, KEDA escala bajo carga sintética).&lt;/p>
&lt;h2 id="nivel-5--handoff-el-cluster-es-plataforma-llm-las-siete-capas-entran-encima">Nivel 5 — Handoff: el cluster es plataforma LLM, las siete capas entran encima&lt;/h2>
&lt;p>Llegado al nivel 4, el cluster cumple el contrato que el &lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">post de las siete capas&lt;/a> asumía como punto de partida. El nivel 5 no añade infraestructura: añade el &lt;strong>stack LLM&lt;/strong> propiamente dicho. Por completitud, los siete componentes del nivel 5 son:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Gateway&lt;/strong> (Envoy AI Gateway) — entra primero, dirige tráfico a inferencia LLM y embeddings.&lt;/li>
&lt;li>&lt;strong>Inferencia LLM&lt;/strong> (vLLM Production Stack o OME con vLLM) — sobre las GPUs ya descubiertas por el GPU Operator del nivel 4.&lt;/li>
&lt;li>&lt;strong>Embeddings + reranker&lt;/strong> (Infinity, TEI) — pod separado del LLM, ya cubierto en el post anterior.&lt;/li>
&lt;li>&lt;strong>Vector store + datos relacionales&lt;/strong> (Qdrant, PostgreSQL CNPG, Ceph RGW para pesos y adapters, CephFS para datasets) — la mayoría ya existía en nivel 2 como datos; ahora se especializa para RAG.&lt;/li>
&lt;li>&lt;strong>Observabilidad LLM-aware&lt;/strong> (Langfuse) — se enchufa a la cadena OTel del nivel 4.&lt;/li>
&lt;li>&lt;strong>Control plane GitOps&lt;/strong> — el del nivel 2 sigue siendo la única autoridad legítima.&lt;/li>
&lt;li>&lt;strong>Dependency tracking&lt;/strong> (Hubble flows + Otterize) — sobre Cilium que ya existía en nivel 1.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>El criterio para promocionar de nivel 4 a nivel 5&lt;/strong> no es técnico: es contractual. El cluster ya soporta LLMs; la decisión es cuándo abrir tráfico real de clientes. La promoción exige: golden eval del modelo verde, runbook de incidentes firmado, SLOs negociados, plan de continuidad, mapeo a ENS / NIS2 / 42001 si aplica.&lt;/p>
&lt;h2 id="las-matemáticas-que-importan-cuánto-cuesta-saltarse-un-nivel">Las matemáticas que importan: cuánto cuesta saltarse un nivel&lt;/h2>
&lt;p>Para cuantificar la tesis del post, una estimación con orden de magnitud del &lt;strong>coste de subir cada nivel a tiempo&lt;/strong> versus &lt;strong>subirlo después de tener producción&lt;/strong>. Las cifras son tiempo de ingeniería con un equipo de plataforma pequeño (2-3 personas), asumiendo plantillas y experiencia previa.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Nivel&lt;/th>
&lt;th>Tiempo a montar sobre cluster vacío&lt;/th>
&lt;th>Tiempo a retrofit con producción rodando&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0 → 1&lt;/td>
&lt;td>1-2 semanas&lt;/td>
&lt;td>1-2 semanas (poco refactor downstream)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1 → 2&lt;/td>
&lt;td>2-3 semanas&lt;/td>
&lt;td>4-8 semanas (migrar todo a git)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2 → 3&lt;/td>
&lt;td>2-4 semanas&lt;/td>
&lt;td>8-16 semanas (rebuild de imágenes, migración de secretos, RBAC retroactivo)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3 → 4&lt;/td>
&lt;td>1-2 semanas&lt;/td>
&lt;td>4-8 semanas (reconfigurar GPU, mover modelos a MIG, instrumentar &lt;code>gen_ai.*&lt;/code>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4 → 5&lt;/td>
&lt;td>1-2 semanas&lt;/td>
&lt;td>2-4 semanas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Total 0 → 5&lt;/strong>&lt;/td>
&lt;td>&lt;strong>~10-15 semanas&lt;/strong>&lt;/td>
&lt;td>&lt;strong>~20-40 semanas si se hace en orden equivocado&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Multiplicador típico observable en la práctica: &lt;strong>2× a 3×&lt;/strong> el coste si se hace en orden equivocado. Y eso asumiendo que &lt;strong>se llega a hacer&lt;/strong> — muchos proyectos no superan el nivel 2 nunca porque &amp;ldquo;lo de la identidad&amp;rdquo; siempre puede esperar a otro sprint. Cuando llega el incidente, ya es tarde para empezar.&lt;/p>
&lt;p>Más allá del tiempo, el coste &lt;strong>operativo&lt;/strong> (incidentes nocturnos, escapes de seguridad, deuda invisible) crece exponencialmente con el desfase entre el nivel real y el nivel necesario. Un cluster en nivel 2 sirviendo LLMs productivos en clientes regulados es una bomba de relojería: técnicamente funciona, organizativamente no.&lt;/p>
&lt;h2 id="diagrama-final-la-escalera-completa-con-piezas">Diagrama final: la escalera completa con piezas&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1rem auto;">
&lt;svg viewBox="0 0 820 520" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="diagrama final de los cinco niveles con sus piezas OSS y el handoff al stack LLM">
&lt;style>.b{stroke:#333;stroke-width:1.4;rx:6}.bg{fill:#fafafa;stroke:#bbb;rx:8}.l0{fill:#f6e2e2;stroke:#a33}.l1{fill:#f4e3cf;stroke:#a63}.l2{fill:#eef0d0;stroke:#7a3}.l3{fill:#d8eecf;stroke:#373}.l4{fill:#dfe9f5;stroke:#356}.l5{fill:#ead8f5;stroke:#634}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:11px sans-serif;fill:#222}.tiny{font:600 10px sans-serif;fill:#222}.note{font:italic 10px sans-serif;fill:#555}.arr{stroke:#666;stroke-width:1.4;fill:none;marker-end:url(#a)}&lt;/style>
&lt;defs>&lt;marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="410" y="22" text-anchor="middle" class="lbl">Cinco niveles de madurez · piezas OSS · handoff al stack LLM&lt;/text>
&lt;rect x="40" y="38" width="740" height="76" class="b l0"/>&lt;text x="60" y="56" class="tiny">NIVEL 0 · CAÓTICO · un servidor con Linux&lt;/text>
&lt;text x="60" y="74" class="sm">Debian / Ubuntu LTS · kernel ≥6.6 · containerd · drivers NVIDIA · LVM · chrony · BMC TLS+MFA&lt;/text>
&lt;text x="60" y="92" class="sm">Red: VLAN gestión, LACP, jumbo frames, BGP en switch&lt;/text>
&lt;text x="60" y="108" class="note">Test: ¿puedo reconstruir el servidor desde notas?&lt;/text>
&lt;rect x="40" y="122" width="740" height="76" class="b l1"/>&lt;text x="60" y="140" class="tiny">NIVEL 1 · REPETIBLE · cluster Kubernetes operativo&lt;/text>
&lt;text x="60" y="158" class="sm">RKE2 (CIS-hardened) · Cilium (kube-proxy replacement + BGP) · Rook-Ceph (RBD + CephFS + RGW)&lt;/text>
&lt;text x="60" y="176" class="sm">Gateway API · kubectl/Helm desde terminal · pods rodando con HA&lt;/text>
&lt;text x="60" y="192" class="note">Test: ¿perder un nodo no requiere acción humana?&lt;/text>
&lt;rect x="40" y="206" width="740" height="76" class="b l2"/>&lt;text x="60" y="224" class="tiny">NIVEL 2 · DEFINIDO · el cluster se reconstruye desde git&lt;/text>
&lt;text x="60" y="242" class="sm">Forgejo + Flux · Forgejo Container Registry · VictoriaMetrics + Grafana + Loki&lt;/text>
&lt;text x="60" y="260" class="sm">Backups Barman Cloud + Ceph snapshots/RGW multisite · Alertmanager + Keep&lt;/text>
&lt;text x="60" y="276" class="note">Test: ¿puedo recrear el cluster desde repo + backups?&lt;/text>
&lt;rect x="40" y="290" width="740" height="76" class="b l3"/>&lt;text x="60" y="308" class="tiny">NIVEL 3 · GESTIONADO · identidad, certs, secretos, políticas&lt;/text>
&lt;text x="60" y="326" class="sm">Defguard (OIDC + WireGuard) · cert-manager · SOPS + ESO · Kyverno · Trivy&lt;/text>
&lt;text x="60" y="344" class="sm">NetworkPolicy default deny · Tetragon · audit log&lt;/text>
&lt;text x="60" y="360" class="note">Test: ¿qué puede hacer un atacante con un portátil de admin?&lt;/text>
&lt;rect x="40" y="374" width="740" height="86" class="b l4"/>&lt;text x="60" y="392" class="tiny">NIVEL 4 · OPTIMIZADO PARA GPU · el scheduler entiende H100&lt;/text>
&lt;text x="60" y="410" class="sm">NVIDIA GPU Operator · DCGM Exporter · MIG manager · Topology Manager NUMA&lt;/text>
&lt;text x="60" y="428" class="sm">KEDA con métricas vLLM · OTel Collector con gen_ai.* · LeaderWorkerSet · OME&lt;/text>
&lt;text x="60" y="446" class="sm">Decisión: passthrough TP=4 para LLM grande, MIG para LLMs pequeños + embeddings&lt;/text>
&lt;text x="60" y="460" class="note" fill="#373">Test: ¿un pod con nvidia.com/gpu:1 se programa con métricas y traza listas?&lt;/text>
&lt;rect x="40" y="468" width="740" height="44" class="b l5"/>&lt;text x="60" y="486" class="tiny">NIVEL 5 · HANDOFF&lt;/text>&lt;text x="60" y="504" class="sm">Stack LLM (7 capas del post anterior) entra encima · gateway, vLLM, embeddings, Qdrant, Langfuse...&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>La escalera no es decorativa: cada nivel &lt;strong>enable&lt;/strong> el siguiente. No se puede tener observabilidad LLM-aware (nivel 4) sin OTel desplegado vía Flux (nivel 2). No se puede tener TLS interno automático (nivel 3) sin un PKI raíz que viva en algún sitio (registro y certificados gestionados desde el nivel 2). No se puede tener KEDA escalando por métricas vLLM (nivel 4) sin Prometheus / VictoriaMetrics scrapeando (nivel 2). Los niveles &lt;strong>no son una jerarquía conceptual&lt;/strong>: son una jerarquía de &lt;strong>dependencias de instalación&lt;/strong>.&lt;/p>
&lt;h2 id="decisiones-de-diseño-típicas-que-rompen-el-progreso">Decisiones de diseño típicas que rompen el progreso&lt;/h2>
&lt;p>Errores que se ven repetidamente y que tiran el cluster atrás de nivel:&lt;/p>
&lt;p>&lt;strong>1. Saltar de nivel 1 a nivel 4 directamente.&lt;/strong> &amp;ldquo;Tenemos prisa por servir el LLM, lo de identidad y GitOps lo hacemos después&amp;rdquo;. Después es siempre dos órdenes de magnitud más caro y siempre llega después del primer incidente.&lt;/p>
&lt;p>&lt;strong>2. Confundir Helm con GitOps.&lt;/strong> Tener Helm charts no es nivel 2. Es nivel 1 con plantillas. Nivel 2 exige que un reconciliador (Flux/ArgoCD) &lt;strong>aplique&lt;/strong> las charts desde un repo, &lt;strong>detecte drift&lt;/strong> y &lt;strong>avise&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>3. cert-manager sin policy de uso.&lt;/strong> Tener certificados auto-renovados pero usar TLS sólo en el ingress, sin mTLS interno entre servicios, deja la promesa de TLS coja y baja el nivel 3 a un cosplay del 3.&lt;/p>
&lt;p>&lt;strong>4. NVIDIA drivers a mano.&lt;/strong> Funciona el día uno y se rompe el día del primer upgrade de kernel. La regla: drivers &lt;strong>siempre vía GPU Operator&lt;/strong>, nunca paquetes del sistema operativo.&lt;/p>
&lt;p>&lt;strong>5. Métricas Prometheus pero retención de 7 días.&lt;/strong> Sin retención larga (≥ 90 días) no hay SLO honesto. VictoriaMetrics con 1 año de retención cuesta poco más que Prometheus con 7 días, y desbloquea cumplimiento y postmortems serios.&lt;/p>
&lt;p>&lt;strong>6. OIDC sólo para kube-apiserver.&lt;/strong> Si Forgejo, Grafana, Defguard y vLLM cada uno tiene su propio sistema de auth, no tienes SSO, tienes islas. Un nivel 3 honesto exige &lt;strong>federación&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>7. Kyverno en modo &lt;em>audit&lt;/em> permanente.&lt;/strong> Las políticas que no rechazan no son políticas, son alertas. En algún momento hay que pasar a &lt;em>enforce&lt;/em>. Mientras tanto, sigues en nivel 2 con cara de 3.&lt;/p>
&lt;p>&lt;strong>8. MIG sin decisión consciente del perfil.&lt;/strong> Configurar MIG con el perfil por defecto sin haber medido el tamaño de los modelos que van a cohabitar deja GPUs fragmentadas con slices que nadie usa. La regla: MIG sólo si has medido y has decidido los perfiles por adelantado.&lt;/p>
&lt;p>Todos comparten una raíz: &lt;strong>declarar el nivel sin pasar el test del nivel&lt;/strong>. Decir &amp;ldquo;ya hicimos GitOps&amp;rdquo; cuando todavía se aplican cosas con &lt;code>kubectl edit&lt;/code> en prod. Decir &amp;ldquo;ya hicimos identidad&amp;rdquo; cuando hay un &lt;code>kubeconfig&lt;/code> admin compartido. Decir &amp;ldquo;estamos listos para LLM&amp;rdquo; cuando no hay DCGM Exporter ni Langfuse enchufado.&lt;/p>
&lt;h2 id="aplicado-a-hardware-on-premise-típico-cluster-4h100-sxm">Aplicado a hardware on-premise típico: cluster 4×H100 SXM&lt;/h2>
&lt;p>Sobre el cluster genérico de referencia (4×H100 SXM 80 GB, NVLink, 640 GB RAM), un setup razonable después de pasar los cinco niveles distribuye así los componentes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">control plane (3 nodos sin GPU, hostnames cp-01..03)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── kube-apiserver, etcd, controller-manager, scheduler
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Flux, Forgejo, cert-manager, External Secrets, Kyverno
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── Tetragon (DaemonSet también aquí)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">worker plane (≥ 3 nodos sin GPU, hostnames worker-cpu-01..03)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Cilium agent (DaemonSet)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Rook-Ceph OSDs + MONs + MDS (CephFS) + RGW (S3)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── VictoriaMetrics + Grafana + Loki
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Defguard (StatefulSet)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── Langfuse + OTel Collector
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">worker plane GPU (≥ 2 nodos con 4×H100 SXM, hostnames worker-gpu-01..02)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── NVIDIA GPU Operator (driver + container toolkit)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── DCGM Exporter (DaemonSet)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── MIG manager (configurando el perfil decidido)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── vLLM (Deployment) — LLM general TP=4 ocupa 4 GPUs (passthrough)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── vLLM (Deployment) — LLM código TP=2 ocupa 2 GPUs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Infinity (embeddings) — 2 réplicas cohabitan en 2 slices MIG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── KEDA scaler escuchando métricas vLLM
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>La regla operativa: &lt;strong>el plano de control y el plano CPU se separan del plano GPU&lt;/strong>. Un incidente en el plano GPU no debe llevarse por delante el plano de control (que es lo que recupera el cluster). Y el plano CPU concentra todo lo que mueve estado relevante (Forgejo, Rook-Ceph, Postgres CNPG, Langfuse): es el corazón a proteger.&lt;/p>
&lt;p>El hardware GPU se especializa al máximo: pods GPU &lt;strong>solamente&lt;/strong> corren en nodos GPU, y los nodos GPU &lt;strong>no corren&lt;/strong> nada CPU-bound aparte del overhead operativo (Cilium, GPU Operator, DCGM). Esto se enforza con &lt;code>nodeSelector&lt;/code> + taints/tolerations + Kyverno policy que rechaza pods sin requests GPU programándose en nodos GPU.&lt;/p>
&lt;h2 id="lo-que-no-hemos-cubierto-próximos-posts">Lo que no hemos cubierto (próximos posts)&lt;/h2>
&lt;p>Este post recorre el camino vertical hacia arriba. Quedan piezas horizontales y otras transversales que merecen su propio artículo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Multi-site activo/standby&lt;/strong>: cómo se federan dos clusters con Cilium Cluster Mesh y qué cambia en cada nivel cuando hay dos sites en lugar de uno.&lt;/li>
&lt;li>&lt;strong>Migración entre niveles con tráfico real&lt;/strong>: cómo se retrofitea un cluster que ya está en producción al nivel siguiente sin downtime.&lt;/li>
&lt;li>&lt;strong>La operación día a día&lt;/strong>: runbooks por nivel, qué dashboards mirar cada mañana, qué SLOs definir por componente.&lt;/li>
&lt;li>&lt;strong>El plano de coste&lt;/strong>: cuánto cuesta cada nivel en hardware, energía, horas de ingeniería, licencias OSS opcionales (soporte comercial de Rancher, Cilium Enterprise, etc.) y cuándo cada gasto se justifica.&lt;/li>
&lt;li>&lt;strong>Cumplimiento operacionalizado&lt;/strong>: cómo se mapean los niveles 3 y 4 a controles ENS Alto, NIS2 e ISO/IEC 42001 sin convertir el cluster en un ejercicio de paperwork.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/siete-capas-stack-inferencia-llm-on-premise/">Anatomía de un stack de inferencia LLM on-premise&lt;/a> — lo que se monta &lt;strong>encima&lt;/strong> de un cluster en nivel 4. Este post es su prequel arquitectónico.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/catalogo-herramientas-oss-llmops/">El catálogo OSS para LLMOps en seis etapas&lt;/a> — fichas individuales de muchas de las piezas citadas aquí.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/tracing-llm-otel-genai/">Tracing LLM con OpenTelemetry GenAI&lt;/a> — el OTel del nivel 4 con detalle de las semantic conventions &lt;code>gen_ai.*&lt;/code>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/pipeline-llmops-seis-etapas/">Pipeline LLMOps de seis etapas&lt;/a> — el marco operacional que vive sobre el cluster nivel 5.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>RKE2 Documentation — &lt;a href="https://docs.rke2.io/">docs.rke2.io&lt;/a>&lt;/li>
&lt;li>Cilium documentation — &lt;a href="https://docs.cilium.io/">docs.cilium.io&lt;/a>&lt;/li>
&lt;li>Rook-Ceph — &lt;a href="https://rook.io/">rook.io&lt;/a>&lt;/li>
&lt;li>Flux GitOps toolkit — &lt;a href="https://fluxcd.io/">fluxcd.io&lt;/a>&lt;/li>
&lt;li>Forgejo — &lt;a href="https://forgejo.org/">forgejo.org&lt;/a>&lt;/li>
&lt;li>cert-manager — &lt;a href="https://cert-manager.io/">cert-manager.io&lt;/a>&lt;/li>
&lt;li>External Secrets Operator — &lt;a href="https://external-secrets.io/">external-secrets.io&lt;/a>&lt;/li>
&lt;li>Kyverno — &lt;a href="https://kyverno.io/">kyverno.io&lt;/a>&lt;/li>
&lt;li>NVIDIA GPU Operator — &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/">docs.nvidia.com/datacenter/cloud-native/gpu-operator&lt;/a>&lt;/li>
&lt;li>DCGM Exporter — &lt;a href="https://github.com/NVIDIA/dcgm-exporter">github.com/NVIDIA/dcgm-exporter&lt;/a>&lt;/li>
&lt;li>KEDA — &lt;a href="https://keda.sh/">keda.sh&lt;/a>&lt;/li>
&lt;li>LeaderWorkerSet API — &lt;a href="https://github.com/kubernetes-sigs/lws">github.com/kubernetes-sigs/lws&lt;/a>&lt;/li>
&lt;li>vLLM Production Stack — &lt;a href="https://docs.vllm.ai/">docs.vllm.ai/en/latest/serving/production_stack.html&lt;/a>&lt;/li>
&lt;li>OpenTelemetry Semantic Conventions for GenAI — &lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">opentelemetry.io/docs/specs/semconv/gen-ai&lt;/a>&lt;/li>
&lt;li>CIS Kubernetes Benchmark&lt;/li>
&lt;li>NIST SP 800-207 — Zero Trust Architecture&lt;/li>
&lt;/ul></description></item></channel></rss>