Common Molecule Use Cases

Docker

Molecule can be executed via an Alpine Linux container by bind-mounting the Docker socket. Currently, we only build images for the latest version of Ansible and Molecule. In the future we may break this out into Molecule/ Ansible versioned pairs. The images are located on quay.io.

To test a role, change directory into the role to test, and execute Molecule as follows.

docker run --rm -it \
    -v "$(pwd)":/tmp/$(basename "${PWD}"):ro \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -w /tmp/$(basename "${PWD}") \
    quay.io/ansible/molecule:3.0.8 \
    molecule test

Docker With Non-Privileged User

The default Molecule Docker driver executes Ansible playbooks as the root user. If your workflow requires a non-privileged user, then adapt molecule.yml and Dockerfile.j2 as follows.

Append the following code block to the end of Dockerfile.j2. It creates an ansible user with passwordless sudo privileges.

The variable SUDO_GROUP depends on the target distribution. centos:8 uses wheel.

# Create `ansible` user with sudo permissions and membership in `DEPLOY_GROUP`
ENV ANSIBLE_USER=ansible SUDO_GROUP=wheel DEPLOY_GROUP=deployer
RUN set -xe \
  && groupadd -r ${ANSIBLE_USER} \
  && groupadd -r ${DEPLOY_GROUP} \
  && useradd -m -g ${ANSIBLE_USER} ${ANSIBLE_USER} \
  && usermod -aG ${SUDO_GROUP} ${ANSIBLE_USER} \
  && usermod -aG ${DEPLOY_GROUP} ${ANSIBLE_USER} \
  && sed -i "/^%${SUDO_GROUP}/s/ALL\$/NOPASSWD:ALL/g" /etc/sudoers

Modify provisioner.inventory in molecule.yml as follows:

platforms:
  - name: instance
    image: centos:8
    # …
provisioner:
  name: ansible
  # …
  inventory:
    host_vars:
      # setting for the platform instance named 'instance'
      instance:
        ansible_user: ansible

Make sure to use your platform instance name. In this case instance.

An example for a different platform instance name:

platforms:
  - name: centos8
    image: centos:8
    # …
provisioner:
  name: ansible
  # …
  inventory:
    host_vars:
      # setting for the platform instance named 'centos8'
      centos8:
        ansible_user: ansible

To test it, add the following task to tasks/main.yml. It fails, because the non-privileged user is not allowed to create a folder in /opt/. This needs to be performed using sudo.

To perform the task using sudo, uncomment become: yes. Now the task will succeed.

- name: Create apps dir
  file:
    path: /opt/examples
    owner: ansible
    group: deployer
    mode: 0775
    state: directory
  # become: yes

Don’t forget to run molecule destroy if image has already been created.

Podman inside Docker

Sometimes your CI system comes prepared to run with Docker but you want to test podman into it. This prepare.yml playbook would let podman run inside a privileged Docker host by adding some required settings:

- name: prepare
  hosts: podman-in-docker
  tasks:
    - name: install fuse-overlayfs
      package:
        name:
          - fuse-overlayfs

    - name: create containers config dir
      file:
        group: root
        mode: a=rX,u+w
        owner: root
        path: /etc/containers
        state: directory

    - name: make podman use fuse-overlayfs storage
      copy:
        content: |
          # See man 5 containers-storage.conf for more information
          [storage]
          driver = "overlay"
          [storage.options.overlay]
          mount_program = "/usr/bin/fuse-overlayfs"
          mountopt = "nodev,metacopy=on"
        dest: /etc/containers/storage.conf
        group: root
        mode: a=r,u+w
        owner: root

    - name: make podman use cgroupfs cgroup manager
      copy:
        content: |
          # See man 5 libpod.conf for more information
          cgroup_manager = "cgroupfs"
        dest: /etc/containers/libpod.conf
        group: root
        mode: a=r,u+w
        owner: root

Another option is to configure the same settings directly into the molecule.yml definition:

driver:
  name: podman
platforms:
  - name: podman-in-docker
    # ... other options
    cgroup_manager: cgroupfs
    storage_opt: overlay.mount_program=/usr/bin/fuse-overlayfs
    storage_driver: overlay

At the time of writing, Gitlab CI shared runners run privileged Docker hosts and are suitable for these workarounds.

Systemd Container

To start a service which requires systemd, in a non-privileged container, configure molecule.yml with a systemd compliant image, tmpfs, volumes, and command as follows.

platforms:
  - name: instance
    image: centos:8
    command: /sbin/init
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro

Note that centos:8 image contains a seccomp security profile for Docker which enables the use of systemd. When needed, such security profiles can be reused (for example the one available in Fedora):

platforms:
  - name: instance
    image: debian:stretch
    command: /sbin/init
    security_opts:
      - seccomp=path/to/seccomp.json
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro

The developer can also opt to start the container with extended privileges, by either giving it SYS_ADMIN capabilities or running it in privileged mode.

Important

Use caution when using privileged mode or SYS_ADMIN capabilities as it grants the container elevated access to the underlying system.

To limit the scope of the extended privileges, grant SYS_ADMIN capabilities along with the same image, command, and volumes as shown in the non-privileged example.

platforms:
  - name: instance
    image: centos:8
    command: /sbin/init
    capabilities:
      - SYS_ADMIN
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro

To start the container in privileged mode, set the privileged flag along with the same image and command as shown in the non-privileged example.

platforms:
  - name: instance
    image: centos:8
    command: /sbin/init
    privileged: True

Monolith Repo

Molecule is generally used to test roles in isolation. However, it can also test roles from a monolith repo.

The role initialized with Molecule (baz in this case) would simply reference the dependant roles via it’s converge.yml or meta dependencies.

Molecule can test complex scenarios leveraging this technique.

$ cd monolith-repo/roles/baz
$ molecule test

Molecule is simply setting the ANSIBLE_* environment variables. To view the environment variables set during a Molecule operation pass the --debug flag.

$ molecule --debug test

DEBUG: ANSIBLE ENVIRONMENT
---
ANSIBLE_CONFIG: /private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/ansible.cfg
ANSIBLE_FILTER_PLUGINS: /Users/jodewey/.pyenv/versions/2.7.13/lib/python2.7/site-packages/molecule/provisioner/ansible/plugins/filters:/private/tmp/monolith-repo/roles/baz/plugins/filters:/private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/plugins/filters
ANSIBLE_LIBRARY: /Users/jodewey/.pyenv/versions/2.7.13/lib/python2.7/site-packages/molecule/provisioner/ansible/plugins/libraries:/private/tmp/monolith-repo/roles/baz/library:/private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/library
ANSIBLE_ROLES_PATH: /private/tmp/monolith-repo/roles:/private/tmp/monolith-repo/roles/baz/molecule/default/.molecule/roles

Molecule can be customized any number of ways. Updating the provisioner’s env section in molecule.yml to suit the needs of the developer and layout of the project.

provisioner:
  name: ansible
  env:
    ANSIBLE_$VAR: $VALUE

Sharing Across Scenarios

Playbooks and tests can be shared across scenarios.

$ tree shared-tests
shared-tests
├── molecule
│   ├── centos
│   │   └── molecule.yml
│   ├── resources
│   │   ├── playbooks
│   │   │   ├── Dockerfile.j2 (optional)
│   │   │   ├── create.yml
│   │   │   ├── destroy.yml
│   │   │   ├── converge.yml  # <-- previously called playbook.yml
│   │   │   └── prepare.yml
│   │   └── tests
│   │       └── test_default.py
│   ├── ubuntu
│   │   └── molecule.yml
│   └── ubuntu-upstart
│       └── molecule.yml

Tests and playbooks can be shared across scenarios.

In this example the tests directory lives in a shared location and molecule.yml points to the shared tests.

verifier:
  name: testinfra
  directory: ../resources/tests/

In this second example the actions create, destroy, converge and prepare are loaded from a shared directory.

provisioner:
  name: ansible
  playbooks:
    create: ../resources/playbooks/create.yml
    destroy: ../resources/playbooks/destroy.yml
    converge: ../resources/playbooks/converge.yml
    prepare: ../resources/playbooks/prepare.yml

Running Molecule processes in parallel mode

Important

This functionality should be considered experimental. It is part of ongoing work towards enabling parallelizable functionality across all moving parts in the execution of the Molecule feature set.

Note

Only the following sequences support parallelizable functionality:

  • check_sequence: molecule check --parallel

  • destroy_sequence: molecule destroy --parallel

  • test_sequence: molecule test --parallel

It is currently only available for use with the Docker driver.

When Molecule receives the --parallel flag it will generate a UUID for the duration of the testing sequence and will use that unique identifier to cache the run-time state for that process. The parallel Molecule processes cached state and created instances will therefore not interfere with each other.

Molecule uses a new and separate caching folder for this in the $HOME/.cache/molecule_parallel location. Molecule exposes a new environment variable MOLECULE_PARALLEL which can enable this functionality.

It is possible to run Molecule processes in parallel using another tool to orchestrate the parallelization (such as GNU Parallel or Pytest). If you do so, make sure Molecule knows it is running in parallel mode by specifying the --parallel flag to your command(s) to avoid concurrency issues.