Perspectivas

Ansible a escala: roles, colecciones, pruebas y pipelines

Cuando la automatización pasa de unos pocos playbooks a varios equipos, necesita disciplina de software: repositorio ordenado, roles y colecciones, pruebas con ansible-lint y Molecule, y promoción controlada entre entornos.

Cuándo un repositorio de playbooks deja de escalar

La mayoría de las iniciativas de Ansible empiezan igual: un administrador escribe un playbook para resolver una tarea repetitiva, funciona, y aparecen diez más. Mientras los mantiene una sola persona, el desorden es tolerable. El problema llega cuando la automatización se vuelve crítica y la usan varios equipos. Las señales son reconocibles:

  • La misma lógica (configurar NTP, instalar el agente de monitoreo, endurecer SSH) está copiada en varios playbooks con pequeñas diferencias.
  • Nadie sabe con certeza qué versión de una colección o de ansible-core usa cada ejecución.
  • Un cambio pensado para un equipo rompe la automatización de otro, y se descubre en producción.
  • Las pruebas consisten en ejecutar contra un servidor de desarrollo "a ver qué pasa".

La solución no es una herramienta nueva, sino aplicar a la automatización lo que ya se exige al software de negocio: estructura, reutilización, pruebas automáticas, versionado y promoción controlada. En un entorno regulado esto además es parte del control de cambios.

Estructura de repositorio

No existe una única estructura correcta, pero sí principios: separar playbooks, roles e inventarios, declarar las dependencias externas y dejar la configuración de la herramienta junto al código. Un punto de partida razonable:

automatizacion/
  ansible.cfg                  # configuración de la herramienta, versionada
  .ansible-lint                # reglas de calidad del repositorio
  collections/
    requirements.yml           # colecciones externas con versión fijada
  inventories/
    dev/
    qa/
    prod/
  playbooks/
    site.yml
    parches_mensuales.yml
  roles/
    base_linux/
      defaults/main.yml        # valores por defecto, sobrescribibles
      handlers/main.yml
      meta/main.yml            # metadatos y dependencias del rol
      tasks/main.yml
      templates/
      molecule/default/        # escenario de pruebas del rol

Los playbooks deberían ser delgados: eligen hosts y llaman roles. La lógica vive en los roles, que reciben variables y no conocen el entorno en el que se ejecutan. Esa regla hace posible probar un rol de forma aislada y reutilizarlo en otro contexto.

Las dependencias externas se declaran en requirements.yml y se instalan con ansible-galaxy collection install -r collections/requirements.yml. Fijar versiones evita que una publicación nueva de una colección cambie el comportamiento de producción sin que nadie lo decida:

---
collections:
  - name: ansible.posix
    version: ">=1.5.0,<2.0.0"   # rango acotado: acepta correcciones, no cambios mayores
  - name: community.general
    version: ">=8.0.0,<9.0.0"
  - name: miempresa.plataforma     # colección interna (nombre de ejemplo)
    version: "2.3.1"               # versión exacta, publicada en un repositorio privado

Roles y colecciones: cuándo usar cada uno

Un rol empaqueta tareas, handlers, plantillas y variables para resolver una responsabilidad concreta. Una colección es la unidad de distribución de Ansible: agrupa roles, módulos, plugins y playbooks bajo un espacio de nombres (namespace.nombre), con una versión declarada en su archivo galaxy.yml. Desde que el contenido de Ansible se distribuye en colecciones, los nombres completos (FQCN), como ansible.builtin.copy o miempresa.plataforma.base_linux, son la forma recomendada de referirse a módulos y roles.

AspectoRol en el repositorioColección
ContenidoTareas, handlers, plantillas, variablesRoles, módulos, plugins y playbooks
DistribuciónJunto al código que lo usaArtefacto empaquetado (Galaxy, Automation Hub o repositorio privado)
VersionadoEl del repositorioPropio, declarado en galaxy.yml
Pruebas típicasansible-lint y Moleculeansible-lint, Molecule y ansible-test
Cuándo convieneLógica usada por un solo equipo o repositorioLógica compartida entre equipos o con módulos propios

Un camino natural es empezar con roles dentro del repositorio y extraer a una colección interna los que varios equipos necesitan. La colección se crea con ansible-galaxy collection init, se empaqueta con ansible-galaxy collection build y se publica en un repositorio privado. A partir de ahí, cada equipo consume una versión explícita y decide cuándo actualizar.

Calidad estática con ansible-lint

ansible-lint revisa playbooks, roles y colecciones contra un conjunto de reglas: uso de FQCN, tareas sin nombre, comandos sin changed_when, permisos de archivos implícitos, sintaxis YAML y otras prácticas. Agrupa las reglas en perfiles de exigencia creciente (de min a production), lo que permite adoptarlo de forma gradual en un repositorio existente.

# .ansible-lint
---
profile: production       # el perfil más exigente; empieza por "basic" si el repositorio es antiguo
exclude_paths:
  - .cache/
  - collections/ansible_collections/   # contenido de terceros instalado localmente
skip_list: []             # toda excepción se documenta y se revisa

Dos recomendaciones. Primero, ejecuta ansible-lint también en el equipo del desarrollador (por ejemplo, con un hook de pre-commit), no solo en el pipeline: el error barato es el que se corrige antes de subir. Segundo, trata las excepciones como deuda: cada regla omitida debería tener un motivo escrito y una fecha de revisión.

Complementa el análisis con ansible-playbook --syntax-check sobre los playbooks principales. Es rápido y detecta errores de estructura antes de cualquier prueba con hosts reales.

Pruebas de roles con Molecule

El análisis estático no dice si un rol funciona. Para eso está Molecule, que crea instancias de prueba (contenedores o máquinas virtuales, según el driver), aplica el rol y verifica el resultado. El comando molecule test recorre una secuencia completa que incluye, entre otros pasos, crear la instancia, aplicar el rol (converge), aplicarlo de nuevo para comprobar la idempotencia, ejecutar la verificación y destruir la instancia.

El paso de idempotencia es el más valioso para operaciones: si la segunda aplicación reporta cambios, la prueba falla. Eso detecta exactamente el tipo de tarea que luego ensucia la evidencia en producción. La verificación puede escribirse como un playbook con aserciones:

# roles/base_linux/molecule/default/verify.yml
---
- name: Verificar el rol base_linux
  hosts: all
  gather_facts: false
  tasks:
    - name: Recolectar el estado de los servicios
      ansible.builtin.service_facts:

    - name: Comprobar que el servicio de hora está activo
      ansible.builtin.assert:
        that:
          - ansible_facts.services['chronyd.service'].state == 'running'
        fail_msg: "chronyd no está en ejecución"

Hay límites prácticos. Probar servicios gestionados por systemd dentro de contenedores requiere imágenes preparadas para ello, y algunos roles (los que tocan el kernel, el almacenamiento o el hardware) solo se prueban de forma realista en máquinas virtuales. Conviene empezar por los roles más reutilizados y aceptar que la cobertura crece de forma gradual.

Para colecciones con módulos o plugins propios, ansible-test aporta pruebas de sanidad, unitarias y de integración, y es la herramienta que el propio proyecto Ansible usa para su contenido.

Pipeline: etapas y promoción entre entornos

Con estructura y pruebas, el pipeline une todo. Independientemente del servidor de CI que uses, las etapas suelen ser estas:

  1. Análisis estático. yamllint, ansible-lint y --syntax-check en cada solicitud de cambio. Bloquea la fusión si falla.
  2. Pruebas de roles. molecule test sobre los roles modificados.
  3. Empaquetado. Se construye un artefacto versionado: la colección, un entorno de ejecución, o ambos.
  4. Publicación. El artefacto se publica en un repositorio privado de colecciones o en un registro de contenedores.
  5. Despliegue en desarrollo y QA. Primero con --check --diff, luego la ejecución real, con pruebas de humo posteriores.
  6. Promoción a producción. La misma versión, con aprobación y registro de cambio.

El principio clave es promover artefactos, no ramas. Lo que llega a producción debe ser exactamente lo que se probó en QA: la misma versión de la colección y el mismo entorno de ejecución. Si en cada entorno se vuelve a construir desde una rama, nada garantiza que el resultado sea idéntico.

Los entornos de ejecución (execution environments) ayudan en ese punto. Son imágenes de contenedor que incluyen ansible-core, las colecciones y sus dependencias de Python. Se construyen con ansible-builder a partir de un archivo de definición y se ejecutan con ansible-navigator o desde Ansible Automation Platform. Así la versión del motor deja de depender de lo que esté instalado en cada nodo de control.

Piensa también en la vuelta atrás. Si una versión nueva falla en producción, la respuesta más rápida suele ser volver a ejecutar la versión anterior del artefacto, que sigue publicada y ya fue probada. Eso solo funciona si los roles son idempotentes, si la versión nueva no introdujo cambios que la anterior no sabe revertir, y si el pipeline permite elegir qué versión desplegar, en lugar de tomar siempre la última. Documenta ese procedimiento y ensáyalo en QA antes de necesitarlo: una reversión que nunca se probó es una hipótesis, no un plan.

Versionado y gobierno del contenido

Las colecciones usan versionado semántico, y conviene respetarlo con rigor porque los consumidores dependen de él:

  • Mayor: cambios incompatibles, como renombrar una variable de un rol o eliminar un parámetro.
  • Menor: funcionalidad nueva compatible, como una opción adicional con valor por defecto.
  • Parche: correcciones sin cambios de interfaz.

Acompaña cada versión con un registro de cambios legible y trata las variables de los roles como una interfaz pública: documentadas en un README o en las especificaciones de argumentos del rol (meta/argument_specs.yml), que además validan los tipos al ejecutarse. Define dueños por colección y revisores obligatorios para los cambios en contenido compartido.

Por último, mide. Algunos indicadores simples dan visibilidad del programa: porcentaje de roles con pruebas de Molecule, tiempo desde la fusión hasta producción, ejecuciones fallidas por versión y número de excepciones activas en ansible-lint. No se trata de perseguir un número, sino de detectar dónde se acumula deuda. Y si necesitas decidir qué automatizar a continuación, el marco Valor × Complejidad ordena el backlog con criterios explícitos.

Fuentes

¿Quieres aplicar esto en tu operación?

Solicitar diagnóstico