Tu servicio está lento. El load average se ve picante. Los usuarios se quejan. Pero dentro de la VM, el “uso” de CPU ni siquiera es tan alto. Escalas el despliegue, asignas una instancia más grande, y de alguna manera sigue pareciendo que corres en cemento húmedo.
Aquí es donde aparecen el tiempo de CPU steal y la sobrecarga de virtualización: silenciosos, como un impuesto que no votaste. Si ejecutas Ubuntu 24.04 sobre KVM, VMware, Xen o en la “nube”, necesitas poder probar (no adivinar) cuándo el hipervisor es el cuello de botella y qué palancas realmente tienes.
Qué es realmente el tiempo de steal de CPU (y qué no es)
El tiempo de steal de CPU es la porción de tiempo en que tu sistema operativo invitado quería ejecutarse en una CPU física, pero el hipervisor dijo “ahora no”. Lo mide el kernel del invitado y se expone como %steal (herramientas como mpstat, top, pidstat) y como tiempo steal en los contadores de contabilidad de CPU de Linux.
En términos sencillos: pagaste por vCPUs. El host tenía otras ideas. Ese cubo de “otras ideas” es el steal. No lo ves como alto user CPU o system CPU dentro de la VM, porque tu VM no estaba ejecutándose. Estaba esperando fuera de escena mientras otros invitados tomaban el micrófono.
El tiempo de steal no es lo mismo que el throttling
El throttling es un comportamiento de límite aplicado: se impide que la VM use más que cierta asignación (a menudo mediante cuotas de cgroups, modelos de créditos de CPU o políticas del proveedor). El tiempo de steal es un comportamiento de contención: estás listo para ejecutarte, pero no se te programa en el núcleo físico porque otro está usándolo.
El tiempo de steal no es I/O wait
%iowait es tiempo esperando la finalización de E/S mientras la CPU está ociosa. %steal es tiempo en que podrías haber estado ejecutando, pero el hipervisor no programó tu vCPU. Ambos hacen que la aplicación sea lenta. Solo uno implica la planificación de CPU del host.
Qué significa la sobrecarga de virtualización en la práctica
Incluso con cero steal, las máquinas virtuales pagan una sobrecarga: salidas de VM, emulación para ciertos dispositivos, virtualización de interrupciones, costes de flush de TLB, tablas de páginas anidadas, y toda la gimnasia del planificador que viene con multiplexar muchas vCPUs sobre menos físicas.
En KVM/VMware modernos esa sobrecarga suele ser pequeña cuando está bien configurado. Cuando no lo es, suele ser porque haces una de estas cosas:
- Ejecutas demasiadas vCPUs por VM en relación con tu paralelismo real (penalización SMP, retrasos de planificación).
- Usas hosts sobremuestreados (alta steal/ready).
- Generas altas tasas de interrupciones (red, almacenamiento) mediante dispositivos virtuales subóptimos o ajustes de offload.
- Mezclas cargas sensibles a latencia con procesos batch que consumen CPU en el mismo host.
Idea parafraseada de Werner Vogels (fiabilidad/operaciones): “Todo falla, todo el tiempo; diseña y opera con esa suposición”. También aplica aquí: asume que el hipervisor está ocupado a menos que puedas demostrar lo contrario.
Broma #1: Si nunca te han hecho gaslighting por un hipervisor, no has vivido. La VM jura que está inactiva mientras tus usuarios juran que está ardiendo.
Hechos interesantes y contexto (vale la pena saber)
- El tiempo de steal apareció con la era de la paravirtualización en Linux: los primeros invitados Xen podían observar explícitamente cuando eran desprogramados, lo que más tarde se convirtió en un concepto estándar de contabilidad.
- “CPU Ready” de VMware es anterior a la mayoría de los dashboards de Linux: muchas empresas aprendieron este problema a través de gráficas de vCenter antes de que los administradores Linux vieran
%stealen sus herramientas. - “vCPU” en la nube nunca ha garantizado “un núcleo físico”: la sobreasignación es normal; lo que compras es un share de programación, no una silla con tu nombre.
- Alto steal suele ser un síntoma del host, no del invitado: dentro de la VM no puedes ver directamente quién es el vecino ruidoso, solo que estás perdiendo tiempo de programación.
- Puede que el steal sea casi cero y aun así estés lento: la sobrecarga de virtualización puede aparecer como mayor coste de cambio de contexto, sobrecarga de interrupciones y mayor latencia en la cola sin picos evidentes de steal.
- El CPU steal suele ser intermitente: el cron de un vecino, un backup o un ETL pueden crear picos periódicos que arruinan la latencia p99 mientras las métricas promedio parecen bien.
- La sincronización de tiempo y la virtualización solían ser un desastre: kernels y hipervisores antiguos tenían deriva de reloj y problemas con TSC; Ubuntu 24.04 lo oculta en gran medida, pero cuando el tiempo se vuelve raro, las métricas de planificación se vuelven engañosas rápido.
- NUMA importa más de lo que la gente admite: la colocación de vCPU a través de sockets y la localidad de memoria pueden causar latencia que parece “CPU” pero en realidad es tráfico de memoria entre nodos.
- La virtualización anidada agrava la sobrecarga: ejecutar un hipervisor dentro de una VM añade más salidas de VM y capas de planificación; el invitado ve más variabilidad incluso si el steal parece moderado.
Síntomas: cómo el steal de CPU se disfraza de todo lo demás
El tiempo de steal de CPU es un maestro del disfraz porque el SO invitado no está “ocupado” de la manera habitual. Tu hilo es ejecutable, pero la vCPU no obtiene tiempo en silicio. Eso crea un patrón específico de dolor:
Patrones de síntomas que debes reconocer
- Load average alto con uso de CPU moderado: tareas ejecutables se acumulan, pero el user/system CPU dentro de la VM no sube proporcionalmente.
- Picos de latencia en endpoints no relacionados: todo se vuelve más lento, no solo un subsistema. p99 se desploma.
- Timeouts “aleatorios”: handshakes TLS, consultas de base de datos y consumidores de colas fallan por timeout con tráfico moderado.
- Caída de throughput tras “escalar” con éxito: añadir más vCPUs o más pods incrementa hilos ejecutables, lo que puede aumentar la contención de planificación y empeorar las cosas.
- Soft lockups y latidos perdidos: demonios del sistema no se ejecutan a tiempo; el monitoreo registra flapping.
- Pausas de GC más largas: no porque el GC haya cambiado, sino porque el proceso es desprogramado a mitad de la colección.
Piensa en el steal como una cola oculta delante de tu CPU. El invitado ve la cola, pero no al cajero.
Guía rápida de diagnóstico (primero / segundo / tercero)
Este es el orden de triaje que ahorra tiempo. No improvises. Estás tratando de responder una pregunta: ¿Estamos faltos de CPU porque el host no nos programa, o porque realmente estamos limitados por CPU dentro de la VM?
Primero: probar o descartar el steal
- Revisa
%steala lo largo del tiempo (mpstat), no un solo snapshot. - Correlaciona con picos de latencia (métricas de la app) o profundidad de colas (métricas del sistema).
- Si el steal es consistentemente >2–5% durante incidentes, trátalo como contención real. Si salta a 20–50%, eso es un incidente aun cuando tus gráficas de CPU parezcan “bien”.
Segundo: separar “CPU ocupada” de “CPU bloqueada”
- Revisa la cola de ejecución y los patrones de cambio de contexto (
vmstat,pidstat). - Revisa
iowaity latencia de disco (iostat) para no culpar al steal de bloqueos de almacenamiento. - Revisa la presión de memoria (
free,psi) para no confundir paradas por reclaim con contención de CPU.
Tercero: identifica la palanca que realmente tienes
- Si estás en una instancia compartida en la nube: migra el tipo/tamaño de instancia o muévete a hosts dedicados/núcleos aislados.
- Si gestionas KVM/VMware propio: arregla la sobreasignación, dimensionado de vCPU, alineación NUMA y colocación para vecinos ruidosos.
- Si estás en Kubernetes: decide si arreglas el nodo (nivel host), limitas el pod (cgroups) o mueves cargas de trabajo.
Tareas prácticas: comandos, interpretación, decisiones
Querías trabajo real, no teoría. Aquí hay tareas concretas que puedes ejecutar en invitados Ubuntu 24.04 para detectar steal y distinguirlo de otros cuellos de botella. Cada tarea incluye (1) el comando, (2) qué significa la salida y (3) la decisión que tomas.
Task 1: Confirmar que estás virtualizado (y cómo)
cr0x@server:~$ systemd-detect-virt
kvm
Significado: Estás dentro de una VM, así que el steal es candidato. Si imprime none, el steal no es tu problema (mira CPU limitada, E/S, bloqueos o throttling).
Decisión: Si estás virtualizado, continúa con las comprobaciones de steal. También nota la familia de hipervisor; influye en qué métricas existen en el lado del host.
Task 2: Snapshot de contabilidad de CPU incluyendo steal
cr0x@server:~$ top -b -n 1 | head -n 5
top - 10:42:18 up 12 days, 3:21, 2 users, load average: 8.21, 7.90, 6.44
Tasks: 312 total, 3 running, 309 sleeping, 0 stopped, 0 zombie
%Cpu(s): 9.2 us, 3.1 sy, 0.0 ni, 79.8 id, 0.4 wa, 0.0 hi, 0.3 si, 7.2 st
MiB Mem : 32085.4 total, 1954.7 free, 11892.3 used, 18238.4 buff/cache
MiB Swap: 4096.0 total, 4096.0 free, 0.0 used. 20193.1 avail Mem
Significado: st es steal. Aquí es 7.2%—no es sutil. El load average es alto mientras el idle es alto: firma clásica de contención.
Decisión: Si st es no trivial, deja de culpar “la app” hasta que midas más. Pasa a muestreo en series temporales a continuación.
Task 3: Serie temporal de steal de CPU por núcleo
cr0x@server:~$ mpstat -P ALL 1 10
Linux 6.8.0-41-generic (server) 12/30/2025 _x86_64_ (8 CPU)
10:42:30 AM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
10:42:31 AM all 12.5 0.0 4.0 0.3 0.0 0.5 9.8 72.9
10:42:31 AM 0 10.0 0.0 3.0 0.0 0.0 0.0 18.0 69.0
10:42:31 AM 1 9.0 0.0 2.0 0.0 0.0 0.0 1.0 88.0
...
Significado: El steal es desigual entre vCPUs. Eso suele indicar contención del planificador y efectos de colocación de vCPU. Si %steal es consistentemente >2–5% durante periodos lentos, es real.
Decisión: Si el steal es intermitente, captura durante incidentes y correlación con latencia. Si es constante, considera migrar o redimensionar; el steal constante es un problema de capacidad.
Task 4: Comparar load average con cola de ejecución y tareas bloqueadas
cr0x@server:~$ vmstat 1 10
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
6 0 0 201312 98244 932112 0 0 1 8 520 1220 10 3 76 1 10
8 0 0 200984 98244 932120 0 0 0 0 610 1405 12 4 70 0 14
Significado: r (ejecutable) es alto; b (bloqueado) es bajo. La CPU tiene st significativo. Esto apunta a contención de planificación de CPU en lugar de bloqueo por E/S.
Decisión: Si b es alto, investiga almacenamiento y red. Si r es alto con steal, enfócate en contención del host o dimensionado de vCPU.
Task 5: Comprobar si te están limitando por cgroups (containers/systemd)
cr0x@server:~$ systemctl show -p CPUQuota -p CPUQuotaPerSecUSec docker
CPUQuota=
CPUQuotaPerSecUSec=infinity
Significado: No hay cuota systemd de CPU a nivel de servicio. En Kubernetes o runtimes de contenedores, el throttling puede ocurrir aún a nivel de pod/container cgroup.
Decisión: Si existen cuotas, arregla el throttling primero; el throttling puede parecer “CPU lenta” incluso con cero steal.
Task 6: Buscar evidencia de throttling de contenedores (cgroup v2)
cr0x@server:~$ cat /sys/fs/cgroup/cpu.stat
usage_usec 389432187
user_usec 312903110
system_usec 76529077
nr_periods 21903
nr_throttled 0
throttled_usec 0
Significado: nr_throttled y throttled_usec muestran throttling de CPU por cgroup. Aquí es cero, así que el invitado no se está auto-throttleando.
Decisión: Si el throttling es distinto de cero y crece, aumenta límites/requests, reduce concurrencia o muévete a pools de CPU dedicados; no culpes al steal todavía.
Task 7: Revisar PSI (Pressure Stall Information) por señales de saturación de CPU
cr0x@server:~$ cat /proc/pressure/cpu
some avg10=2.14 avg60=1.05 avg300=0.34 total=9823412
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
Significado: PSI CPU some indica tiempo con al menos una tarea esperando CPU. Si esto sube con %steal, tienes contención. Si PSI es alto con bajo steal, puede que simplemente estés CPU-bound dentro de la VM.
Decisión: PSI alto + steal alto: mueve hosts/tipos de instancia o reduce overcommit del host. PSI alto + steal bajo: optimiza el uso de CPU de la aplicación o añade cómputo real.
Task 8: Separar I/O wait de steal con iostat
cr0x@server:~$ iostat -xz 1 5
Linux 6.8.0-41-generic (server) 12/30/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
11.9 0.00 3.8 0.4 9.7 74.2
Device r/s w/s rKB/s wKB/s await svctm %util
nvme0n1 2.1 3.4 45.2 88.3 1.20 0.35 0.3
Significado: await del disco es bajo, %util es mínimo; el almacenamiento no es el cuello de botella. Mientras tanto %steal está alrededor de 10%.
Decisión: No afines discos. Escala al host por contención de planificación o al dimensionado/colocación de la VM.
Task 9: Revisar carga de softirq de red (la sobrecarga de NIC virtual puede parecer problemas de CPU)
cr0x@server:~$ cat /proc/softirqs | head
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
HI: 12 8 4 7 6 5 4 6
TIMER: 1234567 1122334 1099887 1044556 1001222 998877 912345 887766
NET_TX: 23456 19876 21011 18765 16543 17002 16001 15888
NET_RX: 345678 312345 321002 300111 280987 275444 260999 255888
Significado: Contadores grandes de NET_RX/NET_TX pueden implicar altas tasas de paquetes, lo que puede aumentar la sobrecarga de CPU y la jitter de programación—especialmente con offloads malos o paquetes pequeños.
Decisión: Si la red está caliente, revisa el tipo de NIC (virtio vs emulada), offloads y distribución de interrupciones. Pero no confundas esto con steal; pueden coexistir.
Task 10: Revisar topología de vCPU expuesta al invitado
cr0x@server:~$ lscpu | egrep 'CPU\\(s\\)|Thread|Core|Socket|NUMA'
CPU(s): 8
Thread(s) per core: 1
Core(s) per socket: 8
Socket(s): 1
NUMA node(s): 1
Significado: Este invitado ve una topología simple de 1 socket. Si ves múltiples sockets/nodos NUMA en una VM y la carga no es NUMA-aware, puedes obtener outliers de latencia desagradables sin gran steal.
Decisión: Para apps sensibles a latencia, prefiere menos sockets y conteos de vCPU sensatos; evita VMs SMP enormes a menos que realmente necesites paralelismo.
Task 11: Buscar rarezas en la fuente de reloj/tiempo (puede amplificar el dolor de planificación)
cr0x@server:~$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock
Significado: kvm-clock es lo esperado bajo KVM. Fuentes de reloj extrañas o saltos frecuentes de tiempo pueden hacer que el análisis de latencia sea engañoso y romper la lógica de timeouts.
Decisión: Si observas saltos de tiempo (logs muestran errores tipo “time went backwards”), investiga la configuración de tiempo del hipervisor y NTP/chrony. No persigas bugs de rendimiento fantasma.
Task 12: Capturar evidencia de retrasos de planificación con perf sched (corto, controlado)
cr0x@server:~$ sudo perf sched record -a -- sleep 10
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 6.214 MB perf.data (12698 samples) ]
cr0x@server:~$ sudo perf sched latency --sort max | head
Task | Runtime ms | Switches | Avg delay ms | Max delay ms
postgres:checkpointer | 120.11 | 1321 | 0.18 | 22.34
nginx:worker | 98.45 | 1887 | 0.12 | 18.02
Significado: Grandes retrasos máximos de planificación sugieren que el invitado no recibe CPU a tiempo. Esto puede deberse a steal (contención del hipervisor) o a saturación de CPU dentro del invitado.
Decisión: Si %steal es alto al mismo tiempo, tienes contención del host. Si %steal es bajo, probablemente estás sobrecargado dentro de la VM (o sufres contención por locks).
Task 13: Revisar mensajes del kernel por soft lockups y stalls de planificación
cr0x@server:~$ journalctl -k --since "1 hour ago" | egrep -i "soft lockup|rcu_sched|stall|watchdog" | tail -n 5
Dec 30 10:11:02 server kernel: watchdog: BUG: soft lockup - CPU#3 stuck for 22s! [java:28199]
Dec 30 10:11:02 server kernel: rcu: INFO: rcu_sched detected stalls on CPUs/tasks: { 3-... } (detected by 2, t=5250 jiffies)
Significado: Los stalls pueden ocurrir con steal extremo porque el invitado simplemente no es programado lo suficiente. También pueden ocurrir por lockups reales de CPU, pero en VMs la contención es un disparador frecuente.
Decisión: Si esto se correlaciona con picos de steal, escala a capacidad de plataforma/host. Si se correlaciona con CPU al 100% y steal cero, investiga hilos desbocados o bugs de kernel.
Task 14: Confirmar pistas del hipervisor (DMI) para migas de escalamiento
cr0x@server:~$ sudo dmidecode -s system-product-name
Standard PC (Q35 + ICH9, 2009)
Significado: Esto puede ayudarte a identificar la pila de virtualización (tipo de máquina QEMU aquí). Es útil cuando necesitas levantar un ticket o emparejar problemas conocidos.
Decisión: Registra esto junto con gráficas de steal y marcas de tiempo del incidente. Al escalar, lleva evidencia, no sensaciones.
Task 15: Si sospechas sobrecommit del host, revisa steal desde contadores estilo Prometheus node_exporter (lado invitado)
cr0x@server:~$ awk '/^cpu /{print "steal_jiffies=" $9}' /proc/stat
steal_jiffies=981223
Significado: El noveno campo de la línea agregada cpu es tiempo de steal en jiffies. Es cómo los exporters calculan la tasa de steal. Útil para verificar lo que tu monitorización debería ver.
Decisión: Si tus dashboards muestran cero steal pero /proc/stat está moviéndose, tu monitorización está rota. Arregla la monitorización antes de “arreglar” producción.
Task 16: Verificar si hay características de virtualización disponibles (pistas paravirt)
cr0x@server:~$ dmesg | egrep -i "kvm-clock|pvclock|Hypervisor detected|Booting paravirtualized kernel" | tail -n 5
[ 0.000000] Hypervisor detected: KVM
[ 0.000000] kvm-clock: Using msrs 4b564d01 and 4b564d00
Significado: El clock paravirtualizado y la detección del hipervisor son normales y generalmente buenos. Si no ves las características virt que esperas, puede que estés usando dispositivos emulados o te falten optimizaciones, lo que aumenta la sobrecarga.
Decisión: Si faltan features virt, revisa la configuración de la VM (controladores virtio, passthrough del modelo de CPU, tipo de máquina correcto). Arreglar eso puede reducir la sobrecarga incluso si el steal es bajo.
Lo que puedes controlar vs lo que controla el proveedor
El tiempo de steal es políticamente incómodo: la causa raíz suele ser “alguien más en el host”. En una nube pública, ese alguien no va a unirse a tu llamada de incidente. Tu trabajo es identificar qué palancas existen en tu entorno y tirar de las que realmente pagan.
Si ejecutas en nube pública con tenancy compartida
- Puedes: cambiar familia de instancia, dimensionar arriba/abajo, mover regiones/zonas de disponibilidad, usar hosts/instancias dedicadas, usar funciones de pinning de CPU donde estén disponibles, ajustar autoscaling y concurrencia, reducir el conteo de vCPU para igualar el paralelismo real.
- No puedes: arreglar la sobreasignación del host directamente, expulsar vecinos ruidosos, controlar versiones del kernel del host, o cambiar la política de scheduling del hipervisor.
Guía práctica: si los picos de steal son regulares y correlacionan con horarios de negocio, trátalo como una desalineación de clase de capacidad. Muévete a un nivel más predecible (hosts dedicados, rendimiento reservado, ofertas de núcleos aislados). Pagar un poco más suele ser más barato que pagar a ingenieros para explicar por qué el p99 está embrujado.
Si ejecutas tu propio KVM/Proxmox/OpenStack
- Puedes: establecer ratios vCPU:pCPU, pinnear vCPUs, aislar cores del host, ajustar el scheduler del host, hacer cumplir la colocación de cargas de trabajo, monitorizar colas de ejecución del host y evitar mezclar servicios de latencia con batch compute.
- No puedes: evitar las leyes de la física; si sobreasignas y todos se ponen ocupados, el steal es el resultado correcto.
Si ejecutas en VMware
VMware llama al primo de esta métrica “CPU Ready”. El %steal del invitado suele correlacionar, pero no perfectamente. El significado operativo es el mismo: la VM es ejecutable pero no programada. Usa métricas del host para el veredicto final, pero no descartes el steal del invitado—suele ser la señal temprana.
Si ejecutas Kubernetes sobre VMs
Tienes dos capas de contención de programación:
- Cgroups de Linux (límites de pod) pueden throttlear CPU, lo cual no es steal pero se siente similar para las apps.
- Planificación del hipervisor puede robar CPU del nodo, que el kubelet no puede arreglar.
Operativamente: si sube el steal a nivel de nodo, reprogramar pods dentro del mismo pool de nodos a menudo no hace nada. Necesitas reemplazo de nodos, otro tipo de instancia o capacidad dedicada.
Tres mini-historias corporativas (anonimizadas, plausibles, dolorosas)
Mini-historia 1: El incidente causado por una suposición equivocada
Ejecutaban una API de pagos en VMs Ubuntu. El incidente empezó como “la base de datos está lenta”. Los equipos de app vieron timeouts, los SREs vieron subir el load average, y todos miraron las gráficas de la base de datos como si les debieran dinero.
La suposición equivocada: “La CPU está bien porque el uso es solo 25%.” Ese número estaba dentro del invitado. Los hosts estaban sobreasignados, y un tenant de análisis batch en el mismo clúster de hipervisores empezó un job pesado de CPU. Las VMs de pagos mostraron %steal alrededor de 15–30% durante cuarenta minutos. No suficiente para poner la CPU al 100%, pero suficiente para destrozar la latencia tail. Las solicitudes caducaron, los reintentos se acumularon y la presión de colas empeoró todo.
La depuración se retrasó porque los dashboards del equipo no incluían tiempo de steal. Tenían el scrape de node exporter, pero su panel de Grafana solo graficaba user/system/idle. Así la narrativa del incidente se volvió “la app está lenta por razones desconocidas”, que es una forma educada de decir “no medimos lo que importa”.
Una vez añadieron rate(node_cpu_seconds_total{mode="steal"}) a su panel estándar, pudieron ver la firma de contención claramente. La solución real fue aburrida: mover la tier de pagos a un clúster menos sobreasignado y limitar la colocación del tenant batch en horas de negocio. La acción del postmortem no fue “optimizar SQL”. Fue “dejar de asumir que el uso de CPU dentro de una VM es toda la historia”.
Mini-historia 2: La optimización que salió mal
Un equipo de plataforma intentó “ayudar” a un servicio Java duplicando sus vCPUs: de 8 a 16. Su razonamiento era limpio: más núcleos, más throughput. El servicio mejoró un poco en pruebas sintéticas, lo que fue suficiente para declarar victoria y poner el cambio en producción.
Luego la latencia p99 empeoró. No siempre. Solo en pico. Así trabajan estos gremlins: esperan hasta que estás confiado.
El clúster de hosts estaba moderadamente sobreasignado. Una VM de 16 vCPU es más difícil de programar que una de 8 vCPU cuando el hipervisor intenta co-programar vCPUs de forma justa. Bajo contención, la VM pasó más tiempo esperando a que el host encontrara ranuras de CPU. El %steal del invitado subió. La aplicación también aumentó sus tamaños de pool de hilos porque “veía” más CPUs, lo que incrementó hilos ejecutables y contención por locks. Habían mejorado la capacidad del sistema para pelear consigo mismo.
El rollback—reducir de nuevo a 8 vCPUs y limitar la concurrencia—estabilizó la latencia. La mejora eventual fue más matizada: moverse a una familia de instancias optimizada para CPU y usar menos vCPUs más rápidas con mejor rendimiento por núcleo. La lección: escalar el número de vCPU puede incrementar la latencia de planificación y la sobrecarga. Más grande no siempre es más rápido; a veces solo es más grande.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Un equipo de infraestructura tenía una costumbre: cada dashboard de nodo de producción incluía steal, PSI y una alerta simple de “sospecha de contención del host” cuando el steal superaba un umbral pequeño por más de unos minutos. A nadie le emocionaba. No era glamoroso. Simplemente estaba ahí, como los cinturones de seguridad.
Un martes, un cliente reportó lentitud intermitente. Las gráficas de la app parecían mayormente bien, pero la latencia p95 de un endpoint clave se disparaba cada 20–30 minutos. La primera vista del nodo mostró picos de %steal que coincidían casi perfectamente con el patrón. Sin adivinanzas. Sin debate de dos horas sobre garbage collection.
Catalogaron el incidente como “contención de plataforma”, movieron la carga a un pool de nodos dedicados y el problema desapareció. Más tarde, descubrieron que los picos periódicos los generaban tareas de mantenimiento en otros invitados que compartían el mismo clúster de hosts. El equipo no necesitó probar quién era el vecino; solo necesitó proteger el servicio.
La práctica que los salvó no fue un tweak clever de kernel. Fue tener las métricas correctas, ya graficadas, y una vía de escalado que no dependía del humor de alguien. Lo aburrido gana otra vez.
Broma #2: La mejor solución de rendimiento a veces es la reubicación. Es como mudarse de apartamento porque el vecino practica batería—técnicamente no es tu bug, pero sigue siendo tu problema.
Errores comunes: síntoma → causa raíz → solución
Esta sección es intencionalmente directa. Estos son los errores que mantienen incidentes vivos más tiempo del necesario.
1) Load average alto, CPU “idle” alto
Síntoma: El load average sube, la CPU de usuario se mantiene moderada, la latencia de la app aumenta.
Causa raíz: Tiempo de steal de CPU (contención del host) o retraso severo de planificación.
Solución: Confirma %steal vía mpstat. Si es sostenido, migra a capacidad menos contendida, reduce conteo de vCPU, o muévete a hosts dedicados. Si controlas el hipervisor, reduce overcommit y aisla workloads ruidosos.
2) “Escalamos y empeoró”
Síntoma: Aumentar vCPUs o réplicas de pods incrementa la latencia tail.
Causa raíz: VMs SMP más grandes son más difíciles de programar; la mayor concurrencia aumenta hilos ejecutables y contención por locks; el host está sobreasignado.
Solución: Escala horizontalmente con más VMs más pequeñas (si la capacidad del host lo permite) o muévete a una familia de instancias mejor. Limita la concurrencia. Prefiere menos vCPUs con mejor rendimiento por núcleo para servicios de latencia.
3) Confundir throttling de cgroup con steal
Síntoma: Servicio lento, uso de CPU se estanca, steal cercano a cero.
Causa raíz: Cuotas de CPU (nr_throttled sube) o límites de Kubernetes demasiado bajos.
Solución: Inspecciona /sys/fs/cgroup/cpu.stat. Ajusta límites/requests, usa QoS Guaranteed para workloads críticos, o muévete a nodos con CPU dedicadas.
4) Culpar al almacenamiento porque “iowait existe”
Síntoma: Aparece un pequeño %wa, la app está lenta, la gente grita “disco”.
Causa raíz: Steal de CPU o retraso de planificación, mientras el almacenamiento está bien.
Solución: Usa iostat -xz para validar await y %util. Si el disco no está ocupado y el steal es alto, deja de ajustar almacenamiento.
5) No capturar durante el incidente
Síntoma: “Lo comprobamos después y parecía bien.”
Causa raíz: El steal es intermitente; te lo perdiste.
Solución: Añade un script ligero de captura para incidentes (snapshots de mpstat/vmstat/iostat), habilita monitorización continua y alerta sobre steal sostenido por encima del umbral.
6) Tratar el steal como “ruido normal de la nube”
Síntoma: Picos regulares de latencia aceptados como destino.
Causa raíz: Desajuste de clase de instancia; la tenancy compartida está demasiado contended para el SLA.
Solución: Usa capacidad dedicada/aislada para servicios críticos en latencia, o muévete a un nivel donde la programación predecible sea parte del producto.
7) Sobre-optimizar el kernel e ignorar la colocación
Síntoma: Semanas de tweaks de sysctl, sin mejora sostenida.
Causa raíz: Contención del host; la afinación no puede ganarle al scheduler cuando no te programan.
Solución: Resuelve capacidad y colocación primero. Afina después, y solo con beneficios medidos.
Listas de verificación / plan paso a paso
Checklist A: Cuando una VM está lenta y sospechas sobrecarga de virtualización
- Confirma virtualización:
systemd-detect-virt. - Snapshot de CPU con steal:
top(buscarst). - Muestra steal a lo largo del tiempo:
mpstat -P ALL 1 60durante el incidente. - Revisa cola de ejecución y steal juntos:
vmstat 1 60. - Descarta disco:
iostat -xz 1 10. - Descarta throttling de cgroup:
cat /sys/fs/cgroup/cpu.stat. - Revisa PSI:
cat /proc/pressure/cpuy PSI de memoria si hace falta. - Si confirmas steal, decide: migrar/redimensionar/dedicar vs afinar el invitado.
Checklist B: Si controlas el hipervisor (KVM/VMware) y el steal es alto
- Encuentra overcommit de CPU del host y presión de cola de ejecución (herramientas del host).
- Reduce ratios vCPU:pCPU para clusters de baja latencia.
- Deja de mezclar hogs batch de CPU con servicios de latencia en los mismos hosts/pools.
- Pinnea o aisla CPUs para workloads críticos si tu modelo operativo lo soporta.
- Valida alineación NUMA: evita abarcar sockets innecesariamente.
- Usa dispositivos virtio y modelos de CPU modernos; evita emulación cuando sea posible.
- Vuelve a probar con
mpstaty p95/p99 de la aplicación tras cada cambio.
Checklist C: Para nodos Kubernetes sobre VMs
- Revisa steal a nivel de nodo (
mpstat) y throttling de contenedores (cpu.stat). - Si el steal es alto: reemplaza nodo(s) o muévete a otro pool de nodos/tipo de instancia.
- Si el throttling es alto: ajusta límites, usa políticas de CPU manager dedicadas, o cambia QoS.
- No “arregles” aumentando réplicas sin entender la contención; puedes amplificarla.
Umbrales operativos (opinión, útiles)
- Steal < 1% la mayor parte del tiempo: aceptable para muchas cargas.
- Steal 1–5% sostenido: investigar para sistemas sensibles a latencia; probablemente contención.
- Steal 5–10% sostenido: espera impacto visible para usuarios bajo carga; trátalo como tema de plataforma.
- Steal > 10% durante incidentes: escalar y migrar; afinar dentro de la VM es mayormente teatral.
Preguntas frecuentes
1) ¿Cuál es un porcentaje “malo” de CPU steal?
Para cargas batch, unos pocos puntos pueden ser tolerables. Para servicios de latencia, sostenido >2–5% es un problema real. Picos >10% que coinciden con picos p99 son básicamente una confesión.
2) ¿Puede el steal ser cero y aun así sufrir sobrecarga de virtualización?
Sí. La sobrecarga puede venir de costes de interrupciones, salidas de VM, efectos NUMA y emulación de dispositivos. Eso aparece como mayor coste de CPU por solicitud y peor latencia tail sin una gran firma de steal.
3) ¿Cómo se relaciona el steal de Linux con CPU Ready de VMware?
Son conceptualmente similares: “ejecutable pero no programado”. CPU Ready se mide en el host y suele ser la más autorizada en entornos VMware. El steal del invitado sigue siendo valioso para el diagnóstico inicial.
4) ¿Por qué sube el load average cuando la CPU dentro de la VM parece idle?
El load incluye tareas ejecutables. Si tu vCPU no está programada (steal), las tareas permanecen ejecutables más tiempo, inflando el load average aunque el invitado no consuma user/system CPU.
5) ¿El CPU steal siempre es un vecino ruidoso?
A menudo, sí. Pero también puede ser mantenimiento del host, migración en vivo, cambios de frecuencia de CPU por restricciones térmicas, o decisiones de sobreasignación hechas por tu propio equipo de virtualización.
6) ¿Debería moverme simplemente a instancias más grandes para arreglar el steal?
A veces ayuda, a veces empeora. Las VMs más grandes pueden ser más difíciles de programar bajo contención. Prefiere moverte a una clase menos contended (núcleos dedicados/aislados, familias optimizadas para cómputo) en lugar de solo aumentar conteo de vCPU.
7) ¿Cómo alerto correctamente sobre steal?
Alerta sobre tasa de steal sostenida, no por picos únicos: por ejemplo, steal >2% durante 5–10 minutos en nodos de workloads sensibles a latencia. Correlaciona con p95/p99 de latencia para evitar falsas alarmas en tiers batch.
8) ¿Puedo arreglar el steal dentro del invitado afinando el kernel?
No realmente. Puedes reducir la demanda de CPU (menos hilos, menos busy polling), lo que reduce la frecuencia con que necesitas CPU, pero no puedes forzar al hipervisor a programarte más. El steal es un problema de capacidad/colocación.
9) ¿Qué hay del escalado de frecuencia de CPU y síntomas “tipo steal”?
El escalado de frecuencia afecta cuánto trabajo obtienes por slice de tiempo programado, pero no aparece como steal. Si ves steal bajo y throughput reducido, revisa rendimiento por núcleo, comportamiento turbo y diferencias de modelo de CPU entre hosts.
10) ¿Pinnear vCPUs lo arregla todo?
Pinnear puede reducir jitter para workloads específicos, pero reduce flexibilidad y puede bajar la utilización global. Es una herramienta para servicios críticos con necesidades claras de CPU, no una solución universal para una mala planificación de capacidad.
Conclusión: siguientes pasos que realmente mueven la aguja
Cuando Ubuntu 24.04 corre en una VM, el “uso” de CPU es solo la mitad de la historia. El tiempo de steal es el capítulo que falta: el tiempo que tu workload quería ejecutarse pero no se le permitió. Si no lo mides, diagnosticarás mal incidentes, perderás tiempo afinando el subsistema equivocado y, de forma accidental, empeorarás el rendimiento con escalados bienintencionados.
Haz esto, en este orden:
- Añade steal a tus dashboards estándar (y verifica que coincida con
/proc/stat). - Configura una alerta para steal sostenido en nodos críticos para latencia.
- Durante el próximo incidente, captura
mpstat,vmstat,iostatycpu.statde cgroups para la ventana exacta. - Si confirmas steal, deja de afinar dentro de la VM y cambia colocación/capacidad: migra, reduce overcommit o compra programación dedicada.
- Dimensiona correctamente el conteo de vCPU: menos núcleos más rápidos suelen vencer a más vCPUs lentas y contendidas para latencia tail.
La mejor parte: una vez que puedas probar el steal, tendrás el argumento correcto con el equipo correcto. Y dejarás de interrogar a la base de datos por crímenes cometidos por el hipervisor.