--- title: "Roles de Ansible y Galaxy: estructura, versiones y pruebas" description: "Crea un rol de Ansible reutilizable con ansible-galaxy, valida sus variables, fija dependencias de Galaxy en requirements.yml y pruébalo con Molecule." category: "Automatización e IaC" language: es source: https://cubepath.com/es/docs/automatización-e-iac/mejores-practicas-de-roles-de-ansible-y-galaxy updated: 2026-09-25 --- # Roles de Ansible y Galaxy: estructura, versiones y pruebas Un rol de Ansible agrupa tareas, plantillas, handlers y variables con una estructura fija, de modo que puedes reutilizarlo en varios playbooks y proyectos. Ansible Galaxy es el repositorio público de roles y colecciones, y `ansible-galaxy` la herramienta para crearlos e instalarlos. En este tutorial crearás desde cero un rol que despliega Nginx con un sitio configurable, aplicarás las prácticas que hacen que un rol sea mantenible, instalarás dependencias de Galaxy con versiones fijadas y probarás el rol con ansible-lint y Molecule en Ubuntu 24.04. ## Requisitos previos Para seguir esta guía necesitas: - Una máquina de control con Ubuntu 24.04 LTS y un usuario no root con privilegios `sudo`. Puede ser tu portátil o un VPS de CubePath. - Docker Engine instalado en la máquina de control, para las pruebas con Molecule (consulta la guía de instalación de Docker en Ubuntu 24.04) y tu usuario en el grupo `docker`. - Opcional: un servidor Ubuntu 24.04 de destino con acceso SSH por clave, para aplicar el rol de verdad. - Conocimientos básicos de playbooks e inventarios de Ansible. ## Paso 1: Instalar Ansible, ansible-lint y Molecule Instalar las herramientas con `pipx` deja cada una en su propio entorno virtual, sin tocar el Python del sistema, y permite actualizarlas por separado: ```bash sudo apt update sudo apt install pipx pipx ensurepath ``` Cierra la sesión y vuelve a entrar para que `~/.local/bin` quede en el `PATH`. Después instala las tres herramientas: ```bash pipx install --include-deps ansible pipx install ansible-lint pipx install molecule pipx inject molecule "molecule-plugins[docker]" ``` `molecule-plugins[docker]` añade el driver que crea contenedores Docker como máquinas de prueba. Comprueba las versiones: ```bash ansible --version | head -1 ansible-lint --version molecule --version ``` Cada comando debe imprimir su versión. En la salida de `molecule --version` debe aparecer una línea con el driver `docker` procedente de `molecule_plugins`; si no aparece, repite el `pipx inject`. ## Paso 2: Preparar el proyecto Crea un directorio para el proyecto con un `ansible.cfg` que le diga a Ansible dónde están los roles y colecciones propios del proyecto: ```bash mkdir -p ~/infra/roles && cd ~/infra nano ansible.cfg ``` ```ini [defaults] roles_path = ./roles collections_path = ./collections inventory = ./inventory.ini ``` Guardar roles y colecciones dentro del proyecto, en lugar de en `~/.ansible`, hace que cada proyecto tenga sus propias versiones y que el resultado sea reproducible en otra máquina o en CI. ## Paso 3: Crear el esqueleto del rol Genera la estructura estándar con `ansible-galaxy`: ```bash ansible-galaxy role init --init-path roles nginx_site ``` ```output - Role nginx_site was created successfully ``` Revisa lo que se ha creado: ```bash tree roles/nginx_site ``` ```output roles/nginx_site ├── README.md ├── defaults │ └── main.yml ├── files ├── handlers │ └── main.yml ├── meta │ └── main.yml ├── tasks │ └── main.yml ├── templates ├── tests │ ├── inventory │ └── test.yml └── vars └── main.yml ``` (Si no tienes `tree`, instálalo con `sudo apt install tree`.) Cada directorio tiene un propósito: | Directorio | Contenido | |------------|-----------| | `defaults/` | Variables con valores por defecto que el usuario del rol puede sobrescribir. Es la "API" del rol. | | `vars/` | Variables internas con prioridad alta, que no deberían cambiarse desde fuera. | | `tasks/` | Las tareas; `main.yml` es el punto de entrada. | | `handlers/` | Acciones que se ejecutan solo si una tarea notifica un cambio, como recargar un servicio. | | `templates/` | Plantillas Jinja2 (`.j2`). | | `files/` | Archivos que se copian tal cual. | | `meta/` | Metadatos para Galaxy, dependencias y validación de argumentos. | Borra lo que no vayas a usar: `rm -r roles/nginx_site/files roles/nginx_site/vars roles/nginx_site/tests`. Menos archivos vacíos hacen el rol más fácil de leer, y las pruebas las harás con Molecule. ## Paso 4: Definir las variables del rol Las variables por defecto son la interfaz pública del rol. Prefija todas con el nombre del rol para evitar colisiones con variables de otros roles: ```bash nano roles/nginx_site/defaults/main.yml ``` ```yaml --- nginx_site_name: example nginx_site_server_name: example.com nginx_site_root: "/var/www/{{ nginx_site_name }}" nginx_site_listen_port: 80 nginx_site_index_content: "Hola desde {{ nginx_site_server_name }}" ``` Declara además qué acepta el rol en `meta/argument_specs.yml`. Ansible valida estas especificaciones antes de ejecutar el rol y falla con un mensaje claro si una variable tiene un tipo incorrecto o un valor no permitido: ```bash nano roles/nginx_site/meta/argument_specs.yml ``` ```yaml --- argument_specs: main: short_description: Instala Nginx y publica un sitio estático. options: nginx_site_name: type: str description: Nombre del sitio, usado para el archivo de configuración y el directorio raíz. nginx_site_server_name: type: str description: Valor de la directiva server_name. nginx_site_root: type: path description: Directorio raíz del sitio. nginx_site_listen_port: type: int description: Puerto en el que escucha el sitio. nginx_site_index_content: type: str description: Contenido de la página index.html inicial. ``` ## Paso 5: Escribir las tareas, la plantilla y el handler Escribe las tareas usando nombres de módulo completos (FQCN, como `ansible.builtin.apt`), que evitan ambigüedades si una colección instala un módulo con el mismo nombre corto: ```bash nano roles/nginx_site/tasks/main.yml ``` ```yaml --- - name: Install Nginx ansible.builtin.apt: name: nginx state: present update_cache: true cache_valid_time: 3600 - name: Create site root directory ansible.builtin.file: path: "{{ nginx_site_root }}" state: directory owner: root group: root mode: "0755" - name: Deploy index page ansible.builtin.copy: content: "{{ nginx_site_index_content }}\n" dest: "{{ nginx_site_root }}/index.html" owner: root group: root mode: "0644" - name: Deploy site configuration ansible.builtin.template: src: site.conf.j2 dest: "/etc/nginx/sites-available/{{ nginx_site_name }}.conf" owner: root group: root mode: "0644" notify: Reload nginx - name: Enable site ansible.builtin.file: src: "/etc/nginx/sites-available/{{ nginx_site_name }}.conf" dest: "/etc/nginx/sites-enabled/{{ nginx_site_name }}.conf" state: link notify: Reload nginx - name: Ensure Nginx is running and enabled ansible.builtin.service: name: nginx state: started enabled: true ``` Crea la plantilla de configuración del sitio: ```bash nano roles/nginx_site/templates/site.conf.j2 ``` ```nginx # {{ ansible_managed }} server { listen {{ nginx_site_listen_port }}; server_name {{ nginx_site_server_name }}; root {{ nginx_site_root }}; index index.html; location / { try_files $uri $uri/ =404; } } ``` Y los handlers, que solo se ejecutan cuando una tarea notifica un cambio. El primero comprueba la sintaxis completa de Nginx y el segundo recarga el servicio; como se ejecutan en el orden en que están definidos, una configuración rota detiene el play antes de la recarga y Nginx sigue sirviendo la configuración anterior: ```bash nano roles/nginx_site/handlers/main.yml ``` ```yaml --- - name: Check nginx configuration ansible.builtin.command: nginx -t changed_when: false listen: Reload nginx - name: Apply nginx reload ansible.builtin.service: name: nginx state: reloaded listen: Reload nginx ``` Las tareas son idempotentes: cada una describe un estado final, no una acción, así que ejecutar el rol dos veces seguidas no debe producir cambios la segunda vez. Evita `ansible.builtin.shell` y `command` cuando existe un módulo que hace lo mismo; si no hay alternativa, añade `creates:` o `changed_when:` para que la tarea siga siendo idempotente. ## Paso 6: Completar los metadatos para Galaxy `meta/main.yml` describe el rol para Galaxy y declara sus dependencias de otros roles. Sustitúyelo por una versión reducida con datos reales: ```bash nano roles/nginx_site/meta/main.yml ``` ```yaml --- galaxy_info: role_name: nginx_site namespace: your_namespace author: your_name description: Instala Nginx y publica un sitio estático configurable. license: MIT min_ansible_version: "2.15" platforms: - name: Ubuntu versions: - noble galaxy_tags: - nginx - web dependencies: [] ``` Sustituye `your_namespace` y `your_name`. Mantén `dependencies` vacío siempre que puedas: las dependencias entre roles se ejecutan de forma implícita y dificultan seguir qué ocurre. Es más claro listar los roles en orden en el playbook. Documenta en `README.md` qué hace el rol, sus variables y un ejemplo de uso. Es lo primero que leerá quien lo reutilice. ## Paso 7: Instalar dependencias de Galaxy con requirements.yml Los proyectos reales combinan roles propios con roles y colecciones de Galaxy. Decláralos en un `requirements.yml` con versiones fijadas, para que una actualización del autor no cambie tu infraestructura sin que lo decidas: ```bash nano requirements.yml ``` ```yaml --- collections: - name: community.general version: ">=10.0.0,<11.0.0" - name: ansible.posix version: ">=1.5.0" - name: community.docker version: ">=3.10.2" roles: - name: geerlingguy.ntp ``` Para los roles, fija una versión concreta con `version:` usando una etiqueta del repositorio del rol; puedes consultar las disponibles en su página de Galaxy o GitHub. Instala todo: ```bash ansible-galaxy install -r requirements.yml ``` Comprueba qué se ha instalado y dónde: ```bash ansible-galaxy collection list -p ./collections ansible-galaxy role list ``` ```output # /home/your_user/infra/collections/ansible_collections Collection Version ----------------- ------- ansible.posix 2.1.0 community.docker 4.7.0 community.general 10.7.3 ... # /home/your_user/infra/roles - geerlingguy.ntp, 3.0.0 - nginx_site, (unknown version) ``` Añade `collections/` y los roles de Galaxy a `.gitignore` y versiona solo `requirements.yml`: cualquiera que clone el proyecto ejecuta el mismo `ansible-galaxy install -r` y obtiene las mismas versiones. ## Paso 8: Revisar el rol con ansible-lint `ansible-lint` detecta errores y malas prácticas: módulos sin FQCN, tareas sin nombre, permisos sin comillas, uso de `shell` evitable y más. Ejecútalo desde la raíz del proyecto: ```bash ansible-lint roles/nginx_site ``` ```output Passed: 0 failure(s), 0 warning(s) on 7 files. Last profile that met the validation criteria was 'production'. ``` Si aparece algún aviso, la salida indica el archivo, la línea y el identificador de la regla. Corrige el código en lugar de silenciar la regla, salvo que tengas una razón concreta. ## Paso 9: Probar el rol con Molecule Molecule crea una máquina de prueba, aplica el rol, comprueba el resultado, vuelve a aplicarlo para verificar la idempotencia y destruye la máquina. El driver de Docker necesita las colecciones `community.docker` y `ansible.posix` en la ruta por defecto de Ansible, ya que Molecule no lee el `ansible.cfg` del proyecto: ```bash ansible-galaxy collection install community.docker ansible.posix ``` Un escenario de Molecule es un directorio dentro del rol con tres archivos. Créalo: ```bash cd ~/infra/roles/nginx_site mkdir -p molecule/default ``` Define el escenario con una imagen de Ubuntu 24.04 preparada para Ansible: ```bash nano molecule/default/molecule.yml ``` ```yaml --- driver: name: docker platforms: - name: ubuntu2404 image: geerlingguy/docker-ubuntu2404-ansible pre_build_image: true command: "" volumes: - /sys/fs/cgroup:/sys/fs/cgroup:rw cgroupns_mode: host privileged: true provisioner: name: ansible verifier: name: ansible ``` La imagen ejecuta systemd, por eso necesita `privileged` y el montaje de cgroups: sin ellos no se puede arrancar Nginx como servicio. Úsala solo en máquinas de prueba. El playbook `converge.yml` aplica el rol. Molecule añade el directorio padre a la ruta de roles, así que el rol se referencia por el nombre de su directorio: ```bash nano molecule/default/converge.yml ``` ```yaml --- - name: Converge hosts: all become: true roles: - role: nginx_site vars: nginx_site_name: molecule nginx_site_server_name: localhost ``` Y en `verify.yml` comprueba que el sitio responde: ```bash nano molecule/default/verify.yml ``` ```yaml --- - name: Verify hosts: all gather_facts: false tasks: - name: Request the site ansible.builtin.uri: url: http://localhost/ return_content: true register: site - name: Check page content ansible.builtin.assert: that: - "'Hola desde localhost' in site.content" ``` Ejecuta el ciclo completo: ```bash molecule test ``` Molecule crea el contenedor, aplica el rol, lo aplica una segunda vez (prueba de idempotencia), ejecuta la verificación y lo destruye. La prueba es correcta si el comando termina con código de salida 0 y en la fase `verify` las dos tareas aparecen como `ok`: ```output TASK [Check page content] ****************************************************** ok: [ubuntu2404] => { "changed": false, "msg": "All assertions passed" } ``` Si la idempotencia falla, Molecule lista las tareas que devolvieron `changed` en la segunda ejecución. Mientras desarrollas, `molecule converge` aplica el rol sin destruir el contenedor y `molecule login` te abre una shell dentro para investigar. ## Paso 10: Usar el rol en un playbook Con el rol probado, aplícalo a tus servidores. Vuelve a la raíz del proyecto y crea el inventario: ```bash cd ~/infra nano inventory.ini ``` ```ini [web] web1 ansible_host=your_server_ip ansible_user=your_user ``` Crea el playbook: ```bash nano site.yml ``` ```yaml --- - name: Configure web servers hosts: web become: true roles: - role: nginx_site vars: nginx_site_name: miweb nginx_site_server_name: your_domain ``` Revisa primero qué cambiaría sin aplicar nada, y después aplícalo: ```bash ansible-playbook site.yml --check --diff ansible-playbook site.yml --ask-become-pass ``` ```output PLAY RECAP ********************************************************************* web1 : ok=9 changed=6 unreachable=0 failed=0 skipped=0 ``` Una segunda ejecución debe mostrar `changed=0`. ## Paso 11: Compartir el rol Para reutilizar el rol en otros proyectos, publícalo en un repositorio Git propio, crea etiquetas semánticas (`v1.0.0`, `v1.1.0`...) y consúmelo desde `requirements.yml`: ```yaml roles: - name: nginx_site src: https://github.com/your_org/ansible-role-nginx_site.git scm: git version: v1.0.0 ``` Si vas a publicar varios roles, módulos o plugins relacionados, empaquétalos como una colección (`ansible-galaxy collection init your_namespace.web`), que es el formato actual de distribución en Galaxy y permite versionarlos juntos. ## Conclusión Has creado un rol de Ansible con variables prefijadas y validadas, tareas idempotentes con módulos FQCN y un handler, has fijado las dependencias de Galaxy en `requirements.yml` y has probado el rol con ansible-lint y Molecule antes de aplicarlo. Como siguientes pasos, ejecuta `ansible-lint` y `molecule test` en tu pipeline de CI para cada cambio, añade más plataformas al escenario de Molecule si el rol debe soportar Debian, y agrupa tus roles en una colección cuando empiecen a crecer.