Las claves en 30 segundos
- Qué ha pasado: Netflix ha publicado cómo funciona single-tenant Titus, un modo de su plataforma de contenedores que da a una tarea su propia máquina virtual sin cambiar la API ni el despliegue.
- El motivo: un cgroup limita CPU, memoria y disco, pero no hay cgroup para la caché de último nivel ni para el ancho de banda de memoria. Dos contenedores pueden cumplir su cuota y uno seguir frenando al otro.
- Lo que más daño les hizo fue la red: el limitador HTB serializa el tráfico con un bloqueo global, y una lluvia de paquetes pequeños acaba quitando CPU a contenedores que no han hecho nada.
- No dedican máquina a todo: son pocas cargas, grandes, de larga duración y sensibles a interrupciones, y hay que estar en una lista blanca. En un nodo pequeño la sobrecarga fija puede comerse decenas de puntos porcentuales.
- El matiz que no sale en su artículo: una VM dedicada no es un servidor dedicado. AWS fija los núcleos a cada instancia, pero avisa de que algunos tamaños pueden compartir líneas de la caché de último nivel.
- Faltan cifras: Netflix explica con detalle su prueba de aislamiento, pero no publica los resultados.
Netflix lanza millones de contenedores a la semana sobre decenas de miles de instancias EC2 con Titus, su plataforma interna basada en Kubernetes. Por defecto, cada servidor aloja contenedores de muchos equipos, y esa densidad es lo que hace rentable el modelo. Lleva años publicando cómo reduce las interferencias entre ellos: reasigna contenedores a núcleos con un modelo predictivo, mide la latencia de planificación con eBPF y ha perseguido bloqueos del kernel. Así que el artículo que publicaron Samartha Chandrashekar y Nick Parker el 22 de octubre tiene su miga, porque viene a decir que todo eso sube el listón para todos, pero hay cargas para las que nunca va a bastar.
El producto es interno, pero el razonamiento sirve igual para quien corre Kubernetes o LXC sobre sus propios servidores. Hemos leído el artículo completo, contrastado sus afirmaciones con la documentación del kernel, de AWS y de Proxmox, y probado en contenedores lo que se puede probar sin hardware específico.
Lo que un cgroup no puede repartir
Titus usa todo lo que ofrece Linux: cgroups para CPU, memoria y disco, namespaces, seccomp, AppArmor, el planificador, el controlador iocost para E/S y limitación de tráfico por contenedor. Sirve para lo que el kernel puede partir. El problema es lo que está compartido en el propio hardware, por debajo de donde llega una cuota.
| Recurso | Qué pone el sistema | Qué se le escapa |
|---|
| CPU | Cuotas y planificador | La latencia de la cola de ejecución sube aunque el contenedor esté dentro de su cuota |
| Caché y ancho de banda de memoria | Nada en cgroups | Un vecino con mucho conjunto de trabajo expulsa tus líneas de caché y satura el camino a la RAM |
| Red | Limitación por contenedor | El procesado de recepción corre en softirq y se cobra a la CPU que toque; los paquetes pequeños agotan un núcleo mucho antes de llegar al límite de ancho de banda |
| Disco | iocost | Un vecino que satura la cola del dispositivo serializa la E/S de todos, y en almacenamiento en bloque cloud los topes por volumen e instancia también son un presupuesto común |
| El resto | Poco o nada | Recuperación de la caché de páginas, tráfico NUMA, interrupciones de TLB, PCIe y GPU, recorte térmico del procesador y bloqueos globales del kernel |
La consecuencia es incómoda para quien monitoriza por consumo: nadie supera su cuota y aun así la cola de latencia empeora. Si tus paneles enseñan CPU y RAM por contenedor, el problema no sale en ninguno.
De todo lo anterior, a Netflix lo que más le ha dolido es la red, y por un motivo con cierta ironía. La limitación por contenedor usa HTB, una disciplina de colas que contabiliza bytes y paquetes bajo un bloqueo global. Si un contenedor manda una avalancha de paquetes pequeños, la pelea por ese bloqueo quema CPU del kernel y deja sin tiempo a los vecinos para procesar su propio tráfico, con retransmisiones TCP y picos de latencia en servicios que no han hecho nada. La herramienta pensada para evitar vecinos ruidosos se convierte en uno.
Quienes peor lo llevan son los trabajos con estado y mucho caudal, como Flink o los conectores de datos. Si uno se queda atrás, puede tener que restaurar desde un checkpoint y parar muchos minutos, así que una interferencia breve no se promedia: acaba despertando a alguien de guardia.
Por qué no eligió microVM ni gVisor
Antes de construir nada, Netflix sopesó cuatro caminos y descartó tres:
| Opción | Lo que da | Por qué no fue la elegida |
|---|
| Seguir afinando el kernel compartido | Mejora a todos sin pedir nada a nadie | Reduce la diferencia pero no la cierra, porque el hardware sigue compartido. Siguen invirtiendo, pero no para estas cargas |
| MicroVM (Firecracker, Kata) | Muy buen aislamiento y arranque de unos 150 ms | Pide instancias metal o virtualización anidada, habría que operar el hipervisor y en su comparación rondan un 15 % de sobrecarga y no admiten GPU |
| gVisor | Aislamiento de tipo sandbox sin hipervisor | Interceptar llamadas al sistema penaliza justo las cargas con mucha E/S, y la versión abierta va por detrás de la interna |
| EC2 directo | Una VM y nada más | El equipo de la aplicación recupera lo que no quería: elegir instancias, parchear y dimensionar. Y volver suele ser muy difícil |
El argumento de fondo es el que más se puede reutilizar. MicroVM y gVisor están pensados para inquilinos que desconfían entre sí, y Netflix no tiene ese problema: sus equipos no se atacan. Necesita aislamiento de recursos y de rendimiento, no una frontera de seguridad más fuerte. Son dos preguntas distintas y se responden con cosas distintas, aunque a menudo se metan en el mismo saco.
Cómo funciona, y cómo se traduce a un Kubernetes cualquiera
La solución es sencilla de enunciar: una tarea, un nodo a su medida y nada más al lado. Ese nodo sigue siendo un host de Titus que parchea el equipo de plataforma, y el equipo de la aplicación ve la misma API y el mismo despliegue. Entrar y salir del modo es cambiar una etiqueta, lo que convierte la decisión en reversible.
Por debajo no hay magia, solo piezas estándar de Kubernetes más un autoescalador. Netflix usa Karpenter para crear al vuelo el nodo más pequeño en el que quepa la tarea, y el resto es esto:
- Antiafinidad con todos los demás pods, para que nada se coloque al lado.
- Un taint en el nodo, para que solo entren cargas que lo toleren.
- Una clase de prioridad que protege la tarea de la expulsión.
- Sin desfragmentación: nadie reubica esa tarea para recompactar la flota.
En un clúster propio, lo esencial cabe en pocas líneas. Marcas el nodo:
kubectl taint nodes nodo-flink-1 dedicado=flink:NoSchedule
kubectl label nodes nodo-flink-1 dedicado=flink
Y en la especificación del pod:
nodeSelector:
dedicado: flink
tolerations:
- key: dedicado
operator: Equal
value: flink
effect: NoSchedule
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: flink-taskmanager
Ojo, esto solo impide que caigan encima otros pods de esa misma aplicación y que entren cargas sin la tolerancia. Los DaemonSet de monitorización y red siguen ahí, igual que los sidecars de plataforma en Titus.
Un detalle organizativo vale tanto como el técnico: está detrás de una lista blanca a propósito. Netflix lo explica sin rodeos, si fuera una casilla en la API, casi todos la marcarían, porque nadie quiere un servidor más ruidoso, y se perdería la eficiencia del conjunto.
El resultado que nadie esperaba: la flota se compacta más
Dedicar nodos debería empeorar el aprovechamiento. Según Netflix, puede pasar lo contrario, por dos motivos.
El primero es que el daño está muy concentrado: pocos causantes y muchas víctimas. Antes, para proteger las cargas sensibles, la plataforma las repartía con reglas de antiafinidad, y un servidor que se deja medio vacío para cumplir esa regla no se puede llenar con otra cosa. Al sacar a esos pocos a nodos propios, las reglas sobran y lo que queda se empaqueta mejor.
El segundo tiene que ver con los tamaños. Sobre el papel, el empaquetado multitenant tiene un techo más alto, pero AWS vende tamaños discretos y en instancias grandes quedan huecos que no se llenan nunca. Ajustar una instancia a cada carga les ha funcionado mejor que perseguir ese techo teórico. Ese segundo argumento depende mucho del catálogo de AWS, y en un servidor físico propio pesa bastante menos.
Hay una ventaja más, en mantenimiento. Parchear un host lleno obliga a una evacuación coordinada de muchos inquilinos con sus presupuestos de interrupción. En un nodo dedicado solo hay que mover una tarea, y los hosts compartidos se vacían antes porque ya no tienen a las cargas más delicadas.
Lo que cuesta de verdad
El artículo no vende humo, y esta es su parte más útil a la hora de decidir:
- Sobrecarga fija: kubelet, sidecars y servicios del sistema consumen una porción de CPU casi constante y unos cuantos GB de memoria. En un nodo pequeño pueden llevarse decenas de puntos porcentuales; en los más grandes, un par.
- Hueco de tamaño: entre lo que pide la tarea y el siguiente tamaño disponible siempre queda algo, y aquí no hay vecino que lo aproveche.
- Arranque: en un host compartido, una tarea tarda segundos porque el servidor ya está en marcha y las capas de la imagen están en caché. En uno dedicado toca esperar decenas de segundos antes siquiera de empezar a bajar la imagen.
- Reservas en caliente menos eficaces: con muchos tamaños distintos, una instancia preparada solo sirve a quien pida justo ese tamaño. Por eso Netflix reduce el menú de formas disponibles y deja poca capacidad preparada.
- Más carga en el plano de control: más nodos pequeños significan más objetos que seguir y más lanzamientos contra la API de EC2, sobre todo en una conmutación regional.
Su recomendación sale de esas cuentas: cargas grandes, de larga duración y sensibles al rendimiento y a las interrupciones, con un tamaño cercano al de un nodo, y mejor pocas réplicas grandes que muchas pequeñas. Para lo pequeño, lo que va a ráfagas o lo muy elástico, el compartido es más barato y escala más rápido.
Lo que el artículo no cuenta
Conviene leerlo con tres matices.
Una VM dedicada no es un servidor dedicado. Una de las figuras del artículo dibuja el nodo dedicado con hardware dedicado, caché y ancho de banda de memoria incluidos. Eso se cumple respecto a otros contenedores de Netflix, pero una r7i.2xlarge sigue siendo un trozo de un servidor físico que AWS comparte con quien toque. El libro blanco de Nitro concreta bastante: los núcleos se asignan en exclusiva a cada instancia de rendimiento fijo, y tampoco se comparten cachés L1 y L2, pero «algunos tamaños de instancia pueden compartir de forma no simultánea algunas líneas de la caché de último nivel». Sobre el ancho de banda de memoria entre clientes no hemos encontrado compromiso. Netflix ha sacado de la ecuación a sus propios vecinos, que no es lo mismo que quedarse sola en el servidor.
No publica resultados. Describe bien su método: un contenedor de referencia se mide a solas y luego junto a otro diseñado para molestar, con inundación UDP, contaminación de caché, tormentas de fsync y abuso del planificador, para sacar una puntuación compuesta. Pero ni el texto ni las figuras traen un solo porcentaje de mejora en aislamiento o en empaquetado. Hay que quedarse con el razonamiento y no atribuirle cifras que no ha dado.
PSI no ve la caché, aunque el esquema lo sugiera. Una figura indica que la aplicación afectada se ve como presión de CPU y memoria en PSI. Con matices: según el código del kernel, para PSI una tarea en CPU es productiva, y una tarea que espera a la RAM tras un fallo de caché sigue en CPU. PSI puede reflejar los efectos indirectos, no la contención en sí.
Qué cambia en un cloud privado
Aquí la situación da la vuelta en varias cosas. Lo primero es tener claro qué se comparte en cada nivel:
| Dónde corre la carga | Kernel | Núcleos | Caché L3 y bus de memoria | Red del host |
|---|
| Contenedor en host compartido (Kubernetes, Docker, LXC) | Compartido | Con cuotas, o fijados si lo configuras | Compartidos | Compartida |
| VM en host compartido (KVM, Proxmox VE, VMware) | Propio | Compartidos salvo que los fijes | Compartidos | Compartida |
| VM con núcleos fijados | Propio | Reservados si nadie más usa esos núcleos | Compartidos dentro del mismo dominio de L3 | Compartida |
| Host para una sola carga | Propio | Tuyos | Tuyos | Tuya |
Pasar de contenedor a VM da un kernel propio y elimina los bloqueos que venían de compartirlo. Pero dos VMs en el mismo host siguen compitiendo por la caché, el bus de memoria y la red física, igual que dos contenedores. En Proxmox, cpuunits y cpulimit reparten tiempo de CPU, nada más, y la opción affinity fija núcleos con un aviso en su propia documentación: añade complejidad, se lleva mal con migrar a nodos con menos núcleos y la E/S de la VM queda fuera del ajuste. Si fijas, fija dentro de un mismo nodo NUMA y comprueba con lscpu -e qué núcleos comparten L3 y qué hilos son hermanos.
A cambio, en infraestructura propia tienes tres ventajas que Netflix no tiene en EC2:
- Ves el hardware. Desde el host puedes leer la ocupación de la caché y el ancho de banda de memoria por grupo de tareas. Dentro de una VM de cloud público, normalmente no.
- Decides quién va con quién. Puedes llevar el trabajo por lotes a otro nodo o a otro socket sin pedir permiso a nadie, y sabes que no hay un cliente desconocido al otro lado de la caché.
- Los nodos ya están encendidos. Netflix dejó las instancias metal en este modo porque tardan más en arrancar. Con servidores permanentes, ese coste se paga una vez.
La contrapartida es que no existe el autoescalador que te crea un nodo a medida en segundos: tu reserva en caliente es la capacidad libre que compres, y si dedicas un servidor a una carga de 8 vCPU, te sobra máquina. Por eso la regla de Netflix sirve tal cual, y antes de dedicar nada merece la pena comprobar si la carga pide bare metal o virtualización y si Kubernetes sobre Proxmox te aporta algo.
Cómo saber si te está pasando
La señal típica son picos en p99 que no cuadran con la carga propia y que dependen del nodo donde cae la réplica. Si tienes la duda, sigue este orden. Es la versión reducida de lo que ya contamos en las seis señales de un servidor Linux lento, pero mirando al vecino.
1. Descarta tu propia cuota. Es la causa más frecuente y la más fácil de confundir. En el directorio del cgroup:
cat /sys/fs/cgroup/<ruta-del-contenedor>/cpu.stat | grep -E 'nr_periods|nr_throttled|throttled_usec'
cat /sys/fs/cgroup/<ruta-del-contenedor>/cpu.pressure
Lo hemos probado con docker run --cpus=0.5 y un bucle de 3 segundos: 30 de 30 periodos limitados, 1,47 s en throttled_usec y otro tanto en cpu.pressure. Es decir, PSI sube también cuando te frena tu propio límite, tal como dice el comentario del kernel. Si cpu.pressure crece pero nr_throttled apenas se mueve, entonces sí estás esperando por otros.
2. Mira la red. mpstat -P ALL 1 y la columna %soft, junto con /proc/softirqs. Un núcleo clavado en softirq con poco ancho de banda suele apuntar a muchos paquetes pequeños. Las retransmisiones se ven con nstat -az TcpRetransSegs.
3. Compara instrucciones por ciclo. Si la CPU va igual de ocupada pero hace menos trabajo, ahí está la caché o el bus:
perf stat -e instructions,cycles,cache-misses -p <PID> -- sleep 30
Compáralo con la misma carga en un nodo tranquilo. En VMs puede que los contadores de hardware no estén disponibles, según proveedor y configuración.
4. Pregunta al hardware desde el host. Linux lo expone en resctrl, el sistema de archivos común a Intel RDT y AMD QoS. Mide la ocupación de L3 y el ancho de banda por grupos, y en procesadores compatibles también permite repartirlos:
grep -owE 'rdt_a|cat_l3|mba|cqm_llc|cqm_mbm_total' /proc/cpuinfo | sort -u
mount -t resctrl resctrl /sys/fs/resctrl
cat /sys/fs/resctrl/mon_data/mon_L3_00/llc_occupancy
Si grep no devuelve nada, tu procesador no lo admite o el hipervisor no lo expone, algo habitual dentro de una VM. Netflix también lo está evaluando para su modo compartido, sin darlo todavía por solución.
5. Si estás dentro de una VM, mira %st. El tiempo robado indica que el hipervisor dio tu turno a otra. No detecta caché ni memoria, pero si sube de forma sostenida ya tienes un motivo para hablar con el proveedor.
6. Haz tu propia prueba diferencial. Es la idea más aprovechable de Netflix y no requiere su plataforma. Mide la aplicación a solas, repite junto a una carga agresiva y compara p99 y rendimiento, varias veces y con la mediana, porque las medias esconden justo los picos que buscas.
Cuándo compensa una máquina para una sola carga
Sí suele compensar si:
- Una caída o un retraso obligan a una recuperación cara, como un checkpoint, una resincronización o una reconstrucción de caché.
- La carga mueve mucho tráfico o muchos paquetes pequeños.
- Importa más un p99 estable que unos puntos de aprovechamiento: pagos, bases de datos con mucha concurrencia, streaming, GraphQL en el borde.
- El tamaño de la carga se acerca al de una máquina, o puedes agrupar pocas réplicas grandes.
- Ya gastas capacidad en reglas de antiafinidad para mantener cargas separadas.
No compensa si:
- Son servicios pequeños, a ráfagas o muy elásticos.
- Es una carga a la que un p99 peor no le cuesta nada, como trabajos por lotes.
- Todavía no has medido nada. Antes de comprar hardware hay que descartar cuotas mal ajustadas, que son lo más común.
Si tu preocupación es la seguridad entre clientes y no el rendimiento, la pregunta es otra. Ahí sí encajan las VMs, las microVM y los sandbox, y la propia documentación de Kubernetes sobre multitenencia distingue ambos problemas.
Preguntas frecuentes
¿Qué es un vecino ruidoso?
Una carga que degrada a otras con las que comparte hardware, sea por CPU, caché, memoria, disco o red. No necesita saltarse ningún límite: basta con que consuma mucho de un recurso que nadie reparte.
¿Los cgroups no aíslan los contenedores?
Aíslan lo que el kernel puede partir, como tiempo de CPU, memoria asignada y E/S. No hay cgroup para la caché de último nivel ni para el ancho de banda de memoria, que están compartidos en el propio procesador. Linux tiene para eso resctrl, una interfaz aparte que depende de que el procesador lo admita.
¿Una máquina virtual resuelve el problema?
En parte. Da kernel propio, con lo que desaparecen los bloqueos que venían de compartirlo. Pero dos VMs en un mismo host siguen compartiendo caché L3, bus de memoria y red física. Para quitarlos de en medio hay que dejar esa carga sola en el host o en su propio dominio de L3.
¿Qué es single-tenant Titus?
Un modo de Titus, la plataforma interna de contenedores de Netflix, en el que una tarea recibe su propia instancia EC2 a medida. Conserva la API y el despliegue, el equipo de plataforma sigue administrando el host, y se accede por lista blanca.
¿Netflix ha dejado la nube o el modelo multitenant?
No. Titus sigue en AWS, y el modo compartido sigue siendo el predeterminado: Netflix dice que es la opción correcta para la mayoría de cargas y sigue invirtiendo en él, por ejemplo cambiando HTB por una limitación con eBPF que no depende de ese bloqueo.
¿PSI sirve para detectar un vecino ruidoso?
Sirve como indicio de espera por CPU, disco o recuperación de memoria, pero con dos límites. También sube cuando te frena tu propia cuota, y no mide directamente la contención de caché o del bus de memoria. Hay que cruzarlo con cpu.stat y con los contadores de hardware.
Fuentes
- Samartha Chandrashekar y Nick Parker, Netflix TechBlog, «Single-tenant Titus: VM-level isolation on a multi-tenant container platform» (22 de octubre de 2026): netflixtechblog.com.
- AWS, «The Security Design of the AWS Nitro System», apartado sobre canales laterales: docs.aws.amazon.com.
- Documentación del kernel Linux, PSI, cgroup v2 y resctrl.
- Código del kernel Linux, definición de tarea productiva en PSI: kernel/sched/psi.c.
- Proxmox VE, opciones de CPU de las máquinas virtuales (
cpuunits, cpulimit, affinity y NUMA): pve.proxmox.com/pve-docs/chapter-qm.html.
- Kubernetes, taints y tolerancias, asignación de pods a nodos y multitenencia.
- Karpenter, documentación del proyecto: karpenter.sh/docs.
- Linux,
perf stat: man7.org/linux/man-pages/man1/perf-stat.1.html.
¿Tienes picos de latencia que no sabes a quién atribuir, o varias cargas que no deberían compartir máquina? En Cloud Privado te ayudamos a medirlo y a decidir qué va en un cloud privado y qué merece un servidor bare metal para esa carga.