La lección anterior terminó con una limitación clara de la shell: un script suelto no es un sistema. Alguien tiene que arrancar meteo-api cuando la máquina enciende, esperar a que /var/lib/meteora esté montado, reiniciarlo si cae, ejecutar el agregador cada hora aunque el servidor estuviera apagado a las 03:00, aplicarles los límites y el endurecimiento del módulo 5, y recoger sus registros. Ese alguien es el gestor de servicios: el proceso con PID 1 que el núcleo arranca al final del arranque y que no muere hasta que la máquina se apaga.

Esta lección tiene dos mitades. En la primera seguimos el encendido de meteo-01 fase por fase, desde que el firmware toma el control hasta que aparece el PID 1, y aprendemos qué se puede observar y tocar en cada fase —porque la mitad de las incidencias graves de arranque se resuelven sabiendo pasar un parámetro al núcleo desde GRUB—. En la segunda desmontamos systemd: su modelo de unidades, la anatomía completa de la unidad de meteo-api, las dependencias, los temporizadores que sustituyen a cron, y el guion de diagnóstico de "mi servicio no arranca", que es una de las cosas que más veces harás en tu vida profesional.

La monitorización de rendimiento es la lección siguiente; aquí nos ocupamos de qué corre, cuándo y bajo qué reglas.

Contenido

  1. El arranque de meteo-01, fase a fase
  2. Qué problema resolvió systemd
  3. El modelo de unidades
  4. Anatomía de un fichero de unidad: meteo-api completo
  5. Tipos de servicio y cómo elegir bien
  6. Dependencias frente a orden
  7. Objetivos y niveles de ejecución
  8. El manejo diario con systemctl
  9. Políticas de reinicio y el bucle que oculta un fallo
  10. Temporizadores frente a cron
  11. Activación por socket y por ruta
  12. Unidades de usuario y loginctl
  13. Análisis y depuración del arranque
  14. Apagado ordenado: SIGTERM, SIGKILL y TimeoutStopSec

El arranque de meteo-01, fase a fase

graph TD
    A["Firmware UEFI<br/>POST + inicialización de hardware"] --> B["Lee la ESP (FAT32)<br/>y ejecuta shimx64.efi / grubx64.efi"]
    B --> C["GRUB: menú, grub.cfg<br/>carga vmlinuz + initrd"]
    C --> D["Núcleo: descomprime, monta initramfs<br/>como raíz temporal en RAM"]
    D --> E["initramfs: carga módulos<br/>(RAID, LUKS, LVM), ensambla /dev/md0"]
    E --> F["switch_root a la raíz real<br/>y execve de /sbin/init"]
    F --> G["systemd, PID 1<br/>alcanza default.target"]
    G --> H["meteo-api escucha en :443"]

Fase 1: firmware. Al pulsar el botón, la CPU empieza a ejecutar código del firmware de la placa. Hay dos mundos:

BIOS heredada UEFI
Dónde está el arranque MBR: 446 bytes en el sector 0 Ficheros .efi en la ESP, una partición FAT32
Tamaño del código inicial Ridículo: cabe una carga en cadena Un ejecutable completo, con controladores
Tabla de particiones MBR (máx. 2 TB, 4 primarias) GPT (sin esos límites, con CRC)
Arranque seguro No existe Secure Boot: firmas verificadas por el firmware
Diagnóstico Casi nulo Shell EFI, efibootmgr, variables NVRAM

meteo-01 arranca por UEFI. Su ESP está montada en /boot/efi y contiene EFI/debian/grubx64.efi y el shimx64.efi firmado por Microsoft que hace posible Secure Boot: el firmware verifica la firma de shim, shim la de GRUB, GRUB la del núcleo y el núcleo la de los módulos. La cadena existe para impedir un bootkit, código malicioso que se ejecuta antes que el sistema operativo y que por tanto ningún antivirus del sistema puede ver. Su precio práctico es que un módulo compilado a mano (por ejemplo un controlador propietario) no cargará sin firmarlo.

[ -d /sys/firmware/efi ] && echo "Arranque UEFI" || echo "Arranque BIOS heredada"
efibootmgr -v | head -3         # entradas de arranque en la NVRAM
mokutil --sb-state              # SecureBoot enabled

Qué demuestran. El núcleo solo crea /sys/firmware/efi si arrancó por UEFI: es la comprobación canónica. efibootmgr lee las variables NVRAM donde el firmware guarda el orden de arranque, y permite añadir o reordenar entradas sin entrar en la pantalla de configuración. mokutil informa del estado de Secure Boot.

Fase 2: gestor de arranque. GRUB lee /boot/grub/grub.cfg —fichero generado, nunca editado a mano: se produce con update-grub a partir de /etc/default/grub y /etc/grub.d/— y presenta el menú. Cada entrada indica un núcleo, un initrd y una línea de parámetros:

linux /vmlinuz-6.1.0-18-amd64 root=UUID=8f3c... ro quiet
initrd /initrd.img-6.1.0-18-amd64

Pulsando e sobre la entrada puedes editar esa línea para un solo arranque, sin tocar el disco. Es la herramienta de rescate más importante que existe:

Parámetro Para qué
single o systemd.unit=rescue.target Modo mínimo con shell de root, sin servicios
systemd.unit=emergency.target Aún más mínimo: solo la raíz montada en solo lectura
Quitar quiet y añadir debug Ver todos los mensajes del núcleo en pantalla
init=/bin/bash Saltarse systemd por completo (raíz en solo lectura; mount -o remount,rw /)
nomodeset Arrancar sin el controlador gráfico que cuelga la máquina
systemd.mask=meteo-api.service Arrancar sin un servicio que impide el arranque

Fase 3: núcleo e initramfs. GRUB carga en memoria el núcleo comprimido y el initrd, y salta al núcleo, que se descomprime, inicializa la gestión de memoria del módulo 2 y monta el initramfs como raíz temporal en RAM.

¿Por qué existe el initramfs? Por un problema de huevo y gallina. La raíz real de meteo-01 está en /dev/md0, un RAID 1 sobre ext4. Para montarla hace falta el módulo raid1 y el de ext4… que están dentro de esa misma raíz. El initramfs rompe el círculo: es un cpio comprimido con los módulos imprescindibles y un /init mínimo que carga los controladores, ensambla el RAID, abre el LUKS si lo hay, activa los volúmenes LVM y solo entonces monta la raíz real. Después ejecuta switch_root, que sustituye la raíz temporal por la real, libera la RAM del initramfs y hace execve de /sbin/init —un enlace a /lib/systemd/systemd— con PID 1.

lsinitramfs /boot/initrd.img-$(uname -r) | grep -E 'raid1|ext4'   # qué módulos lleva
update-initramfs -u -k all                                        # regenerarlo tras un cambio
dmesg -T | head -40                                               # el diario del núcleo
dmesg -T --level=err,warn                                         # solo errores y avisos

Qué hacen y por qué importan. Si añades un disco o cambias el esquema de almacenamiento y olvidas update-initramfs, la máquina arrancará hasta el initramfs y se quedará ahí con un prompt (initramfs) porque no encuentra la raíz: es uno de los ladrillazos más frecuentes. Y dmesg es el búfer circular del núcleo, donde queda todo lo ocurrido desde la primera instrucción: detección de hardware, errores de disco, el OOM killer de 02-04, los mensajes de RAID. -T traduce las marcas de tiempo relativas a fechas legibles.

Fase 4: PID 1. El PID 1 es especial por tres motivos que vienen de 02-01: no se le pueden aplicar las señales por defecto (el núcleo ignora SIGTERM y SIGKILL hacia él, para que nadie pueda matarlo por accidente), adopta a los huérfanos y recoge sus estados de salida evitando los zombis, y si muere, el núcleo entra en pánico. Todo lo demás en el sistema desciende de él.

Qué problema resolvió systemd

Antes de systemd, el arranque lo gobernaba SysV init: guiones de shell en /etc/init.d/ que se ejecutaban en orden alfabético desde /etc/rc3.d/ (S01, S02, S03…), uno detrás de otro, cada uno arrancando su demonio con un start-stop-daemon. El modelo tenía cinco problemas graves:

Problema de SysV init Respuesta de systemd
Ejecución secuencial: si S20 tarda 30 s, todo lo demás espera Paralelismo real: solo se serializa lo que declara depender
Las dependencias se codificaban en el número del enlace Dependencias declaradas (After=, Requires=) y resueltas por un grafo
Todo se arranca siempre, se use o no Activación bajo demanda por socket, ruta o dispositivo
Si el demonio moría, nadie se enteraba Supervisión: systemd es el padre y reinicia según política
Un demonio podía dejar procesos sueltos que stop no mataba Cada servicio vive en su cgroup: se para el grupo entero

Ese último punto es el más subestimado y conecta directamente con 06-02. Un guion SysV rastreaba a su demonio por un fichero .pid; si el proceso se demonizaba mal, cambiaba de PID o lanzaba hijos, el stop mataba el PID equivocado y dejaba procesos huérfanos consumiendo recursos. systemd pone cada servicio en un grupo de control propio, así que la pertenencia es una propiedad del núcleo, no una suposición: parar el servicio significa señalar a todo el cgroup, y las cuentas de CPU, memoria y E/S del servicio son exactas.

systemctl status meteo-api.service | tail -6
# CGroup: /system.slice/meteo-api.service
#         ├─1834 /usr/local/bin/meteo-api --config /etc/meteora/meteora.conf
#         └─1847 /usr/local/bin/meteo-api --worker 1
systemd-cgls /system.slice | head -20     # el árbol de cgroups por servicio

Qué demuestra. El campo CGroup de status lista todos los procesos del servicio, incluidos los que él haya lanzado. Ningún proceso se le escapa a systemd, porque el cgroup lo asigna el núcleo en el fork y se hereda.

El modelo de unidades

Todo en systemd es una unidad: un fichero de texto en formato INI que describe algo que se gestiona. Los tipos que necesitas conocer:

Tipo Extensión Qué describe Ejemplo en meteo-01
Servicio .service Un proceso supervisado meteo-api.service
Socket .socket Un punto de escucha que puede activar un servicio meteo-api.socket
Objetivo .target Un punto de sincronización, un "estado" multi-user.target
Temporizador .timer Ejecución programada de otra unidad agregador.timer
Montaje .mount Un punto de montaje (generado desde /etc/fstab) var-lib-meteora.mount
Ruta .path Vigila un fichero o directorio lecturas-nuevas.path
Porción .slice Un nodo del árbol de cgroups para agrupar límites meteora.slice

Dónde viven, por orden de prioridad creciente:

Directorio Quién lo escribe Se pierde al actualizar
/lib/systemd/system/ El paquete de la distribución : nunca edites aquí
/etc/systemd/system/ El administrador No: es tu territorio
/run/systemd/system/ Unidades transitorias en memoria Sí, al reiniciar

Un fichero en /etc/systemd/system/meteo-api.service sustituye por completo al del paquete. Casi nunca es lo que quieres: si el paquete mejora su unidad, te quedas con la vieja. Lo correcto es un drop-in, un fragmento que se fusiona con el original:

systemctl edit meteo-api.service      # crea .../meteo-api.service.d/override.conf
systemctl cat meteo-api.service       # muestra el original Y todos los drop-ins aplicados
systemctl edit --full meteo-api.service   # copia entera a /etc (usar solo si no hay más remedio)

Qué hacen. systemctl edit abre un editor sobre /etc/systemd/system/meteo-api.service.d/override.conf, y al guardar ejecuta daemon-reload automáticamente. systemctl cat es la orden que deberías usar siempre antes de tocar nada: muestra el fichero de origen y, debajo, cada drop-in con su ruta, de modo que ves la configuración efectiva y de dónde sale cada directiva.

Una trampa que hay que conocer: en un drop-in, las directivas que aceptan listas (como ExecStart o Environment) se acumulan. Para reemplazar un ExecStart hay que vaciarlo primero con una línea ExecStart= sin valor y luego poner el nuevo.

Anatomía de un fichero de unidad: meteo-api completo

# /etc/systemd/system/meteo-api.service
[Unit]
Description=API de consultas meteorológicas de Meteora
Documentation=https://docs.meteora.example/api
After=network-online.target var-lib-meteora.mount
Wants=network-online.target
RequiresMountsFor=/var/lib/meteora /var/log/meteora
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=notify
NotifyAccess=main
User=meteora
Group=meteora
ExecStartPre=/usr/local/bin/verificar-lecturas.sh -d /var/lib/meteora/lecturas
ExecStart=/usr/local/bin/meteo-api --config /etc/meteora/meteora.conf --listen 0.0.0.0:443
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
TimeoutStartSec=60
TimeoutStopSec=30
WatchdogSec=30

# --- Endurecimiento (mecanismo explicado en 05-03) ---
NoNewPrivileges=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/meteora /var/log/meteora /run/meteora
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
LockPersonality=yes
MemoryDenyWriteExecute=yes
UMask=0027

# --- Límites de recursos (cgroups v2, de 06-02) ---
MemoryMax=2G
MemoryHigh=1500M
CPUWeight=200
IOWeight=200
TasksMax=512

[Install]
WantedBy=multi-user.target

Sección por sección:

  • [Unit] describe la unidad y sus relaciones. Description es lo que aparece en systemctl status y en los registros: escríbela para un humano medio dormido. After= fija orden, no dependencia. RequiresMountsFor= es la forma correcta de decir "no arranques si estas rutas no están montadas": systemd deduce solo las unidades .mount implicadas, y es más robusto que nombrarlas a mano, porque el nombre de una unidad de montaje se codifica de forma peculiar (/var/lib/meteoravar-lib-meteora.mount).
  • [Service] define cómo se ejecuta. ExecStartPre corre antes y, si falla, el servicio no arranca: aquí reutilizamos el script de verificación de 07-01 como comprobación previa. ExecStart es el proceso principal, siempre con ruta absoluta (systemd no usa el PATH de tu shell). ExecReload recarga la configuración sin cortar las conexiones; $MAINPID es una de las pocas variables que systemd expande.
  • El bloque de endurecimiento es el módulo 5 hecho directivas. NoNewPrivileges pone el bit del núcleo que impide ganar privilegios por setuid, así que ni un binario setuid comprometido serviría de escalón. AmbientCapabilities=CAP_NET_BIND_SERVICE es lo que permite escuchar en el puerto 443 sin ser root, y CapabilityBoundingSet fija el techo: aunque el proceso quisiera otra capability, el núcleo no se la concede. ProtectSystem=strict monta todo el sistema de ficheros en solo lectura para el servicio, y ReadWritePaths abre las tres excepciones necesarias. PrivateTmp le da un /tmp propio en un namespace de montaje, lo que elimina de raíz los ataques por ficheros temporales predecibles. SystemCallFilter=@system-service aplica un filtro seccomp que deja pasar el conjunto habitual de un servicio y bloquea el resto devolviendo EPERM. UMask=0027 fija los permisos de creación acordados para Meteora.
  • [Install] solo se usa al hacer enable. WantedBy=multi-user.target significa "cuando se habilite, crea un enlace en multi-user.target.wants/". Sin sección [Install], un servicio no se puede habilitar: se puede arrancar a mano, pero no arranca solo. Es un despiste frecuente.

Verifica siempre lo que has escrito antes de confiar en ello:

systemd-analyze verify /etc/systemd/system/meteo-api.service   # errores de sintaxis y referencias
systemd-analyze security meteo-api.service                     # puntuación de exposición
# → Overall exposure level for meteo-api.service: 1.9 OK

Qué aportan. verify detecta directivas mal escritas y unidades referenciadas inexistentes, que de otro modo solo darían la cara al arrancar. security puntúa de 0 (blindado) a 10 (expuesto) evaluando decenas de directivas de aislamiento, y lista una por una las que faltan: es una lista de tareas de endurecimiento generada automáticamente.

Y la unidad del ingestor, más sencilla porque no escucha en un puerto privilegiado:

# /etc/systemd/system/ingestor.service
[Unit]
Description=Receptor de lecturas de las estaciones de Meteora
After=network-online.target var-lib-meteora.mount
RequiresMountsFor=/var/lib/meteora

[Service]
Type=exec
User=meteora
Group=meteora
RuntimeDirectory=meteora
RuntimeDirectoryMode=0750
ExecStart=/usr/local/bin/ingestor --fifo /run/meteora/lecturas.fifo --out /var/lib/meteora/lecturas
Restart=always
RestartSec=2s
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/meteora
PrivateTmp=yes
Nice=-5

[Install]
WantedBy=multi-user.target

Lo nuevo aquí. RuntimeDirectory=meteora hace que systemd cree /run/meteora con el propietario y el modo indicados al arrancar y lo borre al parar: es la forma correcta de gestionar el directorio donde vive la FIFO /run/meteora/lecturas.fifo, en lugar de crearlo a mano en un script. Nice=-5 da al ingestor algo más de prioridad de CPU (02-02) porque perder lecturas entrantes es irrecuperable, mientras que una consulta lenta de la API solo molesta.

Tipos de servicio y cómo elegir bien

Type= le dice a systemd cuándo considerar que el servicio ya está arrancado, lo que gobierna cuándo pueden arrancar los que dependen de él.

Tipo Se considera arrancado cuando… Cuándo usarlo
simple Se hace fork+exec (¡inmediatamente!) Por defecto; el proceso no se demoniza
exec El execve ha tenido éxito Mejor que simple: detecta binario inexistente o sin permisos
forking El proceso padre termina y el hijo queda Demonios clásicos al estilo UNIX; requiere PIDFile=
oneshot El proceso termina (con éxito) Tareas puntuales: migraciones, limpiezas, scripts
notify El proceso envía READY=1 por sd_notify() Servicios que tardan en estar listos de verdad
idle Como simple, pero espera a que no haya más trabajos Solo para no ensuciar la consola

El fallo típico, y es tan frecuente que merece detalle: declarar Type=simple un programa que se demoniza (hace fork, el padre sale, el hijo sigue). systemd ve que el proceso que lanzó ha terminado inmediatamente y, según la política de reinicio, o lo da por muerto y lo reinicia en bucle, o lo da por arrancado y correcto mientras el demonio real corre sin supervisión. El síntoma es un status que dice active (exited) o un ciclo de reinicios de un servicio que en realidad funciona. La solución no es pelearse con forking: es decirle al programa que no se demonice (casi todos tienen --foreground o -D) y usar Type=exec.

Hemos elegido Type=notify para meteo-api por una razón concreta: el servicio tarda unos 8 segundos en cargar el índice y calentar la caché en /dev/shm/meteora-cache. Con simple, systemd lo daría por listo al instante y cualquier unidad que dependa de él —o el balanceador que mira el estado— actuaría sobre un servicio que todavía devuelve errores. Con notify, el programa llama a sd_notify(0, "READY=1") cuando de verdad puede atender, y systemd espera. WatchdogSec=30 añade lo contrario: el servicio debe enviar WATCHDOG=1 cada 30 segundos o systemd lo considera colgado y lo reinicia, lo que detecta bloqueos internos que no matan el proceso.

Dependencias frente a orden

Esta es la distinción que más confusión causa, y es muy simple: son ejes independientes.

Directiva Significa
Requires=B Dependencia: si arranco yo, arranca B; si B falla o se para, yo también me paro
Wants=B Dependencia débil: intenta arrancar B, pero si falla, sigo igualmente
BindsTo=B Como Requires, y además me paro si B desaparece (útil con dispositivos)
Conflicts=B B y yo no podemos estar activos a la vez
After=B Orden: no arranco hasta que B haya terminado de arrancar
Before=B Orden: B no arranca hasta que yo termine

La ilustración crucial. Si escribes solo Requires=var-lib-meteora.mount, systemd arrancará el montaje y tu servicio a la vez, en paralelo: puede que meteo-api intente abrir /var/lib/meteora/lecturas/2026-08-31.dat antes de que el sistema de ficheros esté montado, y falle con ENOENT. Y si escribes solo After=var-lib-meteora.mount, ordenas correctamente pero no pides el montaje: si nadie más lo activa, tu servicio arranca sin datos. Casi siempre necesitas las dos, y por eso RequiresMountsFor= existe: genera ambas de golpe.

Otro matiz importante: en el apagado, el orden se invierte automáticamente. After=network-online.target implica que en la parada meteo-api se detiene antes que la red, que es justo lo que quieres para poder cerrar las conexiones limpiamente.

Y network-online.target merece una advertencia: no significa "hay conectividad a Internet", sino "el gestor de red declara que ha terminado de configurar las interfaces". Necesita que un servicio como systemd-networkd-wait-online esté habilitado; sin él, el objetivo se alcanza de inmediato y no garantiza nada. Un servicio robusto reintenta la conexión en vez de confiar en el orden de arranque.

systemctl list-dependencies meteo-api.service          # qué necesita (árbol hacia abajo)
systemctl list-dependencies --reverse meteo-api.service # quién lo necesita a él
systemctl show meteo-api.service -p After -p Requires   # las relaciones efectivas, ya resueltas

Objetivos y niveles de ejecución

Un objetivo (target) no hace nada por sí mismo: es un punto de sincronización que agrupa unidades. Sustituye a los niveles de ejecución de SysV:

Nivel SysV Objetivo systemd Estado
0 poweroff.target Apagado
1 rescue.target Monousuario, shell de root
3 multi-user.target Multiusuario en red, sin gráficos (el de un servidor)
5 graphical.target Multiusuario con entorno gráfico
6 reboot.target Reinicio
emergency.target Solo la raíz montada en solo lectura
systemctl get-default                          # multi-user.target
systemctl set-default multi-user.target        # objetivo por defecto al arrancar
systemctl isolate rescue.target                # cambiar AHORA (para todo lo que no pertenezca)

Cuidado con isolate. Para todas las unidades que no formen parte del objetivo destino: ejecutado por error en producción, tumba los servicios. Como diagnóstico previo, systemctl list-units --type=target muestra qué objetivos están activos ahora mismo.

El manejo diario con systemctl

systemctl status meteo-api.service
# ● meteo-api.service - API de consultas meteorológicas de Meteora
#      Loaded: loaded (/etc/systemd/system/meteo-api.service; enabled; preset: enabled)
#      Active: active (running) since Mon 2026-08-31 02:14:07 CEST; 1h 3min ago
#    Main PID: 1834 (meteo-api)
#      Status: "Sirviendo; caché 94% llena"
#       Tasks: 17 (limit: 512)
#      Memory: 1.1G (high: 1.4G, max: 2.0G)
#         CPU: 12min 4.031s
#      CGroup: /system.slice/meteo-api.service
#              └─1834 /usr/local/bin/meteo-api --config /etc/meteora/meteora.conf

Campo a campo, que es donde está la información:

Campo Qué te dice Trampa
Loaded Si el fichero se leyó, su ruta y si está habilitado enabledactive: habilitado es "arranca al iniciar", activo es "corre ahora"
Active Estado y desde cuándo Si el "desde cuándo" es de hace 40 segundos y no has tocado nada, se está reiniciando en bucle
Main PID El proceso principal Si cambia entre dos status, hay reinicios
Status Texto que el propio servicio publica con sd_notify Solo aparece en Type=notify
Tasks Hilos y procesos, frente al límite TasksMax Rozar el límite provoca fallos de clone()
Memory Uso real del cgroup, con high y max Es la cifra fiable, no la de top (07-03)
CGroup Todos los procesos del servicio Aquí ves los hijos que se te escapaban con SysV
Orden Qué hace
systemctl start/stop/restart NOMBRE Arrancar, parar, reiniciar ahora
systemctl reload NOMBRE Ejecutar ExecReload sin cortar el servicio
systemctl enable NOMBRE Que arranque en los próximos arranques (crea el enlace de [Install])
systemctl enable --now NOMBRE Habilitar y arrancar de una vez
systemctl disable / mask Quitar del arranque / prohibir que arranque de ninguna forma
systemctl daemon-reload Releer los ficheros de unidad tras editarlos
systemctl list-units --failed La primera orden de cualquier diagnóstico
systemctl is-active NOMBRE Estado en una palabra, con código de salida (para scripts)

enable frente a start es la confusión número uno del principiante: start arranca ahora y no sobrevive al reinicio; enable prepara el arranque futuro pero no arranca nada hoy. Y daemon-reload frente a reload: el primero le dice a systemd que relea las unidades; el segundo le dice al servicio que relea su configuración. Editar una unidad sin daemon-reload es un clásico: systemd sigue usando la versión vieja y te vuelves loco.

Los registros por unidad, con el journalctl que ya conoces de 05-04:

journalctl -u meteo-api.service -n 50 --no-pager    # últimas 50 líneas
journalctl -u meteo-api.service -f                  # seguimiento en vivo
journalctl -u meteo-api.service --since "2026-08-31 03:00" --until "03:30"
journalctl -u meteo-api.service -p err -b           # solo errores del arranque actual
journalctl -u meteo-api.service -b -1               # del arranque ANTERIOR: clave tras un cuelgue
journalctl -u meteo-api.service -o json-pretty -n 1 # todos los campos estructurados

Por qué esto es mejor que un fichero de registro. Como systemd es el padre del proceso y captura su stdout y stderr, cualquier cosa que el servicio imprima queda registrada, incluidos los mensajes de un fallo al arrancar que nunca llegaría a escribir en su propio fichero. Cada entrada lleva la unidad, el PID, el UID y el arranque en que ocurrió, lo que permite filtrar con precisión. -b -1 es la orden que salva la investigación tras un reinicio inesperado.

Políticas de reinicio y el bucle que oculta un fallo

Restart= Reinicia cuando…
no Nunca (por defecto)
on-failure Código de salida ≠ 0, señal, tiempo agotado o fallo del watchdog
on-abnormal Solo señal, tiempo agotado o watchdog (no por código de salida)
always Siempre, incluso tras una parada limpia
on-success Solo si terminó bien (útil en tareas cíclicas)

Para meteo-api usamos on-failure: si alguien lo para a propósito, debe quedarse parado. Para el ingestor usamos always, porque no hay ninguna razón legítima para que termine.

El peligro real es el bucle de reinicio que oculta el fallo. Imagina que alguien introduce un error de sintaxis en /etc/meteora/meteora.conf: el servicio arranca, falla en 200 ms, systemd lo reinicia, vuelve a fallar… A 200 ms por vuelta son 5 intentos por segundo, que llenan el diario, consumen CPU y —lo peor— hacen que un monitor que muestrea cada 30 segundos vea a veces active y a veces failed, con lo que la alerta no acaba de dispararse y nadie investiga.

Por eso están el limitador de arranques y RestartSec:

StartLimitIntervalSec=300     # ventana de observación
StartLimitBurst=5             # máximo de arranques en esa ventana
RestartSec=5s                 # espera entre intentos

Qué consiguen. Con estos valores, si el servicio arranca más de 5 veces en 300 segundos, systemd deja de intentarlo y lo marca failed con el motivo start-limit-hit. Eso convierte un bucle silencioso en un estado estable y visible, que sí dispara la alerta. RestartSec=5s evita además el martilleo. Si el fallo es transitorio (una dependencia que tarda), 5 reintentos separados por 5 segundos dan margen suficiente. Tras corregir la causa hay que ejecutar systemctl reset-failed meteo-api.service para limpiar el contador antes de volver a arrancar; olvidarlo es otro despiste habitual.

Temporizadores frente a cron

El agregador debe ejecutarse cada hora. En systemd son dos unidades: el trabajo y su reloj.

# /etc/systemd/system/agregador.service
[Unit]
Description=Cálculo de medias horarias de Meteora
RequiresMountsFor=/var/lib/meteora

[Service]
Type=oneshot
User=meteora
Group=meteora
ExecStart=/usr/local/bin/agregador --entrada /var/lib/meteora/lecturas --hora-anterior
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/meteora
PrivateTmp=yes
IOSchedulingClass=idle
Nice=10
TimeoutStartSec=900
# /etc/systemd/system/agregador.timer
[Unit]
Description=Ejecuta el agregador de Meteora cada hora

[Timer]
OnCalendar=hourly
AccuracySec=1min
RandomizedDelaySec=180
Persistent=true
Unit=agregador.service

[Install]
WantedBy=timers.target

Directiva por directiva, con el porqué:

  • OnCalendar=hourly equivale a *-*-* *:00:00. La sintaxis admite expresiones como Mon..Fri 06:00, *-*-01 03:30 o daily. Compruébala siempre antes de confiar: systemd-analyze calendar 'Mon..Fri 06:00' --iterations=3 imprime las próximas ejecuciones reales.
  • Persistent=true guarda en disco la marca de la última ejecución y, si la máquina estaba apagada a la hora prevista, ejecuta el trabajo en cuanto arranca. Es lo que en el mundo clásico hacía anacron, aquí con una sola línea.
  • RandomizedDelaySec=180 dispersa el arranque hasta 3 minutos al azar. Con una máquina da igual, pero con cincuenta servidores que agregan a las 03:00 en punto evita la estampida que satura la cabina de almacenamiento compartida. AccuracySec=1min permite además a systemd agrupar despertares y ahorrar energía; ponlo a 1us solo si de verdad necesitas precisión.
  • IOSchedulingClass=idle y Nice=10 en el servicio hacen que el agregador ceda ante meteo-api, tanto en disco como en CPU. Esta es exactamente la mitigación que se aplicará en el caso práctico de 07-04.
  • Type=oneshot con TimeoutStartSec=900: la tarea termina, no se queda corriendo, y si a los 15 minutos no ha acabado se la considera colgada.
systemctl enable --now agregador.timer
systemctl list-timers --all
# NEXT                         LEFT       LAST                         PASSED  UNIT
# Mon 2026-08-31 04:00:00 CEST 47min left Mon 2026-08-31 03:01:12 CEST 11min   agregador.timer
systemctl start agregador.service    # ejecutarlo AHORA, sin esperar al reloj
journalctl -u agregador.service -n 30

Detalle importante: se habilita el .timer, no el .service. Si habilitaras el servicio, arrancaría también en cada inicio del sistema. Y para probar la tarea a mano se arranca el .service, que es la unidad que hace el trabajo.

Comparación con cron:

Aspecto cron / anacron Temporizador de systemd
Sintaxis 0 * * * *, compacta pero críptica OnCalendar=hourly, legible y verificable
Ejecución perdida por apagado Solo con anacron, y con granularidad diaria Persistent=true, a cualquier granularidad
Registros A stdout → correo local que nadie lee Al diario, con journalctl -u
Aislamiento y límites Ninguno: hereda el entorno de cron Todas las directivas de [Service]
Supervisión de solapamiento Manual, con flock Automática: no arranca si ya está activo
Dispersión de carga Manual RandomizedDelaySec
Dependencias No existen Requires=, After=
Ubicuidad Está en todas partes Solo en sistemas con systemd

Ese último punto es el que justifica conocer crontab: sigue siendo omnipresente. Los cinco campos son minuto, hora, día del mes, mes y día de la semana (0 3 * * 1 = lunes a las 03:00); crontab -e edita el del usuario y crontab -l lo lista; en /etc/cron.d/ los ficheros llevan un campo extra con el usuario. Y una advertencia que causa incidentes: cron ejecuta con un PATH mínimo (/usr/bin:/bin) y sin las variables de tu sesión, que es exactamente el error de 07-01 sobre ficheros de configuración de shell. Usa rutas absolutas siempre.

Como regla: en sistemas con systemd, temporizador; y aun así, mantén el flock dentro del script, porque protege también frente a una ejecución manual simultánea.

Activación por socket y por ruta

La activación por socket invierte el orden habitual: systemd abre el punto de escucha y solo arranca el servicio cuando llega la primera conexión.

# /etc/systemd/system/meteo-api.socket
[Socket]
ListenStream=0.0.0.0:443
Backlog=2048
NoDelay=true

[Install]
WantedBy=sockets.target

Qué consigue. El socket lo crea systemd (con privilegios) y lo pasa al servicio como descriptor de fichero heredado, de modo que meteo-api ni siquiera necesita CAP_NET_BIND_SERVICE. Además, como el socket existe desde el arranque, las conexiones que lleguen mientras el servicio se reinicia se encolan en el backlog en vez de rechazarse: se pueden hacer despliegues sin conexiones perdidas. Y las unidades que dependen del servicio pueden arrancar en cuanto el socket está listo, no cuando el proceso está listo, lo que paraleliza el arranque. Es el mismo mecanismo que usa inetd desde los años 80, pero integrado con el resto del modelo.

La activación por ruta vigila el sistema de ficheros:

# /etc/systemd/system/lecturas-nuevas.path
[Path]
PathExistsGlob=/var/lib/meteora/entrada/*.dat
Unit=procesar-entrada.service

[Install]
WantedBy=paths.target

Qué consigue. En cuanto aparece un fichero que encaja con el patrón, systemd arranca procesar-entrada.service. Es la alternativa correcta al bucle while true; do ls ...; sleep 10; done, porque usa inotify del núcleo: cero consumo mientras no pasa nada y reacción inmediata cuando pasa.

Unidades de usuario y loginctl

Cada usuario con sesión tiene su propia instancia de systemd, que gestiona unidades en ~/.config/systemd/user/ sin privilegios de root:

systemctl --user status                              # el gestor del usuario actual
systemctl --user enable --now informe-personal.timer
loginctl list-sessions                               # sesiones activas, con TTY y tipo
loginctl show-user joan -p Linger                    # ¿sobreviven sus servicios al cierre de sesión?
loginctl enable-linger joan                          # que sí sobrevivan

Por qué importa. Por defecto, las unidades de usuario mueren al cerrar la última sesión: es lo correcto para un escritorio, pero sorprende en un servidor. enable-linger mantiene la instancia viva. Y loginctl es la ventana a systemd-logind, el componente que gestiona sesiones, asientos y el pam_systemd que viste en 05-02. Regla práctica: los servicios de infraestructura, como los de Meteora, van siempre en el gestor del sistema; las unidades de usuario son para tareas personales.

Análisis y depuración del arranque

systemd-analyze time
# Startup finished in 4.216s (firmware) + 3.104s (loader) + 2.891s (kernel) + 11.402s (userspace) = 21.613s
systemd-analyze blame | head -5
# 6.812s meteo-api.service
# 3.204s systemd-networkd-wait-online.service
# 1.118s var-lib-meteora.mount
systemd-analyze critical-chain meteo-api.service
# multi-user.target @11.380s
# └─meteo-api.service @4.568s +6.812s
#   └─var-lib-meteora.mount @3.402s +1.118s

Cómo leerlos, que es donde falla todo el mundo. blame ordena por duración, no por impacto: un servicio que tarda 6 segundos pero arranca en paralelo con otros veinte no retrasa nada. critical-chain sí muestra el camino crítico: @ es el instante en que la unidad empezó y + lo que tardó, así que solo reduciendo lo que aparece en esa cadena se acorta el arranque. En el ejemplo, meteo-api sí está en el camino crítico y su ExecStartPre de verificación es parte de esos 6,8 segundos: una decisión consciente de cambiar arranque rápido por arranque seguro.

El guion de "mi servicio no arranca", en orden:

  1. systemctl status meteo-api.service -l --no-pager — el motivo casi siempre está en las últimas líneas. Mira el Active: (¿failed, activating, inactive?) y el código: status=203/EXEC es "no se pudo ejecutar el binario" (ruta mal escrita, sin permiso de ejecución, shebang inválido); status=200/CHDIR es un WorkingDirectory inexistente; status=1 es el propio programa saliendo con error.
  2. journalctl -u meteo-api.service -n 100 --no-pager — el mensaje real del programa. Si está vacío, el fallo es anterior al programa.
  3. systemctl cat meteo-api.service — comprueba que la unidad efectiva es la que crees, con sus drop-ins. ¿Hiciste daemon-reload?
  4. systemd-analyze verify /etc/systemd/system/meteo-api.service — sintaxis y referencias.
  5. Ejecuta el comando a mano, como el usuario del servicio: sudo -u meteora /usr/local/bin/meteo-api --config /etc/meteora/meteora.conf. Si a mano funciona y como servicio no, la diferencia está en el entorno: PATH, directorio de trabajo, variables, o el endurecimiento.
  6. Sospecha del endurecimiento, que es la causa moderna más frecuente. Comenta temporalmente ProtectSystem=strict o SystemCallFilter= en un drop-in y prueba. Si con eso arranca, ya sabes qué directiva afinar; búscalo con journalctl -k | grep -i seccomp o _AUDIT_FIELD_SYSCALL.
  7. Comprueba permisos y SELinux/AppArmor: namei -l /var/lib/meteora/lecturas recorre la ruta entera mostrando propietario y modo de cada componente, y dmesg | grep -i denied delata al control de acceso obligatorio de 05-01.
  8. Dependencias: systemctl list-dependencies --failed y systemctl show -p After -p Requires.

Apagado ordenado: SIGTERM, SIGKILL y TimeoutStopSec

Cuando ejecutas systemctl stop meteo-api.service, systemd sigue una secuencia estricta:

  1. Envía SIGTERM al proceso principal (o ejecuta ExecStop si existe).
  2. Espera hasta TimeoutStopSec (30 s en nuestra unidad).
  3. Si no ha terminado, envía SIGKILL a todos los procesos del cgroup.

La diferencia entre las dos señales es la de siempre (03-03): SIGTERM es una petición educada que el programa puede capturar para terminar las peticiones en curso, volcar la caché de /dev/shm/meteora-cache, hacer fsync de lo pendiente y cerrar; SIGKILL la ejecuta el núcleo y no se puede capturar, así que el proceso desaparece a mitad de lo que estuviera haciendo.

Por qué el valor de TimeoutStopSec importa de verdad. Si es demasiado corto, un meteo-api que está terminando de escribir un bloque recibe SIGKILL a mitad y deja un fichero de lecturas truncado —exactamente el caso que detecta la comprobación tam % 24 != 0 del script de 07-01—. Si es demasiado largo, un reinicio del sistema se queda colgado minutos esperando a un proceso que nunca va a responder. El valor correcto es el peor caso razonable de cierre limpio, más un margen: para meteo-api, con hasta 2 GB de caché que volcar, 30 segundos.

En el apagado completo (systemctl poweroff), systemd para las unidades en orden inverso al de arranque, desmonta los sistemas de ficheros, sincroniza el disco y apaga. Si alguna vez ves el mensaje A stop job is running for ... (1min 30s / 2min) durante un apagado eterno, estás viendo exactamente este mecanismo: una unidad que no responde a SIGTERM y agota su temporizador. journalctl -b -1 -p warning te dirá cuál fue.

Errores Comunes y Consejos

Error Consecuencia Solución
Editar una unidad y no hacer daemon-reload systemd usa la versión antigua systemctl daemon-reload, o usa systemctl edit
Copiar la unidad del paquete a /etc Te pierdes las mejoras del mantenedor Drop-in con systemctl edit
Confundir enable con start El servicio no sobrevive al reinicio, o no arranca hoy enable --now
Olvidar [Install] enable responde que no hay nada que hacer Añade WantedBy=multi-user.target
Type=simple en un demonio que se demoniza Bucle de reinicios o supervisión falsa --foreground + Type=exec
After= sin Requires= (o al revés) Arranca sin su dependencia, o en paralelo con ella RequiresMountsFor=, o ambas directivas
Rutas relativas en ExecStart status=203/EXEC Ruta absoluta siempre
Varias órdenes con ; o && en ExecStart No hay shell: se pasan como argumentos literales ExecStart=/bin/bash -c '...' o varias ExecStartPre=
Habilitar el .service en vez del .timer La tarea se ejecuta también en cada arranque enable --now agregador.timer
No hacer reset-failed tras corregir El servicio no arranca por el límite alcanzado systemctl reset-failed NOMBRE
TimeoutStopSec demasiado corto SIGKILL a mitad de una escritura, datos truncados Ajústalo al peor cierre limpio
Editar grub.cfg a mano Se pierde en el siguiente update-grub Edita /etc/default/grub y regenera

Consejos: usa siempre systemctl cat antes de modificar nada, porque muestra la configuración efectiva; añade Documentation= a tus unidades para que quien te releve sepa dónde mirar; ejecuta systemd-analyze security sobre cada servicio nuevo y trátalo como lista de tareas; y prueba los reinicios de verdad: un servidor que lleva 400 días encendido y nunca se ha reiniciado esconde, casi con seguridad, un servicio que no está habilitado.

Ejercicios

Ejercicio 1: unidad para el verificador de lecturas

Convierte el script verificar-lecturas.sh de 07-01 en un servicio con temporizador que se ejecute cada día a las 06:15, recupere la ejecución si la máquina estaba apagada, corra como meteora sin más privilegios de los necesarios, no pueda escribir en ningún sitio y se dé por colgado a los 10 minutos. Escribe las dos unidades y las órdenes para activarlas y probarlas.

Ejercicio 2: diagnóstico de un servicio que no arranca

Tras un cambio, meteo-api no arranca. systemctl status muestra:

Active: failed (Result: exit-code) since Mon 2026-08-31 03:02:11 CEST; 12s ago
Process: 4471 ExecStart=/usr/local/bin/meteo-api --config /etc/meteora/meteora.conf (code=exited, status=203/EXEC)

Enumera, en orden, las comprobaciones que harías y qué causa confirmaría cada una.

Ejercicio 3: bucle de reinicio

Un compañero define el ingestor con Restart=always y RestartSec=0, sin límite de arranques. Un error de configuración hace que falle al arrancar. Describe qué ocurre en la máquina en los 60 segundos siguientes, por qué la monitorización podría no avisar, y qué tres directivas añadirías.

Soluciones

Solución 1

# /etc/systemd/system/verificar-lecturas.service
[Unit]
Description=Verificación de integridad de los datos de Meteora
RequiresMountsFor=/var/lib/meteora

[Service]
Type=oneshot
User=meteora
Group=meteora
ExecStart=/usr/local/bin/verificar-lecturas.sh -d /var/lib/meteora/lecturas
TimeoutStartSec=600
SuccessExitStatus=0 1
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
CapabilityBoundingSet=
SystemCallFilter=@system-service
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/verificar-lecturas.timer
[Unit]
Description=Verificación diaria de los datos de Meteora

[Timer]
OnCalendar=*-*-* 06:15:00
Persistent=true
RandomizedDelaySec=300
Unit=verificar-lecturas.service

[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now verificar-lecturas.timer
systemctl list-timers verificar-lecturas.timer
systemctl start verificar-lecturas.service          # prueba inmediata
journalctl -u verificar-lecturas.service -n 40 --no-pager
systemd-analyze calendar '*-*-* 06:15:00' --iterations=3

Puntos clave. ProtectSystem=strict sin ReadWritePaths deja todo el sistema en solo lectura, que es justo lo que pide el enunciado: el verificador solo lee (su directorio temporal lo cubre PrivateTmp). CapabilityBoundingSet= vacío elimina todas las capabilities, porque un script de comprobación no necesita ninguna. TimeoutStartSec=600 da los 10 minutos pedidos, y como es Type=oneshot systemd sabe que la unidad ha terminado cuando el proceso sale. SuccessExitStatus=0 1 es el detalle fino: nuestro script devuelve 1 cuando hay advertencias, y no queremos que eso marque la unidad como failed; el 3 (crítico) sí debe hacerlo. Y Persistent=true cubre el requisito de recuperar la ejecución perdida.

Solución 2

status=203/EXEC significa que el execve falló: systemd no llegó a ejecutar nada, así que el diario del servicio estará vacío y el problema es del binario o de su entorno, no del programa. Comprobaciones, de más a menos probable:

  1. ls -l /usr/local/bin/meteo-api — ¿existe? ¿Es la ruta exacta, sin errata? Un despliegue que dejó el binario en /usr/local/sbin da este error.
  2. stat -c '%A %U %G' /usr/local/bin/meteo-api — ¿tiene bit de ejecución? Un scp o un git checkout mal hecho puede quitarlo.
  3. namei -l /usr/local/bin/meteo-api — ¿el usuario meteora puede atravesar todos los directorios de la ruta? Basta que un directorio intermedio pierda el bit x para que falle.
  4. head -c2 /usr/local/bin/meteo-api y file — si es un script, ¿su shebang apunta a un intérprete que existe? Un #!/usr/bin/env python3 sin python3 instalado produce exactamente 203/EXEC.
  5. ldd /usr/local/bin/meteo-api | grep 'not found' — ¿falta alguna biblioteca compartida?
  6. mount | grep ' /usr/local ' — ¿está montado con noexec? Es raro pero ocurre, sobre todo en particiones separadas endurecidas.
  7. systemctl cat meteo-api.service — ¿hay un drop-in reciente que cambió ExecStart o añadió un WorkingDirectory inexistente (eso daría 200/CHDIR)?
  8. Prueba directa: sudo -u meteora /usr/local/bin/meteo-api --config /etc/meteora/meteora.conf. Si funciona a mano, revisa el endurecimiento, empezando por SystemCallFilter y ProtectSystem.

Solución 3

Qué ocurre. Con RestartSec=0 y sin límite, el ciclo arrancar-fallar se repite tan rápido como el sistema pueda hacer fork+execve: si el fallo tarda 50 ms en manifestarse, son unas 20 ejecuciones por segundo, es decir en torno a 1.200 en el primer minuto. Consecuencias: consumo continuo de CPU en creación de procesos, miles de entradas en el diario que pueden llegar a los límites de tasa de journald (RateLimitBurst) y hacer que se descarten mensajes —perdiendo justo los que explican el fallo—, y presión de E/S sobre /var/log.

Por qué la monitorización podría no avisar. Un chequeo que ejecuta systemctl is-active ingestor cada 30 segundos tiene una probabilidad alta de caer justo en una ventana en que el servicio está activating o active, porque el estado oscila decenas de veces por segundo. El resultado es una alerta intermitente ("flapping") que muchos sistemas suprimen por ruidosa, o directamente una serie de comprobaciones correctas. El servicio nunca llega a un estado estable de fallo, que es lo que las alertas saben detectar.

Las tres directivas:

RestartSec=5s                 # separa los intentos: 12 por minuto, no 1.200
StartLimitIntervalSec=300     # ventana de observación
StartLimitBurst=5             # tras 5 arranques en 300 s, se rinde y queda en 'failed'

Con esto, el peor caso son 5 reintentos en 25 segundos y después un estado failed estable, que dispara la alerta a la primera comprobación y deja el diario legible. Añadiría además Restart=on-failure en lugar de always si hubiera algún motivo legítimo para parar el servicio, y una alerta específica sobre la salida de systemctl list-units --failed, que es la señal más barata y fiable de que algo va mal en la máquina.

Conclusión

Has seguido el encendido de meteo-01 completo: el firmware UEFI leyendo la ESP y verificando firmas con Secure Boot; GRUB presentando el menú cuya línea de parámetros es la herramienta de rescate más valiosa que tienes; el núcleo y el initramfs, que existe para romper el círculo de necesitar los módulos de RAID y ext4 que viven dentro de la raíz que aún no puede montar; el switch_root y, por fin, el PID 1, que no se puede matar, adopta huérfanos y cuya muerte provoca un pánico.

Y has desmontado systemd, que resolvió cinco carencias reales de los guiones secuenciales de SysV: paralelismo, dependencias declaradas, activación bajo demanda, supervisión y —la más subestimada— un cgroup por servicio, que convierte "los procesos de este servicio" en un hecho del núcleo y no en una suposición de un fichero .pid. Sobre ese modelo has escrito la unidad completa de meteo-api, donde cada bloque tiene su razón: Type=notify porque tarda 8 segundos en estar realmente listo, AmbientCapabilities=CAP_NET_BIND_SERVICE para escuchar en el 443 sin ser root, ProtectSystem=strict con tres ReadWritePaths, un filtro seccomp, y los límites de memoria y E/S del cgroup.

Y has visto los mecanismos que se usan a diario: la distinción entre dependencia y orden, que explica la mitad de los arranques rotos; enable frente a start y daemon-reload frente a reload; status leído campo a campo; el bucle de reinicio que oculta un fallo y las tres directivas que lo convierten en un estado visible; los temporizadores con Persistent=true y RandomizedDelaySec frente a cron; la activación por socket, que permite reiniciar sin perder conexiones; critical-chain frente a blame; el guion de ocho pasos para "no arranca"; y la secuencia SIGTERMTimeoutStopSecSIGKILL, cuyo valor mal elegido produce exactamente los ficheros truncados que detectábamos en la lección anterior.

Con esto, meteo-01 arranca solo, se recupera solo y ejecuta sus tareas a su hora. Pero un sistema que arranca correctamente puede funcionar mal. El servicio está active (running), el temporizador se dispara puntualmente, no hay ninguna unidad en failed… y aun así los usuarios se quejan de que la API va lenta. Ninguna de las herramientas de esta lección responde a "¿por qué?": para eso hacen falta un método y unos números.

Es el tema siguiente: Monitorización y Diagnóstico de Rendimiento, donde aprenderás qué mirar en los primeros 60 segundos de una incidencia y cómo pasar de un síntoma vago a una causa concreta.

Fundamentos de Sistemas Operativos

Módulo 1: Introducción a los Sistemas Operativos

Módulo 2: Gestión de Recursos

Módulo 3: Concurrencia

Módulo 4: Estructuras de Archivos

Módulo 5: Protección y Seguridad del Sistema

Módulo 6: Virtualización y Contenedores

Módulo 7: Administración y Diagnóstico en la Práctica

© Copyright 2026. Todos los derechos reservados