--- title: "Cómo probar roles de Ansible con Molecule y Docker" description: "Instala Molecule en Ubuntu 24.04 y prueba un rol de Ansible en contenedores Docker con systemd: convergencia, idempotencia, verificación y GitHub Actions." category: "Automatización Avanzada" language: es source: https://cubepath.com/es/docs/automatización-avanzada/ansible-molecule-para-testing-de-roles updated: 2026-09-25 --- # Cómo probar roles de Ansible con Molecule y Docker Molecule es la herramienta del proyecto Ansible para probar roles de forma automática: crea instancias temporales, aplica el rol, comprueba que una segunda ejecución no cambia nada (idempotencia), ejecuta verificaciones y destruye el entorno. En este tutorial instalarás Molecule en Ubuntu 24.04, crearás un rol sencillo que instala Nginx y lo probarás en contenedores Docker con systemd para Ubuntu 24.04, Debian 12 y Rocky Linux 9. Al final ejecutarás las mismas pruebas en GitHub Actions en cada push. ## Requisitos previos - Una máquina con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. - Docker Engine instalado desde el repositorio oficial de Docker, y tu usuario en el grupo `docker` para usarlo sin `sudo`. - Unos 5 GB libres en disco para las imágenes de prueba. - Conocimientos básicos de roles de Ansible. Comprueba que Docker funciona sin `sudo`: ```bash docker run --rm hello-world ``` Si recibes `permission denied while trying to connect to the Docker daemon socket`, ejecuta `sudo usermod -aG docker $USER`, cierra la sesión y vuelve a entrar. ## Paso 1: Instalar Molecule en un entorno virtual Ubuntu 24.04 no permite instalar paquetes con `pip` en el Python del sistema (PEP 668), y además conviene fijar las versiones de las herramientas de pruebas por proyecto. Crea un entorno virtual: ```bash sudo apt update sudo apt install python3-venv python3 -m venv ~/.venvs/molecule source ~/.venvs/molecule/bin/activate ``` Instala Ansible, Molecule, el driver de Docker (incluido en `molecule-plugins`) y ansible-lint: ```bash pip install --upgrade pip pip install ansible-core molecule "molecule-plugins[docker]" ansible-lint ``` El driver de Docker usa módulos de la colección `community.docker`, que no viene con `ansible-core`. Instálala: ```bash ansible-galaxy collection install community.docker ``` Comprueba las versiones: ```bash molecule --version ``` La salida muestra la versión de Molecule, la de Ansible y la lista de drivers disponibles. Debe aparecer una línea con `docker` y `from molecule_plugins`, que confirma que el driver está instalado. Si Molecule avisa de que falta alguna colección requerida por el driver (por ejemplo `ansible.posix`), instálala con `ansible-galaxy collection install` igual que la anterior. > Nota: cada vez que abras una terminal nueva, activa el entorno con `source ~/.venvs/molecule/bin/activate`. ## Paso 2: Crear un rol de ejemplo Crea un directorio de trabajo y un rol con la estructura estándar. El nombre del directorio es el nombre del rol, así que usa solo minúsculas y guiones bajos: ```bash mkdir -p ~/ansible-roles && cd ~/ansible-roles ansible-galaxy role init nginx_basico cd nginx_basico ``` Define las tareas del rol: ```bash nano tasks/main.yml ``` ```yaml --- - name: Instalar Nginx ansible.builtin.package: name: nginx state: present - name: Publicar una página de inicio ansible.builtin.copy: content: "{{ nginx_basico_mensaje }}\n" dest: "{{ nginx_basico_docroot }}/index.html" owner: root group: root mode: "0644" - name: Arrancar y habilitar Nginx ansible.builtin.service: name: nginx state: started enabled: true ``` Las rutas por defecto de la página cambian entre familias de distribuciones. Define los valores por defecto: ```bash nano defaults/main.yml ``` ```yaml --- nginx_basico_mensaje: "Hola desde Molecule" nginx_basico_docroot: "{{ '/var/www/html' if ansible_facts['os_family'] == 'Debian' else '/usr/share/nginx/html' }}" ``` Por último, ajusta los metadatos para que ansible-lint no se queje de los valores de ejemplo. Abre `meta/main.yml`: ```bash nano meta/main.yml ``` Sustituye su contenido por: ```yaml --- galaxy_info: role_name: nginx_basico namespace: miempresa author: Tu Nombre description: Instala Nginx y publica una página de inicio license: MIT min_ansible_version: "2.15" platforms: - name: Ubuntu versions: - noble - name: Debian versions: - bookworm - name: EL versions: - "9" dependencies: [] ``` ## Paso 3: Crear el escenario de Molecule Un escenario es un conjunto de plataformas y playbooks de prueba. El escenario `default` vive en `molecule/default/`. Créalo a mano, así sabes exactamente qué contiene cada archivo: ```bash mkdir -p molecule/default nano molecule/default/molecule.yml ``` ```yaml --- driver: name: docker platforms: - name: instancia image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest" pre_build_image: true privileged: true cgroupns_mode: host volumes: - /sys/fs/cgroup:/sys/fs/cgroup:rw command: "" provisioner: name: ansible verifier: name: ansible ``` Por qué esta configuración: - Las imágenes `geerlingguy/docker-*-ansible` son imágenes mantenidas para probar roles: incluyen Python y systemd como proceso principal, algo que las imágenes oficiales de Ubuntu o Debian no traen. Sin systemd, la tarea que arranca Nginx fallaría. - `privileged`, `cgroupns_mode: host` y el volumen de `/sys/fs/cgroup` son necesarios para que systemd funcione dentro del contenedor con cgroups v2, el modo por defecto en Ubuntu 24.04. - `command: ""` respeta el comando de la imagen (arrancar systemd) en lugar de sustituirlo. - La variable `MOLECULE_DISTRO` permite elegir la distribución al lanzar las pruebas; si no se define, se usa `ubuntu2404`. Crea el playbook que aplica el rol, `converge.yml`: ```bash nano molecule/default/converge.yml ``` ```yaml --- - name: Converge hosts: all become: true pre_tasks: - name: Actualizar la caché de apt ansible.builtin.apt: update_cache: true cache_valid_time: 3600 when: ansible_facts['os_family'] == 'Debian' roles: - role: nginx_basico ``` Molecule añade el directorio padre del proyecto a la ruta de roles, por eso basta con el nombre `nginx_basico`. ## Paso 4: Escribir las verificaciones La fase `verify` comprueba el resultado final con un playbook de Ansible. Crea `verify.yml`: ```bash nano molecule/default/verify.yml ``` ```yaml --- - name: Verify hosts: all become: true gather_facts: true tasks: - name: Recoger la lista de paquetes ansible.builtin.package_facts: - name: Comprobar que Nginx está instalado ansible.builtin.assert: that: "'nginx' in ansible_facts.packages" - name: Recoger el estado de los servicios ansible.builtin.service_facts: - name: Comprobar que Nginx está activo y habilitado ansible.builtin.assert: that: - ansible_facts.services['nginx.service'].state == 'running' - ansible_facts.services['nginx.service'].status == 'enabled' - name: Pedir la página de inicio ansible.builtin.uri: url: http://localhost/ return_content: true register: pagina - name: Comprobar el contenido de la página ansible.builtin.assert: that: "'Hola desde Molecule' in pagina.content" ``` Cada `assert` falla con un mensaje claro si no se cumple, y la fase entera falla si falla cualquiera de ellos. ## Paso 5: Ejecutar las fases durante el desarrollo Mientras desarrollas no hace falta destruir y recrear el contenedor en cada cambio. Crea la instancia y aplica el rol: ```bash molecule converge ``` La primera vez descarga la imagen, lo que tarda un par de minutos. Al final verás el resumen del playbook: ```output PLAY RECAP ********************************************************************* instancia : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Ejecuta las verificaciones: ```bash molecule verify ``` ```output PLAY RECAP ********************************************************************* instancia : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 INFO Verifier completed successfully. ``` Comprueba la idempotencia, es decir, que una segunda ejecución del rol no produce cambios: ```bash molecule idempotence ``` ```output INFO Idempotence completed successfully. ``` Si necesitas investigar algo dentro del contenedor, abre una shell en él: ```bash molecule login ``` Cuando termines, destruye la instancia: ```bash molecule destroy ``` ## Paso 6: Ejecutar la secuencia completa en varias distribuciones `molecule test` ejecuta la secuencia completa: destruir restos anteriores, crear, converger, comprobar idempotencia, verificar y destruir. Lánzalo con la distribución por defecto: ```bash molecule test ``` Para probar en otras distribuciones, cambia la variable `MOLECULE_DISTRO`: ```bash MOLECULE_DISTRO=debian12 molecule test MOLECULE_DISTRO=rockylinux9 molecule test ``` En Rocky Linux 9 la tarea `apt` se omite gracias a la condición `when`, y el rol usa `/usr/share/nginx/html` como raíz. Si las tres ejecuciones terminan sin errores, el rol funciona en las tres familias. Ejecuta también ansible-lint desde la raíz del rol. Molecule ya no lo lanza por sí mismo, así que es un paso independiente: ```bash ansible-lint ``` ```output Passed: 0 failure(s), 0 warning(s) on 10 files. Last profile that met the validation criteria was 'production'. ``` El número de archivos puede variar. Lo importante es `0 failure(s)`. ## Paso 7: Ejecutar las pruebas en GitHub Actions Para que las pruebas corran en cada push y pull request, crea un workflow. Los runners `ubuntu-latest` de GitHub traen Docker instalado: ```bash mkdir -p .github/workflows nano .github/workflows/molecule.yml ``` ```yaml --- name: Molecule on: push: branches: [main] pull_request: jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install ansible-core ansible-lint - run: ansible-lint molecule: runs-on: ubuntu-latest strategy: fail-fast: false matrix: distro: [ubuntu2404, debian12, rockylinux9] steps: - uses: actions/checkout@v4 with: path: nginx_basico - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Instalar dependencias run: | pip install ansible-core molecule "molecule-plugins[docker]" ansible-galaxy collection install community.docker - name: Ejecutar Molecule run: molecule test working-directory: nginx_basico env: MOLECULE_DISTRO: ${{ matrix.distro }} PY_COLORS: "1" ANSIBLE_FORCE_COLOR: "1" ``` El checkout se hace en un subdirectorio llamado `nginx_basico` para que el nombre del directorio coincida con el del rol, igual que en local. La matriz lanza un job por distribución en paralelo, y `fail-fast: false` evita que un fallo en una cancele las demás. Sube el rol a un repositorio de GitHub y abre la pestaña **Actions**: deberías ver un job de lint y tres de Molecule, uno por distribución. ## Solución de problemas **`Failed to connect to bus` o la tarea del servicio falla dentro del contenedor.** systemd no está arrancando como proceso principal. Comprueba que `molecule.yml` incluye `privileged: true`, `cgroupns_mode: host`, el volumen de `/sys/fs/cgroup` y `command: ""`. **`the role 'nginx_basico' was not found`.** El directorio del rol no se llama igual que el rol, o ejecutas Molecule desde otro directorio. Lanza los comandos desde la raíz del rol, donde está la carpeta `molecule/`. **`couldn't resolve module/action 'community.docker.docker_container'`.** Falta la colección `community.docker` en el entorno donde se ejecuta Molecule. Instálala con `ansible-galaxy collection install community.docker`. **La fase de idempotencia falla.** Molecule muestra qué tareas han cambiado en la segunda ejecución. Suele deberse a tareas `command` o `shell` sin `creates`, `removes` o `changed_when`, o a plantillas que incluyen datos variables como fechas. Corrige la tarea para que solo informe de cambios cuando realmente modifica algo. ## Conclusión Tienes un rol de Ansible probado automáticamente en tres distribuciones: Molecule crea contenedores con systemd, aplica el rol, comprueba que es idempotente y verifica el resultado, y GitHub Actions repite las pruebas en cada cambio. Como siguientes pasos puedes añadir escenarios adicionales en `molecule/` para probar combinaciones de variables distintas, cubrir más casos en `verify.yml`, o empaquetar el rol dentro de una colección de Ansible para distribuirlo.