Problema

En infraestructuras con decenas de servidores Linux, aplicar un advisory de kernel rara vez es tan simple como ejecutar yum update y reiniciar. Cada host lleva un conjunto de servicios críticos que deben volver a estar operativos después del reboot. Cuando uno o varios de esos servicios dependen de módulos del kernel, cambios de versión pueden romper rutas, unidades systemd o configuraciones de NFS. El resultado típico es una cadena de pasos manuales: leer el advisory, cruzar versiones, crear una matriz de reinicio, aplicar el parche host por host, validar cada servicio y, en caso de fallo, depurar. En entornos con 40‑50 máquinas, esa rutina consume varias horas de tiempo de ingeniería por advisory, y el coste se multiplica cuando hay tres advisories al mes.

El problema subyacente no es el parche en sí, sino la falta de un proceso reproducible y parcialmente automatizado que:

  1. Identifique rápidamente qué hosts requieren el nuevo kernel.
  2. Genere una lista de servicios que necesitan verificación post‑reboot.
  3. Coordine la aplicación del parche y el seguimiento de resultados.
  4. Capture el tiempo invertido para poder medir y optimizar el proceso.

Sin esas piezas, cada ciclo de parcheo se vuelve una tarea ad‑hoc que depende de la memoria del operador y genera errores evitables.

Causa

Los fallos recurrentes provienen de varios factores habituales:

  • Matriz de reinicio manual – Los equipos suelen crear la lista de servicios a validar a mano, basándose en la experiencia o en documentación desactualizada. Cuando se añaden o eliminan unidades, la matriz queda obsoleta.
  • Dependencias implícitas en módulos del kernel – Un systemd unit puede referenciar un módulo mediante modprobe o ExecStartPre. Si la ruta del módulo cambia entre versiones, el servicio no arranca y el operador pasa tiempo depurando.
  • Montajes NFS o CIFS que no se desmontan limpiamente – Durante el reboot, los clientes pueden quedar con mounts “stale”, obligando a intervención manual.
  • Falta de visibilidad del estado – Sin un registro estructurado de cada paso, es difícil saber cuánto tiempo tomó cada fase y dónde se concentraron los cuellos de botella.
  • Procedimientos de validación dispersos – Algunos equipos usan scripts personalizados, otros confían en inspección visual de logs. La inconsistencia dificulta la automatización.

En la práctica, la combinación de estos factores genera un proceso que escala linealmente con el número de hosts y que se vuelve costoso cuando la frecuencia de advisories aumenta.

Solución

Una solución reutilizable se basa en tres pilares:

  1. Inventario dinámico de hosts y versiones
    Utilice rpm -qa kernel* o dnf repoquery para obtener la versión del kernel instalada en cada nodo. Herramientas como Ansible, Salt o un script SSH paralelo pueden recoger esa información y comparar contra la versión del advisory.

  2. Generación automática de la service‑restart matrix
    Aproveche systemctl list-units --type=service --state=running para obtener todas las unidades activas. Filtre aquellas que dependan directamente del kernel (por ejemplo, que tengan ConditionPathExists=/sys/module/... o ExecStartPre=/sbin/modprobe). Guarde la salida en un CSV o JSON que sirva como matriz de verificación.

  3. Orquestación de parcheo y verificación

    • Patch rollout: Use Ansible playbooks con yum/dnf para instalar el kernel y programar el reboot.
    • Post‑reboot health check: Después del arranque, recorra la matriz y ejecute systemctl is-active <service>; registre fallos.
    • Manejo de excepciones: Defina handlers para casos típicos (módulo faltante, mount stale). Un pequeño script puede intentar modprobe o mount -a antes de marcar el servicio como fallido.
  4. Registro de tiempo y métricas
    Cada paso del playbook debe registrar timestamps en un archivo de log estructurado (por ejemplo, JSON lines). Al final del ciclo, un script de resumen calcula el tiempo total y el tiempo por fase, proporcionando datos para la mejora continua.

Ejemplo de flujo con Ansible

- hosts: all
  become: true
  vars:
    advisory_kernel: "4.18.0-425.el8"
    restart_matrix: "/tmp/restart_matrix.json"
  tasks:
    - name: Collect current kernel version
      command: uname -r
      register: current_kernel

    - name: Decide if patch is needed
      set_fact:
        patch_needed: "{{ current_kernel.stdout != advisory_kernel }}"

    - name: Build service restart matrix (only on first host)
      delegate_to: "{{ groups['all'][0] }}"
      run_once: true
      command: >
        systemctl list-units --type=service --state=running
        --no-pager --no-legend
        | jq -R 'split(" ") | {unit: .[0], load: .[1], active: .[2], sub: .[3]}'
        > {{ restart_matrix }}

    - name: Apply kernel update
      when: patch_needed
      yum:
        name: kernel
        state: latest

    - name: Reboot host
      when: patch_needed
      reboot:
        reboot_timeout: 300

    - name: Verify services from matrix
      when: patch_needed
      block:
        - name: Load matrix
          slurp:
            src: "{{ restart_matrix }}"
          register: matrix_content

        - name: Parse matrix JSON
          set_fact:
            services: "{{ matrix_content.content | b64decode | from_json }}"

        - name: Check each service
          loop: "{{ services }}"
          command: systemctl is-active {{ item.unit }}
          register: svc_status
          ignore_errors: true

        - name: Report failures
          when: svc_status.rc != 0
          debug:
            msg: "Service {{ item.item.unit }} failed after reboot"

Este playbook cubre la detección de versiones, la generación de la matriz, la instalación del kernel, el reboot y la validación de servicios. Los handlers para módulos faltantes o mounts pueden añadirse como tareas adicionales bajo block.

Cuándo aplicar esta solución

Escenarios ideales

  • Entornos con >10 servidores que comparten la misma distribución (RHEL, CentOS, Rocky, etc.).
  • Advisories que requieren un reboot del kernel y que impactan servicios críticos.
  • Necesidad de medir el tiempo de operación para justificar recursos de automatización.

Síntomas que indican que la solución es pertinente

  • Cada advisory consume más de 2 h de trabajo manual.
  • Se han registrado fallos post‑reboot que no aparecen en los tickets de soporte.
  • No existe un registro centralizado de los pasos realizados.

Casos donde no aplica

  • Infraestructuras con un único host donde el overhead de la orquestación supera el beneficio.
  • Parches que no requieren reboot (p.ej., actualizaciones de usuariospace).
  • Entornos donde los servicios críticos están aislados en contenedores y el kernel del host nunca cambia.

Código

#!/usr/bin/env bash
# generate_restart_matrix.sh – crea una matriz JSON de servicios que dependen del kernel
set -euo pipefail

output="/tmp/restart_matrix.json"

systemctl list-units --type=service --state=running --no-legend |
awk '
{
    unit=$1
    # Detecta dependencias de módulos mediante ConditionPathExists=/sys/module/...
    cmd = "systemctl show -p ConditionPathExists " unit " 2>/dev/null"
    cmd | getline line
    close(cmd)
    if (line ~ /\/sys\/module\//) {
        print "{\"unit\":\"" unit "\"}"
    }
}
' | jq -s '.' > "$output"

echo "Matriz guardada en $output"

Este script es útil cuando no se quiere depender de Ansible para la fase de generación de la matriz. Produce un JSON que luego puede ser consumido por cualquier herramienta de orquestación.


## Verificación
1. **Antes del parche**  
   *Ejecutar* `./generate_restart_matrix.sh` y revisar que el archivo contiene todas las unidades esperadas.  
   *Comprobar* que `uname -r` muestra la versión actual del kernel.

2. **Después del reboot**  
   *Ejecutar* `systemctl is-active <unidad>` para cada entrada del JSON.  
   *Validar* que los logs (`journalctl -u <unidad> -b`) no contienen errores de módulo o mount.

3. **Métricas de tiempo**  
   *Revisar* el log JSON generado por Ansible (campo `timestamp`).  
   *Calcular* la diferencia entre `patch_start` y `verification_end` para obtener el tiempo total.

Si todos los servicios aparecen como `active` y no hay errores en los logs, la aplicación del advisory se considera exitosa.

## Notas adicionales
* **Módulos con ruta cambiante** – Cuando el advisory introduce un nuevo paquete de kernel, el nombre del módulo puede variar (`kmod-foo` → `kmod-foo-2`). Añadir una regla en la matriz que ejecute `modprobe` antes de validar el servicio evita la mayoría de los fallos.
* **NFS stale mounts** – Configurar `systemd` para desmontar automáticamente los mounts antes del reboot (`ExecStopPost=/usr/bin/umount -a -t nfs`) reduce la intervención manual.
* **Versiones de kernel paralelas** – Si mantiene varios kernels instalados, use `grubby --set-default=/boot/vmlinuz-<versión>` para forzar el arranque con la versión parcheada y evitar que el boot seleccione una versión antigua.
* **Persistencia de la matriz** – Guardar la matriz en un repositorio Git permite comparar cambios entre ciclos y detectar servicios que se añaden o eliminan sin actualizar la documentación.
* **Alertas** – Integre la salida del playbook con una herramienta de monitoreo (Prometheus, Zabbix) para generar alertas cuando el número de servicios fallidos supera un umbral predefinido.