Clientes de escritorio para Kubernetes en 2026: la nueva ola más allá de Lens (probados en un clúster real)

Clientes de escritorio para Kubernetes en 2026: la nueva ola más allá de Lens (probados en un clúster real)

Durante años, la respuesta a «¿qué GUI de Kubernetes instalo?» era algún miembro de la familia Lens. Luego Lens se volvió comercial, OpenLens se estancó, y FreeLens recogió el fork de la comunidad. He cubierto esa saga en detalle en FreeLens vs OpenLens vs Lens, y las opciones web en Kubernetes Dashboard Alternatives y Headlamp vs FreeLens vs Lens.

Sin embargo, en los últimos seis meses ha pasado algo más. Ha aparecido una nueva hornada de clientes de escritorio, y la mayoría comparten tres rasgos: no son Electron, incluyen integración con IA o MCP como funcionalidad de primera clase, y están construidos por equipos muy pequeños que se mueven muy rápido. Varios de ellos contactaron conmigo directamente: a través de comentarios en mis artículos de Medium, por email o en LinkedIn.

En lugar de repetir las listas de características de los fabricantes, los instalé todos en la misma máquina, los apunté al mismo clúster con las mismas credenciales de solo lectura, y medí qué hacen realmente y cuánta memoria consumen. Este artículo es el resultado.

TL;DR

  • Radar sigue siendo la opción open source más completa y ahora incluye una app de escritorio y un servidor MCP local. También es la más pesada de la nueva hornada en mi prueba.
  • PodSteer es la recién llegada más interesante: Apache 2.0, Go + Wails, una evaluación de clúster genuinamente útil, y un servidor MCP de solo lectura. Tiene tres meses de vida.
  • Aptakube es el cliente no-Electron más maduro y pulido, pero es propietario y solo es gratuito hasta 5 nodos.
  • Spinoza tiene las funciones de seguridad más contundentes (protección de clúster, planes de drain), pero usa la Functional Source License, que no es open source.
  • KubeSpade es la única app Electron del grupo. Aporta un cliente para iPad y un modelo «trae tu propio agente de IA». Código cerrado.
  • Seabird (GTK4, GNOME) parece abandonado: sin releases en 14 meses.
  • «No ser Electron» no implica automáticamente «ser más ligero». La diferencia entre los nuevos clientes fue menor de lo que sugiere el marketing.

Cómo lo probé

Todos los clientes se probaron contra el mismo clúster: mi homelab, un clúster de Talos Linux de tres nodos con Kubernetes 1.37 y unos 95 pods repartidos en 20 namespaces (descrito en la migración de MicroK8s a Talos sobre Proxmox).

Para mantener la prueba justa y segura, creé una ServiceAccount dedicada vinculada al ClusterRole integrado view más acceso de lectura a nodos, CRDs y unas pocas APIs de ámbito de clúster. Sin secrets, sin exec, sin port-forward, sin verbos de escritura. Esa restricción resultó ser en sí misma una prueba útil: se aprende mucho de un cliente por cómo se comporta cuando no es cluster-admin.

Cada app se ejecutó en un directorio home aislado sobre Debian 12 (x86_64), una a la vez, bajo una pantalla X virtual. La memoria es el PSS (proportional set size) de todo el árbol de procesos, medido aproximadamente un minuto después de conectar con el clúster. El PSS contabiliza las librerías compartidas de forma justa, así que es una comparación mejor que sumar el RSS de todos los procesos auxiliares.

Dos matizaciones antes de los números. Primero, esto es Linux con renderizado por software, así que los valores absolutos serán distintos en macOS. Segundo, esto es un clúster. Un lector de mi artículo sobre Radar me comentó que Lens «tranquilamente llegaba a 2 GB» con varios clústeres abiertos, y lo creo: la memoria crece con el número de clústeres y de recursos que cachea cada cliente.

La comparativa de un vistazo

RadarPodSteerAptakubeSpinozaKubeSpadeSeabird
StackGo + webview del sistemaGo + Wails (webview del sistema)Rust + TauriGo + React, binario únicoElectron 43 + backend en GoGo + GTK4/libadwaita
LicenciaApache 2.0Apache 2.0Propietaria (EULA)FSL-1.1-ALv2 (source-available)Propietaria, código cerradoMPL-2.0
PrecioGratis; Radar Cloud es de pagoGratisGratis ≤5 nodos; desde 7 €/mesGratisGratisDe pago, prueba sin límite
PlataformasmacOS, Linux, Windows, navegador, in-clustermacOS, Linux, WindowsmacOS, Linux, WindowsmacOS, Linux, Windows, navegadormacOS, Linux, Windows, iPad (próximamente)Linux (Flathub)
IA / MCPServidor MCP localServidor MCP de solo lectura——Lanza tu CLI de IA con herramientas MCP, aprobación antes de escribir—
Tráfico en vivoHubble, Istio, Caretta, BeylaIstio, Linkerd, Hubble, Caretta, Beyla—Vista de tráfico——
Vistas GitOpsArgo CD, FluxArgo CD, FluxArgo CD, FluxArgo CD, Flux (con grafo)——
Primera versiónene. 2026ago. 20262022jul. 2026ago. 2026ene. 2024
Última (oct. 2026)v1.18.2v0.6.01.22.0v2.2.5v1.5.0v0.6.0 (ago. 2025)
RAM medida (PSS)~610 MB~330 MB~300 MB~350 MB~335 MBno arrancó

Como referencia, FreeLens 1.10.3 (Electron) usaba ~630 MB conectado al mismo clúster bajo las mismas condiciones.

Radar: sigue siendo el referente

Escribí una review completa de Radar en mayo y la actualicé este mes con capturas de la v1.16, así que aquí me centro en lo que ha cambiado.

Radar ahora incluye una app de escritorio en toda regla (radar-desktop) además del plugin kubectl radar y la instalación in-cluster vía Helm. La pantalla de inicio muestra algo que ninguno de los demás tenía hace unos meses: un servidor MCP local en 127.0.0.1, de modo que un agente de código puede consultar el clúster a través del modelo que Radar tiene de él en vez de tirar de kubectl por línea de comandos.

Radar desktop home screen connected to a Talos cluster, showing pods, deployments, nodes and the local MCP server
App de escritorio de Radar v1.18.2. Fíjate en la tarjeta MCP Server Live y en los avisos honestos de RBAC en el timeline y en los paneles de Helm.

Lo que me gustó en la prueba con permisos restringidos: Radar me decía exactamente qué no podía ver. El timeline avisaba de que «algunos tipos de recurso no se monitorizan por restricciones de RBAC» y el panel de Helm decía «permisos insuficientes» en lugar de afirmar que no había releases. La nueva vista de Upgrade impact (APIs eliminadas, PDBs que bloquearían un drain, conversion webhooks sin un Service detrás) es también algo que ningún otro cliente de esta comparativa ofrece.

El precio a pagar es el peso. Con ~610 MB fue el más pesado de los clientes nuevos, prácticamente a la par con FreeLens. Radar construye en memoria una topología completa y un timeline de eventos, y eso se paga. El propio Radar es Apache 2.0. Radar Cloud, la versión alojada multi-cluster, arranca en 149 $ por clúster al mes en el plan Team.

Elígelo si quieres el cliente open source más completo, tráfico en vivo desde tu mesh o tu herramienta de eBPF, y la opción de ejecutarlo in-cluster para todo un equipo.

PodSteer: el recién llegado más prometedor

PodSteer es un cliente de escritorio nativo de Cloudresty, escrito en Go sobre Wails, que usa el webview propio del sistema operativo en lugar de empaquetar Chromium. Es Apache 2.0, no necesita cuenta, y el repositorio tiene solo unos meses: la primera release (v0.1.0) es del 31 de agosto de 2026 y la v0.6.0 salió el 3 de octubre.

Lo encontré a través de un comentario que su autor dejó en mi artículo de Headlamp. Esto es una aclaración, no un motivo para restarle valor, porque fue el cliente que más me sorprendió.

La primera pantalla tras conectar no es una lista de pods sino una evaluación: mi clúster aparecía como Degraded con dos hallazgos de advertencia, seguido de un panel de capacidad que divide CPU y memoria en solicitado, usado, planificable («schedulable») y «eficiencia». La cifra de 154% de eficiencia de memoria (más memoria usada que solicitada) es exactamente el tipo de señal difícil de ver en otras GUI.

PodSteer cluster overview showing a Degraded assessment, capacity bars for CPU, memory, storage and pod slots
PodSteer v0.6.0: la vista general empieza con una evaluación y un desglose de capacidad de solicitado frente a uso real.

También es cuidadoso con los datos que no tiene. Un banner indicaba que la evaluación se había ejecutado «sin los filesystems de los nodos — esas cifras faltan en vez de aparecer a cero». Después de la prueba con permisos de solo lectura, ese tipo de honestidad es lo que más valoro en una herramienta para clústeres.

La vista de Applications agrupa los recursos por la label app.kubernetes.io/instance y muestra quién gestiona cada uno (Helm, un operator) y su versión. Incluso te dice cuántos objetos se quedan fuera por no tener las labels recomendadas.

PodSteer applications view grouping Helm releases in the observability namespace with CPU and memory usage
PodSteer agrupa las workloads en aplicaciones usando las labels recomendadas de Kubernetes.

El resto de la lista de funciones es ambicioso para un proyecto de tres meses: topología con tráfico observado desde Istio, Linkerd, Hubble, Caretta o Beyla; vistas de Argo CD, Flux y Helm; inspección de RBAC; operaciones de día 2 como scale, restart, rollback y drain; y un servidor MCP de solo lectura para agentes de código.

Memoria: ~330 MB. La vista de topología es el único área que necesita trabajo en un namespace con mucha carga: con el zoom por defecto, 81 objetos se renderizaban como cajitas minúsculas. Cuenta con alguna aspereza. El repositorio tenía 7 estrellas cuando lo comprobé, así que eres un early adopter.

Elígelo si quieres un cliente open source, sin Electron, que te diga qué falla en un clúster, no solo qué hay en él.

Aptakube: maduro, pulido, no open source

Aptakube es el veterano de este grupo. Existe desde 2022, está escrito en Rust con Tauri, y su repositorio en GitHub (875 estrellas) se usa para releases e issues, no para el código fuente. La versión 1.22.0 salió el 27 de octubre de 2026. Rob Kooper lo recomendó en los comentarios de mi artículo de Radar, y le prometí que estaría en la siguiente comparativa. Aquí está.

Es el cliente más acabado de esta comparativa. Al abrir un clúster vas directo a un resumen limpio de las workloads, y la lista de pods es densa pero de la buena manera: CPU y memoria con barras de uso frente a los límites, contadores de restarts, hora y motivo del último restart, y nodo.

Aptakube pod list in the observability namespace with CPU and memory usage bars, restarts and last restart reason
Aptakube 1.22.0: la tabla de pods muestra el uso frente a los límites y por qué se reinició por última vez cada contenedor.

Las funciones que lo diferencian tienen que ver con trabajar con varios clústeres a la vez: vistas multi-cluster en una sola ventana, diff de recursos entre clústeres, etiquetas de contexto con «protección de cambios» para producción, además de vistas de Argo CD, Flux, Gateway API y Karpenter. No necesita instalar nada en el clúster.

Con ~300 MB fue el cliente más ligero de mi prueba.

El inconveniente es la licencia. Aptakube es propietario. Es gratuito con todas las funciones en clústeres de hasta 5 nodos. Por encima de eso tienes una prueba de 15 días, y después el plan Personal cuesta 7 €/mes o 79 €/año y el plan Team 5 €/puesto/mes o 59 €/puesto/año, con gestión de licencias basada en SSO en el plan Team. Para un homelab o clústeres pequeños es prácticamente gratis. Para una empresa con clústeres reales es una partida de gasto pequeña pero real.

Elígelo si gestionas muchos clústeres, valoras el acabado por encima de la apertura del código, y no te importa pagar una pequeña suscripción.

Spinoza: seguridad con criterio propio, source-available

Spinoza, de Sophotech, me contactó por LinkedIn en agosto. Es un único binario de Go con un front end en React que funciona tanto en una pestaña del navegador como en una ventana de escritorio. Cuando lo miré por primera vez iba por la v1.13 con un mes de historial. Ahora está en la v2.2.5 (19 de octubre de 2026), así que avanza rápido.

Su personalidad gira en torno a las barreras de seguridad. La primera vez que me conecté, me ofreció proteger el clúster: una vez que un clúster está protegido, borrar, hacer drain, escalar a cero o desinstalar requiere escribir el nombre del objeto. Los drains muestran el plan de evicción antes de ejecutarlo. De todos los clientes que probé, Spinoza es el que más piensa en el clic destructivo.

La vista que usaría a diario es Reserved against used: por namespace (o por workload), CPU y memoria solicitadas frente a las realmente usadas, con la capacidad sobrante resaltada. Es la forma más rápida que he visto de detectar namespaces sobredimensionados.

Spinoza reserved against used view listing namespaces with CPU and memory requested, used and spare
Spinoza v2.2.5: solicitado frente a usado por namespace, a partir de las propias muestras de Spinoza de los últimos 30 minutos.

Spinoza también destaca en GitOps (Flux y Argo CD uno junto al otro, con un grafo de dependencias) y lee los releases de Helm directamente del almacenamiento de Helm sin necesidad del binario helm. En mi prueba de solo lectura eso significó que Helm no mostraba nada. Helm guarda los releases en Secrets, y mi ServiceAccount no podía leerlos. Spinoza lo indicó con claridad («Partial data: secrets could not be listed cluster-wide»), que es el comportamiento correcto.

Memoria: ~350 MB.

En cuanto a la licencia, hay que ser preciso. Spinoza usa FSL-1.1-ALv2, la Functional Source License: puedes leer y usar el código, pero los usos que compitan con el producto están restringidos, y cada versión pasa a Apache 2.0 dos años después de su publicación. Eso es source-available, no open source. Después de la saga de licencias de Lens, esa distinción importa.

Elígelo si quieres un cliente que proteja activamente los clústeres de producción frente a errores y te resulta cómoda una licencia fair-source.

KubeSpade: Electron, iPad y trae tu propio agente de IA

El desarrollador de KubeSpade me escribió un correo tras leer mi comparativa de Headlamp. Funciona en macOS, Windows y Linux, tiene una versión para iPad (listada como «próximamente» en la App Store), guarda las credenciales en el keychain de cada plataforma, y puede conectarse a EKS, GKE y AKS directamente con credenciales cloud, no solo mediante un kubeconfig.

La web lo describe como «un cliente de Kubernetes rápido y nativo». Al descomprimir el build de Linux, resultó ser Electron 43 con un backend en Go 1.25 enlazado estáticamente que habla con la API de Kubernetes. Así que es la única app Electron de esta nueva oleada. El código fuente está en repositorios privados: el repositorio público de GitHub solo alberga los releases. Es gratuito y no requiere cuenta. Ha habido 11 releases desde el 13 de agosto de 2026.

Conectarse es sencillo: detectó mi kubeconfig y me dejó elegir qué contextos importar. La vista general es limpia, con nodos, namespaces, versión de Kubernetes y warnings en la parte superior, seguidos de medidores de CPU, memoria y pods que muestran uso, requests, limits y lo asignable uno junto al otro.

KubeSpade cluster overview with node, namespace and warning counters and CPU, memory and pod gauges
Vista general de KubeSpade v1.5.0. El histórico de Prometheus se usa automáticamente cuando encuentra Prometheus en el clúster.

Su función distintiva es la IA. En lugar de incluir su propio modelo, KubeSpade lanza el agente de codificación que ya uses, como Claude Code, Codex, Gemini, Copilot o Cursor, en una terminal integrada. El agente recibe herramientas MCP para los clústeres conectados, y las acciones de escritura necesitan tu aprobación. La shell se arranca deliberadamente sin kubeconfig, de modo que el agente pasa por las herramientas de KubeSpade en lugar de usar kubectl directamente. En el primer uso detectó las dos CLI que tenía instaladas.

KubeSpade AI assistant picker offering Claude Code and Codex CLI detected on the machine
KubeSpade detecta las CLI de codificación con IA instaladas localmente y las ejecuta con herramientas MCP del clúster.

Aquí está la sorpresa: con ~335 MB, esta app de Electron usaba más o menos la misma memoria que los clientes Wails y Tauri, y aproximadamente la mitad que FreeLens. Una UI ligera con un backend en Go haciendo el trabajo pesado importa más que la «carcasa» que la envuelve.

Matices: es muy joven (unas 260 descargas en total entre todas las releases cuando lo comprobé) y de código cerrado, así que estás confiando en un proveedor pequeño con las credenciales de tu cluster. Las credenciales se quedan en el dispositivo por diseño, pero no puedes auditarlo.

Elígela si quieres manejar clusters desde un iPad, o te gusta la idea de que tu agente de IA actual trabaje a través de una GUI con puntos de aprobación.

Seabird: un cliente para GNOME que se quedó estancado

Seabird es un IDE de Kubernetes construido para el escritorio GNOME con GTK4 y libadwaita, licenciado bajo MPL-2.0 pero distribuido como «software de pago con prueba gratuita ilimitada». Tiene unas saludables 1,4k estrellas.

Por desgracia, tanto la última release (v0.6.0) como el último push al repositorio son de agosto de 2025. El binario precompilado para Linux no arrancaba en Debian 12 porque necesita una GLib más reciente que la que trae Bookworm; el build de Flathub es la forma práctica de ejecutarlo. Con 14 meses de silencio, hoy no estandarizaría un equipo en torno a él. Lo menciono porque todavía aparece en muchas listas de «GUIs de Kubernetes».

¿»No ser Electron» significa ser más ligero?

Esta era la afirmación que más quería poner a prueba, porque es el argumento estrella de casi todos los clientes nuevos.

ClienteShellRAM (PSS, 1 cluster)
AptakubeTauri (Rust)~300 MB
PodSteerWails (Go)~330 MB
KubeSpadeElectron + backend Go~335 MB
SpinozaGo + webview del sistema~350 MB
Radar desktopGo + webview del sistema~610 MB
FreeLensElectron~630 MB

El panorama es más matizado que un simple «Electron malo, nativo bueno»:

  • Los clientes con webview del sistema son más ligeros que FreeLens, pero por ~2x, no en un orden de magnitud.
  • KubeSpade demuestra que Electron no es toda la historia. Un renderer ligero con un backend en Go cae en el mismo rango que Tauri y Wails.
  • Lo que un cliente cachea importa más que su shell. Radar es Go más un webview del sistema, igual que Spinoza, y aun así consume casi lo mismo que FreeLens porque mantiene una topología en vivo y un timeline de todo lo que observa.

En macOS, donde WebKit es el motor del sistema, los clientes con webview suelen quedar aún mejor. El beneficio más grande de Wails y Tauri suele estar en el tamaño de descarga (el AppImage de PodSteer pesa 15 MB, frente a los 141 MB de KubeSpade y los 199 MB de FreeLens) y en menos actualizaciones de seguridad de Chromium que perseguir.

¿Cuál deberías elegir?

  • Quieres código abierto, sin discusión: Radar o PodSteer. Radar por profundidad y madurez, PodSteer si valoras el enfoque «assessment-first» y una huella más ligera.
  • Gestionas muchos clusters y quieres acabado pulido: Aptakube, y reserva unos euros al mes por puesto si tus clusters superan los 5 nodos.
  • Te preocupa que alguien vacíe producción por error: la protección de clusters de Spinoza es aquí la mejor barrera de seguridad.
  • Quieres que una IA opere el cluster a través de una GUI, con aprobación: el modelo «bring-your-own-agent» de KubeSpade, o los servidores MCP de Radar y PodSteer si prefieres mantener el agente en tu propia terminal.
  • Gestionas clusters desde un iPad: KubeSpade es la única opción de esta lista.
  • Necesitas extensiones y una gran comunidad hoy mismo: quédate en FreeLens con sus extensiones, o pasa a Headlamp, que cuenta con el respaldo de Kubernetes SIG UI (comparativa aquí).

Mi propia configuración tras esta prueba: Radar se queda como mi cliente principal, y PodSteer pasa a mi lista de seguimiento. Si mantiene este ritmo durante otros seis meses, será un candidato serio.

Transparencia

El desarrollador de KubeSpade me contactó por correo electrónico y el equipo de Spinoza por LinkedIn. El creador de PodSteer lo recomendó en un comentario de uno de mis artículos. Nadie pagó por la cobertura ni vio este artículo antes de su publicación. Todas las pruebas usaron los builds públicos disponibles el 28 de octubre de 2026.

Preguntas frecuentes

¿Cuál es el mejor cliente de escritorio para Kubernetes en 2026?

No hay un único ganador. Como cliente open source en desarrollo activo, Radar es el más completo y PodSteer es el recién llegado más prometedor. Si quieres la experiencia más pulida y puedes pagar una pequeña suscripción para clústeres de más de 5 nodos, Aptakube es el cliente no basado en Electron más maduro. FreeLens sigue siendo la opción más segura si dependes de extensiones al estilo Lens.

¿Las nuevas GUI de Kubernetes son realmente más ligeras que Lens o FreeLens?

En parte. En mi prueba sobre un clúster de 3 nodos, Aptakube, PodSteer, Spinoza y KubeSpade consumían entre 300 y 350 MB, más o menos la mitad que FreeLens (~630 MB). Radar usaba casi lo mismo que FreeLens porque mantiene en memoria una topología en vivo y un timeline de eventos. El shell (Electron, Tauri o Wails) importa menos que lo que el cliente cachea.

¿Qué clientes de escritorio para Kubernetes son open source?

Radar y PodSteer son Apache 2.0, FreeLens es MIT, y Seabird es MPL-2.0 (aunque se vende como software de pago). Spinoza usa la Functional Source License, que es source-available pero no open source. Aptakube y KubeSpade son propietarios, y el código de KubeSpade no es público.

¿Puedo usar agentes de IA con estos clientes de Kubernetes?

Sí, de formas distintas. Radar y PodSteer exponen un servidor MCP que los agentes de codificación pueden consultar (en el caso de PodSteer, en modo solo lectura). KubeSpade lanza el agente que ya uses, como Claude Code o Codex, dentro de la propia app con herramientas MCP para tus clústeres, y exige aprobación antes de cualquier acción de escritura.

¿Aptakube es gratis?

Aptakube es gratuito con todas las funciones en clústeres de hasta 5 nodos. Para clústeres más grandes hay una prueba de 15 días, tras la cual el plan Personal cuesta 7 € al mes (o 79 € al año) y el plan Team 5 € por puesto al mes (o 59 € por puesto al año).

OrbStack vs Docker Desktop: la forma más ligera de ejecutar contenedores y Kubernetes en un Mac (2026)

OrbStack vs Docker Desktop: la forma más ligera de ejecutar contenedores y Kubernetes en un Mac (2026)

Si ejecutas contenedores en un Mac, ya conoces el ciclo: Docker Desktop el primer día, una VM que silenciosamente retiene gigas de RAM que ya no necesita y, con el tiempo, una pregunta de compras sobre las licencias de Docker Business. Ahí es cuando empieza la búsqueda de una alternativa.

Hace unos años la respuesta era Rancher Desktop: open source, gratis y con Kubernetes integrado. Hoy el panorama es más amplio. OrbStack se ha convertido en la recomendación por defecto en muchos equipos con flota de Mac, Colima es la opción favorita de quienes viven en la terminal, Podman Desktop es la elección cuando quieres contenedores rootless y paridad multiplataforma, y Apple ahora distribuye su propia herramienta container. Docker Desktop, mientras tanto, tampoco se ha quedado quieto.

Este artículo es una comparativa práctica centrada en OrbStack: qué es realmente, por qué se siente más ligero, cuánto cuesta, dónde se queda corto y cómo se compara con Docker Desktop, Rancher Desktop, Colima, Podman Desktop y el container de Apple. Termina con un árbol de decisión, una migración paso a paso desde Docker Desktop y los problemas que suelen aparecer en la primera semana.

Todas las versiones y precios de abajo se verificaron contra las páginas oficiales de los fabricantes el 8 de octubre de 2026.

Qué es OrbStack

OrbStack es una aplicación de macOS que ejecuta contenedores Docker, un clúster de Kubernetes local y máquinas Linux completas dentro de una única máquina virtual de Linux ligera. Desde tu terminal se ve exactamente igual que Docker: la CLI docker, docker compose y docker buildx funcionan sin cambios porque OrbStack ejecuta un motor Docker real (Docker 29.4 en el momento de escribir esto) y expone el socket estándar.

La diferencia está en todo lo que rodea a ese motor. OrbStack no es una app de Electron envolviendo una VM genérica. Sus servicios están escritos en Swift, Go, Rust y C, y están diseñados específicamente para esta tarea. Según su desarrollador, ya ni siquiera depende del Virtualization.framework de Apple: ejecuta un stack de virtualización propio en Rust, con sus propios dispositivos y protocolos para cosas como el compartido de ficheros (comentario en Hacker News del desarrollador de OrbStack). El modelo se parece mucho a WSL2 en Windows: un único kernel Linux compartido (Linux 7.0 en la versión actual) que alberga tus contenedores y cualquier «máquina» Linux que crees.

La versión actual es OrbStack 2.2.3 (7 de agosto de 2026). Requiere macOS 14 o superior y distribuye builds separados para Macs con Apple Silicon e Intel (FAQ de OrbStack, notas de versión).

Por qué OrbStack se siente más ligero

«Es más rápido» es la afirmación que repite todo el mundo, así que merece la pena precisar de dónde viene realmente la diferencia. Hay cuatro mecanismos, y ninguno es magia.

Memoria dinámica

Las configuraciones clásicas de Docker en Mac asignan a la VM una cantidad fija de memoria. En cuanto el kernel Linux dentro de la VM ha tocado una página, macOS la considera usada, incluso después de que el contenedor que la necesitaba haya terminado. El resultado es la típica imagen de una VM reteniendo gigas que no está usando mientras macOS comprime y pagina todo lo demás.

Desde la versión 1.7 (agosto de 2024), OrbStack rastrea qué partes de la RAM del invitado están realmente en uso y devuelve a macOS la memoria que no se usa. Su propio artículo muestra cómo el uso de memoria y la presión caen en cuanto los contenedores se detienen (blog de OrbStack: memoria dinámica). Esta es la razón principal por la que la gente nota la diferencia en portátiles de 16 GB; el Resource Saver de Docker Desktop solo pausa la VM cuando está inactiva.

CPU en reposo

OrbStack afirma que usa alrededor de un 0,1% de CPU en reposo, y a menudo cae al 0% (documentación de eficiencia de OrbStack). Es una cifra del propio fabricante, no un benchmark independiente, pero coincide con la experiencia general: un OrbStack en reposo es casi invisible en el Monitor de Actividad y en la duración de la batería.

Compartido de ficheros

Montar tu árbol de código como bind mount dentro de un contenedor es la parte más lenta de Docker en macOS, porque cada operación de fichero cruza el límite de la VM. OrbStack usa su propia implementación de compartido de ficheros con caché agresiva en lugar de una configuración genérica de VirtioFS. Las cargas con muchas operaciones de ficheros pequeños (npm install, frameworks PHP, repositorios Git grandes montados dentro de un contenedor) son donde más se nota.

Redes

OrbStack distribuye un stack de red virtual propio. Además de ser rápido, elimina toda una categoría de fricción: los contenedores obtienen sus propias IPs accesibles y nombres de dominio .orb.local, IPv6 funciona, y ping y traceroute se comportan como es de esperar.

Funcionalidades de OrbStack que cambian el trabajo diario

El motor es el mismo Docker que ya conoces, así que lo interesante es lo que OrbStack añade alrededor.

Dominios de contenedor y acceso directo por IP

Cada contenedor recibe un nombre de dominio: container-name.orb.local, o service.project.orb.local para proyectos de Compose. OrbStack detecta qué puerto está sirviendo HTTP, así que http://web.myapp.orb.local simplemente funciona sin tener que recordar si mapeaste el 8080 o el 8081. También ofrece HTTPS automático para esos dominios, generando y confiando en los certificados por ti (documentación de dominios de contenedor).

Puedes añadir tus propios dominios con una etiqueta:

services:
  api:
    image: my-api:dev
    labels:
      - dev.orbstack.domains=api.myapp.local
      - dev.orbstack.http-port=8080

Dos matizaciones: los dominios personalizados deben terminar en .local, y la función depende de la opción «Allow access to container domains & IPs» en Settings → Network.

Kubernetes en un solo comando

OrbStack incluye un clúster de Kubernetes de un solo nodo pensado para desarrollo, actualmente en Kubernetes 1.35 (release notes). OrbStack no indica qué distribución usa por debajo, pero todo apunta a k3s.

orb start k8s
kubectl config use-context orbstack
kubectl get nodes

Lo que lo hace realmente cómodo es la integración. Las imágenes que construyes con docker build están disponibles de inmediato para los pods sin tener que subirlas a un registry. Los servicios LoadBalancer y los ingresses son accesibles en *.k8s.orb.local, los NodePorts en localhost, y puedes llegar a los servicios ClusterIP e incluso a las IPs de los pods directamente desde macOS (documentación de Kubernetes). Por defecto, los NodePorts y los puertos de LoadBalancer solo se expinen en localhost; para exponerlos en tu LAN hay que activarlo explícitamente.

Máquinas Linux

OrbStack también ejecuta distribuciones Linux completas (Ubuntu, Debian, Fedora, Arch y otras) como «machines» que comparten la misma VM, arrancan en segundos y montan los ficheros de tu Mac: un sustituto más ligero que una VM de Multipass o UTM.

Imágenes x86 en Apple Silicon

OrbStack usa Rosetta, no QEMU, para ejecutar contenedores linux/amd64 en Apple Silicon:

docker run --platform linux/amd64 alpine uname -m
docker build --platform linux/amd64 -t my-app:amd64 .

Puedes convertirlo en el valor por defecto con export DOCKER_DEFAULT_PLATFORM=linux/amd64, aunque en un Mac con chip de la serie M deberías hacerlo solo para imágenes que realmente no tengan variante arm64.

Novedades recientes

Las versiones 2.x han traído bastantes novedades: una pestaña Builds con historial de builds de Docker y logs, un monitor de actividad con gráficas de CPU, memoria y disco, un gestor de ficheros para contenedores y volúmenes, una terminal integrada, límites de recursos por máquina, y passthrough de dispositivos USB y sonido (release notes). El Debug Shell para contenedores, una forma cómoda de tener un toolbox completo dentro de una imagen mínima, está reservado para el plan Pro. Si depuras imágenes distroless con frecuencia, kubectl debug y las técnicas de nuestra guía de depuración de distroless cubren lo mismo gratis.

Precio y licencia de OrbStack

Aquí es donde a muchos equipos les pilla por sorpresa, así que conviene decirlo claramente: OrbStack no es gratis para uso profesional.

PlanPrecio (consultado el 8 de octubre de 2026)Uso permitido
Free0 $Solo uso personal, no comercial
Pro8 $ por usuario al mes, o 96 $ por usuario al añoUso empresarial y comercial; añade el Debug Shell y soporte prioritario
EnterpriseA medidaAñade SSO con SAML y pago por factura/orden de compra

Fuente: orbstack.dev/pricing y las FAQ. A diferencia de Docker, OrbStack no fija ningún umbral de tamaño de empresa o ingresos: cualquier uso comercial requiere Pro, incluidos freelancers y ONG. Los estudiantes pueden solicitar licencias gratuitas, y los desarrolladores de código abierto pueden obtener una para proyectos no comerciales.

Compáralo con Docker Desktop, que sigue siendo gratis para empresas con menos de 250 empleados y menos de 10 millones de dólares de ingresos anuales, y exige un plan de pago por encima de cualquiera de esos dos umbrales. Los planes de Docker son Pro a 9 $ por usuario al mes facturado anualmente (11 $ mensual), Team a 15 $ (16 $ mensual) y Business a 24 $ (docker.com/pricing).

Así que la comparativa de coste cambia según quién seas. Para una startup de cinco personas, Docker Desktop es gratis y OrbStack cuesta 480 $ al año. Para una empresa de 400 personas, OrbStack Pro a 96 $ por usuario al año queda muy por debajo de Docker Business a 288 $, y en el mismo rango que Docker Pro, aunque Docker Pro está pensado para individuales y las organizaciones más grandes suelen acabar en Team o Business. Ten en cuenta también que OrbStack es de código cerrado. Algunos componentes son abiertos, pero el producto en sí no lo es, algo que importa para equipos con una política de «solo OSS en las máquinas de desarrollo».

Las alternativas de un vistazo

Así compara OrbStack con el resto de opciones realistas en macOS en octubre de 2026.

OrbStackDocker DesktopRancher DesktopColimaPodman DesktopApple container
Versión actual2.2.3 (ago. 2026)4.94.0 (oct. 2026)1.24.0 (jul. 2026)0.10.3 (jun. 2026)1.29.3 (sep. 2026)1.5.0 (sep. 2026)
LicenciaPropietariaPropietariaApache-2.0MITApache-2.0Apache-2.0
Coste para uso profesional8 $/usuario/mesGratis por debajo de 250 empleados y 10 M$; de pago por encimaGratisGratisGratisGratis
PlataformasSolo macOSmacOS, Windows, LinuxmacOS, Windows, LinuxmacOS, LinuxmacOS, Windows, LinuxmacOS 26+, solo Apple Silicon
GUIApp nativa de macOSApp ElectronApp ElectronNinguna (CLI)App ElectronNinguna (CLI)
MotorDockerDockerDocker (moby) o containerd/nerdctlDocker, containerd o IncusPodman (API compatible con Docker)Runtime propio, una VM por contenedor
KubernetesNodo único integradoIntegrado, incluidos clústeres kind multinodok3s, con versión seleccionablek3s con --kubernetesVía extensiones kind/MinikubeExperimental
x86 en Apple SiliconRosettaRosettaRosetta (modo VZ)Rosetta con --vz-rosettaEmulación QEMU por defecto; Rosetta solo con el proveedor applehv y rosetta = true (desactivado por defecto desde Podman 6)Rosetta (--arch amd64)
Punto fuerteVelocidad, memoria, dominios .orb.localEcosistema, extensiones, Model RunnerGratis y OSS con KubernetesMínimo, scriptableRootless, multiplataformaAislamiento fuerte, nativo de Apple

Fuentes: páginas de release de cada proveedor — Docker Desktop, Rancher Desktop, Colima, Podman Desktop y Apple container.

Docker Desktop

Docker Desktop sigue siendo la implementación de referencia, y en 2026 tiene más a su favor de lo que su reputación sugiere. Los últimos releases han añadido Docker Model Runner para inferencia local de LLMs, clústeres de Kubernetes multi-nodo basados en kind que se provisionan desde el dashboard, una vista unificada de logs y una beta de Docker VMM, el hipervisor propio de Docker optimizado para contenedores en Mac y Windows. También cuenta con el marketplace de extensiones, Enhanced Container Isolation y la gestión centralizada de settings que tanto gusta a los equipos de seguridad.

Sus puntos débiles fueron los que iniciaron el éxodo: es más pesado en reposo y la licencia se convierte en un tema de compras a partir de 250 empleados o 10M$. Ahora bien, si tu empresa ya paga Docker Business por Hub, Scout o Build Cloud, Desktop viene prácticamente incluido, y eso cambia el cálculo.

Rancher Desktop

Rancher Desktop, mantenido por SUSE, sigue siendo la mejor opción totalmente gratuita y open source si Kubernetes es el centro de tu workflow. Trae k3s con selector de versión (útil para probar contra la versión minor exacta que tienes en producción), te deja elegir entre el motor de Docker y containerd con nerdctl, y funciona en los tres sistemas operativos de escritorio. En macOS está construido sobre Lima.

Es más pesado que OrbStack y el file sharing es más lento, pero es gratuito y abierto. Nuestra guía de migración de Docker Desktop a Rancher Desktop cubre ese camino en detalle.

Colima

Colima («Containers on Lima») es la opción minimalista: una CLI con licencia MIT que arranca una VM de Lima con Docker, containerd o Incus dentro, opcionalmente con k3s.

brew install colima docker
colima start --cpu 4 --memory 8 --vm-type vz --vz-rosetta
colima start --kubernetes   # optional k3s

No hay GUI, ni magia de dominios, ni memoria dinámica: tú dimensionas la VM de antemano. A cambio es gratuito, scriptable y predecible, y además funciona en Linux. Las versiones recientes añaden cargas de IA aceleradas por GPU a través del tipo de VM krunkit.

Podman Desktop

Podman Desktop, liderado por Red Hat, pone una GUI encima de Podman: sin daemon, rootless por defecto y con una API compatible con Docker, así que la mayoría del tooling de docker funciona contra él. Corre en macOS, Windows y Linux, lo que lo hace atractivo para equipos con sistemas operativos mixtos y para organizaciones que estandarizan en herramientas de Red Hat. Kubernetes llega a través de extensiones de kind o Minikube, y el concepto nativo de pod de Podman mapea bien con los manifiestos de Kubernetes.

La contrapartida está en la compatibilidad en los bordes: el networking rootless y las herramientas que asumen un daemon de Docker real a veces necesitan workarounds.

Apple container

La herramienta propia de Apple, container, anunciada en la WWDC 2025 y ya en la versión 1.5.0, adopta un enfoque distinto: cada contenedor corre en su propia VM ligera, construida sobre el paquete Swift de Containerization de Apple. Eso le da un aislamiento fuerte y buena integración con Apple Silicon, y la herramienta es compatible con OCI, así que puede hacer pull y push de imágenes estándar.

Requiere macOS 26 y Apple Silicon, es solo CLI, y el proyecto es explícito en que las funciones experimentales (como el subcomando k8s) pueden cambiar sin garantías de compatibilidad. A finales de 2026 es una tecnología a seguir y una buena opción para ejecutar contenedores aislados puntuales, pero todavía no es un sustituto directo de un workflow de desarrollo basado en Docker y Compose.

¿Cuál deberías elegir?

Un pequeño árbol de decisión que coincide con lo que vemos en la práctica:

¿Hay alguien en el equipo con Windows o Linux y quieres una sola herramienta para todos? Elige Docker Desktop si estás por debajo de los umbrales de licencia o ya pagas por Docker; si no, Podman Desktop o Rancher Desktop. OrbStack y Apple container son solo para macOS.

¿Todo el equipo usa Mac y lo que más te importa es la batería, la memoria y la velocidad del día a día? Elige OrbStack. Para uso comercial, cuenta con 96$ por desarrollador al año.

¿La herramienta tiene que ser gratuita y open source? Elige Rancher Desktop si quieres GUI y Kubernetes con selector de versión, Colima si prefieres una CLI, y Podman Desktop si quieres contenedores rootless.

¿La fidelidad a Kubernetes es el requisito principal (multi-nodo, versiones específicas, experimentos de CNI)? Ninguna de las apps de escritorio es ideal. Usa kind o k3d encima del motor que elijas, o la integración de kind de Docker Desktop. El clúster integrado de OrbStack es excelente para ejecutar una app contra Kubernetes, pero deliberadamente simple.

¿Dependes de las extensiones de Docker Desktop, de Docker Scout en la GUI o de Model Runner? Quédate en Docker Desktop.

Migrar de Docker Desktop a OrbStack

La migración es realmente sencilla, lo cual es parte del atractivo de OrbStack. Calcula unos 30 minutos, la mayoría copiando imágenes y volúmenes.

1. Instalar OrbStack

Descárgalo desde orbstack.dev o instálalo con Homebrew:

brew install --cask orbstack

En el primer arranque, OrbStack crea un contexto de Docker llamado orbstack y cambia tu CLI a él. También crea el symlink /var/run/docker.sock (te pedirá permisos de administrador) para que las herramientas con esa ruta hardcodeada sigan funcionando.

2. Migra tus datos

OrbStack puede copiar tus containers, volumes e imágenes desde Docker Desktop. Te lo ofrece en el primer arranque, y puedes ejecutarlo en cualquier momento después:

orb docker migrate

La misma opción está disponible en el menú, en File → Migrate Docker Data. La migración crea una copia, así que tus datos de Docker Desktop se mantienen intactos hasta que decidas eliminarlos (documentación de instalación).

Si solo te importan un puñado de imágenes, suele ser más limpio reconstruirlas o volver a hacer pull y empezar de cero. Es también un buen momento para hacer balance de lo que se ha acumulado: nuestra guía para mantener bajo control el uso de disco de tu entorno Docker local también aplica a OrbStack.

3. Comprueba el contexto y haz una prueba rápida

docker context ls
docker context use orbstack
docker run --rm hello-world
docker compose up -d   # in one of your projects

Puedes mantener ambas herramientas instaladas y cambiar entre ellas con docker context use desktop-linux y docker context use orbstack, pero no ejecutes los dos engines a la vez en un portátil con poca memoria.

4. Vuelve a hacer login en tus registries

Docker Desktop usa su propio credential helper desktop; OrbStack usa osxkeychain. Es normal tener que ejecutar docker login de nuevo para cada registry privado, y conviene revisar ~/.docker/config.json si un script de CI de repente no puede hacer pull.

5. Activa Kubernetes si lo necesitas

orb start k8s
kubectl config use-context orbstack

Recuerda que las imágenes construidas localmente solo se usan si Kubernetes no intenta hacer pull de ellas primero. Usa un tag distinto de latest o fija imagePullPolicy: IfNotPresent.

6. Cierra y desinstala Docker Desktop

Tras una semana sin sorpresas, cierra Docker Desktop, quítalo de los elementos de inicio y desinstálalo. Su imagen de disco de VM puede ocupar decenas de gigabytes, así que solo con esto a menudo liberas espacio notable.

Kubernetes local: el clúster de OrbStack frente a kind y k3d

El clúster integrado de OrbStack es ideal cuando la pregunta es «¿funciona mi app en Kubernetes?». Arranca en segundos, usa tus imágenes locales directamente y expone los services en dominios amigables. Para la mayoría de desarrolladores de aplicaciones, eso es todo lo que necesitan.

Es menos adecuado cuando la pregunta va sobre Kubernetes en sí: scheduling multi-nodo, topology spread constraints, probar un driver CNI o CSI, o reproducir un bug que solo aparece en una versión menor concreta de Kubernetes. Para esos casos, ejecuta kind o k3d sobre el engine de Docker de OrbStack. Funcionan sin cambios, porque solo necesitan un socket de Docker.

kind create cluster --name multi --config kind-3nodes.yaml
k3d cluster create dev --agents 2

La nueva integración de kind en Docker Desktop cubre un terreno similar desde su GUI, y el selector de versión de k3s de Rancher Desktop es la forma más cómoda de fijar una versión concreta de Kubernetes. Sea cual sea tu elección, mantén también requests de recursos realistas en los manifiestos locales: un clúster en el portátil es el sitio más barato para detectar los problemas que describimos en nuestra guía sobre requests y limits en Kubernetes.

Trampas de la primera semana

Contextos de Docker y DOCKER_HOST. Si DOCKER_HOST está definida en el perfil de tu shell (un resto de Colima o Podman, por ejemplo), anula el contexto activo y acabarás hablando con el engine equivocado. Desactívala y confía en docker context use.

Testcontainers. La mayoría de librerías de Testcontainers detectan OrbStack automáticamente a través de /var/run/docker.sock. Si la detección falla, o Ryuk no consigue arrancar, fija el socket explícitamente:

export DOCKER_HOST=unix://$HOME/.orbstack/run/docker.sock
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock

Se usa el mismo patrón para Colima y Podman (configuración de Testcontainers).

Dev Containers. Los dev containers de VS Code y JetBrains funcionan con OrbStack porque expone un socket de Docker estándar. Si tu IDE tenía fijada la ruta del socket de Docker Desktop, vuelve a apuntarlo al contexto por defecto.

arm64 frente a amd64. Apple Silicon ejecuta imágenes arm64 de forma nativa, y Rosetta hace que las imágenes amd64 funcionen, aunque más lento y con alguna incompatibilidad ocasional (algunos runtimes muy dependientes de JIT y cualquier cosa que inspeccione las características de la CPU). Construye imágenes multi-arch con docker buildx build --platform linux/amd64,linux/arm64, y asegúrate de que lo que pruebas en local es lo que acabas desplegando. Si el tamaño de la imagen y el layering te importan, consulta nuestras notas sobre Dockerfiles multi-stage y sobre cómo analizar el tamaño de tus imágenes Docker.

VPN y proxies corporativos. El stack de red de OrbStack sigue, en general, el enrutamiento y DNS de macOS, lo que evita muchos de los problemas que las herramientas basadas en VM antiguas tenían con los clientes VPN. Los proxies que interceptan TLS siguen necesitando que su CA raíz sea de confianza dentro de los containers, igual que con cualquier otra herramienta, y algunos clientes zero-trust necesitan una excepción explícita para la red de OrbStack. Prueba tu VPN el primer día, no durante un incidente.

Deriva en el licenciamiento. El uso personal gratuito hace que OrbStack se extienda dentro de las empresas de forma informal. Decide de antemano si vais a comprar asientos Pro o a estandarizar en una alternativa gratuita.

El escaneo de seguridad sigue siendo cosa tuya. Cambiar el runtime no cambia tu cadena de suministro. Sigue escaneando las imágenes con Trivy o el escáner que prefieras, tanto en local como en CI.

Preguntas frecuentes

¿OrbStack es gratis?

OrbStack solo es gratuito para uso personal, no comercial. Cualquier uso empresarial o comercial requiere el plan Pro, que cuesta 8 $ por usuario al mes o 96 $ por usuario al año (comprobado el 8 de octubre de 2026). A diferencia de Docker Desktop, no existe un umbral de tamaño de empresa o de ingresos por debajo del cual el uso comercial sea gratuito. Los estudiantes pueden solicitar licencias gratuitas, y los desarrolladores de proyectos open-source pueden obtener una para proyectos no comerciales.

¿OrbStack es más rápido que Docker Desktop?

En la mayoría de escenarios del día a día en un Mac, sí. La diferencia viene de la memoria dinámica que se devuelve a macOS, un uso de CPU en reposo casi nulo, una implementación de compartición de ficheros más rápida y una pila de red propia. La diferencia es mayor en cargas de trabajo con muchos bind mounts y en laptops con 16 GB de RAM o menos. Eso sí, las cifras publicadas de «x veces más rápido» no son benchmarks controlados, así que mide tu propio build y tu suite de tests si la velocidad es el factor decisivo.

¿OrbStack funciona con docker compose y el tooling de Docker existente?

Sí. OrbStack ejecuta un motor Docker real y expone el socket estándar en /var/run/docker.sock, así que la CLI de Docker, Compose, Buildx, Dev Containers, Testcontainers y la mayoría de herramientas de terceros funcionan sin cambios. Las principales diferencias son el almacén de credenciales (osxkeychain en lugar del helper desktop de Docker Desktop) y la ausencia de las extensiones de Docker Desktop.

¿OrbStack incluye Kubernetes?

Sí. Incluye un clúster de Kubernetes de un solo nodo, actualmente en la versión 1.35, que se arranca con orb start k8s. Las imágenes construidas localmente están disponibles para los pods sin necesidad de un registry, los services LoadBalancer y los ingresses son accesibles en *.k8s.orb.local, y puedes acceder directamente desde macOS a los services ClusterIP y a las IPs de los pods. Para clústeres multi-nodo o versiones concretas de Kubernetes, ejecuta kind o k3d sobre el motor Docker de OrbStack.

¿Puedo migrar mis imágenes y volúmenes de Docker Desktop a OrbStack?

Sí. Ejecuta orb docker migrate, o usa File → Migrate Docker Data en la aplicación, y OrbStack copiará tus containers, volúmenes e imágenes desde Docker Desktop. Trabaja sobre una copia, así que puedes verificar que todo está correcto antes de desinstalar Docker Desktop. Tendrás que volver a iniciar sesión en los registries privados porque el almacén de credenciales cambia.

¿OrbStack es open source?

No. Algunos componentes son open source, pero OrbStack en sí es software propietario de una empresa comercial. Si necesitas una herramienta completamente open source, los equivalentes más cercanos son Rancher Desktop (Apache-2.0, con GUI y k3s), Colima (MIT, solo CLI) y Podman Desktop (Apache-2.0, containers rootless).

¿OrbStack funciona en Windows o Linux?

No, OrbStack es exclusivo de macOS y requiere macOS 14 o superior, ya sea en Apple Silicon o Intel. Los equipos con desarrolladores en Windows o Linux que quieren una única herramienta para todas las máquinas suelen optar en su lugar por Docker Desktop, Podman Desktop o Rancher Desktop.

¿Debería cambiarme a la herramienta container de Apple?

Todavía no, para un flujo de desarrollo típico. La herramienta container de Apple (versión 1.5.0 en septiembre de 2026) ejecuta cada container en su propia VM ligera, lo que ofrece un aislamiento sólido, pero requiere macOS 26 y Apple Silicon, solo tiene CLI y sigue marcando como experimentales funciones como el soporte de Kubernetes. Merece la pena seguirla de cerca, y es útil para containers aislados puntuales, pero hoy los flujos basados en Compose están mejor servidos por OrbStack, Docker Desktop o Colima.

Conclusión

OrbStack gana en lo que los desarrolladores notan a diario: un Mac que se mantiene fresco, memoria que se recupera cuando los containers se detienen, bind mounts rápidos y detalles como los dominios .orb.local y un cluster de Kubernetes con un solo comando. Migrar a él desde Docker Desktop es cosa de media hora, y como ejecuta un motor Docker real, casi nada de tu toolchain lo nota.

Las contrapartidas son igual de claras: solo macOS, código cerrado y de pago para cualquier uso comercial, sin capa gratuita para empresas pequeñas. Si tu equipo trabaja con Macs y 96 $ por desarrollador al año es una aprobación fácil, OrbStack ofrece la mejor experiencia disponible en 2026. Si la herramienta tiene que ser gratuita y abierta, Rancher Desktop y Colima siguen siendo opciones sólidas. Si necesitas una sola herramienta para Windows, Linux y macOS, o vives dentro del ecosistema de Docker de extensiones, Scout y Model Runner, Docker Desktop sigue siendo la opción segura por defecto. Y el container de Apple es el que hay que vigilar de cara al año que viene.

Controlador vs operador en Kubernetes: qué los separa de verdad

Controlador vs operador en Kubernetes: qué los separa de verdad

Tarde o temprano, en cualquier equipo de plataforma alguien propone «escribir un operador» y otra persona pregunta si no bastaría con un controlador. La respuesta corta es que todo operador es un controlador, pero no todo controlador es un operador. La frontera no es técnica: está en lo que sabe el bucle.

Un controlador es un bucle que observa objetos de Kubernetes y lleva el estado real del clúster hacia el estado deseado que declaran esos objetos. Un operador es un controlador que gestiona una aplicación concreta a través de sus propios recursos personalizados y que incorpora el conocimiento operativo que, de otro modo, tendría una persona de guardia: cómo instalarla, actualizarla, hacer copias de seguridad, conmutarla ante un fallo y recuperarla.

En este artículo vemos las definiciones con precisión, una tabla comparativa, cómo es un bucle de reconciliación mínimo, los frameworks que tiene sentido usar en 2026 y, quizá lo más útil, los casos en los que no deberías construir ninguno de los dos. Si prefieres empezar por una introducción más pausada al patrón operador, tienes Operadores de Kubernetes: lo básico.

Qué es un controlador de Kubernetes

Kubernetes se apoya en una idea: tú declaras el estado que quieres y algo se encarga de que se cumpla continuamente. Ese «algo» es un controlador.

Un controlador ejecuta un bucle de reconciliación:

  1. Observa (watch) uno o varios tipos de recurso a través del API server.
  2. Cuando algo cambia, lee el estado deseado (el spec del objeto).
  3. Observa el estado real (otros objetos o el mundo exterior).
  4. Actúa para cerrar la diferencia: crea, modifica o borra cosas.
  5. Escribe lo que ha observado en el status del objeto.
  6. Vuelve a empezar, indefinidamente.

Hay dos propiedades que hacen que este modelo funcione.

Los controladores se disparan por nivel, no por flanco (level-triggered, no edge-triggered). Un controlador no trata «se ha borrado un pod» como un evento que haya que procesar exactamente una vez. Lo trata como «puede que el mundo haya cambiado, ve a mirar». Si se pierde un evento, se reinicia o recibe cinco cambios de golpe, la siguiente reconciliación sigue dando el resultado correcto porque compara el estado deseado completo con el estado real completo. Por eso la función de reconciliación tiene que ser idempotente: ejecutarla dos veces seguidas no puede hacer daño.

Cada controlador se ocupa de una parcela pequeña. El controlador de Deployments gestiona ReplicaSets. El de ReplicaSets gestiona Pods. El scheduler asigna Pods a nodos. El kubelet arranca contenedores. Ninguno hace el trabajo de otro; lo que ves al hacer kubectl apply de un Deployment es el resultado emergente de varios bucles pequeños que cooperan a través del API server.

Los controladores integrados

La mayoría de los controladores que usas a diario viven en un único binario, kube-controller-manager, que corre en el plano de control. Agrupa decenas de bucles, entre ellos:

  • Los controladores de Deployment y ReplicaSet, que despliegan y mantienen las réplicas.
  • Los de StatefulSet, DaemonSet, Job y CronJob.
  • El de EndpointSlice, que mantiene al día los backends de cada Service.
  • El de Namespace, que limpia todo cuando se borra un namespace.
  • El recolector de basura (garbage collector), que borra los objetos dependientes a través de ownerReferences.
  • El de ciclo de vida de nodos, que marca nodos no sanos y desaloja sus pods.
  • El del autoescalado horizontal (HPA), que ajusta réplicas a partir de métricas (tienes cómo se comporta ese bucle en HPA en Kubernetes: mejores prácticas).
  • Los de enlace y expansión de PersistentVolumes.

Los bucles específicos de cada nube (balanceadores, direcciones de nodo, rutas) corren aparte, en el cloud-controller-manager. Y muchos componentes de terceros también son controladores: los ingress controllers, el application controller de ArgoCD, external-dns, cluster-autoscaler o Karpenter.

A ninguno de ellos se le suele llamar operador. Son infraestructura genérica: gestionan primitivas de Kubernetes o una única preocupación externa, sin codificar cómo se opera un software concreto.

Qué es un operador de Kubernetes

El término «operador» lo acuñó CoreOS en 2016 para describir un uso concreto del patrón controlador: meter en software el conocimiento de un operador humano. La documentación oficial de Kubernetes define los operadores como «extensiones de software de Kubernetes que usan recursos personalizados para gestionar aplicaciones y sus componentes».

Un operador tiene tres ingredientes:

  1. Una Custom Resource Definition (CRD) que modela la aplicación con su propio vocabulario. No creas un StatefulSet, un Service, un ConfigMap, un PodDisruptionBudget y un CronJob para los backups: creas un objeto PostgresCluster o Kafka con campos como instances, version o backup.schedule.
  2. Un controlador que reconcilia esos recursos personalizados en los objetos de Kubernetes subyacentes y en el estado externo.
  3. Conocimiento del dominio integrado en ese controlador: las operaciones de día 2 propias de la aplicación. Promocionar una réplica cuando cae el primario. Ejecutar una migración de esquema antes de desplegar una versión nueva. Reequilibrar particiones al escalar los brokers. Hacer un backup consistente. Rotar certificados sin cortar el servicio.

El tercer ingrediente es el que importa. Un controlador que convierte un recurso WebApp en un Deployment más un Service tiene CRD y bucle de reconciliación, así que técnicamente encaja en la definición, pero no sabe nada que no sepa un chart de Helm. Un operador de PostgreSQL que detecta que el primario no responde, elige la réplica más actualizada, la promociona, redirige el Service de lectura-escritura y reincorpora el antiguo primario como réplica cuando vuelve está haciendo algo que ninguna plantilla puede hacer.

Controlador vs operador: comparativa

AspectoControladorOperador
MecanismoBucle de reconciliaciónBucle de reconciliación (un operador es un controlador)
Qué observaTipos integrados (Pods, Deployments, Services, Nodes) o cualquier tipoSus propios recursos personalizados y los tipos integrados que crea
Necesita CRDNoSí, por definición
AlcanceUna preocupación genérica (réplicas, endpoints, DNS, nodos)Una aplicación o sistema concreto (PostgreSQL, Kafka, Prometheus)
Conocimiento que codificaCómo debe comportarse una primitiva de KubernetesCómo instalar, actualizar, respaldar, recuperar y escalar un software concreto
Quién lo escribeEl proyecto Kubernetes, fabricantes de infraestructuraEl fabricante o la comunidad de la aplicación, o un equipo de plataforma
Dónde correA menudo en kube-controller-manager, o como Deployment independienteSiempre como Deployment independiente en el clúster
Ciclo de vidaNormalmente «mantén N copias de X»Día 1 (instalación) y día 2 (actualización, backup, failover, ajuste)
EjemplosDeployment, ReplicaSet, EndpointSlice, HPA, ingress-nginx, external-dnsPrometheus Operator, CloudNativePG, Strimzi, cert-manager (frontera)

En una frase: el controlador es el patrón; el operador es un uso de ese patrón para gestionar una aplicación con recursos personalizados y experiencia operativa incorporada.

La zona gris: ¿cert-manager es un operador?

La frontera es difusa y discutirla suele ser poco productivo, pero algunos ejemplos ayudan a calibrar.

cert-manager introduce CRDs (Certificate, Issuer, ClusterIssuer, CertificateRequest) y las reconcilia. Codifica conocimiento de dominio real: el protocolo ACME, los retos HTTP-01 y DNS-01, las ventanas de renovación, la rotación de claves. Aun así, su propia documentación lo llama controlador, y no gestiona una aplicación en el sentido de «instálame y opérame esta base de datos», sino una preocupación transversal: los certificados. Casi todo el mundo lo llama operador igualmente, y no pasa nada.

ArgoCD tiene CRDs (Application, ApplicationSet, AppProject) y un application controller. Es un controlador de entrega GitOps, no un operador de una carga concreta. Existe, eso sí, un ArgoCD Operator que instala y actualiza el propio ArgoCD, lo que muestra cómo se usa la palabra en la práctica: «lo que te opera X». (Sobre ArgoCD en sí, la guía completa de ArgoCD.)

Una CRD WebApp propia que se expande en Deployment, Service e Ingress es un controlador con CRD. Llamarlo operador no es incorrecto, pero sería un operador de nivel 1, que es justo lo que explica la siguiente sección.

Niveles de capacidad de un operador

El Operator Framework define un modelo de madurez de cinco niveles, que es la forma más útil de distinguir un operador «de verdad» de un controlador que solo rellena plantillas:

NivelNombreQué hace el operador
1Basic InstallAprovisiona la aplicación a partir del recurso personalizado e informa del estado
2Seamless UpgradesActualiza la aplicación (y a sí mismo) entre versiones, incluidos pasos de datos o esquema
3Full LifecycleBackup y restauración, recuperación ante fallos, escalado, reconfiguración
4Deep InsightsExpone métricas, alertas y logs de la aplicación gestionada y refleja su salud en el status
5Auto PilotAutoescalado, autoajuste, detección de anomalías y autorreparación sin intervención

Un operador de nivel 1 hace más o menos lo que hace helm install. La razón de ser de los operadores son los niveles 2 y 3. Si tu diseño se queda en el nivel 1, lo que probablemente necesitas es un chart de Helm.

Un bucle de reconciliación mínimo en Go

La forma más habitual de escribir un controlador u operador en Go es controller-runtime, la librería que hay debajo de Kubebuilder y de Operator SDK. Este es el esqueleto de una función de reconciliación para un recurso personalizado hipotético Website que es dueño de un Deployment:

func (r *WebsiteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    // 1. Leer el estado deseado.
    var site webv1.Website
    if err := r.Get(ctx, req.NamespacedName, &site); err != nil {
        // Borrado entre el evento y ahora: no hay nada que hacer.
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 2. Construir el objeto que queremos que exista.
    deploy := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{Name: site.Name, Namespace: site.Namespace},
    }

    // 3. Crearlo o actualizarlo de forma idempotente, con el Website como dueño.
    op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deploy, func() error {
        deploy.Spec.Replicas = ptr.To(site.Spec.Replicas)
        deploy.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"app": site.Name}}
        deploy.Spec.Template.Labels = map[string]string{"app": site.Name}
        deploy.Spec.Template.Spec.Containers = []corev1.Container{{
            Name:  "web",
            Image: site.Spec.Image,
        }}
        return controllerutil.SetControllerReference(&site, deploy, r.Scheme)
    })
    if err != nil {
        return ctrl.Result{}, err // controller-runtime reencola con backoff
    }
    log.Info("deployment reconciliado", "operacion", op)

    // 4. Informar del estado observado.
    site.Status.ReadyReplicas = deploy.Status.ReadyReplicas
    site.Status.ObservedGeneration = site.Generation
    if err := r.Status().Update(ctx, &site); err != nil {
        return ctrl.Result{}, err
    }
    return ctrl.Result{}, nil
}

func (r *WebsiteReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&webv1.Website{}).      // reconciliar cuando cambia un Website
        Owns(&appsv1.Deployment{}). // ...o un Deployment que le pertenece
        Complete(r)
}

Fíjate en lo que la función no hace: nunca pregunta «¿qué ha cambiado?». Lee el estado deseado completo, calcula el objeto objetivo completo y deja que CreateOrUpdate lo materialice. La owner reference hace que al borrar el Website el recolector de basura se lleve el Deployment, y Owns() hace que una edición manual del Deployment dispare una reconciliación que lo devuelve a su sitio.

Esto es un controlador con CRD. Para convertirlo en un operador digno de ese nombre, la función tendría que crecer con lógica de dominio: detectar que spec.version ha pasado de 3 a 4 y lanzar antes un Job de migración, retener el despliegue hasta que termine, poner una condición Degraded si falla, hacer un backup antes de cualquier cambio destructivo. Es en esa lógica, y no en el andamiaje, donde se van los meses de trabajo.

Hay piezas que acaba necesitando cualquier operador no trivial:

  • Finalizers, para que el controlador pueda limpiar recursos externos (un bucket, un registro DNS, un usuario de base de datos) antes de que desaparezca el recurso personalizado.
  • Condiciones de status con la forma estándar type/status/reason/message y observedGeneration, para que usuarios y herramientas sepan si el último spec ya se ha procesado.
  • Reencolado diferido (ctrl.Result{RequeueAfter: ...}) para lo que hay que consultar periódicamente, como una API externa que no emite eventos de Kubernetes.
  • Elección de líder, para que solo actúe una réplica del operador a la vez.
  • RBAC acotado y watches limitados a namespaces cuando sea posible, porque un operador es un componente con muchos privilegios.

Frameworks para escribir controladores y operadores en 2026

Prácticamente nunca se escribe a mano la maquinaria de watches, caché y colas de trabajo. Estas son las opciones principales, todas con mantenimiento activo a septiembre de 2026.

controller-runtime y Kubebuilder (Go)

controller-runtime (kubernetes-sigs, v0.25 en el momento de escribir esto) es la librería: manager, cliente con caché, informers, colas de trabajo, elección de líder y webhooks. Kubebuilder (v4.16) es el generador que va encima: crea la estructura del proyecto, los tipos de la CRD con marcadores, los manifiestos RBAC, los webhooks y un entorno de pruebas basado en envtest. Si programas en Go y no tienes restricciones especiales, es la opción por defecto y la que usan la mayoría de los operadores del ecosistema CNCF.

Operator SDK (Go, Helm, Ansible)

Operator SDK (Operator Framework, v1.42) usa los plugins de Kubebuilder para los proyectos Go, así que la experiencia en Go es prácticamente la misma. Lo que añade es la integración con Operator Lifecycle Manager (OLM) para empaquetar y distribuir operadores como bundles, un scorecard para validar buenas prácticas, y dos variantes sin Go: operadores basados en Helm, que reconcilian un recurso personalizado renderizando un chart, y operadores basados en Ansible, que ejecutan playbooks. Tiene sentido si apuntas a OpenShift u OperatorHub, o si tu equipo prefiere escribir Ansible antes que Go.

Java Operator SDK (Java)

Java Operator SDK (v5.6, publicada en septiembre de 2026) lleva el mismo modelo a la JVM, con una extensión de Quarkus que genera imágenes nativas. Las últimas versiones han añadido informers compartidos entre controladores y un registrador de eventos integrado. Encaja de forma natural en equipos cuya lógica de dominio y librerías cliente ya viven en Java; los operadores de Strimzi y de Keycloak son ejemplos conocidos del patrón en la JVM.

kopf (Python)

kopf (Kubernetes Operator Pythonic Framework, 1.44) permite escribir los handlers como funciones Python decoradas, por ejemplo @kopf.on.create('websites'). Es excelente para operadores «pegamento», prototipos y equipos de SRE que viven en Python. Es menos habitual en operadores grandes y sensibles al rendimiento, pero mucha automatización interna corre sin problema sobre él.

Metacontroller (cualquier lenguaje, vía webhooks)

Metacontroller (v4.17) le da la vuelta al modelo: se encarga él de observar y reconciliar, y llama a tu webhook con el objeto padre y sus hijos actuales; tu webhook devuelve los hijos deseados. Tu «controlador» es una función HTTP sin estado en el lenguaje que quieras. Es ideal para recursos compuestos sencillos y decoradores, y poco adecuado para operadores con máquinas de estados complejas de varios pasos.

Alternativas declarativas: kro y Crossplane

Si lo que quieres es «un recurso personalizado que se expanda en un conjunto de recursos», puede que no necesites escribir ningún controlador. kro (Kube Resource Orchestrator, subproyecto de SIG Cloud Provider) permite definir un ResourceGraphDefinition que genera una CRD nueva y su reconciliación a partir de un grafo declarativo; ojo, sus APIs siguen por debajo de la 1.0 y el proyecto no se declara listo para producción. Las composiciones de Crossplane hacen algo parecido, con foco en infraestructura cloud. Ambos te dan un «operador» de nivel 1 sin escribir Go.

Cuándo escribir un controlador, un operador o ninguno

Esta es la decisión que importa, y la respuesta por defecto debería ser «ninguno».

Usa un chart de Helm (o Kustomize) cuando la aplicación no tiene estado o su estado se gestiona fuera, la dificultad está en la instalación y actualizar consiste en desplegar una imagen nueva. Plantillas, helm upgrade y una estrategia de despliegue razonable cubren la gran mayoría de servicios internos. Los hooks de Helm resuelven la migración puntual previa a una actualización (lo tienes en la guía de Helm hooks).

Usa un operador existente cuando vas a ejecutar software con estado conocido. PostgreSQL, Kafka, Redis, Elasticsearch, RabbitMQ y Prometheus tienen operadores maduros cuyos autores ya se han topado con los modos de fallo que tú aún no conoces. Escribir tu propio operador de PostgreSQL para no aprender CloudNativePG es un error de varios años.

Escribe un controlador simple cuando necesitas mantener algo sincronizado de forma continua y ninguna herramienta existente lo hace: etiquetar namespaces a partir de una CMDB externa, crear registros DNS o permisos IAM a partir de anotaciones, aplicar una política de limpieza. Puede que ni siquiera haga falta una CRD: el controlador puede observar recursos integrados y actuar sobre anotaciones.

Escribe un operador cuando se cumplen todas estas condiciones:

  • El software tiene operaciones de día 2 con estado que hoy hacen personas siguiendo un runbook: failover, resharding, actualizaciones ordenadas, backup y restauración.
  • Esas operaciones son lo bastante frecuentes o arriesgadas como para que automatizarlas compense la inversión.
  • No existe un operador maduro, o los que hay están abandonados o no se pueden adaptar.
  • Tienes un equipo para mantenerlo a largo plazo. Un operador es software de producción con su propio ciclo de versiones, versionado de API, webhooks de conversión y CVEs.

Operadores reales que merece la pena estudiar

Leer buenos operadores es la forma más rápida de entender la distancia entre un controlador y un operador.

Prometheus Operator gestiona Prometheus y Alertmanager mediante CRDs como Prometheus, Alertmanager, ServiceMonitor, PodMonitor y PrometheusRule. Su gran acierto es convertir la configuración de scraping en objetos de Kubernetes que cada equipo mantiene junto a su aplicación; el operador regenera la configuración de Prometheus y la recarga. Si has usado kube-prometheus-stack, lo has usado. La diferencia entre sus dos CRDs de descubrimiento la explico en ServiceMonitor vs PodMonitor.

CloudNativePG ejecuta PostgreSQL con una CRD Cluster y gestiona directamente los pods primario y réplicas en lugar de usar un StatefulSet, precisamente porque necesita un control más fino sobre qué instancia es primaria, cómo ocurre el failover y cómo se manejan los volúmenes. Cubre switchover, failover automático, backup continuo y recuperación a un punto en el tiempo contra almacenamiento de objetos, y actualizaciones progresivas de versión menor. Es un operador de nivel 3 o superior de manual.

Strimzi ejecuta Apache Kafka con un cluster operator más los operadores de topics y de usuarios, y modela brokers, node pools, topics, usuarios y conectores como recursos personalizados. Hace los reinicios progresivos en el orden que Kafka necesita, gestiona los certificados entre brokers y clientes, y escala con reasignación de particiones a través de Cruise Control.

cert-manager, que ya hemos visto, es el ejemplo canónico de proyecto con forma de controlador y conocimiento profundo de un protocolo, más que de una carga de trabajo.

El Logging Operator es otro buen ejemplo de diseño basado en CRDs: cada equipo declara sus flujos y salidas de logs por namespace, y el operador gestiona los despliegues de Fluent Bit y Fluentd que hay detrás.

Antipatrones

Las formas habituales en que un operador sale mal:

Un operador que solo ejecuta Helm. Un controlador cuya reconciliación renderiza un chart y lo aplica añade una CRD, un Deployment, RBAC y un nuevo modo de fallo, y no te da nada que no te dé helm upgrade en un pipeline GitOps. Si no hay lógica de día 2, sobra.

Pensar por eventos. Escribir la reconciliación como «al crear haz A, al actualizar haz B» acaba en deriva la primera vez que se pierde un evento. Calcula siempre a partir del estado deseado completo.

Efectos secundarios no idempotentes. Enviar un correo, cobrar o crear un recurso cloud sin comprobar antes si ya existe acabará pasando dos veces. Registra lo que has hecho en el status o usa nombres deterministas.

Sin status. Un operador que nunca escribe condiciones obliga a los usuarios a hacer kubectl logs del pod del operador para adivinar qué ha pasado. El status es la interfaz de usuario del operador.

RBAC de cluster-admin. Los operadores son objetivos muy valiosos. Pide solo los verbos y recursos que usas y, si el software lo permite, instálalo limitado a namespaces.

Bucles de reencolado en caliente. Devolver un error o Requeue: true en cada reconciliación por una condición que no va a cambiar en minutos machaca el API server. Usa RequeueAfter con un intervalo razonable.

Finalizers que bloquean. Un finalizer que nunca puede completarse (porque el sistema externo ya no existe) deja objetos atascados en Terminating para siempre. Prevé siempre un timeout o una vía de escape.

Versionar la CRD a posteriori. En cuanto hay objetos guardados como v1alpha1, cambiar el esquema implica webhooks de conversión y migración del almacenamiento. Diseña la API con el mismo cuidado que una API REST pública, porque lo es.

Preguntas frecuentes

¿Cuál es la diferencia entre un controlador de Kubernetes y un operador?

Un controlador es cualquier bucle que observa recursos de Kubernetes y reconcilia el estado real con el deseado; los controladores de Deployment y ReplicaSet son ejemplos. Un operador es un controlador que gestiona una aplicación concreta mediante sus propias Custom Resource Definitions e incorpora el conocimiento operativo necesario para ejecutarla: actualizaciones, backups, failover y recuperación. Todo operador es un controlador, pero solo se suele llamar operador a los controladores que combinan recursos personalizados con lógica de día 2 específica de una aplicación.

¿Todo operador es un controlador?

Sí. Un operador se implementa como uno o varios controladores que ejecutan un bucle de reconciliación sobre recursos personalizados. La diferencia no está en el mecanismo sino en el alcance y el conocimiento: los operadores gestionan el ciclo de vida completo de una aplicación, mientras que los controladores genéricos gestionan primitivas de Kubernetes o una única preocupación de infraestructura.

¿Un controlador necesita una CRD?

No. Muchos controladores solo observan recursos integrados: los de Deployment, EndpointSlice o el recolector de basura de kube-controller-manager, o un controlador externo que reacciona a anotaciones en los Services. La CRD solo es necesaria cuando hace falta un tipo de recurso nuevo para expresar el estado deseado, que es siempre el caso de los operadores.

¿Uso un chart de Helm o un operador?

Usa un chart de Helm cuando lo difícil es la instalación y la configuración, y las actualizaciones son simples despliegues, que es lo que ocurre con la mayoría de servicios sin estado. Usa un operador cuando el software necesita operaciones de día 2 automatizadas como failover, actualizaciones ordenadas con migración de datos, o backup y restauración. Muchos operadores se instalan a su vez con Helm, así que son herramientas complementarias, no excluyentes.

¿Qué framework uso para escribir un operador de Kubernetes?

En Go, Kubebuilder sobre controller-runtime; elige Operator SDK si necesitas empaquetado OLM para OpenShift u OperatorHub, o quieres operadores basados en Helm o Ansible. En Java, Java Operator SDK, y en Python, kopf. Para recursos compuestos sencillos en cualquier lenguaje, Metacontroller o una herramienta declarativa como kro evitan escribir un controlador completo.

¿Cuáles son los niveles de capacidad del Operator Framework?

El Operator Framework define cinco niveles de madurez: Basic Install, Seamless Upgrades, Full Lifecycle (backup, recuperación, escalado), Deep Insights (métricas, alertas, estado de salud) y Auto Pilot (autoescalado, autoajuste, autorreparación). Un operador que solo llega al nivel 1 hace más o menos lo mismo que un chart de Helm; el valor de un operador aparece a partir del nivel 2.

¿Dónde se ejecutan los controladores integrados de Kubernetes?

La mayoría se ejecutan dentro del proceso kube-controller-manager en el plano de control, entre ellos los de Deployment, ReplicaSet, StatefulSet, Job, Namespace, EndpointSlice, ciclo de vida de nodos y autoescalado horizontal. Los controladores específicos de cada proveedor cloud corren en el cloud-controller-manager, y el scheduler y el kubelet son componentes aparte que siguen el mismo modelo de observar y reconciliar.

¿cert-manager es un controlador o un operador?

Está en la zona gris. cert-manager define CRDs como Certificate e Issuer y codifica un conocimiento profundo de ACME y de la renovación de certificados, así que encaja en la definición de operador, pero gestiona una preocupación transversal en lugar de instalar y operar una aplicación, y su propia documentación lo describe como controlador. Se usan habitualmente las dos etiquetas.

Conclusión

El debate entre controlador y operador tiene menos que ver con definiciones que con intención. Un controlador es la pieza fundamental de Kubernetes: un bucle idempotente, disparado por nivel, que mantiene una parte del mundo alineada con un estado declarado. Un operador es lo que obtienes cuando apuntas esa pieza a una única aplicación, le das un vocabulario a través de recursos personalizados y le enseñas todo lo que saben tus ingenieros de guardia sobre cómo operarla.

Eso simplifica mucho la decisión práctica. Helm para instalar; un operador maduro existente para sistemas con estado conocidos; un controlador pequeño cuando necesitas una sincronización continua que nada más te da; y un operador completo solo cuando hay conocimiento de día 2 real que codificar y un equipo dispuesto a mantenerlo durante años. Si estás a punto de construir un operador de nivel 1, probablemente estés a punto de construir un chart de Helm con pasos extra.

OpenSearch, Loki, Quickwit y ClickHouse: ¿Qué plataforma elegir para logs y observabilidad en 2026?

OpenSearch, Loki, Quickwit y ClickHouse: ¿Qué plataforma elegir para logs y observabilidad en 2026?

La gestión de logs y la observabilidad han dejado de ser un problema de «guardar texto y buscarlo rápido». En 2026, el reto real es económico y arquitectónico: ingerir volúmenes crecientes, retener datos durante meses, correlacionar logs con métricas y trazas, y responder durante un incidente sin que el coste se dispare.

Durante años, Elasticsearch fue la opción dominante. Después llegaron OpenSearch, Loki, Quickwit y, con mucha fuerza, ClickHouse como backend analítico. Todas pueden resolver una parte del problema, pero no optimizan para lo mismo. La decisión senior no debería ser «cuál es más rápido» en abstracto, sino qué preguntas hará el equipo, qué retención necesita y cuánta complejidad puede operar.

El estándar histórico: Elasticsearch y OpenSearch

OpenSearch nace como fork open source de Elasticsearch y conserva el modelo mental más familiar para muchos equipos: índices, shards, réplicas, mappings, Query DSL, agregaciones y una experiencia full-text madura. Si tu organización ya viene de Elasticsearch, es la transición más directa.

Arquitectónicamente se apoya en Apache Lucene. Cada índice se divide en shards; cada shard mantiene segmentos e índices invertidos; el clúster coordina escrituras, réplicas, búsquedas distribuidas y agregaciones. Esto da mucha potencia, pero también una huella operativa clara: JVM, heap sizing, merges, tiers hot/warm/cold y tuning de mappings.

En logs, OpenSearch brilla cuando necesitas búsquedas textuales expresivas, filtros ricos, agregaciones, dashboards maduros, tooling existente o casos cercanos a SIEM. El coste aparece cuando el volumen crece: indexar mucho contenido permite buscar rápido, pero consume almacenamiento y memoria.

En 2026 hay que tener en cuenta además la rama 3.x. OpenSearch 3.0 introdujo cambios relevantes como Lucene 10 y JVM 21, además de mejoras de rendimiento y funcionalidades de búsqueda vectorial. Es una señal de vitalidad del proyecto, pero también exige planificar upgrades con más cuidado que en un stack puramente stateless.

Loki: observabilidad centrada en eficiencia

Grafana Loki adopta una filosofía distinta: no indexar el contenido completo de los logs, sino las etiquetas. En la práctica se parece más a «Prometheus para logs» que a Elasticsearch: consultas por labels y filtros posteriores sobre las líneas con LogQL.

Su almacenamiento se basa en chunks comprimidos y un índice pequeño. Los chunks suelen vivir en object storage como S3, GCS, Azure Blob o MinIO, mientras el índice apunta a streams definidos por combinaciones de labels. Esa decisión reduce coste, pero traslada responsabilidad al diseño de etiquetas.

La regla práctica es simple: las labels deben tener cardinalidad controlada. cluster, namespace, app, environment o service suelen funcionar bien. request_id, user_id o trace_id como label pueden destruir el modelo porque multiplican streams, empeoran la compactación y hacen crecer el índice. Para esos valores es mejor filtrar en el contenido del log o apoyarse en trazas.

Loki brilla en Kubernetes porque el modelo de labels encaja con pods, namespaces y servicios. Además, su integración con Grafana es excelente: exploración de logs, saltos desde métricas, alertas con LogQL y correlación con Tempo o Prometheus.

Su debilidad aparece cuando quieres tratar los logs como un corpus de búsqueda arbitraria. Una consulta «búscame este fragmento en todos los logs de seis meses» puede ser mucho más cara que en un sistema con índice full-text. Loki está diseñado para consultas acotadas por labels y tiempo, no para reemplazar un motor de búsqueda general.

En 2026 Loki sigue muy activo. La documentación oficial lista versiones 3.x recientes, incluida la rama 3.7, y Grafana sigue posicionándolo como un sistema de agregación de logs horizontal, altamente disponible y eficiente en costes. La adquisición de Logline por Grafana refuerza además el foco en búsquedas difíciles dentro de logs, aunque no cambia el principio central: el diseño de labels sigue siendo crítico.

Cuándo brilla Loki

Loki es la elección pragmática para equipos Kubernetes que ya viven en Grafana, necesitan retener volumen a bajo coste y consultan por servicio, namespace, cluster, entorno y ventana temporal. Es menos convincente para búsqueda full-text global o análisis forense profundo.

Quickwit: búsqueda cloud-native sobre object storage

Quickwit es una plataforma de búsqueda distribuida en Rust, pensada para cargas append-only como logs y trazas. Su tesis es atractiva: mantener búsqueda potente, pero desacoplar cómputo y almacenamiento usando object storage como capa principal.

En lugar de depender de discos locales calientes, Quickwit escribe índices y splits en almacenamiento compatible con S3, Azure Blob, Google Cloud Storage o MinIO. Los indexers procesan datos, los searchers consultan segmentos y el metastore coordina el estado. El modelo usa índices invertidos optimizados para datos inmutables y almacenamiento remoto: más cercano a OpenSearch en búsqueda que Loki, pero con economía cloud-native.

Quickwit es especialmente interesante para equipos que quieren sustituir parte de un clúster Elasticsearch/OpenSearch caro, tienen logs en S3 o pueden moverlos allí, y necesitan búsquedas textuales con mejor coste operativo. También tiene integración con Grafana y APIs compatibles parcialmente con Elasticsearch/OpenSearch, lo que facilita algunos flujos existentes.

El matiz crítico para 2026 es su estado de proyecto. Datadog adquirió Quickwit en 2024. La adquisición no significó que el repositorio open source desapareciera: el repositorio quickwit-oss/quickwit sigue público y muestra actividad reciente, incluidas etiquetas y discusiones de 2026. También el plugin de Grafana de Quickwit publica compatibilidad reciente con Grafana 12.1+ y 13. Dicho eso, el riesgo de producto es distinto al de Loki u OpenSearch: la dirección estratégica está influida por Datadog y parte del esfuerzo puede orientarse a casos internos o comerciales.

La lectura honesta es esta: Quickwit no debe descartarse por estar «muerto», porque no está archivado según las fuentes revisadas. Pero sí conviene evaluarlo con más diligencia: cadencia de releases, issues críticos, roadmap, compatibilidad real de APIs, facilidad de operación y dependencia del equipo mantenedor. Para una plataforma core de observabilidad, esa evaluación pesa tanto como el benchmark.

ClickHouse: el motor analítico de la nueva observabilidad

ClickHouse nació como base de datos analítica columnar, no como plataforma de logs. Precisamente por eso se ha vuelto relevante para observabilidad. Muchos incidentes no se resuelven buscando una cadena exacta, sino agrupando por servicio, calculando percentiles, explorando cardinalidades, cruzando logs con trazas y consultando billones de eventos por rango temporal.

El modelo de ClickHouse es columnar. Los datos se almacenan por columnas, se comprimen muy bien y se consultan con ejecución vectorizada, índices primarios ordenados, data skipping indexes y motores como MergeTree. Para observabilidad, esto permite almacenar eventos anchos y leer solo las columnas necesarias.

La contrapartida es que ClickHouse no es Elasticsearch. La búsqueda full-text existe y ha mejorado, pero el punto fuerte sigue siendo el análisis estructurado y semiestructurado. Si tus consultas son «dame todos los errores 500 por versión, región y endpoint en los últimos 15 minutos», ClickHouse encaja muy bien. Si tu consulta principal es «encuentra cualquier línea que contenga una frase rara en seis meses de texto no estructurado», probablemente quieras otro componente o una estrategia híbrida.

ClickHouse se ha movido con decisión hacia observabilidad. La adquisición de HyperDX y el lanzamiento de ClickStack cambian el posicionamiento: ya no es solo «usa ClickHouse como backend y construye tú la UI», sino una pila con OpenTelemetry, HyperDX como interfaz y ClickHouse como motor. Brilla en plataformas de alto volumen, logs estructurados, trazas, métricas derivadas y consultas agregadas de baja latencia, aunque exige entender modelado, TTLs, compresión y costes de ingestión.

Comparativa rápida

CaracterísticaOpenSearchLokiQuickwitClickHouse
Modelo principalBúsqueda distribuida sobre LuceneLogs por streams y labelsBúsqueda distribuida cloud-nativeBase analítica columnar
Modelo de almacenamientoShards, segmentos Lucene, tiers hot/warm/coldChunks comprimidos + índice pequeñoÍndices/splits en object storageColumnas comprimidas en MergeTree
ÍndiceInvertido, muy completoLabels, no contenido completoInvertido optimizado para object storageÍndice primario, data skipping, columnas
Full-text searchExcelenteLimitada y dependiente de filtrosMuy buenaBuena, no su principal fortaleza
Coste relativoAltoBajoBajo-medioBajo-medio
Latencia de queryBaja en índices calientes bien dimensionadosBaja si acotas por labels; peor en barridos ampliosBaja-media según caché y object storageMuy baja en agregaciones bien modeladas
EscaladoMaduro, pero operativoHorizontal y económicoDesacopla cómputo y almacenamientoExcelente en analítica e ingestión
KubernetesBuenoExcelenteBuenoBueno, mejor con ClickStack/OTel
GrafanaSíNativoSíSí
Compatibilidad ElasticsearchAlta en OpenSearchNoParcialNo
Mejor caso de usoBúsqueda compleja y compatibilidadLogs Kubernetes de bajo costeBúsqueda eficiente sobre S3Observabilidad analítica unificada
Riesgo principalCoste y complejidadCardinalidad de labelsEcosistema y gobernanza tras adquisiciónModelado y curva SQL/operación

¿Cuál elegir en 2026?

La respuesta depende menos de la moda tecnológica y más del perfil del equipo.

Si eres un equipo de plataforma con herencia Elasticsearch

OpenSearch es la opción conservadora si ya tienes automatizaciones, alertas y conocimiento operacional alrededor de Elasticsearch/OpenSearch. No migres «tal cual» esperando que el coste baje: revisa mappings, retención, ILM/ISM, shards, campos indexados y separación entre datos calientes y archivo.

Si eres un equipo Kubernetes centrado en Grafana

Loki suele ser la primera opción. Es eficiente, se integra muy bien con Grafana y reduce el coste de logs operativos. La clave es gobernar labels desde el día uno: taxonomía permitida, límites de cardinalidad y consultas por contexto antes de buscar texto.

Si buscas reducir coste frente a OpenSearch sin perder búsqueda

Quickwit merece una PoC seria cuando Loki se queda corto en búsqueda y OpenSearch resulta caro. La decisión debe incluir continuidad: actividad del repositorio, releases, clientes, soporte comunitario, integración con Grafana y posición de Datadog.

Si quieres una plataforma unificada de observabilidad

ClickHouse es probablemente la apuesta más fuerte para consolidar logs, trazas, métricas derivadas y eventos en una base analítica común. ClickStack e HyperDX reducen la necesidad de construir toda la experiencia desde cero, pero la adopción depende de madurez en SQL, modelado y operación de bases analíticas.

Si tienes requisitos de seguridad, auditoría o SIEM

OpenSearch sigue siendo más natural para investigación textual y tooling de seguridad. ClickHouse puede funcionar para analítica de seguridad a escala, pero exige modelado. Loki rara vez es la primera opción para SIEM. Quickwit puede ser interesante, con ecosistema más pequeño.

Si el presupuesto es el cuello de botella

No elijas solo por coste de almacenamiento por TB. Calcula coste total: ingestión, consultas, retención, operación, backups, upgrades, formación y tiempo de incidente. Loki y ClickHouse suelen ganar en coste bruto; Quickwit puede ser muy competitivo sobre object storage; OpenSearch puede ser razonable si se limita a datos realmente buscables y calientes.

Cómo plantear una PoC de decisión

Una prueba útil no consiste en instalar cuatro Helm charts y mirar una demo. Define preguntas reales de incidentes pasados y ejecútalas sobre datos representativos:

  • Últimos 7 días de logs de producción, con cardinalidad real.
  • Una ventana caliente de 2 horas con alto volumen.
  • Una búsqueda textual rara sobre un periodo amplio.
  • Una agregación por servicio, endpoint, versión y región.
  • Un flujo de correlación desde alerta a logs y trazas.

Mide ingestión sostenida, coste de almacenamiento, latencia p50/p95, complejidad de operación y experiencia del equipo. Incluye un escenario incómodo: nodo caído, object storage lento, cardinalidad inesperada o consulta mal escrita.

Conclusión

La era en la que Elasticsearch era la única opción viable para logs terminó. En 2026 hay cuatro caminos claros.

OpenSearch es la elección madura para búsqueda full-text y compatibilidad. Loki es eficiente para logs operativos en Kubernetes. Quickwit ofrece búsqueda moderna sobre object storage, con evaluación de gobernanza obligatoria tras la adquisición por Datadog. ClickHouse se consolida como motor analítico para observabilidad unificada, especialmente con ClickStack e HyperDX.

La mejor plataforma no es la que gana todos los benchmarks. Es la que responde a tus preguntas de producción con el menor coste sostenible y la menor fricción operativa para tu equipo.

CTA: decide con datos, no con preferencias

Antes de migrar o estandarizar, ejecuta una PoC de dos semanas con datos reales, consultas reales y una estimación honesta de coste total. Debe revelar qué datos van a búsqueda, cuáles a analítica y cuáles a almacenamiento barato.

Fuentes

  • OpenSearch 3.0: https://opensearch.org/blog/unveiling-opensearch-3-0/
  • OpenSearch 3.0, Lucene 10 y JVM 21: https://opensearch.org/blog/opensearch-3-0-what-to-expect/
  • Grafana Loki, documentación: https://grafana.com/docs/loki/latest/
  • Grafana Loki, releases 3.x: https://grafana.com/docs/loki/latest/release-notes/
  • Grafana Loki, labels y cardinalidad: https://grafana.com/blog/how-labels-in-loki-can-make-log-queries-faster-and-easier/
  • Datadog adquiere Quickwit: https://www.datadoghq.com/blog/datadog-acquires-quickwit/
  • Quickwit OSS: https://github.com/quickwit-oss/quickwit
  • Quickwit datasource para Grafana: https://github.com/quickwit-oss/quickwit-datasource
  • ClickStack: https://clickhouse.com/clickstack
  • ClickStack/HyperDX en ClickHouse Cloud: https://clickhouse.com/docs/use-cases/observability/clickstack/overview
  • ClickHouse adquiere HyperDX: https://clickhouse.com/blog/202504-newsletter
  • ClickHouse y observabilidad 2026: https://clickhouse.com/resources/engineering/what-is-observability

Cluster Autoscaler vs Karpenter: cuándo usar cada uno en 2026

Cluster Autoscaler vs Karpenter: cuándo usar cada uno en 2026

Cluster Autoscaler vs Karpenter: cuándo usar cada uno en Kubernetes (2026)

Tus pods están en estado Pending. Tu ingeniero de guardia está recibiendo alertas. En algún punto de la cadena entre «necesito más cómputo» y «el cómputo está disponible», algo va demasiado lento. Ese algo casi siempre es el aprovisionamiento de nodos — y la herramienta que elegiste para gestionarlo determina si ese retraso son 4 minutos o 45 segundos.

El autoescalado de nodos en Kubernetes es de esas decisiones de infraestructura que parecen sencillas hasta que las tienes corriendo en producción. Dos pods en estado Pending no significan solo un despliegue retrasado: significan picos de latencia, tráfico perdido, SLOs incumplidos e ingenieros depurando problemas que deberían haber sido invisibles. A escala, también significa quemar dinero en nodos sobreaprovisionados o jugársela con un submínimo de recursos en el peor momento posible.

Cluster Autoscaler (CA) ha sido la respuesta por defecto durante años. Karpenter emergió de AWS en 2021, alcanzó estado estable en 2023 y para 2025 ya era la recomendación mayoritaria para clusters nativos en AWS. En 2026, ambas herramientas están maduras, ampliamente desplegadas y son genuinamente buenas — pero resuelven el problema de forma diferente, y elegir la incorrecta para tu entorno tiene consecuencias reales.

Este artículo es una comparativa técnica en profundidad sobre autoescalado de nodos Kubernetes. Asume que ya sabes lo que es Kubernetes y que tienes criterio sobre infraestructura. El objetivo es darte una imagen clara de cómo funciona cada herramienta, dónde gana cada una, y un framework de decisión que puedas aplicar directamente.


Por qué el autoescalado de nodos es difícil

La tensión fundamental en el autoescalado es esta: quieres tener cómputo disponible antes de necesitarlo, pero no quieres pagar por cómputo que no estás usando. Estos objetivos están en conflicto directo, y todo sistema de autoescalado es un intento de encontrar el trade-off menos malo.

Sin autoescalado, estás haciendo una de estas dos cosas:

  1. Sobreaprovisionando — ejecutas suficientes nodos para gestionar la carga pico en todo momento. La utilización media se sitúa en el 20–30%, y estás pagando el otro 70–80% para que esté ocioso.
  2. Subaprovisionando — vas ajustado, y cuando el tráfico se dispara, los pods van a Pending. Tus SLOs se incumplen. Te llaman a las 3 de la mañana para escalar manualmente.

Un patrón de fallo habitual con un autoescalado mal ajustado es el de «estampida en el scale-up»: el HPA crea nuevos pods más rápido de lo que el autoescalado de nodos puede aprovisionar capacidad. La ventana de aprovisionamiento importa. Con CA y node groups respaldados por ASG en AWS, estás hablando de 4–8 minutos. Con Karpenter, 60–90 segundos. A 100 RPS y una ventana de 3 minutos, eso son 18.000 peticiones bajo condiciones degradadas.


Cluster Autoscaler: cómo funciona realmente

Cluster Autoscaler es un proyecto nativo de Kubernetes bajo el repositorio kubernetes/autoscaler, en producción desde 2016, con soporte para AWS, GCP, Azure, Alibaba, DigitalOcean y otros proveedores.

El modelo de node groups

CA opera sobre node groups — ASGs en AWS, MIGs en GCP, VMSSs en Azure. La función de CA es decidir cuándo aumentar o reducir la capacidad deseada de estos grupos. CA no aprovisiona nodos individuales. Escala node groups, y es el propio node group quien aprovisiona los nodos. Esta indirección añade latencia y reduce la flexibilidad.

Scale-up: detección de pods no programables

CA ejecuta un bucle de control (intervalo de escaneo por defecto: 10 segundos). Para cada pod en estado Pending con PodScheduled=False, CA simula añadir un nodo de cada tipo de node group conocido y comprueba si el pod podría programarse. Cuando se selecciona un node group, CA aplica un expander para elegir qué grupo escalar:

  • least-waste — minimiza el desperdicio de CPU/memoria tras la programación (mejor opción por defecto para coste)
  • most-pods — maximiza los pods programados por operación de scale-up
  • priority — permite definir un orden mediante ConfigMap
  • grpc — delega en un servicio gRPC externo
# Despliegue de Cluster Autoscaler — AWS, ajustado para producción
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cluster-autoscaler
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cluster-autoscaler
  template:
    metadata:
      labels:
        app: cluster-autoscaler
      annotations:
        cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: cluster-autoscaler
      containers:
        - image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.30.3
          name: cluster-autoscaler
          resources:
            requests:
              cpu: 100m
              memory: 600Mi
            limits:
              cpu: 200m
              memory: 1Gi
          command:
            - ./cluster-autoscaler
            - --cloud-provider=aws
            - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/my-cluster
            - --expander=least-waste
            - --balance-similar-node-groups=true
            - --scale-down-delay-after-add=10m
            - --scale-down-unneeded-time=10m
            - --scale-down-utilization-threshold=0.5
            - --max-graceful-termination-sec=600
            - --scan-interval=10s

Scale-down: el enfoque conservador

Un nodo es candidato a scale-down solo si:
– La utilización de CPU y memoria (por requests) está por debajo del umbral (por defecto: 50%)
– Todos los pods podrían reprogramarse en otro nodo
– Ningún pod tiene la anotación cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
– El nodo ha estado infrautilizado durante al menos --scale-down-unneeded-time (por defecto: 10 minutos)

Este conservadurismo evita el churn — es una característica, no un defecto.

Hay un matiz importante que a menudo se pasa por alto: CA no distingue entre un nodo que tiene el 45% de uso de CPU y otro que tiene el 5%. Ambos son candidatos a eliminación si están por debajo del umbral. La granularidad es binaria (utilizado / no utilizado lo suficiente), no continua. Esto contrasta con el enfoque de consolidación activa de Karpenter, que veremos a continuación.


Karpenter: cómo funciona realmente

Karpenter fue construido por AWS y donado al proyecto Kubernetes en 2023, donde vive como subproyecto de SIG Autoscaling bajo kubernetes-sigs (no tiene nivel de madurez CNCF propio); alcanzó disponibilidad general (v1.0) a mediados de 2024. Existen providers para AWS (estable), Azure (estable) y GCP (beta).

La idea central: eliminar el node group de la ecuación

Karpenter llama directamente a la API RunInstances de EC2 — sin implicar ASGs. Esto significa:
– Cualquier tipo de instancia en una sola petición, sin necesidad de preconfigurar un node group
– Sin intermediarios: Karpenter → EC2 API → el nodo se une al cluster
– Nodos ajustados exactamente a lo que necesitan las cargas de trabajo, sobre el catálogo completo de instancias
– Karpenter gestiona el ciclo de vida completo del nodo, incluida su terminación

Esta diferencia arquitectónica es la que explica casi todas las ventajas de rendimiento y flexibilidad de Karpenter frente a CA. No es una cuestión de mejor algoritmo; es una cuestión de eliminar una capa completa de indirección.

NodePool y EC2NodeClass

La configuración de Karpenter se expresa mediante dos CRDs principales:

# EC2NodeClass — parámetros específicos del cloud
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiSelectorTerms:
    - alias: al2023@latest
  role: "KarpenterNodeRole-my-cluster"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 50Gi
        volumeType: gp3
        encrypted: true
  metadataOptions:
    httpTokens: required
    httpPutResponseHopLimit: 1
---
# NodePool — intención y restricciones
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general-purpose
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["5"]
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro", "small", "medium", "large"]
      expireAfter: 720h
  limits:
    cpu: "1000"
    memory: 1000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 5m
    budgets:
      - nodes: "5%"
        schedule: "0 8 * * mon-fri"
        duration: 10h
      - nodes: "25%"

La expresividad de este modelo de configuración es significativa: en lugar de mantener varios ASGs con tipos de instancia fijos, defines restricciones y dejas que Karpenter resuelva la instancia óptima en tiempo de ejecución. Para clusters con carga variable o diversa (batch, APIs sin estado, workloads GPU), esto elimina una cantidad enorme de gestión manual de configuración.

Aprovisionamiento just-in-time y bin packing

Cuando los pods van a Pending, Karpenter observa el evento (no hace polling) e inmediatamente:
1. Recopila todos los pods en Pending
2. Simula el bin packing — el mínimo número de nodos posible sobre el catálogo completo de instancias
3. Selecciona las instancias que satisfacen todos los requisitos de los pods
4. Llama a la EC2 API para aprovisionar las instancias óptimas

Disruption y consolidación

El valor diferencial de Karpenter: la consolidación activa del cluster. Evalúa si los nodos pueden eliminarse redistribuyendo los pods en otros, o reemplazándolos con un tipo de instancia más pequeño. Un c5.4xlarge ejecutando pods que consumen 4 vCPU se reemplaza por un c5.xlarge. Los equipos reportan habitualmente reducciones de coste de cómputo del 30–50%.

Las opciones de consolidationPolicy son:
– WhenEmpty — solo elimina nodos sin pods de workload (más conservador)
– WhenEmptyOrUnderutilized — también reemplaza nodos infrautilizados por instancias más pequeñas

La consolidación no es solo una mejora incremental sobre el scale-down de CA — es cualitativamente diferente. CA puede decirte «este nodo está infrautilizado, lo elimino». Karpenter puede decirte «estos tres nodos están infrautilizados, puedo consolidar todo en uno más pequeño, déjame hacerlo». La diferencia en ahorro de costes compuesto a lo largo del tiempo es sustancial.


Comparativa de arquitectura

DimensiónCluster AutoscalerKarpenter
Modelo de aprovisionamientoEscala node groups (ASG/MIG/VMSS)API cloud directa, sin node groups
Flexibilidad de instanciasTipos de node group predefinidosCatálogo completo en tiempo de ejecución
Trigger de scale-upPolling (intervalo de 10s)Watch de eventos (casi instantáneo)
Scale-downElimina nodos infrautilizadosElimina + consolida + ajusta tamaño
Gestión de spotVía ASG + AWS Node Termination HandlerNativa, first-class, sin NTH
Modelo de configuraciónFlags en el DeploymentCRDs declarativos
Soporte cloudTodos los principales + on-premAWS (GA), Azure (estable), GCP (beta)
ConsolidaciónNoSí
Madurez de la comunidadMuy madura (desde 2016)Madura (GA 2024)

Velocidad de escalado: los números reales

Esta es probablemente la diferencia más visible en producción, y merece un análisis secuencia por secuencia.

Cluster Autoscaler en AWS (típico)

  1. Pod en Pending → el escaneo de CA lo detecta (0–10s)
  2. Llamada API UpdateAutoScalingGroup al ASG (~15–30s)
  3. La instancia EC2 arranca (1–2 min)
  4. Bootstrap del nodo + registro del kubelet (30–60s)
  5. El pod se programa (5–10s)

Total: 3–6 minutos (hasta 12 minutos en períodos de alta demanda de capacidad EC2)

Karpenter en AWS (típico)

  1. Pod en Pending → el watch event se dispara (~1s)
  2. Llamada API EC2 RunInstances (~2–3s)
  3. La instancia EC2 arranca (el mismo hardware — 1–2 min)
  4. El nodo hace bootstrap y queda Ready (30–60s)
  5. El pod se programa (~5s)

Total: 90 segundos a 3 minutos

La diferencia de hardware es idéntica — el tiempo de arranque de EC2 es el mismo. La ventaja de Karpenter proviene de eliminar el polling del bucle de control y la capa ASG. A efectos prácticos, esto es una mejora de 2–4x en el tiempo de aprovisionamiento en condiciones normales, y de hasta 8x en condiciones de alta contención de capacidad.

Para workloads event-driven, batch o APIs con picos bruscos de tráfico, esta diferencia determina directamente si los SLOs se cumplen o se incumplen.


Optimización de costes: donde Karpenter toma ventaja

Right-sizing

CA requiere node groups predefinidos. Si tienes cargas de trabajo con necesidades de memoria muy variables — un pod de 2 GiB y otro de 14 GiB — necesitas node groups que cubran ambos casos, o desperdicias recursos. Karpenter selecciona la instancia mínima viable para el workload pendiente desde el catálogo completo, sin necesidad de preconfigurar.

Consolidación frente a scale-down

CA elimina nodos infrautilizados. Karpenter reemplaza un nodo grande infrautilizado por uno más pequeño que aún cabe con todos los pods. Esto produce un ahorro compuesto a lo largo del tiempo que CA estructuralmente no puede alcanzar.

Ejemplo concreto: tienes un m5.4xlarge (16 vCPU, 64 GiB) con pods que solo consumen 3 vCPU y 12 GiB. CA lo deja como está porque tiene pods. Karpenter lo reemplaza por un m5.xlarge (4 vCPU, 16 GiB) y redistribuye. El coste baja un 75% en ese nodo.

Gestión de instancias spot

Karpenter recibe los avisos de interrupción de EC2, pre-aprovisiona un nodo de reemplazo y drena el nodo afectado — todo dentro de la ventana de 2 minutos. No requiere AWS Node Termination Handler. Además, diversifica automáticamente las peticiones de spot entre tipos de instancia para reducir el riesgo de interrupciones simultáneas.

Con CA, gestionar instancias spot de forma fiable requiere: ASGs configurados con múltiples tipos de instancia (opción mixed instances policy), desplegar y mantener el AWS Node Termination Handler, y ajustar los interruptionHandlers en la configuración de CA. Con Karpenter, declaras capacity-type: spot en el NodePool y el resto es automático.


Soporte multicloud en 2026

CloudCluster AutoscalerKarpenter
AWSEstable en producciónEstable en producción (implementación de referencia)
GCPEstable en producciónBeta (karpenter-provider-gcp)
AzureEstable en producciónEstable (karpenter-provider-azure)
Alibaba CloudSoportadoSin provider
DigitalOceanSoportadoSin provider
On-premises / Cluster APISoportadoNo soportado

Esta tabla es probablemente el factor más determinante en la decisión para muchos equipos. Si tu organización no está en AWS o Azure, la decisión prácticamente se toma sola: Cluster Autoscaler es la opción razonable.

Una excepción notable: GKE en GCP tiene su propio mecanismo de autoescalado (Node Auto Provisioning / NAP) que es técnicamente superior a CA en ese entorno. Si estás en GKE, la respuesta suele ser «ninguno de los dos — usa NAP».


Cuándo Cluster Autoscaler sigue siendo la elección correcta

A pesar del innegable avance técnico de Karpenter, hay escenarios donde CA sigue siendo la respuesta correcta:

1. Entornos no-AWS
Si estás en GCP (sin GKE NAP), Alibaba, DigitalOcean o on-premises con Cluster API, Karpenter no es una opción viable. CA es la herramienta madura y bien soportada para esos entornos.

2. Arquitectura de node groups existente con inversión significativa
Si tienes años de diseño de ASGs, tooling de compliance vinculado a tags de ASG y pipelines de CI/CD que asumen la existencia de node groups específicos, el coste de migración puede superar fácilmente el beneficio. No migres por migrar.

3. Restricciones regulatorias
Algunos marcos de cumplimiento (PCI-DSS, entornos gubernamentales con requisitos específicos de auditoría) requieren trazabilidad de aprovisionamiento a través de ASGs. El modelo directo de Karpenter puede complicar las evidencias de auditoría en estos contextos.

4. Cluster API / bare metal
CA es la única opción madura para entornos con Cluster API o bare metal. Karpenter requiere un provider de cloud para funcionar.

5. Despliegues CA que funcionan bien y equipo sin tiempo para migrar
Si tu CA está bien configurado, los SLOs se cumplen y no tienes presión de costes urgente, la relación coste-beneficio de migrar puede no ser favorable a corto plazo. «Si funciona, no lo toques» es un principio válido de ingeniería.


Cuándo Karpenter es la elección correcta

1. Clusters AWS-nativos con prioridad de optimización de costes
La combinación de right-sizing + consolidación activa produce reducciones de coste reales y sostenidas. Si tu factura de cómputo en AWS es significativa, el ROI de migrar a Karpenter es generalmente positivo en semanas.

2. Workloads diversas y variables
Batch, spot, GPU, APIs sin estado — Karpenter gestiona todo esto con unos pocos NodePools bien definidos, sin la proliferación de ASGs que requeriría CA para el mismo nivel de flexibilidad.

3. Clusters spot-heavy
La gestión nativa de interrupciones de spot, con diversificación automática entre tipos de instancia y pre-aprovisionamiento de reemplazos, es cualitativamente superior al stack CA + NTH. Si más del 30–40% de tu capacidad es spot, Karpenter simplifica enormemente la operación.

4. Cultura infrastructure-as-code declarativa
Los NodePools se versionan limpiamente en Git, se revisan en pull requests, y expresan intención en lugar de configuración imperativa. Para equipos con GitOps maduro, esto es una mejora real en mantenibilidad.

5. Requisitos de escalado de baja latencia
Workloads event-driven, jobs desencadenados por KEDA, picos bruscos de tráfico. Si los 3–6 minutos de CA están dentro de tu ventana de incumplimiento de SLO, Karpenter soluciona el problema estructuralmente.


Ejecutar ambos: ruta de migración y gotchas

Hay escenarios válidos para ejecutar CA y Karpenter en paralelo — especialmente durante la migración o cuando tienes workloads con requisitos contradictorios. Lo más importante es que no gestionen los mismos nodos.

Separar la responsabilidad

Usa labels y taints para evitar que CA y Karpenter gestionen los mismos nodos:

# NodePool con taint — los pods gestionados por CA no tolerarán esto
spec:
  template:
    metadata:
      labels:
        provisioner: karpenter
    spec:
      taints:
        - key: karpenter.sh/provisioned
          value: "true"
          effect: NoSchedule

Migración gradual recomendada

Fase 1 — Karpenter gestiona workloads spot y batch. CA gestiona los nodos on-demand de producción. Esto valida Karpenter con workloads menos críticos.

Fase 2 — Migrar workloads spot completamente a Karpenter. Eliminar AWS NTH (ya no es necesario). Validar el comportamiento de consolidación.

Fase 3 — Migrar nodos on-demand. Reducir gradualmente la capacidad de los node groups de CA mientras se monitorizan los NodePool limits de Karpenter.

Fase 4 — Desmantelar CA una vez que todos los grupos están vacíos. Limpiar ASGs y tooling asociado.

Budget mínimo para una migración bien hecha en un cluster de tamaño medio: un sprint completo. Las prisas aquí tienen consecuencias directas en disponibilidad.

Gotchas que te van a costar tiempo

Karpenter consolidation + PDBs permisivos: maxUnavailable: 100% causará consolidación disruptiva. Audita tus PodDisruptionBudgets antes de habilitar WhenEmptyOrUnderutilized. Un PDB mal configurado puede provocar una interrupción de servicio durante la consolidación.

Los NodePool limits son paradas duras: cuando un NodePool alcanza su límite de CPU o memoria, los pods van a Pending indefinidamente. CA tiene el mismo problema, pero los equipos nuevos en Karpenter tienden a descubrirlo en producción. Monitoriza la utilización de los límites activamente.

AMI drift: el alias @latest selecciona nuevas AMIs en nuevos nodos. Esto significa que puedes tener nodos con AMIs diferentes conviviendo en el cluster. Para entornos con control de cambios estricto, considera anclar la versión de AMI o implementar tests de validación antes de actualizar el alias.

Conflictos de scale-down simultáneos: durante la migración, CA y Karpenter pueden intentar escalar hacia abajo al mismo tiempo. La segregación estricta por labels y taints es la única garantía real contra esto.


Framework de decisión

FactorCluster AutoscalerKarpenter
Soporte cloudTodos los clouds + on-premAWS (GA), Azure (estable), GCP (beta)
Velocidad de aprovisionamiento4–8 minutos60–120 segundos
Flexibilidad de instanciasRequiere preconfiguración de node groupsCatálogo completo, selección en tiempo de ejecución
Optimización de costesSolo scale-downScale-down + consolidación + right-sizing
Integración spotVía ASG + NTHNativa, first-class
Complejidad operativaMenorModerada
Cluster API / bare metalSíNo
Consolidación activaNoSí

El árbol de decisión simplificado:

¿Ejecutas en AWS?
├── No → ¿Azure? → Karpenter (estable) o CA
│        ¿GCP?   → CA o GKE NAP (preferido)
│        ¿Otro?  → Cluster Autoscaler
│
└── Sí → ¿Restricciones regulatorias que exigen ASG?
          ├── Sí → Cluster Autoscaler
          └── No → ¿Prioridad de optimización de costes o workloads diversas?
                   ├── Sí → Karpenter
                   └── No → Cualquiera (elige según preferencia del equipo)

Preguntas frecuentes

¿Es Karpenter un sustituto directo de Cluster Autoscaler?

No. Son modelos de configuración y conceptos diferentes. La migración requiere re-expresar la configuración de node groups como NodePools/NodeClasses, auditar PDBs y ejecutar ambos en paralelo durante la transición. Presupuesta al menos un sprint para un cluster de tamaño medio. No hay un botón de «migrar».

¿Puedo usar Karpenter en Kubernetes autogestionado (sin EKS)?

Sí, pero con trabajo extra. Karpenter requiere credenciales IAM (IRSA o equivalente) para llamar a las APIs de EC2. En clusters autogestionados, esto requiere más configuración que en EKS donde IRSA es nativo. Es viable, pero no es el camino de menor resistencia.

¿Cómo interactúa Karpenter con HPA y VPA?

Sin conflicto. El flujo es: HPA crea pods → los pods van a Pending si no hay nodos suficientes → Karpenter aprovisiona nodos → los pods se programan. VPA ajusta los resource requests de los pods, que Karpenter usa como inputs para el bin packing. Los tres pueden coexistir sin problemas.

¿Qué ocurre si Karpenter se cae?

Los nodos existentes y los pods continúan con normalidad. Los pods nuevos que requieran aprovisionamiento quedan en Pending hasta que Karpenter se recupere. El scale-down y la consolidación se pausan. Para producción, despliega múltiples réplicas con leader election.

¿Karpenter soporta nodos GPU?

Sí. Los tipos de instancia GPU (p3, p4, g4, g5) pueden incluirse en los requirements del NodePool. La recomendación es crear NodePools dedicados con taints apropiados para los pods que soliciten recursos GPU, manteniendo la separación clara de workloads GPU frente al resto.

¿Cómo gestiona Karpenter las actualizaciones de AMI?

El campo expireAfter fuerza la rotación de nodos. Cuando un nodo expira, Karpenter pre-aprovisiona un reemplazo con la AMI más reciente según el EC2NodeClass, luego drena y termina el nodo antiguo. Es efectivamente un mecanismo de rolling AMI update sin tooling adicional. La alternativa es anclar la versión de AMI explícitamente y gestionar las actualizaciones de forma controlada.

¿Cluster Autoscaler sigue con mantenimiento activo?

Sí. CA sigue en desarrollo activo bajo kubernetes/autoscaler, con releases que siguen las versiones minor de Kubernetes. No está siendo deprecado. Para entornos no-AWS y despliegues CA que funcionan bien, sigue siendo una elección completamente soportada y racional. La narrativa de «CA está muerto» es incorrecta — simplemente tiene un ámbito de aplicación más claro en 2026.


Conclusión

Ni Cluster Autoscaler ni Karpenter son la respuesta universal. Son herramientas diferentes para contextos diferentes, y en 2026 ambas están maduras.

Si estás en AWS y el coste de cómputo o la velocidad de escalado son prioridades reales, Karpenter es la elección técnicamente superior. La combinación de aprovisionamiento directo vía EC2 API, consolidación activa y gestión nativa de spot produce beneficios compuestos que CA estructuralmente no puede igualar.

Si no estás en AWS, tienes restricciones regulatorias específicas, o tu CA actual funciona bien y el equipo no tiene ancho de banda para una migración, Cluster Autoscaler sigue siendo la respuesta correcta. No migres por moda.

La pregunta que vale la pena hacerse no es «¿cuál es mejor?» sino «¿cuál resuelve mi problema concreto con el menor coste operativo?». Para la mayoría de clusters AWS en 2026, esa respuesta es Karpenter — pero solo si estás dispuesto a hacer la migración bien.


Validado con Kubernetes 1.28–1.32. API de Karpenter v1.x (GA). CA v1.30.x. Los ejemplos usan el provider de AWS; los detalles de los providers de Azure y GCP pueden diferir.

Requests y Limits en Kubernetes: Guía Completa para Producción

Requests y Limits en Kubernetes: Guía Completa para Producción
Tus pods mueren con OOMKill a las 3 de la madrugada. El p99 de latencia se dispara cada pocos minutos sin causa aparente. El scheduler coloca cargas de trabajo en nodos que no pueden sostenerlas. En la mayoría de los incidentes de producción con Kubernetes, los requests y limits mal configurados son o bien la causa directa o un factor que acelera el problema. Esta no es una guía de «qué son los requests y los limits». Es una referencia técnica en profundidad para ingenieros que operan Kubernetes en producción y necesitan entender qué ocurre realmente dentro del kernel cuando se establecen estos valores — y cuáles son las consecuencias cuando se configuran mal.

Qué son realmente los Requests y los Limits

La documentación de Kubernetes explica requests y limits a nivel de API. Lo que no explica con suficiente detalle es el mecanismo de aplicación: cgroups. Cuando el kubelet admite un pod en un nodo, crea una jerarquía de cgroups para ese pod bajo /sys/fs/cgroup/. Cada contenedor del pod recibe su propio cgroup. Los valores que defines en el spec del pod se traducen directamente en parámetros de cgroup: CPU request → cpu.shares (cgroups v1) o cpu.weight (cgroups v2) CPU limit → cpu.cfs_quota_us y cpu.cfs_period_us Memory request → memory.soft_limit_in_bytes (orientativo, usado para scoring de evicción) Memory limit → memory.limit_in_bytes (aplicación estricta, desencadena OOMKill) El scheduler usa los requests para tomar decisiones de ubicación. No conoce la utilización real — conoce la capacidad comprometida. Un nodo con 4 cores donde los pods en ejecución tienen un total de CPU request de 3,5 cores tiene 0,5 cores de capacidad schedulable restante, aunque la utilización real de CPU sea del 15%. Por eso puedes tener un clúster «totalmente utilizado» (según requests) donde los nodos están ociosos, y también puedes tener nodos al 95% de utilización de CPU que siguen aceptando nuevos pods porque sus requests son bajos. El kubelet usa los limits para aplicar restricciones en tiempo de ejecución mediante esos parámetros de cgroup. El scheduler nunca ve los limits.

CPU vs Memoria: Por Qué Se Comportan de Forma Fundamentalmente Distinta

Esta es la diferencia más importante que hay que entender sobre la gestión de recursos en Kubernetes, y es algo que se malinterpreta sistemáticamente incluso entre ingenieros con experiencia.

La CPU es un Recurso Compresible

La CPU es un recurso compartido en el tiempo. Si tu contenedor intenta usar más CPU de la que su limit permite, el scheduler CFS de Linux simplemente lo throttlea — deja de recibir tiempo de CPU hasta el siguiente período de planificación. El proceso continúa. Solo espera. Desde la perspectiva de la aplicación: las cosas van más lentas. La latencia aumenta. El throughput cae. Pero el proceso no muere.

La Memoria no es un Recurso Compresible

La memoria no se comparte en el tiempo. Si tu contenedor intenta asignar memoria por encima de su limit, no hay una «ruta de degradación gradual». El OOM killer de Linux selecciona un proceso en el cgroup y lo mata. El contenedor muere. Desde la perspectiva de la aplicación: el proceso termina. Kubernetes reinicia el contenedor. Ves OOMKilled en kubectl describe pod.
PropiedadCPUMemoria
AplicaciónCFS throttlingOOM Kill
¿Sobrevive el proceso?Sí (rendimiento degradado)No (muerto y reiniciado)
¿Compresible?SíNo
Visibilidad del schedulerSolo requestsSolo requests
Consecuencia por exceder el limitPicos de latenciaReinicio del contenedor
¿Recomendado poner limits?Situacional (ver más abajo)Siempre
Esta asimetría guía todas las recomendaciones del resto de esta guía.

Clases QoS: Prioridad de Evicción Bajo Presión

Kubernetes asigna a cada pod una clase Quality of Service (QoS) en función de los requests y limits establecidos en todos sus contenedores. Esta clase determina la prioridad de evicción cuando un nodo está bajo presión de memoria.

Guaranteed

Condición: Todos los contenedores tienen definidos requests y limits de CPU y memoria, y los requests son iguales a los limits tanto para CPU como para memoria.
resources:
  requests:
    cpu: "500m"
    memory: "512Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"
Los pods Guaranteed son los últimos en ser eviccionados. El kubelet agotará los pods BestEffort y Burstable antes de tocar estos. Reciben la asignación de recursos más predecible en el nodo. Advertencia: Guaranteed no significa «siempre disponible». Significa «el último en ser matado». En un nodo con sobrecarga severa, incluso los pods Guaranteed pueden ser eviccionados.

Burstable

Condición: Al menos un contenedor tiene definido un request o limit de CPU o memoria, pero el pod no cumple los criterios de Guaranteed.
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "1000m"
    memory: "1Gi"
Los pods Burstable se eviccionan después de BestEffort pero antes que Guaranteed. Pueden superar su request cuando la capacidad está disponible, pero no están protegidos cuando el nodo está bajo presión.

BestEffort

Condición: Ningún contenedor del pod tiene definido ningún request ni limit de CPU ni de memoria.
# Sin bloque resources en absoluto
Los pods BestEffort son los primeros en ser eviccionados, siempre. Reciben la capacidad que queda después de que las cargas de trabajo planificadas consuman su parte solicitada. En un nodo con carga, pueden quedar completamente sin recursos. En producción: nunca ejecutes cargas de trabajo con estado ni servicios críticos de negocio como BestEffort. El scheduler de Kubernetes los colocará en cualquier sitio, y el kubelet los matará primero.

Patrones de Mala Configuración y sus Consecuencias

Patrón 1: Sin Requests ni Limits

Efecto: QoS BestEffort. Primeros en ser eviccionados bajo presión de memoria. El scheduler coloca los pods de forma arbitraria — no tiene datos para tomar decisiones de ubicación, por lo que recurre a LeastRequestedPriority, lo que en la práctica significa que estos pods pueden acabar en los mismos nodos que cargas de trabajo muy cargadas. Consecuencia real: Tus jobs de background «ligeros» matan tus servidores de API a las 3 de la madrugada cuando un pico de memoria desencadena evicción y los pods BestEffort resultan estar en el mismo nodo que ellos.

Patrón 2: Requests Iguales a Limits (QoS Guaranteed)

Este es el patrón «seguro» común recomendado en documentación antigua de Kubernetes. No es incorrecto, pero tiene una trampa: CPU limits = CPU requests significa que el CPU throttling está garantizado que se activará. Tu pod será throttleado en el momento en que intente superar el request — durante el arranque, durante GC, durante un pico de tráfico — aunque el nodo tenga CPU libre abundante. Para aplicaciones sensibles a la latencia, esto significa picos de throttling predecibles exactamente en los momentos en que más CPU necesitas. Memoria: Establecer memory request = memory limit es apropiado y recomendable. El comportamiento es correcto: el pod funciona dentro de un presupuesto de memoria controlado.

Patrón 3: Limits Muy Superiores a los Requests (Burstable con Ratio Alto)

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "4000m"
    memory: "4Gi"
Este es el extremo opuesto. El scheduler cree que este pod necesita 100m de CPU y 128Mi de memoria. Docenas de estos pueden planificarse en un solo nodo. Cuando todos hacen burst simultáneamente — que lo harán, durante un despliegue, un evento de tráfico o un ciclo de GC — el nodo queda sobrecargado, la presión de memoria desencadena cascadas de OOMKill, y el scheduler no tiene ni idea de que algo va mal porque la capacidad comprometida (según requests) parece correcta. El ratio limit:request importa. Un ratio de memory limit:request de 10x o 20x en muchos pods es una receta para la inestabilidad del nodo. Un punto de partida razonable es 2x–4x para memoria, menos para CPU.

Patrón 4: CPU Limits Establecidos «Por Seguridad»

Esta es la mala configuración más sutil y la que tiene mayor impacto oculto en la latencia. La analizamos en profundidad en la siguiente sección.

El Problema del CPU Throttling: CFS Bandwidth y Latencia Oculta

Aquí es donde muchos despliegues de Kubernetes en producción tienen un problema de rendimiento silencioso que no es fácil de diagnosticar.

Cómo Funciona el CFS Bandwidth Throttling

El Linux Completely Fair Scheduler (CFS) aplica los CPU limits mediante control de ancho de banda. Los parámetros relevantes son:
  • cpu.cfs_period_us: el período de contabilización, por defecto 100ms
  • cpu.cfs_quota_us: cuántos microsegundos de tiempo de CPU puede usar el cgroup por período
Si estableces cpu: "500m" como limit, Kubernetes establece cpu.cfs_quota_us = 50000 (50ms por período de 100ms). Esto significa que el contenedor puede usar como máximo el 50% de un núcleo de CPU por ventana de 100ms. El problema: la quota se aplica por período, no como media móvil. Si tu contenedor usa su asignación completa de 50ms en los primeros 60ms de un período, es throttleado durante los 40ms restantes — aunque el nodo tenga 7 CPUs ociosas. La CPU permanece inactiva. Tu contenedor espera.

Por Qué Esto Causa Picos de Latencia Incluso con Baja Utilización

Esto es contraintuitivo y el origen de muchos misterios en producción. Puedes tener un contenedor funcionando al 10% de utilización media de CPU que es throttleado regularmente, porque su uso instantáneo de CPU dentro de una sola ventana de 100ms supera su quota. Las aplicaciones Java con garbage collection de la JVM son especialmente vulnerables. El GC provoca un burst de CPU de corta duración. Si ese burst supera la quota por período, la pausa de GC se alarga artificialmente por throttling — aunque el propio evento de GC hubiera sido breve. Lo mismo ocurre con el procesamiento del event loop de Node.js, la importación de Python al arrancar, y cualquier aplicación con comportamiento de CPU en ráfagas (que son la mayoría).

La Evidencia de Cloudflare y Netflix

Cloudflare publicó resultados mostrando que el CPU throttling era responsable de incrementos significativos en la latencia de cola en sus cargas de trabajo en contenedores, y que eliminar los CPU limits redujo la latencia p99 sustancialmente para servicios que aparentemente tenían margen. Netflix ha documentado patrones similares en sus trabajos de capacity planning, señalando que la aplicación de quota por período no modela con precisión el comportamiento real de CPU de las aplicaciones. La comunidad del kernel lleva años siendo consciente de esto. La solución — migrar a cgroups v2 con mejor integración del scheduler — ayuda pero no elimina el problema. Kubernetes 1.25+ con nodos cgroups v2 experimenta menos throttling bajo los mismos limits, pero el problema fundamental persiste: los CPU limits throttlean aplicaciones con ráfagas de forma impredecible.

La Recomendación: Considera No Establecer CPU Limits

Esto es controvertido pero está respaldado por la evidencia: Para servicios sensibles a la latencia: no establezcas CPU limits. Establece CPU requests con precisión y confía en el scheduler para la ubicación. El argumento: – El CPU throttling es un modo de fallo suave que es difícil de observar y diagnosticar – OOMKill es un modo de fallo duro que es visible y recuperable – Los CPU requests dan al scheduler datos precisos de ubicación sin crear throttling – Los nodos gestionan la sobresuscripción de CPU de forma elegante mediante time-sharing; no gestionan la sobresuscripción de memoria de forma elegante Cuándo seguir estableciendo CPU limits: – Clústeres multi-tenant donde el aislamiento de vecinos ruidosos (noisy neighbor) es crítico – Cargas de trabajo batch donde la asignación predecible de CPU importa más que la latencia – Cuando tu monitorización y alertas pueden detectar CPU starvation a nivel de nodo Cuando no estableces CPU limits, debes establecer CPU requests con precisión. Un request de 100m para un servicio que normalmente usa 800m significa que el scheduler lo coloca en un nodo que realmente no puede sostenerlo. El resultado es starvation real de CPU, no throttling artificial — pero starvation de CPU igualmente.

Memoria: Establece Siempre los Limits

El contraste con la CPU es directo. La memoria es no compresible. Un contenedor que tiene una fuga de memoria o una asignación desbocada consumirá toda la memoria disponible del nodo si no tiene restricciones. Esto no degrada de forma elegante — activa el OOM killer, que puede matar procesos no relacionados en el nodo. Establece siempre memory limits. Siempre. La consecuencia — OOMKill — es visible, queda registrada, y Kubernetes la gestiona reiniciando el contenedor. Un código de salida OOMKilled es accionable: o tienes una fuga de memoria, o tu limit es demasiado bajo, o tu metodología de sizing es incorrecta. Los tres son diagnosticables. La alternativa — sin memory limit — significa que un único pod con fuga puede desestabilizar un nodo entero y desencadenar cascadas de evicción que afectan a cargas de trabajo no relacionadas. Establece los memory requests igual al uso en estado estable del p95 de tu aplicación. Establece los memory limits en 1,5x–2x el request para absorber picos de tráfico y presión de GC. Perfila tu aplicación bajo carga para establecer estas líneas base.

Vertical Pod Autoscaler (VPA)

El VPA es el componente de Kubernetes diseñado para resolver automáticamente el problema del sizing. Observa la utilización real de recursos y recomienda (o aplica) requests ajustados.

Cómo Funciona el VPA

El VPA tiene tres componentes:
  • Recommender: Observa métricas históricas y calcula requests recomendados basándose en la utilización observada. No modifica pods.
  • Updater: Evicta pods cuyos requests actuales difieren significativamente de las recomendaciones (cuando el modo VPA es Auto o Recreate).
  • Admission Controller: Muta los specs de pods en el momento de admisión para aplicar las recomendaciones del Recommender.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-server-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  updatePolicy:
    updateMode: "Off"   # Solo recomendar — no evictar pods
  resourcePolicy:
    containerPolicies:
    - containerName: api-server
      minAllowed:
        cpu: 100m
        memory: 128Mi
      maxAllowed:
        cpu: 4000m
        memory: 4Gi
      controlledResources: ["cpu", "memory"]
      controlledValues: RequestsAndLimits

Modos del VPA

ModoComportamiento
OffCalcula solo recomendaciones. Sin mutaciones de pods.
InitialAplica recomendaciones solo a pods nuevos. No evicta pods en ejecución.
RecreateEvicta pods cuando las recomendaciones cambian significativamente.
AutoActualmente equivalente a Recreate. Puede cambiar en versiones futuras.

Cuándo Usar el VPA

Right-sizing durante el despliegue inicial: Ejecuta VPA en modo Off durante 1–2 semanas en un servicio nuevo. Revisa las recomendaciones antes de aplicarlas. Este es el caso de uso más valioso. Servicios con patrones de carga impredecibles o estacionales: El VPA adapta los requests según el comportamiento observado. Combinado con HPA para el escalado horizontal, obtienes réplicas bien dimensionadas que escalan hacia afuera horizontalmente. El VPA y el HPA no pueden gestionar la misma métrica simultáneamente. Si el HPA está escalando por utilización de CPU, no uses VPA con controlledValues: RequestsAndLimits para CPU — entrarán en conflicto. Usa controlledValues: RequestsOnly y deja que el HPA gestione el escalado. Limitaciones del VPA: – Requiere reinicios de pods para aplicar recomendaciones (el Updater evicta pods) – No funciona bien con cargas de trabajo con estado en ventanas de disponibilidad estrictas – El Recommender necesita historial suficiente (al menos unos días) para producir recomendaciones fiables – No tiene en cuenta picos de tráfico que todavía no se han observado

LimitRange y ResourceQuota: Salvaguardas a Nivel de Namespace

Los requests y limits en pods individuales resuelven el problema por carga de trabajo. LimitRange y ResourceQuota resuelven el problema de gobernanza a nivel de namespace y clúster.

LimitRange

LimitRange establece requests y limits por defecto para los contenedores de un namespace, y aplica límites mínimos y máximos. Cualquier pod admitido en el namespace que no tenga requests/limits explícitos recibirá los valores por defecto.
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: production
spec:
  limits:
  - type: Container
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    max:
      cpu: "4000m"
      memory: "8Gi"
    min:
      cpu: "50m"
      memory: "64Mi"
  - type: Pod
    max:
      cpu: "8000m"
      memory: "16Gi"
  - type: PersistentVolumeClaim
    max:
      storage: "50Gi"
    min:
      storage: "1Gi"
Comportamientos clave: – default se aplica como limit para contenedores que establecen un request pero no un limit – defaultRequest se aplica como request para contenedores que no establecen ningún request – max y min causan que la admisión falle si se violan – LimitRange se aplica en el momento de la admisión — cambiarla no afecta a los pods en ejecución Usa LimitRange para: – Prevenir que se admitan pods BestEffort (estableciendo valores de defaultRequest) – Aplicar estándares organizativos para las especificaciones mínimas de recursos – Proteger el clúster de pods que solicitan recursos sin límite

ResourceQuota

ResourceQuota limita la cantidad total de recursos que pueden consumir todos los pods de un namespace. Esta es la herramienta de gobernanza multi-tenant.
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "20"
    requests.memory: "40Gi"
    limits.cpu: "40"
    limits.memory: "80Gi"
    pods: "100"
    persistentvolumeclaims: "20"
    requests.storage: "500Gi"
    count/deployments.apps: "50"
    count/services: "50"
    count/secrets: "100"
    count/configmaps: "100"
Interacción crítica con LimitRange: Cuando ResourceQuota está activo en un namespace, todos los pods deben tener requests y limits establecidos o serán rechazados. Por eso los valores por defecto de LimitRange son importantes — aseguran que los pods sin recursos explícitos no sean rechazados por el sistema de quotas. Usa ResourceQuota para: – Aplicar presupuestos de recursos por equipo/aplicación en clústeres compartidos – Prevenir que despliegues desbocados consuman toda la capacidad del clúster – Implementar políticas de chargeback (seguimiento del consumo de recursos por namespace)

Metodología Práctica de Sizing

Paso 1: Instrumenta Antes de Establecer Valores

Despliega inicialmente con solo requests establecidos (sin CPU limits, memory limits establecidos conservadoramente altos) y monitoriza durante 2–4 semanas bajo carga realista. Consultas PromQL útiles para el sizing:
# Uso de CPU p95 en los últimos 7 días
histogram_quantile(0.95,
  rate(container_cpu_usage_seconds_total{
    container="api-server",
    namespace="production"
  }[5m])
)

# Memory working set p99 en los últimos 7 días
quantile_over_time(0.99,
  container_memory_working_set_bytes{
    container="api-server",
    namespace="production"
  }[7d]
)

# Ratio de CPU throttling (alertar si >5%)
rate(container_cpu_cfs_throttled_seconds_total{container="api-server"}[5m])
/
rate(container_cpu_cfs_periods_total{container="api-server"}[5m])

Paso 2: Establece los CPU Requests desde Observaciones del p95

Establece el CPU request = uso de CPU p95 bajo carga de producción realista. Para servicios sensibles a la latencia: no establezcas CPU limits. Para jobs batch o en background: establece CPU limits en 2x–4x el request.

Paso 3: Establece Memory Requests y Limits

Establece el memory request = memory working set p95 durante al menos 7 días. Establece el memory limit = max(pico observado, 1,5 × request). Para Java/Python con procesamiento intensivo, usa 2x.
# Ejemplo de producción: microservicio Java
resources:
  requests:
    cpu: "500m"       # p95 observado: ~420m
    memory: "768Mi"   # p95 observado: ~680Mi
  limits:
    # Sin CPU limit — servicio sensible a la latencia
    memory: "1.5Gi"   # 2x request, cubre presión de GC

Paso 4: Usa las Recomendaciones del VPA para Validar

Ejecuta VPA en modo Off junto a tus valores establecidos manualmente. Después de 1–2 semanas, compara las recomendaciones del VPA con tu configuración actual.

Paso 5: Ajusta para Eventos del Ciclo de Vida de la Carga de Trabajo

Ten en cuenta: el calentamiento de la JVM al arrancar (pico de CPU de 3–10x el estado estable), la superposición durante despliegues rolling (margen en la quota del namespace) y los picos de tráfico conocidos (dimensiona para el pico, no para la media).

Configurar Recursos en Kubernetes: Sizing por Tipo de Carga de Trabajo

Una de las preguntas más frecuentes al configurar recursos en Kubernetes para producción es qué valores poner según el tipo de servicio. No existe una respuesta única. El perfil de CPU y memoria de un microservicio Go es fundamentalmente distinto al de un job de procesamiento batch en Python. La siguiente tabla recoge las recomendaciones según el tipo de carga de trabajo más habitual en entornos productivos:
Tipo de Carga de TrabajoCPU RequestCPU LimitMemory RequestMemory LimitQoS Objetivo
API sensible a latencia (Go, Java, Node)p95 observadoNo establecerp95 observado1,5–2x requestBurstable
Jobs batch / backgroundp50 observado2–4x requestp95 observado1,5x requestBurstable
Sistema crítico (coredns, metrics-server)ConservadorIgual al requestConservadorIgual al requestGuaranteed
Stateful / bases de datos (in-cluster)p95 observadoNo establecerp99 observado1,25x requestBurstable
Cargas de trabajo dev/testBajo (100m)2x requestBajo (128Mi)2x requestBurstable
Contenedores sidecar (envoy, otel-collector)Perfilar individualmenteContextualPerfilar individualmente1,5x requestIgual al principal
La columna CPU Limit merece una explicación adicional. Para APIs y servicios stateful, la recomendación de no establecer CPU limit no es un descuido — es una decisión deliberada fundamentada en el comportamiento del CFS throttling descrito anteriormente. En clústeres multi-tenant con requisitos de aislamiento estrictos, esta recomendación debe evaluarse caso a caso.

QoS Kubernetes en Profundidad: Interacción con el kubelet

El comportamiento del QoS no se limita a la evicción. Tiene implicaciones directas sobre cómo el kubelet gestiona la presión de memoria en el nodo y cómo interacciona con los umbrales de evicción configurados en la kubelet config.

Umbrales de Evicción y QoS

El kubelet tiene dos tipos de umbral de evicción:
  • Soft eviction: El nodo ha estado por encima de un umbral durante un período (eviction-soft-grace-period). El kubelet puede evictar pods Burstable y BestEffort.
  • Hard eviction: El nodo supera un umbral crítico de forma inmediata. El kubelet evicta pods sin período de gracia.
Dentro de cada clase QoS, el kubelet prioriza la evicción según cuánto ha excedido el pod su request. Un pod Burstable que usa el 180% de su memory request será eviccionado antes que uno que usa el 110%.

OOM Score Adjustment

El kernel de Linux asigna a cada proceso un oom_score_adj. El kubelet manipula este valor según la clase QoS:
  • Guaranteed: -998 (muy bajo, el OOM killer los evita casi siempre)
  • Burstable: valor proporcional al exceso sobre el request
  • BestEffort: 1000 (el OOM killer los selecciona primero)
Este mecanismo funciona independientemente de los memory limits — es el comportamiento del kernel Linux actuando sobre los procesos del contenedor directamente.

Monitorización y Alertas para Recursos en Kubernetes

Configurar recursos en Kubernetes para producción no termina en el despliegue inicial. Necesitas visibilidad continua sobre el comportamiento real de los recursos.
# OOMKill rate
- alert: ContainerOOMKilled
  expr: increase(kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}[5m]) > 0
  for: 0m
  labels:
    severity: warning

# CPU throttling >10%
- alert: CPUThrottlingHigh
  expr: |
    rate(container_cpu_cfs_throttled_seconds_total[5m])
    /
    rate(container_cpu_cfs_periods_total[5m])
    > 0.10
  for: 5m
  labels:
    severity: warning

# Memoria cerca del limit >85%
- alert: MemoryNearLimit
  expr: |
    container_memory_working_set_bytes
    /
    (container_spec_memory_limit_bytes > 0)
    > 0.85
  for: 5m
  labels:
    severity: warning
Además de estas alertas, es recomendable incluir en tus dashboards de Prometheus/Grafana:
  • Ratio de utilización de requests: container_cpu_usage_seconds_total / kube_pod_container_resource_requests{resource="cpu"} — si está consistentemente por encima de 0,8, el request está subdimensionado.
  • Headroom de memoria por nodo: cuánta memoria schedulable queda en cada nodo según los requests comprometidos, independientemente de la utilización real.
  • Distribución QoS del clúster: cuántos pods son BestEffort, Burstable y Guaranteed. Un porcentaje alto de BestEffort en namespaces de producción es una señal de alerta.

Preguntas Frecuentes sobre Requests y Limits en Kubernetes

Mi aplicación Java sigue muriendo con OOMKill aunque he establecido el limit al doble del uso medio. ¿Qué me estoy perdiendo?

El heap de la JVM (-Xmx) no es el único consumidor de memoria. Los buffers off-heap, el Metaspace, los stacks de threads y el overhead de la JVM añaden entre un 25% y un 40% adicional. Establece -Xmx al ~75% de tu memory limit del contenedor. Para un limit de 1Gi: -Xmx768m es un punto de partida seguro.

¿Debo establecer los mismos recursos en todos los entornos?

No. Dev/test puede usar valores más bajos. Pero el ratio entre request y limit debería ser similar, y el perfil de recursos debería ser lo suficientemente parecido como para detectar malas configuraciones antes de llegar a producción.

¿Puedo usar HPA y VPA juntos?

Sí, con cuidado. Usa HPA para el escalado de réplicas (CPU o métricas personalizadas) y VPA en modo Off o con controlledValues: RequestsOnly como guía de right-sizing. Nunca tengas a ambos gestionando la misma métrica simultáneamente.

Mi clúster usa cgroups v2. ¿El CPU throttling sigue siendo un problema?

Mejorado, pero no eliminado. cgroups v2 usa un scheduler basado en pesos que reduce los artefactos de throttling. Sin embargo, la aplicación de cpu.cfs_quota_us sigue existiendo cuando se establecen CPU limits. Para cargas de trabajo sensibles a la latencia, el argumento de no establecer CPU limits sigue siendo válido en cgroups v2.

¿Cuál es un ratio de sobrecompromiso (overcommit) realista para el clúster?

CPU: un overcommit de 5–10x (total de requests vs cores físicos) es habitual para cargas de trabajo mixtas con requests precisos. Memoria: un overcommit de 1,5–2x a nivel de clúster es manejable con ratios de request:limit de 1,5x. Por encima de 2x, los eventos de presión de memoria en los nodos se vuelven frecuentes.

LimitRange está configurado pero los pods siguen siendo admitidos sin recursos. ¿Por qué?

Los valores por defecto de LimitRange solo se aplican a contenedores sin ninguna especificación de recursos en absoluto. Si un contenedor especifica requests.cpu pero no limits.cpu, el default de LimitRange para CPU no rellena el limit que falta. Verifica también que el LimitRange esté en el namespace correcto: kubectl get limitrange -n <namespace>.

¿Qué le hace a un nodo un pod sin memory limit?

Puede consumir toda la memoria disponible del nodo sin restricciones. Esto activa el OOM killer de Linux a nivel de nodo, que puede matar procesos fuera del contenedor — incluyendo el propio kubelet en casos extremos. Los memory limits no son negociables en producción.

¿Cómo detectar CPU throttling en producción si no tengo alertas configuradas?

El primer indicador es una latencia p99 inusualmente alta en servicios que aparentemente tienen baja utilización media de CPU. Para confirmar, consulta directamente: kubectl exec -it <pod> -- cat /sys/fs/cgroup/cpu/cpu.stat (cgroups v1) o kubectl exec -it <pod> -- cat /sys/fs/cgroup/cpu.stat (cgroups v2). La métrica throttled_time muestra el tiempo acumulado de throttling.


Checklist de Configuración de Recursos para Producción

Antes de poner un servicio en producción, verifica:
  • [ ] Todos los contenedores tienen requests.memory y limits.memory definidos
  • [ ] El ratio limits.memory / requests.memory está entre 1,5x y 2x
  • [ ] Los requests.cpu están basados en datos de utilización real (p95), no en estimaciones
  • [ ] Se ha tomado una decisión explícita sobre limits.cpu (no establecido por defecto para APIs)
  • [ ] El namespace tiene LimitRange configurado con valores defaultRequest razonables
  • [ ] El namespace tiene ResourceQuota si es un entorno compartido
  • [ ] Hay alertas activas para OOMKill, CPU throttling y memoria cerca del limit
  • [ ] El VPA está configurado en modo Off para observar recomendaciones durante las primeras semanas
  • [ ] La clase QoS resultante es la esperada (kubectl get pod <name> -o yaml | grep qosClass)

Validado contra Kubernetes 1.28–1.32. El comportamiento de cgroups v2 se indica donde difiere de v1. Los ejemplos de VPA usan la API autoscaling.k8s.io/v1 (VPA 0.14+).

Integrar Kyverno CLI en pipelines CI/CD con GitHub Actions

Integrar Kyverno CLI en pipelines CI/CD con GitHub Actions

Integrar Kyverno CLI en Pipelines CI/CD con GitHub Actions para Validación de Políticas Kubernetes

Introducción

A medida que Kubernetes se convierte en el estándar de facto para orquestar cargas de trabajo en entornos empresariales, garantizar el cumplimiento de políticas de seguridad y configuración deja de ser una opción para convertirse en una necesidad operativa. En muchos equipos, la validación de los manifiestos se hace de forma manual o directamente en el clúster, cuando el daño ya está hecho: un recurso mal configurado ha llegado a producción, ha violado una política de seguridad o ha consumido recursos de forma descontrolada.

Kyverno es un motor de políticas nativo de Kubernetes que permite definir, aplicar y auditar reglas de configuración directamente en formato YAML, sin necesidad de aprender un lenguaje específico como Rego. Y lo más importante: su interfaz de línea de comandos, el Kyverno CLI, puede integrarse en cualquier pipeline CI/CD para interceptar esos problemas antes de que lleguen al clúster.

En este artículo veremos cómo integrar Kyverno CLI en un pipeline de GitHub Actions para validar automáticamente manifiestos Kubernetes en cada pull request o push. El resultado es una capa de cumplimiento automatizada que refuerza la seguridad y la calidad del código de infraestructura sin fricciones para el equipo de desarrollo.


¿Qué es Kyverno CLI?

Kyverno es una herramienta de gestión de políticas nativa de Kubernetes. A diferencia de Open Policy Agent (OPA), que utiliza Rego como lenguaje de políticas, Kyverno define sus reglas en YAML puro, lo que lo hace mucho más accesible para equipos que ya trabajan con Kubernetes a diario.

El proyecto cuenta con dos modalidades de uso principales:

  • Kyverno como admission controller: se despliega como un webhook en el clúster y evalúa los recursos en tiempo de admisión, bloqueando o mutando los manifiestos antes de que sean persistidos.
  • Kyverno CLI: una herramienta de línea de comandos que permite aplicar y testear políticas contra manifiestos YAML localmente o en pipelines CI/CD, sin necesidad de un clúster activo.

Esta segunda modalidad es la que nos interesa aquí. El Kyverno CLI permite ejecutar el mismo motor de evaluación de políticas que usarías en el clúster, pero en un contexto offline, lo que lo convierte en la herramienta perfecta para el shift-left en validación de seguridad Kubernetes.

Casos de uso del Kyverno CLI en CI/CD

  • Validar que ningún manifiesto usa imágenes con el tag latest
  • Verificar que todos los contenedores definen requests y limits de CPU y memoria
  • Comprobar que los Deployments tienen configurado readinessProbe y livenessProbe
  • Asegurarse de que no se desplieguen contenedores en modo privilegiado
  • Validar labels obligatorios en todos los recursos (por ejemplo, app, team, environment)
  • Rechazar el uso de hostNetwork: true o hostPID: true

Beneficios de Integrar Kyverno CLI en un Pipeline CI/CD

1. Validación de políticas de forma automática y temprana

El principal beneficio es detectar violaciones de políticas en la fase más temprana posible del ciclo de desarrollo: en el mismo momento en que el desarrollador abre un pull request. Esto es lo que se conoce como shift-left security: mover los controles de seguridad lo más a la izquierda posible en el pipeline, cuando el coste de corrección es mínimo.

Si la violación se detecta en el clúster mediante el admission controller, ya hay contexto de ejecución involucrado, posiblemente un despliegue fallido, y hay que identificar el problema, comunicarlo al equipo y esperar a que lo corrijan. Si se detecta en el pipeline CI/CD, el desarrollador recibe feedback inmediato en su PR y puede corregirlo en segundos.

2. Consistencia entre entornos

Cuando el mismo conjunto de políticas se aplica tanto en el admission controller del clúster como en el pipeline CI/CD, se garantiza que lo que pasa en CI también pasa en producción. No hay sorpresas al desplegar: si el pipeline verde, el clúster también lo aceptará.

3. Separación de responsabilidades

Los equipos de plataforma o de seguridad pueden definir y mantener las políticas en Kyverno, mientras que los equipos de desarrollo las consumen de forma transparente a través del pipeline. Esto crea un modelo de gobernanza claro sin necesidad de bloquear el flujo de trabajo de los desarrolladores.

4. Sin dependencias de clúster en CI

A diferencia de otros enfoques que requieren un clúster de Kubernetes activo para validar (con herramientas como kubectl apply --dry-run=server), el Kyverno CLI opera completamente en local. Esto lo hace más rápido, más barato y más portable.

5. Desarrollo más ágil

Recibir feedback inmediato sobre violaciones de políticas acelera el ciclo de desarrollo. En lugar de descubrir problemas en la fase de despliegue o, peor aún, en producción, los desarrolladores pueden corregirlos mientras el código todavía está fresco en su mente.


Configuración de Kyverno CLI en GitHub Actions

Paso 1: Instalar el Kyverno CLI

Lo primero es añadir un step en el workflow de GitHub Actions que descargue e instale el binario del Kyverno CLI. El binario está disponible en las releases oficiales del repositorio GitHub de Kyverno.

El siguiente ejemplo muestra un workflow completo con la instalación incluida:

name: CI Pipeline con validación de políticas Kyverno

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  kyverno-policy-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout del código
        uses: actions/checkout@v4

      - name: Instalar Kyverno CLI
        run: |
          curl -LO https://github.com/kyverno/kyverno/releases/download/v1.13.0/kyverno-cli_v1.13.0_linux_x86_64.tar.gz
          tar -xzf kyverno-cli_v1.13.0_linux_x86_64.tar.gz
          sudo mv kyverno /usr/local/bin/
          kyverno version

Nota sobre versiones: Sustituye v1.13.0 por la versión que necesites para tu proyecto. Se recomienda fijar una versión concreta en lugar de usar latest para garantizar reproducibilidad en el pipeline. Puedes consultar las versiones disponibles en la página de releases de Kyverno.

Instalación alternativa mediante Krew

Si el runner ya tiene Krew instalado (el gestor de plugins de kubectl), también puedes instalar el CLI así:

kubectl krew install kyverno

Sin embargo, en runners de GitHub Actions la instalación directa del binario es más rápida y no requiere dependencias adicionales.


Paso 2: Definir las políticas de validación

Las políticas de Kyverno son recursos YAML estándar de Kubernetes. Para el CI/CD, la práctica recomendada es almacenarlas en el propio repositorio, bajo un directorio dedicado. Una estructura habitual es la siguiente:

.
└── .github
    └── policies
        ├── disallow-latest-tag.yaml
        └── require-requests-limits.yaml

De esta forma, las políticas viajan junto al código y están sujetas al mismo proceso de revisión mediante pull requests.

Política 1: Prohibir el uso del tag latest

El uso de latest en imágenes de contenedor es una práctica desaconsejada porque elimina la reproducibilidad: dos despliegues con la misma definición pueden usar versiones distintas de la imagen. Esta política rechaza cualquier Deployment, DaemonSet, StatefulSet o Pod que utilice imágenes sin tag o con el tag latest:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
  annotations:
    policies.kyverno.io/title: Prohibir tag latest en imágenes
    policies.kyverno.io/description: >-
      El tag 'latest' hace que los despliegues no sean reproducibles.
      Todos los contenedores deben usar un tag de versión específico.
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: require-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Las imágenes de contenedor deben especificar un tag distinto de 'latest'."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

Política 2: Requerir requests y limits de recursos

Sin límites de recursos, un contenedor defectuoso o malicioso puede consumir todos los recursos del nodo y provocar una interrupción de servicio. Esta política garantiza que todos los contenedores definen explícitamente sus requests y limits:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-requests-limits
  annotations:
    policies.kyverno.io/title: Requerir requests y limits de recursos
    policies.kyverno.io/description: >-
      Todos los contenedores deben definir requests y limits de CPU y memoria
      para garantizar una planificación predecible y evitar el agotamiento de recursos.
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: validate-resources
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Los contenedores deben definir resources.requests y resources.limits."
        pattern:
          spec:
            containers:
              - resources:
                  requests:
                    memory: "?*"
                    cpu: "?*"
                  limits:
                    memory: "?*"
                    cpu: "?*"

Política 3: Prohibir contenedores privilegiados

Una política de seguridad crítica en cualquier entorno de producción es evitar que los contenedores se ejecuten en modo privilegiado, ya que esto les otorga acceso casi completo al host:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
  annotations:
    policies.kyverno.io/title: Prohibir contenedores privilegiados
    policies.kyverno.io/description: >-
      Los contenedores privilegiados tienen acceso completo al host.
      Esta política prohibe el uso de securityContext.privileged: true.
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: no-privileged
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Los contenedores no pueden ejecutarse en modo privilegiado."
        pattern:
          spec:
            containers:
              - =(securityContext):
                  =(privileged): "false"

Paso 3: Añadir el step de validación al workflow

Con las políticas definidas y almacenadas en el repositorio, el siguiente paso es añadir al workflow de GitHub Actions el step que ejecuta la validación. El comando kyverno apply aplica las políticas del directorio indicado contra todos los manifiestos del directorio de recursos:

      - name: Validar manifiestos Kubernetes con Kyverno
        run: |
          kyverno apply .github/policies/ -r manifests/

El flag -r (o --resource) indica el directorio o fichero que contiene los manifiestos a validar. Puedes especificar múltiples rutas o usar globs.

Gestión de fallos con notificaciones

Para mejorar la visibilidad, puedes añadir un step condicional que notifique al equipo cuando se detecte una violación de política:

      - name: Notificar violación de política
        if: failure()
        run: echo "Se han detectado violaciones de política. Revisa los manifiestos antes de mergear."

En entornos más avanzados, este step podría enviar una notificación a Slack, crear un comentario en el PR o publicar los resultados como artefacto del workflow.


Paso 4: Workflow completo integrado

Aquí tienes el workflow de GitHub Actions completo, integrando instalación, validación y notificación:

name: Validación de políticas Kyverno

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  kyverno-policy-check:
    name: Kyverno Policy Check
    runs-on: ubuntu-latest

    steps:
      - name: Checkout del código
        uses: actions/checkout@v4

      - name: Instalar Kyverno CLI
        run: |
          KYVERNO_VERSION="v1.13.0"
          curl -LO "https://github.com/kyverno/kyverno/releases/download/${KYVERNO_VERSION}/kyverno-cli_${KYVERNO_VERSION}_linux_x86_64.tar.gz"
          tar -xzf "kyverno-cli_${KYVERNO_VERSION}_linux_x86_64.tar.gz"
          sudo mv kyverno /usr/local/bin/
          kyverno version

      - name: Validar manifiestos Kubernetes con Kyverno
        run: |
          kyverno apply .github/policies/ -r manifests/

      - name: Informar sobre violaciones de política
        if: failure()
        run: |
          echo "::error::Se han detectado violaciones de política Kyverno."
          echo "Revisa los manifiestos en el directorio 'manifests/' y corrígelos antes de continuar."

Ejemplo práctico: Detectando una violación

Supongamos que un desarrollador hace push de un Deployment de nginx con la siguiente configuración:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest  # Esto debería activar una violación

Este manifiesto viola la política disallow-latest-tag. Cuando el pipeline ejecuta kyverno apply .github/policies/ -r manifests/, el resultado sería algo similar a:

Applying 2 policy rule(s) to 1 resource(s)...

policy disallow-latest-tag -> resource default/Deployment/nginx-deployment
  Rule require-image-tag failed 🚫
  -> Las imágenes de contenedor deben especificar un tag distinto de 'latest'.
  -> Image nginx:latest violates the constraint

pass: 0, fail: 1, warn: 0, error: 0, skip: 0

El pipeline falla con código de salida no cero, bloqueando el merge del PR hasta que el desarrollador corrija la imagen. La solución sería usar un tag de versión específico:

          image: nginx:1.27.3

Buenas prácticas para Kyverno CLI en CI/CD

Separar políticas de audit y enforce

En entornos donde todavía se está adoptando Kyverno, puede ser útil diferenciar entre políticas en modo Audit (que informan pero no bloquean) y políticas en modo Enforce (que bloquean el pipeline). Esto permite una adopción progresiva sin interrumpir el flujo de trabajo:

spec:
  validationFailureAction: Audit  # O Enforce

En el pipeline CI/CD, puedes mantener ambos conjuntos de políticas en directorios separados (por ejemplo, .github/policies/enforce/ y .github/policies/audit/) y ejecutarlos de forma independiente, tratando los fallos de audit como warnings y los de enforce como errores que bloquean el pipeline.

Cachear el binario de Kyverno CLI

Para acelerar el pipeline, puedes cachear el binario de Kyverno entre ejecuciones usando la acción actions/cache:

      - name: Cachear Kyverno CLI
        uses: actions/cache@v4
        with:
          path: /usr/local/bin/kyverno
          key: kyverno-cli-v1.13.0

      - name: Instalar Kyverno CLI (si no está en caché)
        run: |
          if ! command -v kyverno &> /dev/null; then
            curl -LO "https://github.com/kyverno/kyverno/releases/download/v1.13.0/kyverno-cli_v1.13.0_linux_x86_64.tar.gz"
            tar -xzf kyverno-cli_v1.13.0_linux_x86_64.tar.gz
            sudo mv kyverno /usr/local/bin/
          fi

Usar kyverno test para políticas más complejas

Para políticas más complejas que requieren testear tanto casos válidos como inválidos, Kyverno CLI ofrece el subcomando kyverno test. Este comando permite definir fixtures con el resultado esperado para cada recurso, lo que resulta muy útil en pipelines de CI para los propios repositorios de políticas:

kyverno test .github/policies/tests/

La estructura de un test de Kyverno incluye:
– El fichero de política
– Los manifiestos de recursos a testear
– Un fichero kyverno-test.yaml que define qué resultado se espera para cada combinación de política y recurso

Integración con repositorios de políticas centralizados

En organizaciones grandes, lo habitual es mantener las políticas en un repositorio centralizado separado del código de aplicación. En ese caso, el workflow de CI puede clonar ese repositorio de políticas como parte del pipeline:

      - name: Clonar repositorio de políticas
        uses: actions/checkout@v4
        with:
          repository: mi-org/kyverno-policies
          path: kyverno-policies
          token: ${{ secrets.POLICIES_REPO_TOKEN }}

      - name: Validar manifiestos contra políticas corporativas
        run: |
          kyverno apply kyverno-policies/enforce/ -r manifests/

Relación entre Kyverno CLI y el Admission Controller

Un punto importante a tener en cuenta es que Kyverno CLI y el admission controller de Kyverno son complementarios, no sustitutos.

CaracterísticaKyverno CLI (CI/CD)Kyverno Admission Controller
Cuándo actúaEn el pipeline, antes del despliegueEn el clúster, en tiempo de admisión
Requiere clústerNoSí
Velocidad de feedbackInmediata (en el PR)Al intentar desplegar
Puede mutar recursosNoSí
Coste de correcciónMínimoMayor

La estrategia óptima es aplicar ambas capas: el CLI en CI/CD para detección temprana y el admission controller en el clúster como última línea de defensa. De esta forma, incluso si alguien intenta saltarse el pipeline y desplegar directamente con kubectl apply, el clúster seguirá protegido.


Troubleshooting habitual

El CLI no encuentra los manifiestos

Asegúrate de que la ruta especificada con -r existe y contiene ficheros YAML válidos. Puedes verificarlo con un step previo:

      - name: Listar manifiestos
        run: find manifests/ -name "*.yaml" -type f

Las políticas no se aplican a todos los recursos

Kyverno evalúa las políticas según el kind especificado en la regla. Si tus políticas apuntan a Pod pero tus manifiestos son Deployment, puede que necesites ajustar el match o saber que Kyverno CLI en modo apply evalúa los recursos tal como están definidos, no las plantillas de pod anidadas. En algunos casos puede ser necesario incluir los kinds de nivel superior:

      match:
        any:
          - resources:
              kinds:
                - Deployment
                - StatefulSet
                - DaemonSet
                - Pod

El pipeline falla pero no se ven los detalles del error

Añade el flag --detailed-results al comando de Kyverno para obtener más información sobre cada violación:

kyverno apply .github/policies/ -r manifests/ --detailed-results

Conclusión

Integrar Kyverno CLI en un pipeline de GitHub Actions es una de las formas más eficaces de aplicar el principio de shift-left security en entornos Kubernetes. Con una inversión mínima — unas pocas líneas de YAML en el workflow y un directorio de políticas en el repositorio — se obtiene una capa de validación automática que:

  • Detecta violaciones de políticas de seguridad y configuración antes de que lleguen al clúster
  • Proporciona feedback inmediato a los desarrolladores en cada pull request
  • Mantiene la consistencia entre los entornos de desarrollo, staging y producción
  • Reduce el coste de corrección al desplazar los controles a la fase más temprana posible

El Kyverno CLI no reemplaza al admission controller desplegado en el clúster, sino que lo complementa formando una estrategia de defensa en profundidad. Juntos, garantizan que los recursos de Kubernetes cumplan con las políticas corporativas tanto en el momento de revisión del código como en el momento del despliegue.

Para profundizar más en Kubernetes y sus patrones de seguridad, consulta la guía completa de Kubernetes Architecture Patterns donde encontrarás todos los conceptos fundamentales y avanzados explicados paso a paso.

Node Affinity en Kubernetes: Reglas, Trade-offs y Buenas Prácticas

Node Affinity en Kubernetes: Reglas, Trade-offs y Buenas Prácticas

Node Affinity en Kubernetes: Reglas de Scheduling, Trade-offs y Buenas Prácticas

El scheduling de pods en Kubernetes puede parecer un proceso automático y transparente. Y durante la mayor parte del tiempo, lo es. El scheduler elige un nodo disponible con recursos suficientes y la carga de trabajo arranca. Sin problemas. Sin configuración adicional. Sin que el equipo tenga que intervenir.

El problema aparece cuando ese comportamiento por defecto ya no es suficiente. Cuando tienes nodos con hardware especializado —GPUs, SSDs NVMe, instancias de alta memoria— y necesitas garantizar que ciertos workloads acaben en los nodos correctos. O cuando regulaciones de licencias obligan a aislar determinadas cargas de trabajo. O cuando simplemente quieres colocar los pods de una aplicación en la misma zona de disponibilidad que su base de datos para minimizar la latencia.

Ahí es exactamente donde entra en juego node affinity en Kubernetes: el mecanismo que te permite expresar, con mayor o menor grado de obligatoriedad, en qué nodos quieres que el scheduler coloque tus pods.

Este artículo cubre todo lo que necesitas saber para usar node affinity de forma efectiva: conceptos fundamentales, diferencias con nodeSelector, los dos tipos de reglas, sus trade-offs reales en producción y buenas prácticas para no encontrarte con sorpresas cuando falle un nodo a las 3 de la madrugada.


Qué es Node Affinity en Kubernetes

Node affinity es una funcionalidad del scheduler de Kubernetes que permite definir restricciones sobre en qué nodos pueden programarse los pods, basándose en las etiquetas (labels) de esos nodos.

En la práctica, es la respuesta de Kubernetes a una pregunta muy concreta: «¿cómo le digo al scheduler que este pod solo debe ir a nodos con esta característica?» La respuesta, antes de node affinity, era nodeSelector. Y aunque nodeSelector sigue funcionando y es perfectamente válido para casos simples, node affinity expande notablemente esa capacidad.

Node Affinity vs nodeSelector: cuál es la diferencia

nodeSelector es el mecanismo más básico de scheduling por etiquetas. Añades un campo nodeSelector al spec del pod con los labels que debe tener el nodo, y el scheduler filtra en consecuencia. Funciona. Es directo. Pero tiene limitaciones importantes:

  • Solo permite igualdad exacta (key: value). No puedes expresar «cualquier valor de esta lista» o «que NO tenga este label».
  • No distingue entre restricciones obligatorias y preferentes. O se cumple, o el pod no se programa.
  • La semántica de error no es especialmente informativa cuando el scheduling falla.

Node affinity resuelve estas limitaciones con una API más expresiva:

  • Soporta operadores como In, NotIn, Exists, DoesNotExist, Gt y Lt.
  • Distingue entre reglas required (obligatorias) y preferred (preferentes).
  • Se integra mejor con las demás restricciones del scheduler, como pod affinity, pod anti-affinity y taints/tolerations.

La recomendación general es usar nodeSelector para casos muy simples donde la igualdad directa es suficiente, y migrar a node affinity en cuanto necesitas más expresividad o cuando el comportamiento ante fallos importa.


Los dos tipos de reglas de Node Affinity

Node affinity se configura en el campo affinity.nodeAffinity del spec del pod, y distingue dos modos fundamentales:

1. requiredDuringSchedulingIgnoredDuringExecution

Esta es la regla obligatoria (hard constraint). El scheduler solo colocará el pod en nodos que cumplan exactamente los criterios definidos. Si ningún nodo disponible satisface la regla, el pod permanece en estado Pending indefinidamente.

El nombre completo merece atención: IgnoredDuringExecution significa que, si un nodo cambia sus labels después de que el pod ya está ejecutándose, Kubernetes no lo desaloja. El pod sigue corriendo. La regla solo se aplica en el momento del scheduling inicial.

Ejemplo de configuración:

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: node-type
              operator: In
              values:
                - database
                - storage-optimized

Con esta configuración, el pod solo se programará en nodos que tengan el label node-type con valor database o storage-optimized. En cualquier otro nodo, no se programará.

2. preferredDuringSchedulingIgnoredDuringExecution

Esta es la regla preferente (soft constraint). El scheduler intentará colocar el pod en nodos que cumplan los criterios, pero si no hay ninguno disponible, lo programará en cualquier otro nodo elegible.

Cada regla preferente tiene un campo weight (1-100) que permite ponderar su importancia relativa cuando hay varias reglas preferentes simultáneas. El scheduler suma los pesos de las reglas satisfechas por cada nodo y elige el nodo con mayor puntuación.

affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values:
                - eu-west-1a
      - weight: 20
        preference:
          matchExpressions:
            - key: node-type
              operator: In
              values:
                - compute-optimized

En este ejemplo, el scheduler intentará colocar el pod en nodos de la zona eu-west-1a (peso 80) y preferentemente de tipo compute-optimized (peso 20), pero si no existen nodos que cumplan ninguna de las dos condiciones, el pod se programará igualmente en otro nodo.

Pueden combinarse

Nada impide usar ambas reglas simultáneamente. Un caso habitual: la regla required asegura que el pod solo va a nodos de un tipo determinado, y la regla preferred optimiza dentro de ese subconjunto por zona de disponibilidad o tier de hardware.

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: workload-class
              operator: In
              values:
                - gpu
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
            - key: gpu-model
              operator: In
              values:
                - nvidia-a100

Trade-offs reales: Required vs Preferred en producción

Aquí está el núcleo del artículo, y la parte que mucha documentación glosa demasiado rápido. Porque node affinity no es simplemente «elige la opción que más se adapte a tu caso de uso». Cada elección tiene implicaciones directas sobre la disponibilidad, la operabilidad y el comportamiento ante fallos.

Para ilustrarlo, consideremos un escenario concreto: una base de datos distribuida —etcd, ZooKeeper o Cassandra— desplegada con tres réplicas para garantizar quórum y tolerancia a fallos. El equipo ha designado tres nodos específicos del clúster para alojar estos workloads y ha configurado los labels correspondientes. La pregunta es: ¿required o preferred?

El problema con required en este escenario

Si defines la regla como required, el scheduler solo programará las réplicas en los tres nodos etiquetados. Mientras todo funciona, perfecto: cada réplica está exactamente donde debería estar.

Pero imagina que uno de esos tres nodos cae por un fallo de hardware o una actualización. El pod que corría en ese nodo necesita ser reprogramado. ¿Dónde puede ir? Solo a otro de los nodos etiquetados. Si esos dos nodos ya tienen sus réplicas respectivas, y además tienes configurado pod anti-affinity para garantizar que cada réplica esté en un host diferente, el pod queda en Pending.

El clúster de base de datos está ahora con dos de tres réplicas activas. Si tienes quórum con dos, sigues funcionando. Pero si el nodo caído no vuelve pronto y otro falla, pierdes quórum. Y el pod sigue en Pending porque no puede ir a ningún nodo no etiquetado.

La combinación de required node affinity + pod anti-affinity puede ser más restrictiva de lo que parece a primera vista, y puede dejarte sin capacidad de recuperación automática en situaciones donde más la necesitas.

El problema con preferred en este escenario

Ahora supón que optas por preferred. El pod siempre se programará, incluso si ningún nodo etiquetado está disponible. Resuelves el problema de disponibilidad, ¿verdad?

En parte, sí. Pero introduces otro problema: la opacidad del estado del clúster. Si uno de los nodos etiquetados tiene problemas esporádicos de recursos, el scheduler podría empezar a colocar pods en nodos no etiquetados silenciosamente. Al cabo de un tiempo, tienes réplicas de tu base de datos crítica repartidas entre nodos etiquetados y no etiquetados, sin que ningún mecanismo te haya avisado explícitamente.

Administrar esa situación es mucho más difícil. No puedes garantizar que el workload está donde tiene que estar. Las asunciones sobre aislamiento de recursos o rendimiento del hardware se rompen.

El escenario más peligroso: preferred + taints

El caso más problemático aparece cuando combinas preferred node affinity con nodos que tienen taints. Considera este setup:

  • Los tres nodos etiquetados para tu base de datos tienen un taint dedicated=database:NoSchedule para evitar que otros workloads los invadan.
  • Los pods de la base de datos tienen la toleration correspondiente.
  • La regla de node affinity es preferred.

Si los nodos etiquetados están saturados, el scheduler intentará programar los pods en otros nodos. Pero esos otros pods —los que no son la base de datos— no tienen toleration para los nodos etiquetados con taint, y si los nodos sin etiquetar también se están llenando, el scheduler puede quedarse sin opciones.

Resultado: los pods de la base de datos acaban en nodos aleatorios que no son los designados, y otros workloads no pueden usar los nodos designados porque están tainteados. Tienes un clúster desorganizado donde ni el aislamiento ni la distribución están funcionando como esperabas. Y lo que es peor: todo esto puede estar pasando silenciosamente, sin alarmas obvias.


Prepararse para los fallos inesperados

La conclusión práctica de todo lo anterior es sencilla pero importante: las decisiones de scheduling que tomas en tiempo de diseño se manifiestan en tiempo de fallo. Mientras el clúster funciona con normalidad, tanto required como preferred se comportan igual de bien. La diferencia emerge exactamente cuando las cosas van mal.

Esto significa que antes de definir una regla de node affinity en producción, deberías hacerte estas preguntas:

¿Qué ocurre si uno de los nodos etiquetados falla?
– Con required: ¿hay suficientes nodos etiquetados para absorber la carga? ¿El pod anti-affinity no va a bloquearlo?
– Con preferred: ¿es aceptable que el workload acabe en nodos no designados? ¿Qué impacto tiene en rendimiento o compliance?

¿Cuántos nodos etiquetados tengo y cuántos necesito para tolerar fallos?
Si tienes tres réplicas y tres nodos etiquetados, un fallo deja el sistema al límite. Si tienes cuatro nodos etiquetados, tienes margen de maniobra con required.

¿Tengo taints en estos nodos? ¿Qué tolerations tienen los pods?
Tener claro el mapa completo de taints/tolerations antes de definir affinity rules evita los escenarios de deadlock descritos arriba.

¿Cómo monitorizo si los pods están donde se supone que deben estar?
Con preferred especialmente, necesitas alertas o dashboards que confirmen que el workload está realmente en los nodos designados, no solo que está programado y corriendo.


Node Affinity e interacción con Taints y Tolerations

Node affinity y el sistema de taints/tolerations son mecanismos complementarios, pero operan en fases distintas del proceso de scheduling.

El scheduler aplica primero las reglas de node affinity para filtrar el conjunto de nodos candidatos. Una vez tiene ese subconjunto, aplica el filtro de taints y tolerations: solo los nodos cuyos taints el pod puede tolerar son elegibles. Un pod solo acaba en un nodo que supera ambos filtros.

Entender este orden es clave. Si un nodo tiene el label correcto para tu affinity rule pero también tiene un taint que el pod no tolera, el pod no va a ese nodo. No importa cuánto peso le hayas dado a la preferred rule.

La tabla siguiente resume la interacción:

Pod tiene affinity ruleNodo tiene label correctoNodo tiene taintPod tolera el taintResultado
RequiredSíNoN/AProgramado
RequiredSíSíSíProgramado
RequiredSíSíNoNo programado
RequiredNoN/AN/ANo programado (Pending)
PreferredSíNoN/AProgramado (nodo preferido)
PreferredNoNoN/AProgramado (nodo no preferido)
PreferredSíSíNoNo programado en ese nodo; busca alternativa

La recomendación es diseñar siempre affinity rules y taints/tolerations como un sistema coherente, no como capas independientes. Mapea explícitamente: qué nodos tienen qué labels, qué nodos tienen qué taints, y qué pods tienen qué tolerations y qué affinity rules. Un diagrama simple de esto puede evitar horas de debugging cuando el scheduler no hace lo que esperas.


Buenas prácticas para definir Node Affinity en producción

Usa labels descriptivos y consistentes

Los labels son el contrato entre tus workloads y tus nodos. Defínelos con cuidado:

  • Prefiere los well-known labels de Kubernetes cuando existan: topology.kubernetes.io/zone, topology.kubernetes.io/region, kubernetes.io/arch, node.kubernetes.io/instance-type. Son más portables y compatibles con herramientas del ecosistema.
  • Para labels personalizados, usa un prefijo de dominio propio: workload.mycompany.com/class: database es mejor que simplemente class: database. Evita colisiones con otros sistemas.
  • Documenta el propósito de cada label custom en tu wiki o en anotaciones del propio nodo. Los labels sin documentar se convierten en deuda técnica.

Dimensiona los nodos etiquetados pensando en fallos

Si vas a usar required node affinity para un workload con N réplicas, necesitas al menos N+1 nodos etiquetados para que el sistema pueda recuperarse ante un fallo sin quedarse en Pending. Idealmente N+2 si quieres tolerancia a dos fallos simultáneos.

No etiquetes el mínimo indispensable. Etiqueta con margen.

Combina con pod anti-affinity con cuidado

La combinación de required node affinity + pod anti-affinity es especialmente poderosa para garantizar distribución, pero también especialmente peligrosa si no se dimensiona bien. Cada regla adicional reduce el espacio de nodos elegibles para el scheduler. Haz el cálculo explícito: con X nodos etiquetados, Y réplicas, y anti-affinity de host, ¿cuántos fallos puedo absorber antes de que el scheduler se bloquee?

Prefiere preferred para optimización, required para compliance

Una heurística útil: usa required cuando el incumplimiento de la regla es inaceptable (restricciones de licencia, compliance regulatorio, hardware imprescindible como GPU). Usa preferred cuando es una optimización (latencia, coste, rendimiento) que puede sacrificarse temporalmente si la disponibilidad lo requiere.

Simula fallos antes de ir a producción

No te fíes únicamente del razonamiento teórico. Con herramientas como kubectl cordon o kubectl drain puedes simular la indisponibilidad de un nodo en staging y observar exactamente qué hace el scheduler. ¿Los pods quedan en Pending? ¿Se van a nodos no esperados? ¿Cuánto tardan en reprogramarse?

Esta validación debería ser parte del proceso de revisión antes de mergear cambios en la configuración de scheduling.

Monitoriza el estado real del placement

Especialmente con preferred rules, implementa alertas que verifiquen que los pods están en los nodos esperados, no solo que están en estado Running. Un simple script que compare los labels del nodo donde corre cada pod contra los labels esperados, ejecutado periódicamente, puede darte esa visibilidad. Herramientas como Prometheus + kube-state-metrics facilitan este tipo de queries.


Ejemplo completo: base de datos distribuida con node affinity

Para cerrar con algo concreto, aquí está un ejemplo de configuración para el escenario de la base de datos distribuida con un enfoque razonado:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: distributed-db
spec:
  replicas: 3
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: workload.mycompany.com/class
                    operator: In
                    values:
                      - database
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: topology.kubernetes.io/zone
                    operator: In
                    values:
                      - eu-west-1a
                      - eu-west-1b
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - distributed-db
                topologyKey: kubernetes.io/hostname
      tolerations:
        - key: "dedicated"
          operator: "Equal"
          value: "database"
          effect: "NoSchedule"

Observa las decisiones deliberadas aquí:

  • La node affinity required garantiza que los pods solo van a nodos etiquetados como database. Es una restricción de compliance/aislamiento que no queremos que se rompa.
  • La preferencia de zona es preferred, no required, porque si una zona tiene problemas, preferimos que el pod se programe en otra zona a que quede en Pending.
  • El pod anti-affinity también es preferred (no required), lo que permite al scheduler romper la restricción de un-pod-por-host si es necesario para garantizar disponibilidad. Puede que dos réplicas acaben en el mismo host en un escenario de fallo extremo, pero al menos estarán corriendo.
  • Los nodos de base de datos tienen un taint dedicated=database:NoSchedule y el pod tiene la toleration correspondiente.

Este diseño prioriza disponibilidad sobre aislamiento perfecto, que es la elección correcta para la mayoría de workloads en producción.


Preguntas frecuentes sobre Node Affinity en Kubernetes

¿Cuál es la diferencia entre nodeSelector y node affinity?

nodeSelector es un campo simple que requiere que el nodo tenga exactamente los labels especificados. Node affinity es una API más expresiva que soporta operadores como In, NotIn, Exists y DoesNotExist, y distingue entre restricciones hard (required) y soft (preferred). Usa nodeSelector para necesidades básicas de igualdad directa; usa node affinity para cualquier lógica de scheduling más compleja.

¿Cuándo usar required y cuándo preferred?

Usa required cuando el incumplimiento es inaceptable: hardware específico (GPU, FPGA), restricciones de licencia, compliance regulatorio, segmentación de red. Usa preferred cuando la regla es una optimización que puede sacrificarse: colocación en la misma zona de disponibilidad para reducir latencia, preferencia por instancias de un determinado tipo. Ten en cuenta que required puede dejar pods en Pending ante fallos de nodo, mientras que preferred puede no garantizar el placement esperado.

¿Qué riesgos tiene usar required node affinity?

El riesgo principal es el bloqueo de scheduling. Si ningún nodo satisface las reglas required (por fallo, mantenimiento o cambio de labels), el pod queda en Pending indefinidamente. Este riesgo se amplifica cuando se combina con pod anti-affinity, que reduce aún más el conjunto de nodos elegibles. La solución es dimensionar correctamente los nodos etiquetados para tener capacidad de sobra ante fallos.

¿Cómo interactúan node affinity y taints/tolerations?

Operan en fases distintas del scheduling. Primero, el scheduler filtra nodos según las affinity rules. Después, del conjunto resultante, filtra los nodos cuyos taints el pod no puede tolerar. Un pod solo se programa en un nodo que pasa ambos filtros. Diseña siempre ambos mecanismos como un sistema coherente.

¿Qué buenas prácticas debo seguir al definir labels para node affinity?

Usa well-known labels de Kubernetes cuando existan (topology.kubernetes.io/zone, node.kubernetes.io/instance-type). Para labels propios, usa un prefijo de dominio para evitar colisiones. Documenta el propósito de cada label. Dimensiona los nodos etiquetados con margen para tolerar fallos. Valida el comportamiento del scheduler simulando indisponibilidad de nodos antes de pasar a producción.

¿Puedo combinar required y preferred en la misma definición?

Sí. Es un patrón habitual: la regla required establece un filtro duro (solo nodos de un determinado tipo), y las reglas preferred optimizan dentro de ese subconjunto (preferir la zona más cercana, el hardware más rápido, etc.). El scheduler siempre respeta primero las reglas required y después puntúa los candidatos según las preferred.


Conclusión

Node affinity en Kubernetes es una herramienta poderosa para controlar el scheduling de workloads con precisión. Pero esa precisión tiene un coste: introduce fragilidad si no se diseña pensando en los escenarios de fallo.

La clave está en entender que la elección entre required y preferred no es solo una decisión de «qué quiero», sino de «qué pasa cuando las cosas fallan». Required te da certeza a cambio de disponibilidad. Preferred te da disponibilidad a cambio de certeza. Ninguna de las dos es incorrecta: depende del contrato de tu workload con el negocio.

Antes de definir cualquier regla de node affinity en producción, mapea explícitamente el conjunto de nodos etiquetados, sus taints, las tolerations de los pods, y simula al menos un escenario de fallo. Ese análisis previo vale más que cualquier configuración perfecta en papel que nadie ha probado bajo presión.


Este artículo forma parte de la guía completa de patrones de arquitectura Kubernetes en alexandre-vazquez.com.

Drivers de Helm explicados: Secrets, ConfigMaps y almacenamiento de estado

Drivers de Helm explicados: Secrets, ConfigMaps y almacenamiento de estado

Cuando trabajas en serio con Helm en entornos de producción, uno de los temas menos discutidos —pero con mayor impacto operativo— es cómo Helm almacena y gestiona el estado de los releases. Aquí es donde entran en juego los drivers de Helm. Entender este mecanismo no es un ejercicio académico: afecta directamente a la seguridad, la escalabilidad, la resolución de problemas y las estrategias de recuperación ante desastres.

Los drivers de Helm son una pieza fundamental que todo equipo que gestione Kubernetes en producción debería conocer en profundidad. En este artículo desgranaremos qué son, cómo han evolucionado, cuándo usar cada uno y qué consecuencias prácticas tiene elegir mal el backend de almacenamiento.


Qué es un driver de Helm y cómo se configura

Un driver de Helm define el mecanismo de almacenamiento backend que Helm utiliza para persistir la información de cada release: manifiestos generados, valores aplicados e historial de revisiones. Todo release de Helm tiene estado, y ese estado tiene que residir en algún lugar. El driver determina dónde y cómo se guarda esa información.

Los drivers de Helm se configuran mediante la variable de entorno HELM_DRIVER. Si no se establece explícitamente, Helm utiliza por defecto Kubernetes Secrets:

export HELM_DRIVER=secrets

Esta elección, aparentemente trivial, puede tener consecuencias operativas profundas, especialmente en entornos regulados o en clusters de gran escala donde la seguridad, la auditabilidad y el rendimiento son requisitos no negociables.

A diferencia de otras configuraciones de Helm que se establecen en ficheros de valores o en el propio chart, el driver opera a nivel de proceso del cliente Helm. Esto significa que distintos operadores o pipelines de CI/CD podrían, en teoría, utilizar drivers distintos sobre el mismo cluster si no se establece una política coherente. Esa inconsistencia puede llevar a situaciones confusas en las que Helm no encuentra el estado de un release simplemente porque el driver activo no coincide con el que se usó al instalar.


Los drivers de Helm disponibles

Helm ofrece actualmente tres drivers estables: secrets, configmaps y memory. Cada uno tiene un perfil de uso distinto y unas implicaciones de seguridad específicas que conviene conocer.

Driver Secrets (predeterminado)

El driver secrets almacena la información de los releases de Helm como Kubernetes Secrets en el namespace de destino. Es el driver por defecto desde la introducción de Helm 3 y la opción recomendada para la mayoría de entornos de producción.

Los Secrets en Kubernetes están codificados en base64. Si bien base64 no es cifrado en sí mismo, los clusters que tienen activado el cifrado en reposo (encryption at rest) de la API de Kubernetes cifrarán estos objetos antes de escribirlos en etcd. Esto convierte al driver secrets en la opción alineada con los modelos de seguridad nativos de Kubernetes.

Además, los Secrets se integran de forma natural con RBAC (Role-Based Access Control). Se puede restringir quién tiene acceso de lectura o escritura a los Secrets del namespace donde se gestiona el release, lo que permite aplicar el principio de mínimo privilegio sobre la información de estado de Helm.

Cada release almacenado por este driver genera un Secret con un nombre de la forma sh.helm.release.v1.<release-name>.v<revision>. Helm mantiene un historial de revisiones, y cada revisión tiene su propio Secret. El flag --history-max de Helm permite limitar cuántas revisiones se retienen:

helm upgrade my-release ./chart --history-max 5

Sin ese límite, en releases con muchos upgrades, el número de Secrets puede crecer indefinidamente y saturar el namespace o generar presión innecesaria sobre etcd.

Cuándo usarlo: prácticamente siempre en producción. Es el estándar de facto y el que mejor encaja con las políticas de seguridad empresariales.


Driver ConfigMaps

El driver configmaps almacena el estado de los releases de Helm como Kubernetes ConfigMaps. Funcionalmente, el comportamiento es muy similar al driver secrets: se crean objetos por release y revisión, se integra con RBAC y permite gestionar el historial de upgrades.

La diferencia fundamental está en la confidencialidad. Los ConfigMaps no tienen ningún tratamiento especial de privacidad en Kubernetes. Su contenido es texto plano legible para cualquier actor con permisos de lectura sobre el namespace. Esto incluye operadores, scripts de auditoría, herramientas de backup o cualquier proceso que liste recursos del cluster.

export HELM_DRIVER=configmaps

Para activar el driver de forma puntual en un comando:

HELM_DRIVER=configmaps helm list -n production

La legibilidad humana de los ConfigMaps —a diferencia de los Secrets, que requieren decodificar el base64— puede ser ventajosa en escenarios de depuración. Si un upgrade ha fallado y necesitas inspeccionar el manifiesto que Helm intentó aplicar, hacerlo desde un ConfigMap es más directo:

HELM_DRIVER=configmaps helm get manifest my-release -n staging

Sin embargo, esa misma legibilidad es un riesgo en entornos donde los valores del release contienen información sensible: credenciales de base de datos, tokens de acceso, configuración de conexiones a servicios externos. En esos casos, exponer esos valores en ConfigMaps es inadmisible desde el punto de vista de seguridad.

Cuándo usarlo: en entornos de desarrollo, staging sin datos sensibles, o cuando necesitas hacer troubleshooting específico del estado de un release. Nunca en producción si los valores contienen secretos.


Driver Memory

El driver memory almacena el estado de los releases únicamente en memoria RAM del proceso Helm. En el momento en que el proceso Helm termina —ya sea porque el comando completó o porque falló—, todo el estado se pierde irrecuperablemente.

export HELM_DRIVER=memory

A primera vista, un driver que no persiste nada puede parecer inútil. Sin embargo, tiene un nicho de uso muy concreto y valioso: pipelines de CI/CD donde no se quiere contaminar el cluster con estado de prueba.

Considera un pipeline de integración continua que valida un chart antes de desplegarlo:

HELM_DRIVER=memory helm upgrade --install test ./chart --dry-run

Con el driver memory, el proceso de Helm puede simular un upgrade, renderizar los manifiestos, validar los templates y ejecutar los hooks de pre-upgrade sin escribir nada en el cluster. Una vez que el proceso termina, no queda ningún rastro en etcd, ningún Secret ni ConfigMap que limpiar, ninguna revisión que gestionar.

Esto es especialmente útil en entornos de CI con permisos limitados sobre el cluster, o donde múltiples pipelines paralelas ejecutan validaciones simultáneamente sobre el mismo namespace y podrían colisionar si cada una intentara escribir su estado en Kubernetes.

El driver memory también es relevante en escenarios de testing local donde simplemente se quiere probar el renderizado de un chart sin necesidad de un cluster en absoluto —aunque en ese caso, helm template suele ser suficiente.

Cuándo usarlo: exclusivamente en CI/CD para validaciones, dry-runs, o testing de charts sin necesidad de persistencia. Nunca en despliegues reales.


Almacenamiento de estado en Helm: qué se guarda y por qué importa

Para entender por qué la elección del driver tiene consecuencias reales, es útil saber exactamente qué información persiste Helm en cada release.

Cada objeto de estado generado por Helm (sea un Secret o un ConfigMap) contiene:

  • El manifiesto renderizado: los manifiestos YAML de todos los recursos Kubernetes que componen el release en esa revisión.
  • Los valores aplicados: el resultado de la fusión de los valores por defecto del chart con los values sobrescritos por el operador. Esto incluye cualquier valor pasado con --set, --set-string o -f values.yaml.
  • Metadatos del release: nombre del release, namespace, versión del chart, timestamps, estado (deployed, failed, superseded…) y número de revisión.
  • Los hooks: definición de los hooks del chart para esa revisión.

La presencia de los valores aplicados en el estado es lo que hace que la elección del driver sea una decisión de seguridad. Si esos valores incluyen una contraseña de base de datos o un token de API, ese secreto queda persistido en el objeto de estado de Helm. Con el driver secrets y cifrado en reposo activo, esto es aceptable. Con el driver configmaps en texto plano, es un problema de seguridad.

Esto también explica por qué comandos como helm get values my-release pueden recuperar los valores que se usaron en el último upgrade: Helm los lee directamente del objeto de estado almacenado por el driver activo.


Evolución histórica de los drivers de Helm

Para entender el diseño actual de los drivers, hay que conocer la historia de Helm 2 y la decisión arquitectónica central que motivó Helm 3.

Helm 2 y Tiller: el origen del problema

Helm 2 utilizaba un componente servidor llamado Tiller, que se desplegaba como un Pod en el cluster Kubernetes, normalmente en el namespace kube-system. Tiller era el responsable de gestionar el estado de los releases y de aplicar los cambios al cluster.

En Helm 2, el driver por defecto era ConfigMaps, y Tiller era el único que tenía acceso a ellos. Esto simplificaba el modelo de seguridad —solo Tiller necesitaba permisos sobre esos objetos— pero a costa de convertir a Tiller en un punto único de fallo y, más problemáticamente, en un vector de escalada de privilegios.

Tiller solía desplegarse con permisos de cluster-admin para poder gestionar recursos en cualquier namespace. Esto era conveniente pero representaba un riesgo de seguridad inaceptable en entornos empresariales: comprometer el Pod de Tiller equivalía a comprometer todo el cluster.

Helm 3: eliminación de Tiller y Secrets como default

La llegada de Helm 3 en noviembre de 2019 supuso una reescritura significativa de la arquitectura. Las decisiones clave:

  1. Eliminación de Tiller: Helm 3 opera únicamente como cliente. No existe componente servidor. Helm habla directamente con la API de Kubernetes usando las credenciales del cliente (kubeconfig).

  2. Cambio del driver por defecto: de ConfigMaps a Secrets. Dado que ahora era el cliente Helm —con sus propias credenciales de usuario o service account— quien escribía el estado directamente en el cluster, tenía sentido usar Secrets para que esa información tuviera al menos el nivel de protección que Kubernetes ofrece para ese tipo de objeto.

  3. Arquitectura de drivers pluggable: la capa de almacenamiento se abstrajo mediante una interfaz interna limpia, lo que teóricamente permite implementar drivers alternativos (SQL, almacenamiento externo) sin modificar el núcleo de Helm.

Desde Helm 3, las mejoras en los drivers han sido incrementales: mejor manejo de errores, optimizaciones de rendimiento en clusters con muchos releases, y ajustes en la lógica de limpieza del historial. La abstracción central no ha cambiado de forma sustancial, lo que es una señal de madurez del diseño.


Implicaciones de seguridad: Helm Secrets vs ConfigMaps

La distinción entre el driver secrets y el driver configmaps va más allá de la nomenclatura. Tiene consecuencias prácticas en varios vectores de seguridad:

Control de acceso (RBAC)

Tanto los Secrets como los ConfigMaps son objetos de la API de Kubernetes y están sujetos a RBAC. La diferencia es que en muchas organizaciones los Secrets tienen políticas de acceso más restrictivas que los ConfigMaps por convención.

Si tu organización aplica políticas que limitan quién puede leer Secrets en namespaces de producción, usar el driver secrets se beneficia automáticamente de esas restricciones. Con ConfigMaps, cualquier rol con get/list sobre ConfigMaps en el namespace tendría acceso al estado de Helm, incluyendo valores sensibles.

Cifrado en reposo

Los Kubernetes Secrets son elegibles para cifrado en reposo mediante la configuración EncryptionConfiguration en el API server. Esta funcionalidad no aplica a los ConfigMaps: no existe un mecanismo nativo de Kubernetes para cifrar ConfigMaps en reposo.

# Ejemplo de configuración de cifrado en reposo para Secrets
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-key>
      - identity: {}

Con esta configuración activa, los objetos de estado de Helm almacenados con el driver secrets se cifran antes de escribirse en etcd. Los valores sensibles en el estado de Helm quedan protegidos incluso si alguien obtiene acceso directo a la base de datos de etcd.

Auditoría y trazabilidad

En entornos regulados (PCI-DSS, ENS, ISO 27001), los accesos a Secrets suelen registrarse en los audit logs de Kubernetes con mayor granularidad. Esto permite auditar quién accedió al estado de un release de Helm y cuándo, lo cual es un requisito común en auditorías de seguridad.


Casos prácticos y cuándo usar cada driver

Producción: siempre Secrets

En clusters de producción, el driver secrets debe ser el estándar sin excepciones. Las razones son claras:

  • Se integra con las políticas RBAC existentes.
  • Soporta cifrado en reposo.
  • Es el default de Helm, por lo que herramientas del ecosistema (Helm Operator, ArgoCD con Helm, Flux CD) lo esperan.
  • Minimiza la superficie de exposición de valores sensibles.

Si tu pipeline de despliegue en producción usa un service account de Kubernetes, asegúrate de que ese service account tenga permisos para crear, leer, actualizar y borrar Secrets en el namespace de destino:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: helm-release-manager
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

Desarrollo y troubleshooting: ConfigMaps con precaución

En entornos locales o de desarrollo donde no se manejan valores sensibles, el driver configmaps puede simplificar la inspección del estado. Ver el contenido de un ConfigMap con kubectl get configmap es más directo que decodificar un Secret.

Para inspeccionar el manifiesto de un release en un entorno de desarrollo:

HELM_DRIVER=configmaps helm get manifest my-app -n development

O para listar todos los releases almacenados como ConfigMaps:

HELM_DRIVER=configmaps helm list -n development

La clave es no usar este driver en entornos con valores sensibles y asegurarse de que el equipo es consciente del driver activo para evitar situaciones en las que Helm “no encuentra” un release porque el driver configurado no coincide con el que se usó al instalarlo.

CI/CD: Memory para validaciones sin efectos secundarios

En pipelines de integración continua, el driver memory permite ejecutar validaciones de charts sin escribir nada en el cluster:

# Validación de templates en CI
HELM_DRIVER=memory helm upgrade --install \
  --dry-run \
  --namespace ci-validation \
  my-chart ./charts/my-chart \
  -f values-ci.yaml

Este patrón es especialmente útil cuando el pipeline de CI tiene acceso al cluster (para poder hacer el dry-run contra la API real y validar los manifiestos), pero no se quiere dejar rastro de las ejecuciones de CI en etcd.

Otra aplicación: validar que un chart funciona con múltiples combinaciones de valores sin acumular historial de revisiones:

for env in dev staging prod; do
  HELM_DRIVER=memory helm upgrade --install test ./chart \
    -f values-${env}.yaml \
    --dry-run \
    --namespace validation 2>&1 | grep -E "(HOOK|MANIFEST|ERROR)"
done

Gestión del historial de revisiones y su impacto en almacenamiento

Un aspecto que muchos equipos descubren tarde es el crecimiento del número de objetos de estado en clusters con releases activos durante meses. Cada upgrade, rollback o instalación genera un nuevo objeto Secret (o ConfigMap) en el namespace.

Por defecto, Helm mantiene las últimas 10 revisiones. Esto se puede configurar al instalar o al actualizar:

helm install my-app ./chart --history-max 3
helm upgrade my-app ./chart --history-max 3

O de forma global mediante la variable de entorno:

export HELM_MAX_HISTORY=3

Para verificar cuántos objetos de estado ha generado Helm en un namespace:

kubectl get secrets -n production -l owner=helm | wc -l

En clusters con decenas de releases y años de historial sin limpiar, es habitual encontrar cientos o incluso miles de Secrets relacionados con Helm. Esto no es un problema crítico de rendimiento en Kubernetes, pero sí genera ruido en las herramientas de monitorización y puede complicar las auditorías de seguridad si nadie ha revisado ese estado en meses.


Migración entre drivers: consideraciones importantes

Una pregunta frecuente en equipos que adoptan estas buenas prácticas tardíamente es: ¿cómo migro los releases existentes de ConfigMaps a Secrets?

La respuesta corta es que Helm no proporciona un comando de migración nativo entre drivers. El cambio de driver no hace que Helm “adopte” automáticamente el estado existente en el formato anterior.

La aproximación práctica para migrar es:

  1. Exportar el estado actual de los releases relevantes:
    bash
    HELM_DRIVER=configmaps helm get values my-release -n production > values-backup.yaml

  2. Documentar la revisión actual:
    bash
    HELM_DRIVER=configmaps helm history my-release -n production

  3. Cambiar el driver a secrets en todos los entornos del equipo y en el pipeline de CI/CD.

  4. Hacer un upgrade del release (que puede ser idempotente, con los mismos valores) para que Helm cree el primer objeto de estado con el nuevo driver:
    bash
    HELM_DRIVER=secrets helm upgrade my-release ./chart -f values-backup.yaml -n production

  5. Limpiar manualmente los ConfigMaps de estado anteriores:
    bash
    kubectl get configmaps -n production -l owner=helm
    kubectl delete configmap sh.helm.release.v1.my-release.v1 -n production
    # Repetir para cada revisión

Este proceso requiere coordinación en equipos donde múltiples personas ejecutan comandos Helm sobre el mismo cluster, precisamente para evitar que alguien con el driver antiguo configurado ejecute un comando que no encuentre el estado o genere entradas duplicadas en el formato incorrecto.


Herramientas del ecosistema y drivers de Helm

Las herramientas GitOps más populares que usan Helm internamente —ArgoCD, Flux CD, Helmfile— tienen su propia gestión del driver:

  • ArgoCD usa el driver secrets por defecto cuando gestiona releases de Helm. No expone directamente la configuración del driver al usuario, lo que en la práctica significa que siempre usarás secrets si gestionas releases a través de ArgoCD.

  • Flux CD con HelmRelease también usa secrets por defecto a través del Helm Controller.

  • Helmfile respeta la variable de entorno HELM_DRIVER del shell donde se ejecuta.

Esto tiene una implicación importante: si gestionas un cluster con una mezcla de releases instalados directamente con Helm CLI y releases gestionados por ArgoCD, ambos usarán el driver secrets siempre que no hayas cambiado la configuración por defecto. La consistencia aquí es positiva.


Reflexión final: el driver como decisión arquitectónica

Los drivers de Helm raramente aparecen en las revisiones de arquitectura ni en los runbooks de los equipos de plataforma. Y sin embargo, influyen en la fiabilidad, la seguridad y la observabilidad de todos los flujos de trabajo que dependen de Helm.

Tratar la elección del driver como una decisión arquitectónica deliberada —y no como una configuración por defecto que nadie ha cuestionado— es uno de esos detalles pequeños que diferencian una plataforma Kubernetes madura de una automatización improvisada.

Las recomendaciones prácticas para cualquier equipo:

  • Estandariza el driver secrets en producción y documéntalo en el runbook del equipo.
  • Establece un límite de historial razonable (--history-max 5 o 10) para evitar la acumulación de objetos de estado.
  • Activa el cifrado en reposo de Secrets en el API server si manejas valores sensibles en tus releases de Helm.
  • Usa el driver memory en CI para validaciones y dry-runs que no deben persistir estado.
  • Audita periódicamente los objetos de estado de Helm en tus namespaces con kubectl get secrets -l owner=helm.

Estos pasos no requieren herramientas adicionales ni cambios en los charts. Son configuraciones operativas que cualquier equipo puede aplicar hoy y que reducen la superficie de riesgo y el ruido operativo de forma inmediata.


Este artículo forma parte de la guía completa de gestión de paquetes con Helm en alexandre-vazquez.com.

MinIO en modo mantenimiento: alternativas S3 open source para 2026

MinIO en modo mantenimiento: alternativas S3 open source para 2026

MinIO en modo mantenimiento: qué significa y cuáles son las mejores alternativas S3 open source

Si llevas tiempo usando MinIO como capa de almacenamiento de objetos en tu infraestructura —ya sea en Kubernetes, en bare metal o en un entorno híbrido— probablemente ya hayas visto el aviso: el repositorio upstream de la Community Edition ha pasado a modo mantenimiento. Sin nuevas funcionalidades, sin binarios oficiales, sin imágenes de contenedor.

La pregunta que está sobre la mesa en muchos equipos de infraestructura ahora mismo no es técnica, sino estratégica: ¿qué hago a partir de ahora?

Este artículo analiza qué ha cambiado exactamente, cuál es el impacto real según tu caso de uso y cuáles son las alternativas de almacenamiento de objetos compatibles con S3 más sólidas disponibles hoy. Con especial atención al contexto europeo: soberanía del dato, cumplimiento del RGPD y opciones reales para entornos on-premise en España y Europa.


MinIO fue durante años la referencia de facto para el almacenamiento de objetos compatible con S3 en entornos self-hosted. Su popularidad se asentó sobre tres pilares:

  • Compatibilidad con la API S3 de AWS: cualquier cliente o herramienta que hablase con S3 hablaba sin cambios con MinIO.
  • Facilidad de despliegue: un solo binario, una imagen Docker, y en minutos tenías un almacén de objetos operativo.
  • Rendimiento: optimizado para cargas de trabajo de alta concurrencia y grandes volúmenes de datos.

Era el componente predeterminado para backups, artefactos de CI/CD, logs, almacenamiento interno en Kubernetes (MLflow, Loki, Tempo, Thanos…) y cualquier carga de trabajo que necesitase un endpoint S3 sin depender de AWS.

A finales de 2025, MinIO anunció que la Community Edition pasaba a modo mantenimiento y que la distribución sería solo de código fuente, sin binarios ni imágenes de contenedor oficiales. Ese anuncio reabrió el debate sobre sostenibilidad del open source, gobierno de proyectos y los riesgos de depender de una infraestructura controlada por un único proveedor privado.


Qué ha cambiado exactamente

1. Modo mantenimiento: lo que implica en la práctica

«Modo mantenimiento» no significa que el proyecto esté muerto, pero sí marca un cambio fundamental en el contrato implícito con la comunidad:

  • Sin nuevas funcionalidades: el roadmap de MinIO Community Edition está cerrado.
  • Sin mejoras planificadas: las evoluciones de rendimiento, nuevas APIs o integraciones son exclusivas de la edición comercial.
  • Correcciones limitadas: solo se esperan parches para problemas críticos, no mantenimiento rutinario.
  • Sin revisión activa de pull requests: las contribuciones de la comunidad quedan en el aire.

El resultado práctico es una base de código estable pero estancada. Toda la innovación queda explícitamente reservada para las ofertas comerciales de MinIO Inc. El modelo de negocio es legítimo, pero cambia radicalmente el cálculo para los equipos que usaban la Community Edition como infraestructura crítica.

2. Distribución solo de código fuente

Este punto es el que genera más fricción operativa en el día a día. Si antes bastaba con un docker pull minio/minio, ahora:

  • No hay imágenes de contenedor oficiales para la Community Edition.
  • No hay binarios pre-compilados publicados por el proyecto.
  • Los equipos deben compilar MinIO desde el código fuente, mantener sus propias imágenes, gestionarlas en un registro privado y encargarse de todo el ciclo: firma, escaneo de vulnerabilidades y trazabilidad de procedencia.

Para muchos equipos, esto convierte lo que era un «componente plug-and-play» en una dependencia con carga operativa significativa. En entornos regulados —banca, sanidad, administración pública— justificar el uso de una dependencia de infraestructura sin soporte upstream activo ni distribución oficial se vuelve muy complicado.


Impacto real según tu caso de uso

No todas las organizaciones se ven afectadas igual. El impacto depende fundamentalmente de cómo estás usando MinIO.

Si usas MinIO como servicio S3 externo

Tienes una aplicación que apunta a un endpoint S3. Da igual si detrás hay AWS, MinIO u otra cosa. En este caso:

  • El impacto es moderado.
  • La migración es principalmente operativa: cambiar el endpoint y las credenciales.
  • El código de aplicación normalmente no requiere cambios.
  • El riesgo principal es el operativo: compilar y mantener tus propias imágenes mientras planificas la migración.

Si embutes o redistribuyes MinIO

Si tu producto:

  • Incluye MinIO como componente interno.
  • Construye pasarelas o funcionalidades sobre los internos de MinIO.
  • Depende del tooling operativo específico de MinIO.

Entonces el impacto es alto:

  • Heredas toda la responsabilidad de mantenimiento y seguridad.
  • A medio plazo, un fork interno se vuelve casi inevitable.
  • Las implicaciones de la licencia AGPL v3 deben revisarse cuidadosamente: distribuir software que incluye código AGPL sin cumplir sus condiciones es un riesgo legal nada trivial.

Para ISVs y vendors que embuten MinIO en sus productos, esto no es un ajuste táctico: es una reevaluación estratégica del componente de almacenamiento.


Seguridad y cumplimiento: el contexto europeo importa

En España y Europa, este cambio tiene una dimensión adicional que los equipos de infraestructura no pueden ignorar.

RGPD y soberanía del dato

El Reglamento General de Protección de Datos exige, entre otras cosas, que las organizaciones puedan demostrar el control efectivo sobre los sistemas que tratan datos personales. Usar software de infraestructura crítica sin soporte upstream activo ni distribución oficial verificable complica esta demostración, especialmente en auditorías.

Los equipos de seguridad y compliance que ya tenían dificultades para justificar el uso de software «comunitario» en entornos de producción con datos personales ahora tienen un argumento adicional para acelerar la migración hacia alternativas con gobernanza más clara.

Proveedores europeos de cloud y alternativas soberanas

En el ecosistema europeo existen alternativas relevantes:

  • Scaleway Object Storage (Francia): compatible con S3, infraestructura 100% en Europa.
  • Exoscale (Suiza/Austria): enfocado en soberanía del dato y cumplimiento europeo.
  • OVHcloud Object Storage (Francia): compatible con S3, con capacidad de despliegue en múltiples regiones europeas.
  • Hetzner Object Storage (Alemania): opción económica y sólida para cargas no críticas.

Para entornos self-hosted que necesitan permanecer on-premise, las alternativas open source analizadas más abajo son la respuesta natural.

Entornos regulados: la posición ha cambiado

En sectores como banca, sanidad o administración pública, donde los requisitos de seguridad y auditoría son más estrictos, la situación ha cambiado sustancialmente. No se trata solo de que MinIO Community Edition sea inseguro —no lo es por defecto— sino de que el modelo de responsabilidad compartida ha cambiado radicalmente. La organización ahora asume una carga que antes correspondía al proveedor upstream.


Forks y reacción de la comunidad

A fecha de publicación de este artículo, la situación de los forks es la siguiente:

  • Existen varios forks comunitarios centrados en preservar la experiencia de la consola web de MinIO (la UI).
  • No existe un fork de servidor ampliamente adoptado que sustituya plenamente al servidor MinIO con mantenimiento activo y comunidad consolidada.
  • El debate en la comunidad, refleja cautela: la mayoría de organizaciones no están apostando por forks apresurados sino por migraciones planificadas.

La ausencia de un fork de servidor sólido es significativa. Mantener un fork de un sistema de almacenamiento distribuido de alta complejidad requiere un nivel de compromiso en seguridad, corrección de bugs y evolución que pocas comunidades pueden sostener sin financiación. La señal del mercado es clara: migrar es preferible a forkear.


Las mejores alternativas de almacenamiento de objetos compatibles con S3

La respuesta del sector no está en encontrar «el nuevo MinIO» —un sustituto drop-in perfecto—, sino en seleccionar sistemas de almacenamiento cuyo modelo de gobernanza y mantenimiento encaje mejor con las necesidades a largo plazo. Aquí están las opciones más relevantes.

Ceph RGW (RADOS Gateway)

Mejor para: entornos empresariales que requieren alta disponibilidad y escalabilidad masiva.

Ceph es el sistema de almacenamiento distribuido open source más maduro y ampliamente desplegado en entornos de producción exigentes. Su componente RADOS Gateway (RGW) ofrece una API compatible con S3 (y también con Swift) sobre la capa de almacenamiento de objetos RADOS.

Puntos fuertes:

  • Ecosistema muy maduro, con más de una década de uso en producción.
  • Comunidad enorme y gobernanza sólida bajo la Linux Foundation.
  • Alta disponibilidad real: diseñado desde cero para resistir fallos de discos, nodos y racks.
  • Compatible con la mayoría de herramientas del ecosistema S3.
  • Soporte comercial disponible de múltiples proveedores (Red Hat, SUSE, etc.).

Consideraciones:

  • La complejidad operativa es real y no debe subestimarse. Ceph tiene una curva de aprendizaje pronunciada.
  • No es una opción para equipos pequeños sin experiencia en sistemas de almacenamiento distribuido o sin recursos para invertir en formación.
  • El despliegue mínimo recomendado implica varios nodos y discos dedicados.

Veredicto: si necesitas la opción más robusta y con mejor gobernanza a largo plazo para un entorno Kubernetes on-premise con requisitos serios de HA, Ceph RGW es la elección más sólida.


SeaweedFS

Mejor para: equipos que buscan simplicidad, rendimiento y licencia permisiva.

SeaweedFS es un sistema de almacenamiento de objetos y archivos distribuido que ha ganado tracción significativa como alternativa a MinIO, especialmente por su licencia Apache 2.0 —mucho más permisiva que la AGPL de Ceph o Garage— y su desarrollo activo.

Puntos fuertes:

  • Licencia Apache 2.0: sin las restricciones copyleft de AGPL, relevante para productos comerciales y OEMs.
  • Desarrollo activo y mantenimiento regular.
  • API S3 integrada, relativamente completa.
  • Arquitectura orientada al rendimiento para escrituras y lecturas masivas.
  • Más sencillo de operar que Ceph para casos de uso moderados.
  • Soporte para volúmenes FUSE y otras interfaces además de S3.

Consideraciones:

  • La compatibilidad S3 tiene algunas lagunas en casos extremos y operaciones avanzadas (Object Lock, ciertos comportamientos de versionado…).
  • La comunidad es activa pero más pequeña que la de Ceph.
  • Menor historial en entornos de producción de alto nivel que Ceph.

Veredicto: excelente punto de partida para equipos que migran desde MinIO y valoran la simplicidad operativa. La licencia Apache 2.0 es un diferenciador importante si estás construyendo un producto.


Garage

Mejor para: entornos self-hosted distribuidos geográficamente, homelab avanzado y proyectos con enfoque en resiliencia.

Garage es un proyecto relativamente joven pero con una filosofía muy bien definida: almacenamiento de objetos diseñado para la resiliencia en entornos distribuidos geográficamente, incluso con conectividad intermitente entre nodos. Es particularmente relevante para organizaciones que operan en múltiples ubicaciones físicas o que quieren el nivel máximo de control sobre su infraestructura de datos.

Puntos fuertes:

  • Arquitectura pensada desde cero para distribución geo-redundante: nodos en diferentes ubicaciones físicas, zonas o incluso países.
  • Desarrollo abierto y activo, con gobernanza transparente.
  • Muy ligero en recursos: puede correr en hardware modesto.
  • Compatible con la API S3 para operaciones habituales.
  • Filosofía «infraestructura soberana»: sin dependencia de servicios externos.

Consideraciones:

  • Licencia AGPL v3: las mismas consideraciones que con Ceph para productos comerciales que distribuyen el software.
  • Más joven que Ceph y SeaweedFS: menor historial en producción de gran escala.
  • La compatibilidad S3 es funcional pero no completa al nivel de Ceph.
  • Comunidad más pequeña, aunque activa y comprometida.

Veredicto: si operas en múltiples ubicaciones físicas o tu requisito principal es la resiliencia ante fallos de conectividad entre sitios, Garage merece una evaluación seria. También es una opción sólida para homelabs avanzados donde la soberanía del dato es prioritaria.


Zenko / CloudServer (Scality)

Mejor para: arquitecturas multi-cloud y casos de uso orientados a la federación de almacenamiento.

CloudServer (anteriormente S3 Server, parte del proyecto Zenko de Scality) es una implementación open source de la API S3. Scality es una empresa francesa con fuerte presencia en el mercado europeo de almacenamiento empresarial.

Puntos fuertes:

  • Implementación open source de la API S3, con buen nivel de compatibilidad.
  • Contexto europeo: Scality es una empresa francesa, relevante para organizaciones que valoran el origen del proveedor.
  • Orientado a escenarios multi-cloud y federación de backends de almacenamiento.

Consideraciones:

  • Las asunciones arquitectónicas son diferentes a las de MinIO: no es un reemplazo directo.
  • Requiere evaluación específica según el caso de uso.
  • El proyecto Zenko en su conjunto es más complejo que una simple capa S3.

Veredicto: interesante para casos de uso de federación multi-cloud o cuando el contexto empresarial europeo es relevante para la decisión de compra.


Estrategias recomendadas según tu situación

No hay una respuesta única. La estrategia correcta depende de dónde estés hoy y hacia dónde necesitas ir.

Si necesitas reducir el riesgo de forma inmediata

El primer paso no es migrar, sino estabilizar:

  1. Congela la versión de MinIO que estás usando hoy. No actualices de forma irreflexiva; en modo mantenimiento, cualquier cambio es tu responsabilidad.
  2. Construye, escanea y firma tus propias imágenes si estás en contenedores. Integra el proceso en tu CI/CD.
  3. Define y ensaya un plan de migración. No tienes que ejecutarlo mañana, pero sí tenerlo listo.
  4. Monitoriza los advisories de seguridad del proyecto de forma proactiva. Sin upstream activo, la detección temprana depende de ti.

Si operas Kubernetes on-premise con requisitos de alta disponibilidad

Ceph RGW es habitualmente la opción más sólida a largo plazo. La inversión en aprender Ceph es significativa, pero el retorno en estabilidad, gobernanza y ecosistema lo justifica para entornos de producción críticos.

Alternativa: si Ceph es demasiado complejo para tu equipo hoy, considera SeaweedFS como paso intermedio mientras creces en madurez operativa.

Si la flexibilidad de licencia es crítica (OEMs, ISVs)

Empieza la evaluación con SeaweedFS (Apache 2.0). Evita por defecto las opciones AGPL (Ceph, Garage) hasta tener claridad jurídica sobre las implicaciones para tu modelo de distribución.

Si operas en múltiples ubicaciones físicas o necesitas distribución geográfica real

Garage es el candidato natural. Evalúa si su nivel de compatibilidad S3 cubre tus casos de uso específicos antes de comprometerte.

Si el foco está en la soberanía del dato y el cumplimiento europeo

  • Para self-hosted: cualquiera de las opciones anteriores, con preferencia por las que tienen gobernanza más transparente (Ceph, Garage).
  • Para managed: considera proveedores europeos como Scaleway, OVHcloud o Exoscale antes de recurrir a AWS S3.

Si la UX operativa es un factor clave

No dependas de la consola web como infraestructura crítica. Los forks de la UI de MinIO son interesantes, pero:

  • Trata la UI como herramienta secundaria, no como componente core.
  • Invierte en workflows de automatización: infraestructura como código, pipelines de gestión, dashboards propios sobre las APIs de la alternativa elegida.
  • La dependencia de una UI web específica es precisamente el tipo de lock-in que te llevó a esta situación.

Preguntas frecuentes

¿Qué significa exactamente el modo mantenimiento para MinIO Community Edition?

Significa que la base de código upstream está efectivamente congelada. No habrá nuevas funcionalidades, solo correcciones de bugs críticos, y los pull requests de la comunidad no serán revisados activamente. Además, ya no se publican binarios precompilados ni imágenes de contenedor oficiales: los usuarios deben compilar desde el código fuente y mantener sus propias distribuciones.

¿Sigue siendo seguro usar MinIO Community Edition?

El código en sí no es inherentemente inseguro, pero el modelo de responsabilidad compartida ha cambiado. Los parches de seguridad para problemas no críticos serán más lentos o inexistentes. En producción —especialmente en entornos regulados o que manejan datos personales bajo RGPD— ahora debes monitorizar activamente los advisories, construir y escanear tus propias imágenes, y asumir una carga operativa que antes recaía en el upstream. Eso eleva el riesgo operativo de forma objetiva.

¿Cuál es la mejor alternativa open source a MinIO para Kubernetes?

Depende del caso de uso. Para entornos de alta disponibilidad a nivel empresarial, Ceph RGW es la opción más robusta. Para simplicidad y licencia Apache 2.0, SeaweedFS es excelente. Para distribución geo-redundante y resiliencia ante fallos de conectividad entre sitios, evalúa Garage. No existe un reemplazo drop-in universal: cada alternativa requiere evaluación específica.

¿Debo forkear MinIO o migrar a otra solución?

Para la mayoría de organizaciones, migrar es claramente preferible a forkear. Mantener un fork de un sistema de almacenamiento distribuido de alta complejidad implica un compromiso a largo plazo con seguridad, corrección de bugs y potencial backporting de funcionalidades que pocas comunidades pueden sostener. La tendencia del sector apunta inequívocamente hacia la migración a sistemas con desarrollo upstream activo y gobernanza clara.

¿Cómo afecta esto a productos que embuten MinIO (uso OEM)?

El impacto para OEMs e ISVs es alto. Heredáis toda la responsabilidad de mantenimiento y seguridad. A medio plazo, un fork interno se vuelve casi inevitable, con todo el coste que eso implica. Y las implicaciones de la licencia AGPL v3 deben revisarse con asesoramiento jurídico: distribuir software que incluye código AGPL sin cumplir sus condiciones es un riesgo legal real. Esto no es un ajuste táctico; es una reevaluación estratégica del componente de almacenamiento.

¿Qué opciones existen para entornos con requisitos de soberanía del dato en Europa?

Para entornos self-hosted, las alternativas open source analizadas (Ceph, SeaweedFS, Garage) son todas válidas, con diferente perfil de complejidad y licencia. Para entornos managed que necesitan permanecer en infraestructura europea, proveedores como Scaleway, OVHcloud, Exoscale o Hetzner ofrecen almacenamiento de objetos compatible con S3 con centros de datos exclusivamente en Europa, lo que simplifica el cumplimiento del RGPD y las auditorías de localización de datos.


Conclusión: no es una crisis, pero sí un punto de inflexión

El cambio de MinIO Community Edition a modo mantenimiento no supone una rotura técnica inmediata. No hay un día D en que todo deje de funcionar. El riesgo no es técnico a corto plazo; es de gobernanza y sostenibilidad a medio y largo plazo.

La pregunta real es: ¿quieres que tu infraestructura de almacenamiento de objetos sea un componente que gestionas activamente y cuya evolución controlas, o prefieres depender de un upstream que ha decidido reservar su desarrollo para una oferta comercial?

Para muchos equipos, este momento es la oportunidad natural de hacer algo que probablemente debería haberse hecho antes: tratar el almacenamiento de objetos como infraestructura crítica real, con todos los criterios de gobernanza, seguridad y sostenibilidad que eso implica.

Las opciones están sobre la mesa. La decisión no puede postergarse indefinidamente.