Picos de CPU cada pocos minutos: la tarea programada que debes revisar primero

¿Te fue útil?

Todo está bien… hasta que deja de estarlo. Los gráficos parecen un mar en calma y luego—cada pocos minutos—tu CPU se dispara como si compitiera en un sprint. Aumentan las latencias. Los ventiladores se vuelven ruidosos. El teléfono de guardia empieza a hacer eso de vibrar hasta caerse de la mesita.

Si los picos son periódicos, asume que es una tarea programada hasta que se demuestre lo contrario. Y la tarea programada que debes revisar primero es la que tú no programaste: los propios temporizadores del sistema y los agentes del proveedor, no tu aplicación. Específicamente: systemd timers y cron (y sus “amigos útiles” como logrotate, updatedb, temporizadores de apt, agentes de monitorización y clientes de backup).

Playbook de diagnóstico rápido (primeros 10 minutos)

Los picos periódicos son un regalo. Los picos aleatorios son una novela de horror. Los picos periódicos son una invitación del calendario.

Minuto 0–2: confirma el patrón y captura al culpable

  1. Confirma la periodicidad. Mira tu monitorización: ¿los picos ocurren cada 1, 5, 10, 15, 60 minutos, o en :00 o :30? Esas son cadencias clásicas de temporizadores.
  2. Durante el siguiente pico, captura a los principales ofensores. Usa top o pidstat. No “mires después”; el nombre del proceso es la mitad de la batalla.
  3. Revisa la cola de ejecución y el iowait. Alto %usr/%sys sugiere trabajo de CPU; alto %wa sugiere un cuello de botella de I/O con efectos en CPU (compresión, checksums, trabajo tipo fsck, cifrado, etc.).

Minuto 2–6: identifica el planificador

  1. Lista los timers de systemd. Esto atrapará los casos de “juro que no hay cron”.
  2. Lista las fuentes de cron. Crontabs de usuario, /etc/crontab y los directorios /etc/cron.*.
  3. Revisa agentes del proveedor y monitorización. Muchos se instalan como servicios systemd y programan sus propias ejecuciones internamente.

Minuto 6–10: correlaciona con logs y toma una acción segura

  1. Correlaciona por tiempo. Los logs del journal alrededor del timestamp del pico normalmente mostrarán la unidad que se inició.
  2. Mitigación inmediata más segura. Si no es crítico (updatedb, logrotate, un script de informes), pospónlo o añade jitter. Si es crítico (backups, escaneos de seguridad), reduce concurrencia o alcance en lugar de deshabilitarlo.

Idea parafraseada atribuida a Gene Kim: La fiabilidad proviene de hacer el trabajo visible y repetible, no de héroes a las 3 a.m.

Por qué los picos periódicos gritan “tarea programada”

La CPU no se dispara “cada cinco minutos” por accidente. Los humanos programan cosas en números redondos. Los sistemas operativos también. Y los agentes “empresariales” que alguien desarrolló sin tener que compartir un hipervisor con tu base de datos también lo hacen.

Los picos periódicos de CPU suelen provenir de una de estas categorías:

  • Tareas de mantenimiento: logrotate, updatedb (mlocate), limpieza de tmp, comprobaciones de actualizaciones de paquetes, hooks de renovación de certificados.
  • Backups e indexación: escaneos de sistema de archivos, scripts de snapshot, indexado de dedupe, sincronización a object-store, escaneos antivirus.
  • Monitorización y telemetría: scrapes de métricas, recolectores de inventario, comprobaciones de postura de seguridad.
  • Trabajo de CPU por almacenamiento: compresión, checksums, cifrado, paridad RAID, operaciones tipo scrub, cálculos de diff de snapshots.
  • “Utilidad” de la aplicación: calentamiento de cachés, generación periódica de informes, compactación (bases de datos), trabajos de reindexado.

Aquí está la trampa: puedes mirar gráficos de CPU todo el día y aun así perder la causa si no alineas las marcas de tiempo. Cada tarea periódica deja una huella: un inicio de proceso, una línea en el log, la activación de una unidad, un archivo tocado, una ráfaga de red.

Broma #1: Las tareas programadas son como los cumpleaños de la oficina: de alguna manera ocurren todos los años y, sin embargo, siempre sorprenden a todos.

La tarea programada que debes revisar primero (y por qué)

Lo primero que reviso son los timers de systemd, luego cron. No porque cron sea raro—cron está en todas partes—sino porque los equipos suelen comprobar “cron” y detenerse. Mientras tanto, los timers de systemd están disparándose en silencio:

  • apt-daily.timer y apt-daily-upgrade.timer (Debian/Ubuntu)
  • fstrim.timer (TRIM de SSD; puede afectar subsistemas de almacenamiento)
  • logrotate.timer (o logrotate desde cron, según la distro)
  • man-db.timer (sí, en serio)
  • updatedb.timer (escaneo de sistema de archivos)
  • Timers de proveedores para agentes de backup, EDR, escáneres de cumplimiento

¿Por qué primero? Porque los timers de systemd pueden ejecutarse con comportamiento de recuperación persistente. Si un host estuvo apagado en el momento programado, un timer con Persistent=true puede dispararse inmediatamente al arrancar. Así consigues “picos de CPU cada pocos minutos” tras una incidencia: una manada de timers perdidos recuperando el tiempo perdido.

Además, los timers pueden configurarse con retrasos aleatorios. Eso es bueno. Pero cuando no lo están, los nodos del fleet realizan trabajo sincronizado. Si alguna vez viste 500 nodos ejecutar la misma tarea en el minuto 0, ya sabes cómo termina esa historia.

Manos a la obra: 12+ tareas con comandos, salidas y decisiones

A continuación hay tareas prácticas que ejecutaría en una máquina Linux en producción. Para cada una: comando, qué significa la salida y qué decisión tomo a continuación. Ejecútalas durante un pico si puedes. Si no puedes, aún puedes reunir suficiente evidencia entre picos.

Tarea 1: Vigilar la CPU por proceso a lo largo del tiempo (capturar el pico)

cr0x@server:~$ pidstat -u -h 1
Linux 6.5.0 (server)  02/05/2026  _x86_64_  (16 CPU)

# Time        UID      PID    %usr %system  %CPU   Command
12:00:01     0       1423     0.00    0.00  0.00   systemd
12:00:02     0      22891    92.00    6.00  98.00  updatedb
12:00:03     0      22891    88.00    5.00  93.00  updatedb

Significado: updatedb está saturando la CPU durante el pico. No es tu app. Es una actualización indexada del sistema de archivos programada.

Decisión: Identificar quién dispara updatedb (timer o cron), luego reprogramar, limitar o excluir rutas.

Tarea 2: Confirmar el modo general de CPU (user/system/iowait)

cr0x@server:~$ mpstat -P ALL 1 5
Linux 6.5.0 (server)  02/05/2026  _x86_64_  (16 CPU)

12:00:01 PM  all  %usr %nice %sys %iowait %irq %soft %steal %idle
12:00:01 PM  all  62.50  0.00 12.30  0.20 0.00  0.50   0.00 24.50
12:00:02 PM  all  89.10  0.00  8.40  0.10 0.00  0.40   0.00  2.00

Significado: Alto %usr sugiere cómputo puro (hashing/compresión/escaneo) más que espera por disco.

Decisión: Centrarte en el proceso y su programación. Si %iowait fuera alto, pivotarías hacia latencia de almacenamiento y profundidad de cola.

Tarea 3: Ver carga media vs cola de ejecución (contención de CPU vs “simplemente ocupado”)

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0  81234  10240 913024    0    0     2     5  120  300 12  3 85  0  0
12  0      0  80120  10240 913500    0    0     0     0 3400 9800 88  9  3  0  0

Significado: r=12 en una máquina de 16 núcleos significa muchas hebras listos para ejecutarse; no están bloqueadas, simplemente compiten por CPU.

Decisión: Si esto se alinea con un timer que se está activando, querrás reducir la concurrencia (nice/ionice, ajustes de la tarea o la propia tarea).

Tarea 4: Listar timers de systemd (los sospechosos habituales)

cr0x@server:~$ systemctl list-timers --all
NEXT                         LEFT          LAST                         PASSED       UNIT                         ACTIVATES
Mon 2026-02-05 12:05:00 UTC  2min 12s      Mon 2026-02-05 12:00:01 UTC  2min 47s     updatedb.timer               updatedb.service
Mon 2026-02-05 12:10:00 UTC  7min 12s      Mon 2026-02-05 12:00:08 UTC  2min 40s     apt-daily.timer              apt-daily.service
Mon 2026-02-05 12:15:00 UTC  12min 12s     Mon 2026-02-05 12:00:10 UTC  2min 38s     logrotate.timer              logrotate.service

Significado: Tienes múltiples timers disparándose en el minuto 0 y repitiéndose. La columna LAST es oro: compárala con la hora de tu pico de CPU.

Decisión: Inspeccionar la configuración del timer y el servicio que activa. Aplicar jitter, cambiar horario o deshabilitar con seguridad si procede.

Tarea 5: Inspeccionar la definición de un timer y su servicio (qué se ejecuta exactamente)

cr0x@server:~$ systemctl cat updatedb.timer
# /lib/systemd/system/updatedb.timer
[Unit]
Description=Update a database for mlocate

[Timer]
OnCalendar=*:0/5
Persistent=true
RandomizedDelaySec=0

[Install]
WantedBy=timers.target

Significado: Cada 5 minutos, recuperación persistente y cero jitter. Eso es prácticamente una invitación al sufrimiento sincronizado.

Decisión: Añadir RandomizedDelaySec, reducir la frecuencia o deshabilitar si no necesitas locate en un servidor.

Tarea 6: Revisar cron (a nivel sistema y por usuario)

cr0x@server:~$ sudo ls -l /etc/cron.hourly /etc/cron.daily /etc/cron.d
/etc/cron.d:
total 12
-rw-r--r-- 1 root root  240 Jan 10  2026 backup-agent
-rw-r--r-- 1 root root  180 Dec  2  2025 mlocate
-rw-r--r-- 1 root root  210 Nov 18  2025 sysstat

Significado: Cron sigue en juego. /etc/cron.d/mlocate probablemente esté impulsando updatedb en distros que no usan el timer, o además de él.

Decisión: Asegúrate de no tener programación duplicada (timer + cron). Elige uno y deshabilita el otro.

Tarea 7: Ver entradas de crontab para un usuario específico (donde se esconden sorpresas)

cr0x@server:~$ sudo crontab -l -u root
*/5 * * * * /usr/local/sbin/inventory-scan --json --upload
15 * * * * /usr/local/sbin/storage-report

Significado: La cadencia de cinco minutos está ahí mismo. inventory-scan huele a “trabajo de agente”, a menudo costoso en CPU (recorrido de sistema de archivos, consulta de paquetes, hashing).

Decisión: Mide el tiempo de ejecución y CPU, luego reduce frecuencia, limita alcance (excluir directorios) o mueve la tarea fuera de hosts críticos.

Tarea 8: Correlacionar con los logs del journal en el timestamp del pico

cr0x@server:~$ sudo journalctl --since "2026-02-05 11:58:00" --until "2026-02-05 12:02:00" --no-pager
Feb 05 12:00:01 server systemd[1]: Started Update a database for mlocate.
Feb 05 12:00:01 server updatedb[22891]: updatedb: pruning "/var/lib/docker/overlay2"
Feb 05 12:00:10 server systemd[1]: Started Rotate log files.
Feb 05 12:00:10 server logrotate[22940]: rotating pattern: /var/log/*.log  forced from command line (1 rotations)

Significado: Ahora tienes una línea de tiempo. No es “carga misteriosa”; son dos trabajos de mantenimiento apilándose.

Decisión: Escalónalos. Las tareas de mantenimiento colisionando es el clásico fenómeno de “¿por qué los picos son peores a medianoche?”.

Tarea 9: Identificar a qué cgroup/unidad pertenece un PID caliente

cr0x@server:~$ cat /proc/22891/cgroup
0::/system.slice/updatedb.service

Significado: El proceso es propiedad de updatedb.service. Eso facilita ajustarlo con parámetros de systemd.

Decisión: Aplicar CPUQuota, Nice o IOSchedulingClass en un drop-in si no puedes cambiar la tarea en sí.

Tarea 10: Ver qué línea de comando ejecuta realmente el proceso

cr0x@server:~$ ps -p 22891 -o pid,ppid,ni,etimes,cmd
  PID  PPID  NI ELAPSED CMD
22891     1   0      18 /usr/bin/updatedb.mlocate --prunepaths=/tmp /var/lib/docker /var/lib/kubelet

Significado: Está escaneando directorios pesados (Docker, kubelet). Esos paths churnean y contienen millones de inodos. Eso conlleva CPU más I/O de metadatos.

Decisión: Excluir esas rutas o dejar de ejecutar updatedb en hosts con contenedores. “Locate” no es un SLO de producción.

Tarea 11: Revisar picos históricos de CPU con sar (demostrar que es periódico)

cr0x@server:~$ sar -u -s 11:30:00 -e 12:10:00
Linux 6.5.0 (server)  02/05/2026  _x86_64_  (16 CPU)

11:35:00 AM     %user     %system     %iowait      %idle
11:35:00 AM      6.20        1.10        0.10      92.60
11:40:00 AM     68.30        8.90        0.20      22.60
11:45:00 AM      7.10        1.20        0.10      91.60
11:50:00 AM     70.40        9.10        0.10      20.40
11:55:00 AM      6.90        1.00        0.10      92.00
12:00:00 PM     71.20        9.00        0.10      19.70

Significado: Picos cada 10 minutos (o cada 5 según el muestreo). Eso coincide con timers y crons.

Decisión: Deja de debatir “¿es la app?” y empieza a emparejar horarios con esas marcas de tiempo.

Tarea 12: Usar perf para verificar dónde va el tiempo de CPU (cuando no es obvio)

cr0x@server:~$ sudo perf top -p 22891
Samples: 2K of event 'cpu-clock', Event count (approx.): 2000000000
  35.12%  updatedb   libc.so.6        [.] __memmove_avx_unaligned_erms
  22.40%  updatedb   libz.so.1        [.] deflate_slow
  10.08%  updatedb   updatedb         [.] hash_path

Significado: La compresión y el hashing dominan. Eso es intensivo en CPU por diseño, no un bug del kernel.

Decisión: Reducir datos escaneados, bajar frecuencia, añadir límites de cgroup o mover el trabajo fuera de la máquina.

Tarea 13: Encontrar qué unidad se inició recientemente (grep rápido del journal)

cr0x@server:~$ sudo journalctl -S "12:00:00" -U "12:01:00" -g "Started " --no-pager
Feb 05 12:00:01 server systemd[1]: Started Update a database for mlocate.
Feb 05 12:00:10 server systemd[1]: Started Rotate log files.

Significado: Esta es la vista más rápida de “¿qué se inició justo ahora?” cuando puedes acotar la hora del pico.

Decisión: Investiga las unidades iniciadas antes de lanzarte a perfilar la aplicación.

Tarea 14: Limitar un servicio systemd de forma segura (override drop-in)

cr0x@server:~$ sudo systemctl edit updatedb.service
# (creates a drop-in override)
cr0x@server:~$ sudo cat /etc/systemd/system/updatedb.service.d/override.conf
[Service]
Nice=10
CPUQuota=20%
IOSchedulingClass=best-effort
IOSchedulingPriority=7
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart updatedb.service

Significado: No “arreglaste” updatedb; hiciste que deje de acosar al resto del host.

Decisión: Usa el throttling como mitigación y luego aplica la solución real: reprogramar, añadir jitter y reducir el alcance del escaneo.

Tarea 15: Añadir jitter a un timer (evitar estampidas en el fleet)

cr0x@server:~$ sudo systemctl edit updatedb.timer
cr0x@server:~$ sudo cat /etc/systemd/system/updatedb.timer.d/override.conf
[Timer]
RandomizedDelaySec=180
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart updatedb.timer

Significado: La tarea aún se ejecuta, pero no simultáneamente en todo el fleet ni exactamente en el minuto.

Decisión: Si muchos nodos se disparan a la vez, el jitter suele ser el cambio con mayor ROI que puedes hacer.

Cron vs systemd timers vs “agentes”: cómo se comportan realmente

No necesitas guerras de religión sobre planificadores. Necesitas un modelo mental correcto.

Cron: simple, ubicuo y fácil de duplicar accidentalmente

Cron ejecuta comandos en un horario. Eso es todo. Su simplicidad es la razón por la que sigue en todas partes—y por la que se usa como vertedero de scripts “temporales” que viven cinco años.

Dónde cron oculta trabajo:

  • Crontab del sistema: /etc/crontab
  • Directorios drop-in: /etc/cron.d/, /etc/cron.hourly/, /etc/cron.daily/, etc.
  • Crontabs de usuario: en /var/spool/cron o /var/spool/cron/crontabs

El modo de fallo clásico de cron en fleets es la sincronización: cada máquina ejecuta lo mismo a la misma hora porque todo el mundo copió la misma entrada de crontab.

systemd timers: más visibles, más potentes y capaces de sorprender

Los timers son objetos de primera clase en systemd. Tienen logs. Tienen estado. Pueden recuperar ejecuciones después de un downtime. Pueden añadir jitter. Pueden tener ventanas de precisión que retrasan el disparo levemente para coalescer wakeups.

El modo de fallo clásico de los timers es persistencia + estampidas en el arranque. Tras mantenimiento, reboot o eventos de autoscaling, muchos timers perdidos se disparan. Si cada timer es “pequeño”, la carga combinada no lo es.

Agentes: “nos ejecutamos cada 5 minutos” no es un contrato, es una amenaza

Agentes de monitorización, recolectores de inventario, seguridad endpoint, agentes de backup y herramientas de “postura de cumplimiento” a menudo implementan sus propios horarios internamente. Puede que no veas un timer o cron; verás un daemon de larga ejecución que despierta y realiza mucho trabajo.

El truco diagnóstico es buscar:

  • Ráfagas regulares de CPU en un solo proceso daemon.
  • Ráfagas regulares de procesos hijo forked por ese daemon.
  • Entradas de log que se repiten cada N minutos.

El ángulo del almacenamiento: picos de CPU disfrazados de trabajo I/O

Como ingeniero de almacenamiento, diré en voz alta lo que muchos callan: muchos “picos de CPU” son tareas de almacenamiento poniéndose un disfraz de CPU.

Los recorridos de metadatos son trabajo de CPU

Tareas como updatedb, scans antivirus, enumeración de backups y escáneres de integridad hacen recorridos masivos de directorios. Aunque tus discos sean rápidos, convertir millones de dentries e inodos en una lista cuesta CPU. En sistemas de archivos en red es peor: cada llamada de metadatos es un viaje de red, además de parsing y caching en el cliente.

Compresión y checksumming son trabajo de CPU

Si activaste compresión “para ahorrar espacio”, felicidades: también te apuntaste a pagar CPU en cada escritura (y a veces en lecturas). Muchas herramientas de backup comprimen por defecto. Los pipelines de envío de logs comprimen. Incluso logrotate puede comprimir archivos rotados. Esos trabajos suelen alinearse a límites temporales y crear ráfagas.

TRIM y scrubs no son gratuitos

fstrim puede causar actividad notable del dispositivo. Un scrub RAID o scrub de sistema de archivos puede provocar uso de CPU en hilos del kernel, especialmente si hay checksums. Podrías ver el pico en CPU, pero la causa raíz es mantenimiento de almacenamiento programado que colisiona con tu tráfico pico.

El cifrado es predecible—y picos cuando se programa

El cifrado at-rest y los backups cifrados consumen ciclos de CPU. Está bien cuando es constante. Es doloroso cuando es en ráfagas. Un backup nocturno que cifra y comprime a la vez puede parecer un “pico misterioso de CPU” para quien no esté mirando la programación del backup.

Broma #2: Nada dice “listo para empresa” como un agente de backup que benchmarquea tu CPU al mediodía sin pedir permiso.

Tres mini-historias corporativas (basadas en la realidad)

Mini-historia 1: El incidente causado por una suposición errónea (“No usamos cron”)

Una empresa mediana ejecutaba un conjunto de servidores API detrás de un balanceador. Cada cinco minutos, las tasas de error subían un poco. No un fallo total—solo lo suficiente para hacer que el SLO se consumiera lentamente. El primer supuesto del on-call fue el habitual: recolección de basura o un vecino ruidoso en el hipervisor.

Revisaron logs de la aplicación. Nada obvio. Revisaron despliegues. Nada. Revisaron “cron”. No había entradas relevantes en crontab -l. Declararon, con confianza, “no usamos cron en estas máquinas”. La investigación derivó en perfilar endpoints y ajustar pools de hilos.

El avance sucedió cuando alguien comparó timestamps de los picos de CPU con systemctl list-timers. Ahí estaba: updatedb.timer ejecutándose cada cinco minutos, persistente, sin jitter. Venía de una imagen base del SO que había sido “endurecida” en todo excepto en la parte donde hacía un escaneo de sistema de archivos repetidamente.

¿Por qué afectó a la API? Los servidores también alojaban runtimes de contenedores. El escaneo de updatedb recorría enormes árboles de overlay filesystem. No solo consumía CPU; removía cachés de páginas y cachés de metadatos. La app se volvía un poco más lenta, luego se recuperaba—una y otra vez, como un pequeño ataque de denegación de servicio desde dentro.

La solución fue aburrida: deshabilitar updatedb en esos hosts y añadir una política para que las imágenes base documenten los timers habilitados. La lección del postmortem no fue sobre locate. Fue sobre suposiciones. “No cron” no significaba “sin tareas programadas”. Significaba “no miramos en el planificador correcto”.

Mini-historia 2: La optimización que salió mal (compresión por todas partes)

Un equipo de plataforma interna quería reducir costes de almacenamiento. Los logs eran grandes, los backups más grandes y el CFO había descubierto la palabra “eficiencia”. El equipo activó compresión agresiva en la pipeline de backups, configuró el trabajo para ejecutarse cada 15 minutos para mejorar el RPO y celebró el ahorro de espacio.

Entonces vinieron los picos de CPU: agudos, rítmicos y horribles. Empezaron a ver aumento de latencia en peticiones y timeouts ocasionales en servicios que compartían los mismos nodos que el cliente de backup. El equipo de backups insistía en que nada había cambiado “para la app”, lo cual era técnicamente cierto e inútil operacionalmente.

Lo que realmente pasó: comprimir lotes pequeños cada 15 minutos provocó ráfagas constantes de CPU en vez de una quema predecible nocturna. Las ráfagas se alinearon con otros timers de plataforma—logrotate, compactación de métricas, comprobaciones de paquetes—y crearon un “minuto ocupado” recurrente. El sistema no estaba sobrecargado de media; estaba sobrecargado en un patrón repetitivo.

La optimización—backups incrementales más frecuentes con compresión máxima—salió mal porque ignoró la contención y la convivencia. Lo arreglaron bajando el nivel de compresión, añadiendo cuotas de CPU al servicio de backup y agregando retrasos aleatorios. El uso de espacio aumentó ligeramente. La estabilidad mejoró mucho. El CFO obtuvo un gráfico que bajó. El on-call volvió a dormir.

Mini-historia 3: La práctica aburrida que salvó el día (jitter y presupuestos)

Una financiera gestionaba una gran flota Linux con requisitos estrictos de latencia. Habían aprendido por las malas que el “mantenimiento a medianoche” es simplemente una forma de crear desastres sincronizados a medianoche. Así que trataron el trabajo en background como tráfico de producción: necesitaba presupuestos, observabilidad y dispersión.

Cada nueva tarea programada debía declarar: frecuencia, tiempo esperado de ejecución, perfil de CPU (aprox.) y si podía retrasarse. Las tareas se añadían como timers de systemd con jitter por defecto. Usaban RandomizedDelaySec en todo lo no urgente y evitaban programar todo a la hora en punto.

Un día desplegaron una actualización de un agente de cumplimiento que era más pesada de lo esperado. Hizo un inventario de sistema de archivos y verificación criptográfica. El uso de CPU aumentó—pero no se disparó en todo el fleet. ¿Por qué? El timer tenía un horario de 10 minutos con un retraso aleatorio de 6 minutos, y el servicio tenía una cuota de CPU. El trabajo se distribuyó. Los balanceadores nunca vieron una caída sincronizada.

No fue ingeniería glamorosa. Fue el equivalente operativo de guardar tus herramientas después de usarlas. Pero convirtió un incidente potencialmente feo en una no-incidencia, que es lo más agradable que puede hacer producción.

Errores comunes: síntoma → causa raíz → solución

Esta es la parte donde la mayoría de los equipos pierde horas. No lo hagas.

1) Picos cada 5 minutos, “no hay jobs de cron”, nada en logs de la app

  • Síntoma: La CPU salta al 80–100% cada 5 minutos. La latencia de la app sube. Nada obvio en logs de la app.
  • Causa raíz: Timer de systemd (a menudo updatedb.timer, timers de agentes, timers de actualización de paquetes) o un daemon que despierta periódicamente.
  • Solución: systemctl list-timers --all, correlacionar con journalctl, añadir jitter o deshabilitar timers no esenciales; limitar CPU con un override drop-in.

2) Picos en :00 en muchos hosts (a nivel de flota) y los gráficos se ven sincronizados

  • Síntoma: Todo el clúster presenta picos de CPU en la hora en punto; servicios downstream reportan aumento de p95 de latencia.
  • Causa raíz: horarios sincronizados (cron o timers sin RandomizedDelaySec), a menudo por configuraciones idénticas de imagen base.
  • Solución: añadir jitter, escalonar horarios, usar la precisión y el retraso aleatorio de systemd; evitar convenciones de “minuto 0”.

3) Picos de CPU coinciden con disco ocupado, pero la CPU parece la culpable

  • Síntoma: Picos de CPU, pero %iowait también sube; alertas de latencia de almacenamiento; hilos de la app se bloquean.
  • Causa raíz: mantenimiento I/O programado (compresión en logrotate, backup, scrub/trim) que desencadena trabajo de CPU (compresión, checksums) y satura colas de almacenamiento.
  • Solución: reprogramar tareas I/O fuera de picos; reducir paralelismo; usar ionice/IOSchedulingClass; verificar salud del almacenamiento y profundidad de colas.

4) Picos empezaron después de habilitar “escaneo de seguridad” o “inventario”

  • Síntoma: ráfagas periódicas de CPU, muchos procesos cortos, muchas estadísticas del sistema de archivos.
  • Causa raíz: agente EDR/compliance recorriendo el sistema de archivos, hasheando binarios, escaneando contenedores.
  • Solución: ajustar exclusiones (directorios de contenedores, caches de build), reducir frecuencia de escaneos, presionar al proveedor por un modo menos agresivo; aislar en nodos dedicados si es necesario.

5) Picos solo después de reiniciar o tras downtime

  • Síntoma: el arranque parece bien, pero en minutos la CPU se dispara repetidamente, a veces con múltiples tareas una tras otra.
  • Causa raíz: timers de systemd con Persistent=true “recuperando” ejecuciones perdidas; múltiples timers perdidos ejecutándose al arranque.
  • Solución: añadir RandomizedDelaySec, revisar la opción Persistent y asegurar que servicios críticos de arranque no compitan con tareas de mantenimiento.

6) Los picos desaparecen cuando ejecutas el job manualmente “para probar”

  • Síntoma: ejecutas el script sospechoso a mano y parece bien; los picos siguen ocurriendo más tarde.
  • Causa raíz: el entorno programado difiere: PATH distinto, nice/ionice distintos, argumentos distintos, directorio de trabajo distinto, o se ejecuta junto a otras tareas.
  • Solución: captura la línea de comando exacta y el entorno desde la unidad systemd o logs de cron; reproduce con los mismos parámetros y la misma concurrencia.

Listas de comprobación / plan paso a paso

Lista A: Host único con picos periódicos

  1. Anota la cadencia del pico (¿cada 5 minutos? ¿en :00?).
  2. Captura los procesos con mayor CPU durante un pico (pidstat o top -H).
  3. Revisa el modo de CPU (mpstat) y la cola de ejecución (vmstat).
  4. Lista timers de systemd y empareja timestamps (systemctl list-timers --all).
  5. Lista fuentes de cron (/etc/cron.d, crontab -l para usuarios clave).
  6. Correlaciona con logs (journalctl alrededor del tiempo del pico).
  7. Confirma la unidad/cgroup del PID caliente (/proc/PID/cgroup).
  8. Mitiga con seguridad (CPUQuota/Nice, añadir jitter, reprogramar).
  9. Arregla la raíz: excluir rutas pesadas, reducir frecuencia, eliminar duplicados.
  10. Verifica durante al menos 3 intervalos de pico con sar o tu monitorización.

Lista B: Picos sincronizados a nivel de flota

  1. Confirma sincronización en múltiples nodos (misma minuto, misma forma).
  2. Identifica timers comunes habilitados en la imagen base (systemctl list-timers en varios nodos).
  3. Busca agentes de proveedores desplegados en todas partes (inventario/EDR/backup).
  4. Aplica jitter de forma universal para tareas no urgentes.
  5. Establece presupuestos de CPU para trabajo en background (cgroups vía overrides de systemd).
  6. Escalona tareas pesadas inevitables por rol (por ejemplo, horarios distintos para nodos web vs db).
  7. Vuelve a comprobar latencia y tasa de errores tras los cambios.

Lista C: Cuando el culpable es “trabajo de almacenamiento” y no “trabajo de CPU”

  1. Revisa iowait y utilización de disco durante los picos.
  2. Identifica trabajos que hacen compresión/hashing (backup, logrotate, escáneres).
  3. Mover la compresión pesada fuera del host o a horas fuera de pico cuando sea posible.
  4. Limitar la clase de I/O para trabajos en background (ionice o ajustes I/O de systemd).
  5. Evitar que trabajos de recorrido de directorios escaneen rutas de contenedores y caches de build.

Datos interesantes e historia que importan en producción

  • Dato 1: El diseño de cron data de Unix temprano; su comportamiento de “ejecutar en el minuto exacto” es la razón por la que las flotas todavía estampidan en :00.
  • Dato 2: Los timers de systemd pueden ser persistentes, lo que significa que ejecutarán trabajos perdidos después de un downtime—genial para portátiles, picante para servidores.
  • Dato 3: Muchas distros migraron tareas clásicas de cron (como logrotate) a timers de systemd, así que “revisamos cron” dejó de ser suficiente hace años.
  • Dato 4: La base de datos de updatedb / locate existe para acelerar búsqueda de nombres de archivos—útil en máquinas de desarrollo, a menudo inútil en servidores de producción.
  • Dato 5: logrotate puede comprimir logs rotados, y la compresión es intensiva en CPU; un archivo grande puede causar un pico mayor que una docena de archivos pequeños.
  • Dato 6: TRIM (fstrim) se programa semanalmente en muchos sistemas Linux; en algunos backends de almacenamiento puede crear ráfagas notables.
  • Dato 7: Los picos periódicos son más fáciles de diagnosticar que la carga constante porque puedes correlacionarlos con metadatos del planificador—si te das el trabajo de mirar.
  • Dato 8: El retraso aleatorio (jitter) existe específicamente para prevenir manadas; no usarlo en una flota es un error evitable.
  • Dato 9: Muchos “agentes” ejecutan sus propios horarios internos y pueden no mostrarse como cron/timers en absoluto, así que debes observar su comportamiento de CPU directamente.

Preguntas frecuentes (FAQ)

1) ¿Qué es lo primero que debo comprobar ante picos periódicos de CPU?

Timers de systemd: systemctl list-timers --all. Es la forma más rápida de detectar tareas de mantenimiento del SO y timers de proveedores que la gente olvida que existen.

2) Revisé cron y no encontré nada. ¿Qué hago ahora?

Revisa timers de systemd, luego busca daemons de larga ejecución (agentes) que despierten periódicamente. Correlaciona con journalctl en el tiempo del pico.

3) ¿Por qué los picos ocurren exactamente cada 5 minutos?

Porque alguien puso */5 * * * * en cron o OnCalendar=*:0/5 en un timer. Los ordenadores son literales. A la gente le encantan los números redondos.

4) ¿Debería simplemente deshabilitar el timer problemático?

A veces sí (por ejemplo, updatedb en un servidor de producción). A veces no (escaneos de seguridad, backups). Lo preferible: reducir frecuencia, añadir jitter, excluir rutas pesadas y limitar CPU.

5) ¿Cómo pruebo que un timer es la causa y no mera correlación?

Empareja el LAST del timer con la marca temporal del pico, luego comprueba la unidad iniciada en journalctl y confirma que el PID caliente pertenece al cgroup de esa unidad.

6) El pico es mayormente %sys (CPU de sistema). ¿Qué sugiere eso?

Trabajo intensivo en kernel: churn de metadatos del sistema de archivos, overhead de red, cifrado o hilos del kernel haciendo mantenimiento de almacenamiento. Busca escaneos, backups, scrubs o logging intenso.

7) ¿Por qué los picos empeoraron después de un reinicio?

Los timers persistentes pueden “recuperar” ejecuciones tras el downtime. Múltiples tareas perdidas pueden ejecutarse poco después del arranque, acumulando carga de CPU.

8) ¿Cómo evito picos sincronizados en toda la flota?

Añade jitter (RandomizedDelaySec) y evita programar todo en :00. Para tareas pesadas, aplica cuotas de CPU para que el trabajo en background no pueda dejar sin recursos a tu servicio.

9) ¿El almacenamiento puede causar picos de CPU aunque los discos estén bien?

Sí. Recorridos de directorios, checksums, compresión y cifrado son trabajo de CPU disparado por tareas relacionadas con almacenamiento. “El disco está bien” no significa “no ocurre trabajo relacionado con almacenamiento”.

10) ¿Qué hago si el nombre del proceso no ayuda, por ejemplo “python”?

Captura la línea de comando completa (ps -p PID -o cmd), revisa su proceso padre y la pertenencia a cgroup. Si es una unidad systemd, systemctl status UNIT a menudo revela la ruta del script.

Conclusión: próximos pasos prácticos

Si tus picos de CPU son periódicos, actúa como un SRE, no como un adivino. Trátalo como trabajo programado hasta que tengas evidencia de lo contrario.

  1. En un host: captura el proceso caliente durante un pico con pidstat, luego mapea a una unidad o entrada de cron.
  2. En el planificador: lista timers de systemd primero, luego cron. Busca patrones de cinco minutos y en hora en punto.
  3. Mitiga con seguridad: limita con drop-ins de systemd (CPUQuota/Nice) y añade jitter para evitar picos sincronizados en la flota.
  4. Arregla la raíz: excluye rutas pesadas, reduce frecuencia, elimina horarios duplicados y deja de ejecutar utilidades de desarrollador en servidores de producción.
  5. Verifica: observa al menos unos cuantos intervalos y confirma que el pico desapareció—o al menos que ya no impacta tu presupuesto de latencia.

Haz esto bien y el gráfico dejará de parecer un monitor cardíaco. Lo cual es agradable, porque significa que puedes dejar de tratar tu infraestructura como un paciente en triage.

← Anterior
Cortafuegos Linux: el diseño limpio de nftables que sigue legible con 500 reglas
Siguiente →
DNS: Split-Horizon Mal Implementado — La solución que detiene la locura «inside/outside»

Deja un comentario