Perspectivas

Del runbook al playbook: automatizar procedimientos auditables

Cómo convertir un procedimiento manual en un playbook auditable: elegir el candidato con criterio, traducir cada paso en tareas con validaciones, manejar fallos y conservar la aprobación humana donde aporta control.

El runbook como punto de partida

Casi toda operación de TI tiene runbooks: documentos que describen, paso a paso, cómo reiniciar un servicio, rotar un certificado o liberar espacio en un servidor. Son valiosos porque condensan conocimiento operativo. También tienen problemas conocidos: se desactualizan, cada operador los interpreta a su manera, algunos pasos se saltan bajo presión y la evidencia de lo que se hizo queda dispersa entre el chat, el ticket y la consola.

Convertir un runbook en un playbook de Ansible no es solo ahorrar tiempo. Es transformar una instrucción que alguien debe seguir en un procedimiento que se ejecuta siempre igual, valida sus propias condiciones y deja registro de cada paso. Bien hecho, el playbook es más auditable que el procedimiento manual al que reemplaza.

Este artículo usa un ejemplo concreto y frecuente: el reinicio de un servicio de aplicación con verificaciones previas y posteriores. El método aplica a cualquier procedimiento repetible.

Elegir el candidato con Valor × Complejidad

No todo runbook merece automatizarse, y menos todos a la vez. Para elegir, aplicamos el marco Valor × Complejidad, que tiene tres pasos: definir, clasificar y priorizar.

Definir. Cada candidato se documenta en una ficha: proceso, disparador, frecuencia, volumen, sistemas involucrados, dueño, resultado esperado y riesgo actual. Para nuestro ejemplo, la ficha podría decir: reinicio del servicio de pagos, disparado por una alerta de degradación o una solicitud del equipo de aplicación, con una frecuencia de, por ejemplo, ocho veces al mes, sobre cuatro servidores, con dueño en el equipo de operaciones.

Clasificar. Se puntúan de 1 a 5 los dos ejes. El Valor es el promedio de ahorro de esfuerzo, reducción de riesgo y errores, impacto en el cliente o el SLA, y aporte al cumplimiento o la auditoría. La Complejidad es el promedio de cantidad de sistemas e integraciones, variabilidad del proceso, calidad de los datos y accesos disponibles, riesgo del cambio en producción y dependencia de terceros. Una puntuación de ejemplo:

EjeCriterioPuntuación (ejemplo)
ValorAhorro de esfuerzo3
ValorReducción de riesgo y errores4
ValorImpacto en el cliente o el SLA4
ValorAporte al cumplimiento o la auditoría4
ComplejidadSistemas e integraciones2
ComplejidadVariabilidad del proceso2
ComplejidadCalidad de datos y accesos2
ComplejidadRiesgo del cambio en producción3
ComplejidadDependencia de terceros1

Priorizar. El corte es 3: un promedio de 3 o más cuenta como alto, y uno menor que 3, como bajo. Con un Valor de 3,75 (alto) y una Complejidad de 2 (baja), el candidato cae en Victorias rápidas: se ejecuta primero. Si la puntuación hubiera dado complejidad alta (por ejemplo, porque el reinicio exige coordinar con un proveedor externo), sería una Apuesta estratégica y convendría dividirla en entregas menores.

Descomponer el runbook: de paso a tarea

Antes de escribir YAML, conviene reescribir el runbook como una lista de pasos atómicos, cada uno con su criterio de éxito. Los pasos ambiguos ("revisar que todo esté bien") son justamente los que hay que precisar: qué se revisa, contra qué umbral, y qué se hace si no se cumple.

Paso del runbookTarea en el playbookValidación
Verificar espacio en discoConsultar el uso de /varUso por debajo del 90 %, o se detiene
Confirmar que el servicio existeansible.builtin.service_factsEl servicio figura en los hechos del host
Reiniciar un nodo a la vezserial: 1 y ansible.builtin.serviceSi un nodo falla, no se toca el siguiente
Comprobar que respondeansible.builtin.uri con reintentosCódigo HTTP 200 dentro del tiempo límite
Si no responde, escalarBloque rescueRecoge el log y falla con un mensaje accionable

Este ejercicio suele revelar pasos que el runbook daba por obvios y que nadie había escrito. Esa es una ganancia en sí misma, incluso antes de automatizar. Conviene hacerlo con quien ejecuta el procedimiento hoy, no solo con quien lo documentó: las diferencias entre ambas versiones suelen ser las excepciones que el playbook tendrá que contemplar.

El playbook: verificaciones previas, cambio y verificaciones posteriores

El playbook resultante sigue la estructura del runbook, pero cada paso tiene una condición explícita. Los nombres de servicio, puerto y umbral son ejemplos:

---
- name: Reinicio controlado del servicio de pagos
  hosts: app_pagos
  become: true
  serial: 1                  # un nodo por vez: el resto sigue atendiendo
  max_fail_percentage: 0     # si un nodo falla, la ejecución se detiene
  vars:
    servicio: app-pagos
    url_salud: http://localhost:8080/health
  tasks:
    - name: Verificación previa. Consultar el uso de /var
      ansible.builtin.command: df --output=pcent /var
      register: uso_var
      changed_when: false    # solo lectura
      check_mode: false      # se ejecuta también con --check

    - name: Verificación previa. Exigir uso de /var por debajo del 90 %
      ansible.builtin.assert:
        that:
          - (uso_var.stdout_lines[1] | trim | replace('%', '') | int) < 90
        fail_msg: "Uso de /var demasiado alto: no se reinicia"

    - name: Verificación previa. Recolectar el estado de los servicios
      ansible.builtin.service_facts:

    - name: Verificación previa. Confirmar que el servicio existe
      ansible.builtin.assert:
        that:
          - (servicio ~ '.service') in ansible_facts.services

    - name: Reinicio con verificación posterior
      block:
        - name: Reiniciar el servicio
          ansible.builtin.service:
            name: "{{ servicio }}"
            state: restarted

        - name: Verificación posterior. Esperar HTTP 200 del endpoint de salud
          ansible.builtin.uri:
            url: "{{ url_salud }}"
            status_code: 200
          register: salud
          until: salud.status == 200
          retries: 10            # hasta 10 intentos
          delay: 6               # 6 segundos entre intentos
      rescue:
        - name: Recoger las últimas líneas del log del servicio
          ansible.builtin.command: journalctl -u {{ servicio }} -n 50 --no-pager
          register: log_servicio
          changed_when: false

        - name: Mostrar el log para adjuntarlo al ticket
          ansible.builtin.debug:
            var: log_servicio.stdout_lines

        - name: Detener con un mensaje accionable
          ansible.builtin.fail:
            msg: "Verificación posterior fallida en {{ inventory_hostname }}. Escalar a guardia."
      always:
        - name: Dejar constancia del resultado en la salida
          ansible.builtin.debug:
            msg: "{{ inventory_hostname }}: estado de salud {{ salud.status | default('sin respuesta') }}"

Algunas decisiones merecen explicación. serial: 1 reproduce la regla del runbook de no reiniciar todos los nodos a la vez, y max_fail_percentage: 0 garantiza que un fallo en el primer nodo protege a los demás. Las consultas de solo lectura llevan changed_when: false para que el resumen final muestre como cambio únicamente el reinicio. Y la verificación posterior no se limita a comprobar que el proceso arrancó: consulta el endpoint de salud, que es lo que de verdad indica si el servicio atiende.

Cuando algo falla: block, rescue y always

En un runbook, la sección de errores suele ser la más pobre: "si falla, contactar al responsable". En un playbook, el manejo de fallos es parte del diseño. La estructura block, rescue y always funciona de forma parecida a un try, catch y finally:

  • block agrupa las tareas del cambio.
  • rescue se ejecuta solo si alguna tarea del bloque falla. Aquí va la respuesta al error: recoger diagnóstico, intentar una reversión si existe una segura, y dejar el fallo explícito.
  • always se ejecuta en cualquier caso. Es el lugar para registrar el resultado o notificar.

Un detalle importante: si el rescue termina sin errores, Ansible considera que el host se recuperó y la ejecución continúa. Por eso el ejemplo termina el rescate con ansible.builtin.fail. Ocultar un fallo detrás de un rescate silencioso es peor que no tener automatización, porque la evidencia dice que todo salió bien.

Decide también qué hacer con los nodos pendientes. Con serial: 1 y max_fail_percentage: 0, un fallo detiene la ejecución antes de tocar el siguiente nodo. Para procedimientos donde un fallo parcial deja el sistema en un estado inconsistente, any_errors_fatal: true detiene la ejecución en todos los hosts.

Dónde mantener la aprobación humana

Automatizar no significa eliminar a las personas del proceso. Significa decidir con precisión dónde aportan control. Algunas pautas:

  • Antes de ejecutar, la aprobación sigue siendo necesaria cuando el procedimiento es un cambio normal según tu práctica de habilitación de cambios. Una vez que el playbook demostró ser estable, muchos procedimientos pueden registrarse como cambios estándar preaprobados.
  • Durante la ejecución, si hace falta una pausa para que alguien confirme (por ejemplo, entre el primer nodo y el resto), las plataformas como AWX o Ansible Automation Platform ofrecen nodos de aprobación en los flujos de trabajo. El módulo ansible.builtin.pause también permite pedir confirmación, pero solo sirve en ejecuciones interactivas desde la consola.
  • Después de un fallo, la decisión de reintentar o revertir debería quedar en manos de una persona, con el diagnóstico que el playbook recogió.

La aprobación también debe dejar evidencia: quién aprobó, cuándo y sobre qué ejecución. Un "sí" en un chat no cumple esa función; un nodo de aprobación en la plataforma o un estado en el registro de cambio, sí.

Del playbook al procedimiento auditable

El trabajo no termina cuando el playbook funciona. Para que sea un procedimiento auditable, cierra estos puntos:

  1. El playbook vive en un repositorio con revisión obligatoria, y el runbook original se actualiza para indicar que el procedimiento ahora es automático y cómo lanzarlo.
  2. La ejecución se hace desde una plataforma central, con control de acceso sobre quién puede lanzarla.
  3. Cada ejecución queda asociada a un ticket o registro de cambio, y su salida se conserva con la retención que exija tu marco de control.
  4. Existe un procedimiento manual de respaldo, por si la plataforma de automatización no está disponible.
  5. El playbook se prueba en un entorno no productivo antes de cada modificación.

Por último, vuelve a puntuar el backlog. Al automatizar un procedimiento cambian el valor y la complejidad de los siguientes: un rol de verificaciones de salud ya escrito reduce la complejidad de todos los procedimientos que lo reutilicen. Si quieres ver cómo llevar estas mismas verificaciones a una respuesta automática ante alertas, sigue con Remediación automática con Event-Driven Ansible.

Fuentes

  • Documentación oficial de Ansible: bloques y manejo de errores, estrategias de ejecución (serial, max_fail_percentage), módulos ansible.builtin. https://docs.ansible.com/
  • ITIL 4, práctica de habilitación de cambios (Change enablement), AXELOS.

¿Quieres aplicar esto en tu operación?

Solicitar diagnóstico