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:
| Eje | Criterio | Puntuación (ejemplo) |
|---|---|---|
| Valor | Ahorro de esfuerzo | 3 |
| Valor | Reducción de riesgo y errores | 4 |
| Valor | Impacto en el cliente o el SLA | 4 |
| Valor | Aporte al cumplimiento o la auditoría | 4 |
| Complejidad | Sistemas e integraciones | 2 |
| Complejidad | Variabilidad del proceso | 2 |
| Complejidad | Calidad de datos y accesos | 2 |
| Complejidad | Riesgo del cambio en producción | 3 |
| Complejidad | Dependencia de terceros | 1 |
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 runbook | Tarea en el playbook | Validación |
|---|---|---|
| Verificar espacio en disco | Consultar el uso de /var | Uso por debajo del 90 %, o se detiene |
| Confirmar que el servicio existe | ansible.builtin.service_facts | El servicio figura en los hechos del host |
| Reiniciar un nodo a la vez | serial: 1 y ansible.builtin.service | Si un nodo falla, no se toca el siguiente |
| Comprobar que responde | ansible.builtin.uri con reintentos | Código HTTP 200 dentro del tiempo límite |
| Si no responde, escalar | Bloque rescue | Recoge 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:
blockagrupa las tareas del cambio.rescuese 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.alwaysse 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.pausetambié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:
- 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.
- La ejecución se hace desde una plataforma central, con control de acceso sobre quién puede lanzarla.
- 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.
- Existe un procedimiento manual de respaldo, por si la plataforma de automatización no está disponible.
- 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ódulosansible.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