Ubuntu 24.04: Servicios en bucle de reinicio — detén el bucle y captura el error raíz

¿Te fue útil?

Conoces el olor: paneles llenos de rojo, el pager chillando y un servicio que “arranca” cientos de veces por minuto mientras no hace ni una sola tarea útil. En Ubuntu 24.04, systemd no está siendo dramático. Está haciendo lo que le dijiste: reiniciar un proceso que falla, hasta que alcanza un límite de frecuencia o convierte tus logs en confeti.

Así es como detienes el bucle sin perder la evidencia, extraes el primer error real (no el número 500) y luego corriges la causa subyacente. Lo haremos como operadores: heroísmo mínimo, máxima señal y con preferencia por cambios que puedas defender en un postmortem.

Qué significa realmente un bucle de reinicio en systemd

Un “bucle de reinicio” es simplemente un lazo de retroalimentación entre la política de reinicio de una unidad y un proceso que vuelve a systemd demasiado rápido. El proceso sale (crash, mala configuración, dependencia ausente, problema de permisos), systemd aplica las reglas de Restart= del archivo de unidad e intenta de nuevo. Repetir. Si es lo bastante rápido, systemd finalmente dice “basta” usando el limitador de frecuencia de arranques (StartLimitIntervalSec + StartLimitBurst) y marca la unidad como fallida.

Dos puntos clave para operadores:

  • El error raíz casi siempre está en los primeros intentos. Después de eso solo obtienes ruido repetido: “Main process exited” y “Scheduled restart job.”
  • systemd no sabe “saludable”, sabe “proceso salió”. Un servicio puede estar “en ejecución” y aun así no hacer nada útil, o estar “fallado” cuando en realidad tuvo éxito y se demonizó incorrectamente.

Hay un anti-patrón común aquí: “Arreglar” el bucle poniendo Restart=no y dar por terminado el asunto. Eso no es arreglar; es apagar la alarma contra incendios porque hace ruido.

Guía de diagnóstico rápido (haz esto primero)

Este es el orden que ahorra tiempo bajo presión. Está optimizado para “encontrar el cuello de botella rápidamente”, no para completitud teórica.

1) Confirma que tratas con un bucle de reinicio y captura su ritmo

  • Comprueba si systemd lo está reiniciando activamente y con qué rapidez.
  • Busca limitación de frecuencia (“Start request repeated too quickly”) lo que indica que te perdiste los primeros errores y necesitas extraer logs por ventana temporal.

2) Extrae los logs del primer fallo, no los últimos

  • Usa journalctl con --since y --reverse para encontrar la primera línea mala después de un arranque.
  • Obtén el estado de salida y la información de señal de los propios mensajes de systemd.

3) Congela al paciente (detén el bucle) sin borrar evidencia

  • Maskea o detén la unidad, o establece temporalmente Restart=no vía un override.
  • No borres el journal. No reinicies “solo para limpiarlo”. Así es como pierdes la única prueba que tenías.

4) Clasifica el modo de fallo

La mayoría de los bucles caen en uno de estos grupos:

  • Salida inmediata: mala configuración, variable de entorno ausente, directorio de trabajo incorrecto, flag inválido, binario ausente.
  • Crash: segfault, abort, instrucción ilegal (a menudo incompatibilidad de librería o características de CPU), OOM.
  • Desajuste de readiness: el servicio dice que inició pero systemd espera Type=notify, o se demoniza inesperadamente.
  • Fallo de dependencia: red no lista, DNS roto, montaje ausente, permisos/SELinux/AppArmor.
  • Límites de recursos: descriptores de archivos, memlock, tareas, diferencias de ulimit bajo systemd.

5) Reproduce con el mismo entorno que usa systemd

  • Ejecuta el mismo ExecStart en un entorno limpio, o usa systemd-run para emular.
  • Revisa opciones de sandboxing de la unidad que puedan bloquear acceso a ficheros (por ejemplo, ProtectSystem=, PrivateTmp=).

Detén el bucle de forma segura (y conserva la evidencia)

Si un servicio está flapping, está dañando activamente tu sistema: quema CPU, spamea logs, reabre sockets, agita cachés y puede disparar límites externos. Tu primera tarea es detener la hemorragia sin borrar la escena del crimen.

Opción A: Detener la unidad (pausa temporal)

Este es el botón de “pausa” rápido. systemd todavía puede intentar reiniciar si otras unidades dependen de ella y la traen, así que verifica después de detener.

Opción B: Maskear la unidad (parada fuerte)

Maskear evita arranques (manuales o disparados por dependencias). Es la jugada correcta cuando el bucle está causando daños colaterales y necesitas un host estable para investigar.

Opción C: Override temporal: desactivar política de reinicio

Esto es más limpio que editar archivos de unidad del proveedor. Creas un drop-in override para cambiar Restart= y opcionalmente añadir un RestartSec= mayor para poder leer logs entre intentos.

Broma #1: Un bucle de reinicio es como un becario con entusiasmo infinito y cero contexto—rápido, persistente y de algún modo empeorando todo.

Captura el error raíz: logs, códigos de salida y coredumps

systemd es bueno diciendo que algo murió. Necesitas que te diga por qué. El “por qué” suele estar en uno de estos lugares:

  • Logs del journal para la unidad y sus dependencias
  • Metadatos de salida de systemd: código de estado, señal, notas de coredump
  • Logs de la aplicación: ficheros de log, captura de stderr/stdout, logs estructurados
  • Coredumps: para crashes reales
  • Logs del kernel: OOM killer, segfault, errores de montaje

Una cita con la que puedes construir una respuesta de incidente:

«La esperanza no es una estrategia.» — idea parafraseada a menudo atribuida en círculos de gestión de ingeniería

Traducción: deja de adivinar. Captura evidencia. Haz un cambio a la vez.

Tareas prácticas: comandos, salidas, decisiones (12+)

Estas son las tareas que realmente ejecuto cuando un servicio está flapping en Ubuntu 24.04. Cada una incluye qué significa la salida y qué decisión sigue.

Task 1: Confirmar estado actual, fallos recientes y contadores de reinicio

cr0x@server:~$ systemctl status myapp.service --no-pager -l
● myapp.service - MyApp API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: activating (auto-restart) (Result: exit-code) since Mon 2025-12-29 10:14:07 UTC; 2s ago
    Process: 18422 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
   Main PID: 18422 (code=exited, status=1/FAILURE)
        CPU: 38ms

Dec 29 10:14:07 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:07 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 10:14:07 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 17.

Qué significa: Sale con estado 1 rápidamente y se ha reiniciado 17 veces. Eso no es un “arranque lento”, es una falla determinista.

Decisión: Detén el bucle y extrae los logs más tempranos alrededor del primer reinicio. No pierdas tiempo afinando timeouts aún.

Task 2: Detén el bucle inmediatamente (pausa)

cr0x@server:~$ sudo systemctl stop myapp.service
cr0x@server:~$ systemctl is-active myapp.service
inactive

Qué significa: systemd lo detuvo. Si vuelve a “activating”, algo más lo está trayendo por dependencias.

Decisión: Si se reinicia por dependencias, máscalo (siguiente tarea).

Task 3: Máscalo para prevenir reinicios disparados por dependencias

cr0x@server:~$ sudo systemctl mask myapp.service
Created symlink /etc/systemd/system/myapp.service → /dev/null.
cr0x@server:~$ systemctl status myapp.service --no-pager -l
● myapp.service
     Loaded: masked (Reason: Unit myapp.service is masked.)
     Active: inactive (dead)

Qué significa: No puede iniciarse hasta que se desmasquee. Esto es reversible y deja los logs intactos.

Decisión: Investiga con calma. Desmáscala solo cuando estés listo para probar otra vez.

Task 4: Extrae logs específicos de la unidad para el último arranque, los más recientes primero

cr0x@server:~$ journalctl -u myapp.service -b --no-pager -n 200
Dec 29 10:13:59 server myapp[18391]: FATAL: cannot read config file: open /etc/myapp/config.yml: permission denied
Dec 29 10:13:59 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:00 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 10:14:00 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.

Qué significa: Esto es oro: permiso denegado al leer la configuración.

Decisión: Arregla permisos/propiedad, o revisa opciones de hardening que cambiaron la vista del sistema de ficheros.

Task 5: Identifica el ExecStart exacto y las opciones de la unidad

cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp API
After=network-online.target
Wants=network-online.target

[Service]
User=myapp
Group=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=always
RestartSec=1
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

Qué significa: ProtectSystem=strict puede hacer grandes partes del sistema de ficheros sólo lectura; ProtectHome=true bloquea acceso a /home. Si la config está bajo una ruta protegida o necesita ficheros auxiliares, puedes romper el servicio “mejorando la seguridad”.

Decisión: Si los permisos parecen correctos, sospecha del hardening de la unidad. Ajusta rutas o relaja opciones concretas con overrides quirúrgicos.

Task 6: Revisa permisos y restricciones SELinux/AppArmor (Ubuntu: normalmente AppArmor)

cr0x@server:~$ namei -l /etc/myapp/config.yml
f: /etc/myapp/config.yml
drwxr-xr-x root root /
drwxr-xr-x root root etc
drwx------ root root myapp
-rw------- root root config.yml

Qué significa: El directorio /etc/myapp es 700, y el fichero es 600 propiedad de root. Si la unidad corre como usuario myapp, no puede leerlo.

Decisión: Cambia la propiedad o ACLs. No ejecutes el servicio como root “porque funciona”. Así es como tienes otro incidente la próxima semana.

Task 7: Arregla la propiedad de forma segura (ejemplo) y valida

cr0x@server:~$ sudo chown -R root:myapp /etc/myapp
cr0x@server:~$ sudo chmod 750 /etc/myapp
cr0x@server:~$ sudo chmod 640 /etc/myapp/config.yml
cr0x@server:~$ sudo -u myapp test -r /etc/myapp/config.yml && echo OK
OK

Qué significa: El usuario del servicio puede leer el fichero ahora.

Decisión: Desmáscala y arranca una vez; vigila los logs. Si sigue fallando, eliminaste una causa raíz y mantuviste el cambio mínimo.

Task 8: Desmáscala, arranca una vez y sigue los logs en vivo

cr0x@server:~$ sudo systemctl unmask myapp.service
Removed "/etc/systemd/system/myapp.service".
cr0x@server:~$ sudo systemctl start myapp.service
cr0x@server:~$ journalctl -u myapp.service -f --no-pager
Dec 29 10:22:41 server myapp[19012]: INFO: loaded config /etc/myapp/config.yml
Dec 29 10:22:41 server myapp[19012]: INFO: listening on 0.0.0.0:8080

Qué significa: Inicia y permanece arriba (no aparecen más reinicios).

Decisión: Si está estable, re-habilita las alertas de monitorización y procede a verificar dependencias (BD, almacenamiento, upstream).

Task 9: Si los logs están ruidosos, encuentra la primera ventana de fallo por tiempo

cr0x@server:~$ journalctl -u myapp.service --since "2025-12-29 10:10:00" --until "2025-12-29 10:15:00" --no-pager
Dec 29 10:13:59 server myapp[18391]: FATAL: cannot read config file: open /etc/myapp/config.yml: permission denied
Dec 29 10:13:59 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:00 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.

Qué significa: Puedes aislar el error más temprano aunque los reinicios spameen el journal.

Decisión: Siempre limita temporalmente las consultas al journal durante tormentas de reinicio. Te mantiene cuerdo y hace las notas del incidente creíbles.

Task 10: Inspecciona la cadena de dependencias y el orden

cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
myapp.service
● multi-user.target

Qué significa: No depende mucho de él (bien). Si la lista inversa es enorme, detenerlo podría romper otras unidades.

Decisión: Para árboles de dependencia grandes, máscale con cuidado y comunica el impacto. “Detuve la cosa” no es un plan completo.

Task 11: Captura crashes: revisa coredumps

cr0x@server:~$ coredumpctl list myapp
TIME                            PID   UID   GID SIG COREFILE  EXE
Mon 2025-12-29 09:58:12 UTC   17021  1001  1001  11 present   /usr/local/bin/myapp
cr0x@server:~$ coredumpctl info 17021
           PID: 17021 (myapp)
           UID: 1001 (myapp)
           GID: 1001 (myapp)
        Signal: 11 (SEGV)
     Timestamp: Mon 2025-12-29 09:58:12 UTC (17min ago)
  Command Line: /usr/local/bin/myapp --config /etc/myapp/config.yml
    Executable: /usr/local/bin/myapp
 Control Group: /system.slice/myapp.service
          Unit: myapp.service
       Message: Process 17021 (myapp) of user 1001 dumped core.

Qué significa: Si ves SIGSEGV/SIGABRT, estás en territorio de crash real: parseo de config corrupto, librería incompatible o bug de memoria.

Decisión: Deja de intentar tweaks aleatorios en la unidad. Recopila el core, build ID y versiones de paquete; luego reproduce en staging o con el proveedor.

Task 12: Revisa logs del kernel para OOM kills y segfaults

cr0x@server:~$ journalctl -k -b --no-pager -n 200
Dec 29 10:01:44 server kernel: Out of memory: Killed process 17555 (myapp) total-vm:812340kB, anon-rss:612220kB, file-rss:1100kB, shmem-rss:0kB, UID:1001 pgtables:1872kB oom_score_adj:0
Dec 29 10:01:44 server kernel: oom_reaper: reaped process 17555 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Qué significa: El kernel mató el proceso. systemd lo reinicia. Bucle logrado.

Decisión: Arregla la presión de memoria (límites, fugas, concurrencia) o añade controles de memoria de systemd (MemoryMax=) para fallar rápido y predeciblemente. También revisa swap y cargas co-ubicadas.

Task 13: Inspecciona el limitador de frecuencia de arranques (por qué “deja de intentar”)

cr0x@server:~$ systemctl show myapp.service -p StartLimitIntervalUSec -p StartLimitBurst -p NRestarts -p Restart -p RestartUSec
StartLimitIntervalUSec=10s
StartLimitBurst=5
NRestarts=17
Restart=always
RestartUSec=1s

Qué significa: Permite 5 arranques por 10 segundos. Con RestartSec=1, alcanzarás el limitador rápido.

Decisión: No “arregles” subiendo StartLimitBurst a 10.000. Aumenta RestartSec para desacelerar la tormenta y preservar recursos mientras depuras.

Task 14: Reproduce con systemd-run para coincidir el entorno del servicio

cr0x@server:~$ sudo systemd-run --unit=myapp-debug --property=User=myapp --property=Group=myapp \
  /usr/local/bin/myapp --config /etc/myapp/config.yml
Running as unit: myapp-debug.service
cr0x@server:~$ journalctl -u myapp-debug.service --no-pager -n 50
Dec 29 10:30:10 server myapp[19411]: FATAL: cannot open /var/lib/myapp/state.db: permission denied

Qué significa: Tu test “funciona cuando lo ejecuto” probablemente se ejecutó como root, o en un directorio de trabajo distinto, o con variables de entorno diferentes.

Decisión: Reproduce siempre con el usuario del servicio y el contexto de systemd. Ahorra horas y reduce el folclore.

Task 15: Verifica y arregla propiedad de RuntimeDirectory/StateDirectory (patrón moderno de systemd)

cr0x@server:~$ systemctl show myapp.service -p StateDirectory -p RuntimeDirectory
StateDirectory=myapp
RuntimeDirectory=myapp
cr0x@server:~$ ls -ld /var/lib/myapp /run/myapp
ls: cannot access '/var/lib/myapp': No such file or directory
drwxr-xr-x 2 root root 40 Dec 29 10:12 /run/myapp

Qué significa: Si StateDirectory= está configurado, systemd debería crearlo bajo /var/lib con la propiedad correcta—a menos que la unidad sea antigua, mal especificada o sobrescrita. Aquí falta y el runtime dir está en propiedad de root.

Decisión: Arregla la unidad para que systemd gestione los directorios, o créalos con la propiedad correcta. Los bugs de propiedad de directorios son combustible clásico para bucles de reinicio.

Cuando solo falla bajo systemd

Este caso confunde porque parece sobrenatural: ejecutas el binario manualmente y funciona. Bajo systemd, falla inmediatamente. No es sobrenatural. Es entorno.

Esto es lo que cambia bajo systemd y que comúnmente importa:

  • Usuario/grupo y grupos suplementarios: las pruebas manuales a menudo se hacen como root o un usuario con sesión.
  • Directorio de trabajo: systemd por defecto usa / a menos que se defina WorkingDirectory=.
  • Variables de entorno: tu shell carga scripts de perfil; systemd no. Si la app necesita ajustes en PATH o JAVA_HOME, decláralos explícitamente.
  • Límites de descriptores: systemd puede fijar distintos valores por defecto, y tu servicio puede alcanzar EMFILE (demasiados ficheros abiertos).
  • Sandboxing/hardening: ProtectSystem, PrivateTmp, RestrictAddressFamilies y similares pueden romper apps sutilmente.
  • Tipo de arranque: si la app se demoniza o hace fork, pero la unidad es Type=simple (por defecto), systemd puede tratarla como salida y reiniciarla.

Broma #2: systemd no “odía tu app.” Simplemente se niega a participar en la danza interpretativa de tu app alrededor del PID 1.

Modos de fallo de almacenamiento y sistema de ficheros que parecen “bugs” de la app

Como persona de almacenamiento, diré la parte de forma clara: un número sorprendente de bucles de reinicio son problemas de almacenamiento disfrazados de problemas de aplicación.

Re-montajes en solo lectura

ext4 y afines pueden remontar en solo lectura tras ciertos errores de I/O para prevenir más daños. Tu servicio entonces falla al crear PID files, escribir estado, rotar logs o actualizar SQLite. systemd lo reinicia para siempre porque el proceso sigue saliendo “por alguna razón”.

Disco lleno (o tabla de inodos llena)

Un servicio que no puede escribir en /var o /tmp suele morir temprano. Peor: el spam de logs llena el espacio restante y tumba otros servicios. Revisa bloques e inodos.

Orden de montaje y sistemas de ficheros en red

Servicios que esperan que /mnt/data exista se crashan si no está montado. Esto se complica con NFS, iSCSI y volúmenes cifrados: el montaje puede estar “presente” pero no listo, o la red está arriba pero DNS no, o viceversa.

Permisos en rutas de estado

Tras una restauración, rsync o una “mejora”, la propiedad deriva. Muchos demonios salen si detectan permisos incorrectos en ficheros sensibles (SSH, Postgres, etc.). Es una característica, pero crea bucles de reinicio si no ves la primera línea del log.

Trampas de red y DNS

Ubuntu 24.04 típicamente usa systemd-resolved y Netplan. Un bucle de reinicio frecuentemente ocurre cuando un servicio intenta resolver un hostname o conectar a una dependencia upstream durante el arranque, falla rápido y sale. Luego systemd lo reinicia. Enhorabuena: tu inestabilidad DNS ahora es una prueba de CPU.

Desencadenantes comunes impulsados por la red:

  • El servicio inicia antes de que la red esté “online” (link up no es lo mismo que enrutabilidad).
  • DNS apunta a un resolvedor caído; resolv.conf está gestionado y tus viejas suposiciones fallan.
  • Reglas de firewall bloquean egress; la app lo trata como fatal en vez de reintentar.
  • Mal comportamiento de IPv6: la app vincula sólo a v6, o intenta v6 primero y se queda esperando.

Orden y disponibilidad de dependencias

Los bucles de reinicio no siempre son por la app fallando. A veces la app arranca correctamente, pero systemd piensa que no lo hizo, o la inicia en el momento equivocado.

Tipo de servicio incorrecto

Si tu demonio se demoniza al fondo pero la unidad es Type=simple, systemd puede interpretar la salida del padre como fallo (o éxito, según el caso), y luego reiniciar. La solución suele ser Type=forking con un PIDFile=, o mejor: configurar la app para que no se demonice y mantener Type=simple.

Desajuste de notificación de readiness

Type=notify espera que el proceso llame a sd_notify. Si no lo hace, systemd espera, caduca, lo mata y reinicia. Ese bucle parece un crash de app, pero es una violación de contrato.

Targets de montaje y red no son mágicos

After=network-online.target ayuda, pero solo funciona si el servicio “wait online” relevante está habilitado para tu pila de red. Para montajes, usa RequiresMountsFor= para vincular una unidad a una ruta que debe estar montada.

Límites de frecuencia, política de reinicio y cómo ajustar sin mentir

La política de reinicio no es solo “hacerlo altamente disponible”. Es “declarar cómo se comporta el sistema ante fallos”. En producción, ese comportamiento debe ser deliberado.

Restart=always raramente es el predeterminado que quieres

Restart=always reinicia incluso después de una salida limpia. Es genial para workers diseñados para correr siempre; es terrible para jobs oneshot y para demonios que salen intencionalmente tras una migración completa.

Patrones mejores:

  • Restart=on-failure para la mayoría de demonios.
  • Restart=on-abnormal cuando la salida limpia es legítima.
  • No reiniciar para jobs por lotes; deja que el scheduler maneje reintentos.

Usa RestartSec para evitar una autonegación de servicio

Si un servicio va a fallar repetidamente, haz que falle lo bastante lento como para que los humanos puedan leer logs y la máquina pueda respirar. Aumentar RestartSec de 100ms a 5s puede marcar la diferencia entre “incidente menor” y “host inutilizable”.

StartLimit… es un disyuntor, no una solución

Los límites de arranque detienen la tormenta inmediata, pero no resuelven la causa raíz. Trata “Start request repeated too quickly” como systemd diciendo educadamente: estás depurando demasiado tarde en la línea temporal.

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

Esta es la parte que reconocerás en diez segundos, porque la has vivido.

1) Síntoma: “Active: activating (auto-restart)” con status=1/FAILURE

Causa raíz: fallo determinista al iniciar (parseo de config, fichero ausente, permiso denegado).

Solución: stop/mask, luego journalctl -u por ventana temporal. Arregla permisos o config. Si la unidad corre como non-root, valida acceso con sudo -u.

2) Síntoma: “Start request repeated too quickly” y luego se queda fallado

Causa raíz: limitador de frecuencia disparado; errores tempranos se perdieron por scroll.

Solución: consulta logs con --since/--until, o -b y busca el primer fallo. Considera aumentar RestartSec mientras depuras, no StartLimitBurst.

3) Síntoma: servicio funciona al ejecutarlo manualmente, falla bajo systemd

Causa raíz: desajuste de entorno (usuario, directorio de trabajo, PATH, ulimits, sandboxing).

Solución: reproduce con systemd-run y el usuario del servicio. Revisa systemctl cat por hardening y opciones de directorio. Establece WorkingDirectory= y Environment= o EnvironmentFile= explícitamente.

4) Síntoma: estado muestra “status=203/EXEC”

Causa raíz: ExecStart apunta a un binario ausente o no ejecutable, o arquitectura/formato incorrecto.

Solución: verifica ruta y permisos; revisa shebangs en scripts. Asegura que el binario existe en el host, no solo en tu memoria.

5) Síntoma: status muestra “code=killed, signal=KILL” con mensajes TimeoutStartSec

Causa raíz: systemd lo mató tras timeout de arranque; a menudo desajuste de readiness o dependencia lenta.

Solución: corrige Type=, notificaciones de readiness, o evita que el arranque bloquee dependencias. Si el arranque legítimamente tarda más, aumenta TimeoutStartSec con justificación.

6) Síntoma: logs del kernel muestran entradas del OOM killer para el servicio

Causa raíz: presión de memoria o fuga; podría ser co-tenencia o concurrencia descontrolada.

Solución: reduce uso de memoria, añade swap si procede, o aplica MemoryMax=. Luego arregla la fuga. No solo añadas RAM y lo llames “plan de capacidad”.

7) Síntoma: “permission denied” al escribir en /run o /var/lib

Causa raíz: directorios runtime/state ausentes o propiedad root tras despliegue o restauración.

Solución: usa RuntimeDirectory= y StateDirectory=, o arregla propiedad en tmpfiles.d. Evita mkdir ad-hoc en ExecStartPre salvo que te gusten las condiciones de carrera.

8) Síntoma: fallos correlacionados con reboots; ruta de montaje ausente en arranque

Causa raíz: orden de dependencias y readiness de montajes; FS de red no listo aún.

Solución: añade RequiresMountsFor=/path y corrige After=. Para network-online, asegúrate de que el servicio de espera adecuado esté habilitado.

Tres mini-historias corporativas (dolor, aprendizaje, recibos)

Mini-historia 1: El incidente causado por una suposición equivocada

El equipo tenía una API interna pequeña corriendo como servicio systemd. Nada fancy. Leía un YAML en /etc/company/app.yml y escribía un poco de estado en /var/lib/app. Había funcionado así durante meses.

Entonces llegó un sprint de hardening de seguridad. El archivo de unidad fue “mejorado” con ProtectSystem=strict y NoNewPrivileges=true. Todos asintieron. Seguro por defecto es bueno. El servicio se reiniciaba cada segundo y generó alertas por todo el entorno.

La suposición equivocada fue sutil: la config era “obviamente legible” porque siempre había funcionado. Pero los permisos habían quedado perezosamente solo para root desde el primer día, y el servicio había estado corriendo como root. El sprint de hardening también lo cambió para correr como un usuario sin privilegios. La postura de seguridad mejoró y todo se rompió a la vez.

La solución fue aburrida: corregir la propiedad y permisos, y luego mantener el hardening. La lección no fue “no endurecer”. La lección fue “no mezcles cambios de hardening con cambios en el modelo de privilegios sin un plan de pruebas”. En el lado positivo, el servicio quedó más seguro y el equipo dejó de fingir que root era una bandera de característica.

Mini-historia 2: La optimización que salió mal

Un grupo de plataforma quería tiempos de recuperación más rápidos. Alguien bajó RestartSec en una flota de 5s a 100ms. La idea: si los procesos caen, volver a arriba inmediatamente. En laboratorio parecía rápido. En producción, convirtió un fallo pequeño en un problema sistémico.

Un servicio empezó a fallar por un fichero secreto rotado con permisos erróneos. En vez de un fallo cada pocos segundos con logs legibles, intentaba diez reinicios por segundo en docenas de nodos. Los journals se inflaron. La amplificación de escritura en disco subió. Los nodos empezaron a mostrar latencia. Otros servicios en los mismos hosts se hicieron más lentos. La tormenta de reinicios se convirtió en el incidente.

El postmortem fue incómodo porque el fallo original era menor y local. La optimización añadió un megáfono gigante y un impuesto de recursos. Volvieron atrás el timing agresivo, introdujeron retrasos por servicio y requirieron una justificación para valores muy bajos de RestartSec. Lo más importante: dejaron de tratar al reinicio como remediación. Es solo una herramienta para resiliencia si el sistema también puede exponer la causa raíz eficientemente.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Otra organización corría un servicio relacionado con pagos con un proceso de cambios estricto. No glamoroso. Cada unidad de servicio tenía: un override de debug almacenado en gestión de configuración, usuarios/grupos explícitos, propiedad de directorios explícita usando StateDirectory=, y un “override de depuración” estándar que podía poner Restart=no y aumentar la verbosidad de logs temporalmente.

Durante una ventana de actualización de Ubuntu, un nodo empezó a flapping por una incompatibilidad de librería tras una actualización parcial de paquetes. El on-call no improvisó ediciones en el nodo. Aplicó el override de debug conocido: detener reinicios, capturar logs, tomar versiones de paquetes y restaurar el nodo completando la transacción de actualización.

¿Por qué importó? Porque el incidente no se expandió. El nodo se mantuvo estable lo suficiente para recoger evidencia. La flota no recibió una ola de hotfixes inconsistentes. El equipo terminó con una remediación limpia: arreglar la automatización de upgrades para evitar actualizaciones parciales y asegurar que el servicio solo arranque después de que los paquetes correctos estén presentes.

La práctica aburrida funciona porque reduce el número de variables nuevas que introduces mientras el sistema está en llamas. No es emocionante. Es fiable.

Listas de verificación / plan paso a paso

Paso a paso: detén el bucle, luego diagnostica

  1. Captura el estado actual: systemctl status -l y copia las líneas de estado de salida en tus notas del incidente.
  2. Detén o máscalo: si el servicio causa churn de recursos, máscalo.
  3. Extrae los logs del primer fallo: usa journalctl -u con --since/--until alrededor del primer tiempo de fallo conocido.
  4. Clasifica el modo de fallo: permisos/config vs crash vs dependencia vs timeout vs OOM.
  5. Revisa el archivo de unidad por trampas: usuario/grupo, directorio de trabajo, hardening, Type, timeouts, política de reinicio.
  6. Reproduce bajo contexto de systemd: systemd-run o ejecuta como el usuario del servicio.
  7. Revisa la capa de plataforma: espacio en disco/inodos, montajes, DNS, logs kernel OOM.
  8. Haz un cambio: el cambio más pequeño posible que aborde la causa raíz.
  9. Prueba una vez: desmáscala y arranca; sigue los logs.
  10. Restaura la política de reinicio intencionalmente: no la dejes en modo debug.
  11. Escribe lo que cambiaste: el tú del futuro no recordará, y los auditores definitivamente no.

Reglas duras cuando estás privado de sueño

  • No edites el archivo de unidad del proveedor en /lib/systemd/system. Usa drop-ins.
  • No “arregles” ejecutando el servicio como root salvo que sea inherentemente privilegiado (y aun así, minimiza el alcance).
  • No aumentes StartLimitBurst como primer movimiento. Haz los reinicios más lentos en su lugar.
  • No reinicies solo para que los logs desaparezcan. Eso no es higiene; es destrucción de evidencia.

Datos interesantes y contexto histórico

  • systemd introdujo semánticas de reinicio agresivas y explícitas en comparación con scripts init tradicionales, haciendo el “flapping” más visible—y más común cuando hay mala configuración.
  • Los límites de frecuencia existen en parte porque demonios antiguos podían fork-bombear PID 1 con ciclos rápidos de fallo/reinicio; el limitador es una válvula de seguridad.
  • El movimiento de Ubuntu hacia systemd-resolved cambió cómo se gestiona DNS; viejas suposiciones sobre /etc/resolv.conf estático frecuentemente fallan en upgrades.
  • El manejo de coredumps pasó de “core files por todas partes” a recolección centralizada con systemd-coredump, mejorando el debug en flota pero confundiendo a quienes esperaban core en el directorio de trabajo.
  • Systemd moderno añadió gestión declarativa de directorios (StateDirectory, RuntimeDirectory, LogsDirectory) para reducir scripts frágiles en ExecStartPre.
  • Opciones de hardening como ProtectSystem y PrivateTmp se volvieron comunes porque reducen clases enteras de compromiso—pero también exponen suposiciones descuidadas sobre el sistema de ficheros.
  • “Funciona en un shell” ha sido una mentira desde los inicios de Unix; los entornos de demonio siempre han diferido (PATH distinto, sin TTY, ulimits distintos). systemd simplemente hace el contrato más explícito.
  • Los límites de frecuencia y políticas de reinicio son herramientas de fiabilidad, no cosmética; patrones tempranos de HA intentaban “reiniciar todo al instante” y aprendieron sobre fallos en cascada de la peor manera.

Preguntas frecuentes (FAQ)

1) ¿Cómo detengo un bucle de reinicio sin desinstalar nada?

Máscalo: sudo systemctl mask myapp.service. Eso evita arranques manuales y arranques disparados por dependencias. Desmáscala cuando estés listo.

2) ¿Por qué systemd sigue reiniciando un servicio que claramente no puede iniciar?

Porque el archivo de unidad lo indica. Busca Restart=always o Restart=on-failure. systemd asume que reiniciar es deseable a menos que se le diga lo contrario.

3) ¿Dónde está el “error real” si systemctl status solo muestra mensajes de reinicio?

Normalmente en journalctl -u myapp.service, y a menudo en las primeras líneas tras el primer intento de arranque. Usa --since/--until para apuntar al inicio del bucle.

4) ¿Qué significa “status=203/EXEC”?

systemd no pudo ejecutar el comando en ExecStart=. Causas comunes: ruta equivocada, binario ausente, no es ejecutable, o script con shebang incorrecto.

5) ¿Qué significa “Start request repeated too quickly”?

El limitador de arranques se disparó. La unidad inició y falló demasiadas veces dentro de StartLimitIntervalSec, excediendo StartLimitBurst. Es un disyuntor, no la causa raíz.

6) ¿Cómo distinguo si está crashando (segfault) vs saliendo limpiamente?

En systemctl status y el journal, busca salidas basadas en señales (SIGSEGV, SIGABRT). Luego revisa coredumpctl list y coredumpctl info.

7) El servicio funciona cuando lo ejecuto manualmente. ¿Por qué no bajo systemd?

Usuario distinto, entorno distinto, límites distintos, directorio de trabajo distinto y posiblemente restricciones de sandboxing. Reproduce con systemd-run y el usuario del servicio.

8) ¿Está bien aumentar TimeoutStartSec para arreglar un bucle?

Sólo si has probado que el servicio es sano pero lento para estar listo. Si falla rápido, un timeout mayor solo retrasa lo inevitable y hace perder tiempo.

9) ¿Está bien poner Restart=always en todo para que “se autocure”?

No. Puede crear tormentas de reinicio y ocultar fallos deterministas. Usa Restart=on-failure para la mayoría de demonios y diseña apps para reintentar internamente dependencias.

10) ¿Cómo evito que los logs inunden el disco durante una tormenta de reinicio?

Máscala y luego considera ralentizar reinicios (RestartSec) una vez la vuelvas a habilitar. Si journald ya usa mucho disco, rota/vacía solo después de haber capturado la ventana crítica.

Conclusión: siguientes pasos para mantenerte fuera de problemas

Los bucles de reinicio parecen caóticos, pero suelen ser deterministas: un permiso ausente, una dependencia rota, un desajuste de readiness o un crash real. Tu trabajo es detener el churn, coger el primer error significativo y cambiar lo mínimo que haga que el servicio se comporte.

Haz esto a continuación, en orden:

  1. Máscala si está flapping y causando daño colateral.
  2. Extrae el fallo más temprano del journal por ventana temporal.
  3. Clasifica el modo de fallo (permiso/config, crash, timeout, dependencia, recurso).
  4. Reproduce bajo contexto systemd con el usuario correcto.
  5. Aplica un arreglo mínimo, desmáscala, arranca una vez y sigue los logs.
  6. Restaura una política de reinicio sensata (on-failure, con un RestartSec razonable) para que futuros fallos sean soportables y depurables.

Si no te llevas otra cosa: no luches contra el bucle haciendo que systemd sea más silencioso. Haz que el fallo sea más fuerte, más temprano y más fácil de probar. Así es como los incidentes dejan de repetirse.

← Anterior
Duron: el chip económico que la gente overclockeó como un tope de gama
Siguiente →
Filtraciones IPv6 en Docker: evita la exposición “Ups, es pública”

Deja un comentario