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
dockerpara usarlo sinsudo. - 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:
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:
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:
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:
ansible-galaxy collection install community.docker
Comprueba las versiones:
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.
Notacada 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:
mkdir -p ~/ansible-roles && cd ~/ansible-roles
ansible-galaxy role init nginx_basico
cd nginx_basico
Define las tareas del rol:
nano tasks/main.yml
---
- 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:
nano defaults/main.yml
---
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:
nano meta/main.yml
Sustituye su contenido por:
---
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:
mkdir -p molecule/default
nano molecule/default/molecule.yml
---
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-*-ansibleson 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: hosty el volumen de/sys/fs/cgroupson 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_DISTROpermite elegir la distribución al lanzar las pruebas; si no se define, se usaubuntu2404.
Crea el playbook que aplica el rol, converge.yml:
nano molecule/default/converge.yml
---
- 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:
nano molecule/default/verify.yml
---
- 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:
molecule converge
La primera vez descarga la imagen, lo que tarda un par de minutos. Al final verás el resumen del playbook:
PLAY RECAP *********************************************************************
instancia : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ejecuta las verificaciones:
molecule verify
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:
molecule idempotence
INFO Idempotence completed successfully.
Si necesitas investigar algo dentro del contenedor, abre una shell en él:
molecule login
Cuando termines, destruye la instancia:
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:
molecule test
Para probar en otras distribuciones, cambia la variable MOLECULE_DISTRO:
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:
ansible-lint
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:
mkdir -p .github/workflows
nano .github/workflows/molecule.yml
---
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.
