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-coreusa 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.
| Aspecto | Rol en el repositorio | Colección |
|---|---|---|
| Contenido | Tareas, handlers, plantillas, variables | Roles, módulos, plugins y playbooks |
| Distribución | Junto al código que lo usa | Artefacto empaquetado (Galaxy, Automation Hub o repositorio privado) |
| Versionado | El del repositorio | Propio, declarado en galaxy.yml |
| Pruebas típicas | ansible-lint y Molecule | ansible-lint, Molecule y ansible-test |
| Cuándo conviene | Lógica usada por un solo equipo o repositorio | Ló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:
- Análisis estático. yamllint, ansible-lint y
--syntax-checken cada solicitud de cambio. Bloquea la fusión si falla. - Pruebas de roles.
molecule testsobre los roles modificados. - Empaquetado. Se construye un artefacto versionado: la colección, un entorno de ejecución, o ambos.
- Publicación. El artefacto se publica en un repositorio privado de colecciones o en un registro de contenedores.
- Despliegue en desarrollo y QA. Primero con
--check --diff, luego la ejecución real, con pruebas de humo posteriores. - 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
- Documentación oficial de Ansible: roles, colecciones,
ansible-galaxy,ansible-testy entornos de ejecución. https://docs.ansible.com/ - Documentación de ansible-lint. https://ansible.readthedocs.io/projects/lint/
- Documentación de Molecule. https://ansible.readthedocs.io/projects/molecule/
- Semantic Versioning 2.0.0. https://semver.org/
¿Quieres aplicar esto en tu operación?
Solicitar diagnóstico