Dominando los Despliegues Multi-Etapa con Playbooks Secuenciales de Ansible

Aprende a diseñar y ejecutar despliegues de aplicaciones complejos y multi-etapa usando Ansible. Esta guía cubre la creación de playbooks secuenciales para fases de despliegue distintas, la implementación de un manejo de errores efectivo y el desarrollo de estrategias de reversión. Domina el arte de la entrega de aplicaciones robusta y automatizada con ejemplos prácticos y mejores prácticas.

Dominando los Despliegues Multi-Etapa con Playbooks Secuenciales de Ansible

Los despliegues multi-etapa de Ansible se vuelven necesarios cuando "copiar los archivos y reiniciar el servicio" ya no es honesto. Un despliegue real puede necesitar una migración de base de datos, un cambio de feature flag, un despliegue de paquete, una recarga de servicio, una verificación de salud y una ruta de reversión si la nueva versión falla. Si todo eso vive en un solo playbook grande con límites poco claros, cada despliegue fallido se convierte en un ejercicio de lectura.

Los playbooks secuenciales le dan a cada etapa un trabajo claro. Puedes ejecutarlos desde un pipeline CI/CD, AWX, Ansible Automation Platform o un script de shell simple. La parte importante no es la herramienta que presiona el botón. La parte importante es que el despliegue tenga un orden, cada etapa pueda reintentarse de forma segura y el manejo de fallos sea explícito.

¿Por qué Playbooks Secuenciales para Despliegues Multi-Etapa?

Desplegar una aplicación a menudo implica más que solo copiar archivos. Podrías necesitar:

  • Preparar el entorno: Crear directorios, establecer permisos, instalar dependencias.
  • Actualizar la base de datos: Ejecutar migraciones de esquema, sembrar datos iniciales.
  • Desplegar el código de la aplicación: Transferir nuevas versiones de código, reiniciar servicios.
  • Configurar servicios: Actualizar configuraciones de la aplicación, recargar demonios.
  • Realizar comprobaciones posteriores al despliegue: Ejecutar pruebas de humo, verificar la disponibilidad del servicio.

La secuencia importa porque algunas operaciones son fáciles de revertir y otras no. Revertir un enlace simbólico a la versión anterior suele ser simple. Revertir una migración de base de datos destructiva puede no serlo. Esa diferencia debería dar forma al plan de despliegue antes de que alguien escriba YAML.

Dividir estas en playbooks secuenciales distintos proporciona varias ventajas:

  • Modularidad: Cada playbook se enfoca en una sola etapa, lo que los hace más fáciles de entender, mantener y reutilizar.
  • Legibilidad: La lógica compleja se divide en partes manejables.
  • Control: Puedes ejecutar etapas específicas de forma independiente o como parte de un flujo de trabajo más grande.
  • Aislamiento de Errores: Si ocurre un fallo en una etapa, es más fácil identificar la causa y revertir cambios específicos sin afectar otras partes del despliegue.
  • Idempotencia: Los playbooks bien escritos son inherentemente idempotentes, lo que significa que ejecutarlos varias veces tiene el mismo efecto que ejecutarlos una vez. Esto es crucial para reintentos seguros.

Hay una compensación. Los playbooks separados añaden trabajo de orquestación. Las variables, los artefactos y el estado pueden necesitar moverse de una etapa a otra. Para un servicio interno pequeño, un playbook con bloques etiquetados puede ser suficiente. Para una aplicación orientada al cliente con migraciones y requisitos de reversión, la estructura adicional suele amortizarse.

Diseñando tu Flujo de Trabajo de Despliegue Multi-Etapa

Antes de escribir cualquier código de Ansible, planifica tus etapas de despliegue. Identifica los pasos lógicos, sus dependencias y el orden de ejecución. Un flujo de trabajo común podría verse así:

  1. Comprobaciones Previas al Despliegue: Asegúrate de que el entorno de destino esté listo.
  2. Migración de Base de Datos: Aplica los cambios necesarios en el esquema de la base de datos.
  3. Despliegue de la Aplicación: Despliega la nueva versión del código de la aplicación.
  4. Reinicio/Recarga del Servicio: Pon en línea los servicios de la aplicación con el nuevo código.
  5. Verificación Posterior al Despliegue: Ejecuta pruebas para confirmar el éxito del despliegue.

Para cada etapa, considera qué tareas de Ansible se requieren y qué playbook las contendrá.

También decide qué etapas pueden cambiar el estado de producción. Un playbook de prueba de humo no debería reparar silenciosamente la configuración. Un playbook de verificación previa no debería instalar paquetes faltantes a menos que eso sea explícitamente parte del contrato de despliegue. Mantener las comprobaciones de solo lectura separadas de los pasos mutantes hace que el flujo de trabajo sea más confiable.

Aquí hay un diseño de directorio práctico:

deploy/
  inventories/
    staging.ini
    production.ini
  group_vars/
    all.yml
    production.yml
  playbooks/
    00-preflight.yml
    01-migrate-db.yml
    02-deploy-app.yml
    03-reload-services.yml
    04-smoke-test.yml
    rollback-app.yml

Los números no son mágicos. Solo hacen visible el orden en los listados de archivos y los registros de CI.

Ejecutando Playbooks Secuencialmente

Ansible proporciona una forma directa de ejecutar playbooks uno tras otro usando los comandos --playbook-dir y ansible-playbook. El método más simple es encadenar comandos en tu pipeline CI/CD o en la línea de comandos.

Supongamos que tienes los siguientes archivos de playbook:

  • 01-database-migration.yml
  • 02-deploy-application.yml
  • 03-restart-services.yml
  • 04-smoke-tests.yml

Puedes ejecutarlos secuencialmente así:

ansible-playbook -i inventory.ini 01-database-migration.yml
ansible-playbook -i inventory.ini 02-deploy-application.yml
ansible-playbook -i inventory.ini 03-restart-services.yml
ansible-playbook -i inventory.ini 04-smoke-tests.yml

En la práctica, envuelve esa secuencia para que una etapa fallida detenga el pipeline:

set -euo pipefail

ansible-playbook -i inventories/production.ini playbooks/00-preflight.yml
ansible-playbook -i inventories/production.ini playbooks/01-migrate-db.yml
ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml
ansible-playbook -i inventories/production.ini playbooks/03-reload-services.yml
ansible-playbook -i inventories/production.ini playbooks/04-smoke-test.yml

set -e no es una estrategia de despliegue por sí mismo, pero previene el peor error: continuar después de una etapa fallida como si nada hubiera pasado. Los sistemas CI generalmente proporcionan su propio comportamiento de fallo, pero la misma idea aplica.

Usando ansible-playbook --skip-tags o --limit

En escenarios más avanzados, podrías combinar múltiples pasos lógicos en un solo playbook pero usar etiquetas para controlar la ejecución. Sin embargo, para una verdadera separación multi-etapa, generalmente se prefieren playbooks distintos. Si quieres ejecutar un subconjunto de playbooks o saltarte algunos, puedes usar argumentos de línea de comandos.

Saltarse un playbook: Si 03-restart-services.yml falla debido a un problema temporal del servicio, podrías volver a ejecutar solo esa etapa después de solucionar la causa. No te saltes etapas ciegamente cuando las etapas anteriores producen artefactos o estado del que dependen las etapas posteriores.

Limitando a una etapa específica: También puedes limitar la ejecución a un host o grupo específico usando el flag --limit, lo que puede ser útil para pruebas.

Para despliegues rotativos, --limit también puede reducir el radio de explosión:

ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml --limit web_canary

Ejecuta el despliegue contra un host o un grupo pequeño, verifícalo, luego continúa con el resto del parque. Esto es especialmente útil cuando tu balanceador de carga soporta drenar hosts antes de recargar o reiniciar.

Incorporando Manejo de Errores y Estrategias de Reversión

Los despliegues robustos requieren un plan para cuando las cosas salen mal.

ignore_errors y failed_when

Por defecto, Ansible detiene la ejecución si una tarea falla. Puedes controlar este comportamiento:

  • ignore_errors: true: Permite que el playbook continúe incluso si una tarea falla. Úsalo con precaución, típicamente para tareas no críticas o cuando tienes una tarea posterior para limpiar o compensar.
  • failed_when:: Define condiciones personalizadas bajo las cuales una tarea debe considerarse fallida. Esto es poderoso para manejar errores no fatales esperados o validar resultados específicos.
- name: Verificar estado del servicio (potencialmente no fatal)
  command: systemctl status myapp
  register: service_status
  ignore_errors: true

- name: Fallar si el servicio no está activo
  fail:
    msg: "¡El servicio myapp no se está ejecutando!"
  when: "service_status.rc != 0"

Usa ignore_errors con moderación. A menudo es mejor registrar el resultado y tomar una decisión clara. Un registro de despliegue lleno de fallos ignorados enseña a la gente a dejar de leer los fallos.

Para comandos, prefiere módulos con un propósito específico cuando existan. Por ejemplo, usa ansible.builtin.service, ansible.builtin.systemd, ansible.builtin.copy, ansible.builtin.template y módulos de paquetes en lugar de usar shell. Los módulos generalmente proporcionan mejor idempotencia y estados de cambio y fallo más claros.

Playbooks de Reversión

Para despliegues críticos, ten playbooks de reversión dedicados. Estos playbooks deben diseñarse para revertir los cambios realizados por sus playbooks de despliegue correspondientes.

  • 01-database-migration-rollback.yml: Revierte los cambios de esquema.
  • 02-deploy-application-rollback.yml: Despliega la versión anterior de la aplicación o restaura una copia de seguridad.
  • 03-restart-services-rollback.yml: Reinicia los servicios en su estado anterior.

La reversión de la base de datos merece un cuidado especial. Algunas migraciones no se pueden revertir de forma segura después de que las escrituras comienzan a usar el nuevo esquema. Un patrón más seguro es a menudo expandir y contraer: agregar cambios de esquema compatibles hacia atrás, desplegar código de aplicación que pueda funcionar con ambas formas antiguas y nuevas, rellenar datos si es necesario, luego eliminar columnas o campos antiguos en un despliegue posterior.

Con ese modelo, la reversión generalmente significa revertir el código de la aplicación y dejar el esquema compatible en su lugar, no intentar deshacer un cambio de base de datos riesgoso bajo presión.

Ejemplo de Disparador de Reversión: En tu pipeline CI/CD, si el playbook 04-smoke-tests.yml falla, dispararías la ejecución de los playbooks de reversión en orden inverso.

# Si 04-smoke-tests.yml falla:
ansible-playbook -i inventory.ini 03-restart-services-rollback.yml
ansible-playbook -i inventory.ini 02-deploy-application-rollback.yml
ansible-playbook -i inventory.ini 01-database-migration-rollback.yml

Usando block, rescue y always

Las construcciones block, rescue y always de Ansible proporcionan una forma más estructurada de manejar errores dentro de un solo playbook. Aunque no son para secuenciar entre playbooks, son excelentes para encapsular una serie de tareas que podrían fallar y definir qué hacer en caso de fallo.

- block:
    - name: Desplegar nuevo código de aplicación
      copy:
        src: /path/to/new/app/
        dest: /var/www/myapp/

    - name: Reiniciar servicio de aplicación
      service:
        name: myapp
        state: restarted

  rescue:
    - name: Intentar revertir a la versión anterior
      copy:
        src: /path/to/old/app/
        dest: /var/www/myapp/

    - name: Reiniciar servicio de aplicación después de la reversión
      service:
        name: myapp
        state: restarted

  always:
    - name: Registrar intento de despliegue
      debug:
        msg: "Intento de despliegue finalizado."

Este enfoque es útil para agrupar tareas relacionadas dentro de un solo playbook de etapa de despliegue.

Para la reversión entre playbooks, deja que el orquestador tome la decisión. Un pipeline CI puede ejecutar playbooks de reversión solo si una etapa posterior falla. Los flujos de trabajo de AWX pueden modelar las mismas ramas de éxito y fallo visualmente. Mantén el comando de reversión aburrido y ensayado.

Pasando el Estado del Lanzamiento Entre Etapas

Los playbooks secuenciales a menudo necesitan un identificador de lanzamiento compartido. Por ejemplo, la etapa de despliegue necesita saber qué artefacto instalar, la prueba de humo necesita saber qué versión esperar y la reversión necesita saber la versión anterior.

Pasa ese estado explícitamente:

ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml \
  -e release_version=2026.05.24.3 \
  -e artifact_url=https://artifacts.example.com/myapp/2026.05.24.3.tar.gz

Dentro del playbook, registra lo que cambió:

- name: Escribir marcador de lanzamiento actual
  ansible.builtin.copy:
    dest: /opt/myapp/current-release.txt
    content: "{{ release_version }}\n"
    owner: root
    group: root
    mode: "0644"

Ese marcador ayuda durante incidentes. Cuando alguien se conecta por SSH a un host, puede ver qué versión cree el host que está ejecutando. También puedes hacer que el playbook de prueba de humo lea el marcador y lo compare con el lanzamiento esperado.

Consideraciones Avanzadas

Gestionando el Estado Entre Playbooks

A veces, una tarea en un playbook necesita informar a otro playbook sobre su resultado. Puedes lograr esto usando:

  • Caché de Facts: Si la caché de facts está habilitada, los facts recopilados por un playbook pueden estar disponibles para los posteriores ejecutados dentro de la misma sesión de Ansible.
  • Archivos/Bases de Datos Temporales: Escribe información de estado crítica o salidas en un archivo temporal o una tabla de estado dedicada que los playbooks posteriores puedan leer.

Prefiere el estado explícito sobre el estado oculto. La caché de facts puede ser útil, pero también puede confundir a las personas cuando los valores están desactualizados o cuando un ejecutor tiene la caché habilitada y otro no. Los archivos de lanzamiento, los metadatos de artefactos, las variables de CI y los registros de despliegue son más fáciles de inspeccionar.

Control de Versiones y Herramientas de Orquestación

Para orquestaciones complejas, considera integrar tus playbooks secuenciales de Ansible en una herramienta de nivel superior:

  • Pipelines CI/CD: Herramientas como Jenkins, GitLab CI, GitHub Actions o CircleCI son excelentes para definir y disparar despliegues multi-etapa. Defines la secuencia de comandos ansible-playbook dentro de la configuración del pipeline.
  • Ansible Tower/AWX: Para orquestación de nivel empresarial, Ansible Tower (ahora Automation Platform) o su contraparte de código abierto AWX proporciona una interfaz de usuario robusta para programar, monitorear y gestionar plantillas de trabajo complejas que pueden encadenar múltiples playbooks.

Si varias personas despliegan el mismo sistema, la orquestación centralizada se vuelve menos sobre conveniencia y más sobre control. Te da inventarios consistentes, credenciales, registros de auditoría, aprobaciones y un historial visible de qué etapa falló. Esos detalles importan durante un incidente de producción.

Etiquetado para Control Granular

Si bien abogamos por playbooks separados para etapas distintas, también puedes usar etiquetas dentro de los playbooks. Si tienes un playbook muy grande para una sola etapa (por ejemplo, migración de base de datos), puedes etiquetar tareas específicas y ejecutar solo aquellas usando ansible-playbook --tags <nombre_etiqueta>.

Esto se trata más de control granular dentro de una etapa que de secuenciación entre etapas.

Mejores Prácticas para Despliegues Multi-Etapa

  • Mantén los Playbooks Enfocados: Cada playbook debe hacer una cosa bien (por ejemplo, migración de base de datos, despliegue de aplicación).
  • Nombra los Playbooks Claramente: Usa una convención de nomenclatura que refleje la etapa y el orden (por ejemplo, 01-, 02-).
  • Implementa Idempotencia: Asegúrate de que todas las tareas sean idempotentes para permitir reintentos seguros.
  • Prueba las Reversiones: Prueba regularmente tus procedimientos de reversión para asegurarte de que funcionen como se espera.
  • Usa Control de Versiones: Almacena todos tus playbooks y archivos de inventario en un sistema de control de versiones (como Git).
  • Automatiza la Orquestación: Usa pipelines CI/CD o herramientas como Ansible Tower/AWX para automatizar la ejecución de tus playbooks secuenciales.
  • Documenta tu Flujo de Trabajo: Documenta claramente las etapas, su propósito, dependencias y procedimientos de reversión.
  • Haz que las pruebas de humo sean reales: Verifica el endpoint real, la ruta de inicio de sesión, el worker de cola o el trabajo en segundo plano que importa. Una simple verificación de proceso no es suficiente.
  • Protege los inventarios de producción: Usa inventarios y credenciales separados para staging y producción. Un error tipográfico en --limit no debería desplegar en el lugar equivocado.
  • Usa despliegue serial cuando sea posible: serial te permite actualizar unos pocos hosts a la vez y detenerte antes de que todo el parque se vea afectado.
- name: Desplegar aplicación gradualmente
  hosts: web
  serial: 2
  tasks:
    - name: Instalar lanzamiento
      ansible.builtin.unarchive:
        src: "{{ artifact_path }}"
        dest: /opt/myapp/releases/{{ release_version }}
        remote_src: true

Con serial, Ansible procesa los hosts en lotes. Combínalo con el drenaje del balanceador de carga si tu aplicación no puede reiniciarse sin soltar solicitudes activas.

Un Flujo de Despliegue Concreto

Un despliegue seguro de Ansible para una aplicación web podría verse así:

00-preflight.yml verifica el espacio en disco, confirma que el lanzamiento de destino existe, verifica la conectividad de la base de datos y se asegura de que los hosts estén en el entorno esperado. No cambia el sistema.

01-migrate-db.yml ejecuta solo migraciones compatibles hacia atrás. Registra la versión de la migración y falla si la base de datos ya está por delante del lanzamiento solicitado.

02-deploy-app.yml descarga el artefacto, lo descomprime en un directorio de lanzamiento versionado, genera la configuración y actualiza un enlace simbólico current. Aún no reinicia servicios.

03-reload-services.yml drena cada host del balanceador de carga, recarga o reinicia el servicio, espera el endpoint de salud local y luego devuelve el host al servicio.

04-smoke-test.yml llama al endpoint público a través de la misma ruta que toman los usuarios. Verifica el cuerpo de la respuesta o el endpoint de versión, no solo un 200 de una página predeterminada del balanceador de carga.

Este flujo es más lento que un reinicio de un solo comando. También es mucho más fácil de razonar cuando el despliegue falla a mitad de camino.

El Hábito Que Hace Que Esto Funcione

Los playbooks secuenciales de Ansible funcionan mejor cuando cada uno tiene un contrato estrecho: qué espera, qué cambia, cómo prueba el éxito y qué hacer si falla. Ese contrato importa más que el número de archivos YAML.

Comienza con las etapas que reflejan tu riesgo real: verificación previa, migración, despliegue, recarga, prueba de humo, reversión. Mantén los comandos aburridos. Prueba la reversión antes de que la necesites. Cuando un despliegue se rompe, deberías poder señalar la etapa exacta que falló y decidir el siguiente paso sin releer todo el árbol de automatización.