Developing and Testing Ansible Roles Using Molecule and Podman

One of the main advantages of the Red Hat Ansible Automation Platform is that its automation language is readable not only for a couple of gurus, but also for almost everyone involved in IT. Therefore, any specialists can contribute to automation, which greatly facilitates the organization of inter-team interaction and the introduction of automation at the level of corporate culture.







However, with such a mass of people involved, it is very important to test everything thoroughly. When developing Ansible content such as playbooks, roles, or collections, we highly recommend that you test everything in a test environment before rolling out to production. The purpose of this testing is to make sure everything works as it should, to avoid unpleasant surprises on "production" systems.



Testing automation content is a tricky thing because it requires deploying a dedicated testing infrastructure and setting up testing conditions to ensure that the tests themselves are relevant. Molecule is a comprehensive testing framework that helps you develop and test Ansible roles so you can focus on developing automation without being distracted by managing your test infrastructure.



This is how it is declared in the project documentation :



"Molecule is designed to help develop and test Ansible roles, and fosters an approach that results in well-written roles that are well written, easy to understand and maintain."


Molecule allows you to test a role on multiple target instances to test it across a variety of operating systems and virtualization environments. Without it, for each of these combinations, you would have to create and maintain a separate test environment, set up connections to test instances, and roll them back to their original state before each test. Molecule does it all for you, in an automated and reproducible way.



In this two-part series, we'll show you how to use Molecule to develop and test Ansible roles. In the first part, we will look at installing and configuring Molecule, in the second, developing roles with it.



If the role is part of a collection, use this approach to develop and unit test the role. In the next article, we'll show you how to use Molecule to run integrated tests on collections.



Molecule uses drivers to deliver target instances across a variety of technologies, including Linux containers, virtual machines, and cloud providers. By default, it comes with three drivers preinstalled: Docker and Podman for containers, and a Delegated driver for creating custom integrations. Drivers for other providers are provided by the project development community.



In this post we will be using the Podman driverto develop and test a new role using Linux containers. Podman is a lightweight container engine for Linux, it doesn't need a running daemon, and it allows rootless containers to run, which is good for security.



Using Molecule with the Podman driver, we will develop and test a new Ansible role from scratch that deploys a web application based on an Apache web server and should run on Red Hat Enterprise Linux (RHEL) 8 or Ubuntu 20.04.



We are analyzing a typical scenario when a role should work on different versions of the operating system. Using Podman and Linux containers, we can create multiple instances to test the role on different OS versions. Because of their lightness, containers allow you to quickly iterate through the functionality of a role during development. Using containers to test roles is applicable in this situation, since the role only configures running Linux instances. For testing on other target systems or cloud infrastructures, you can use the delegated driver or other community-provided drivers.



What do we need



The examples in this article need a physical or virtual Linux machine with Python 3 and Podman installed (we're using RHEL 8.2). Also Podman had to be configured to run rootless containers. Installing Podman is beyond the scope of this article, see the official documentation for related information . Installing Podman on RHEL 8 is also covered in the RHEL 8 container documentation .



Let's get started



Molecule is designed as a Python package and is therefore installed via pip. The first step is to create a dedicated Python environment and install our Molecule into it:



$ mkdir molecule-blog
$ cd molecule-blog
$ python3 -m venv molecule-venv
$ source molecule-venv/bin/activate
(molecule-venv) $ pip install "molecule[lint]"


Note that we are installing Molecule with the "lint" option so that pip will also supply the "yamllint" and "ansible-lint" tools, which will allow us to use Molecule to statically analyze the role code against Ansible coding standards.



The installation downloads all the necessary dependencies from the Internet, including Ansible. Now let's see what we have installed:



$ molecule --version
molecule 3.0.4
   ansible==2.9.10 python==3.6


Well, it's time to use the "molecule" command to initialize the new Ansible role.



Initializing a new Ansible role



Generally speaking, when developing a new Ansible role, it is initialized with the "ansible-galaxy role init" command, but we will use the "molecule" command instead. This will give us the same role structure as with the "ansible-galaxy" command, as well as a basic piece of code for running Molecule tests.



By default, Molecule uses the Docker driver to run tests. Since we want to use podman instead, when initializing the role with the "molecule" command, we must specify the appropriate driver using the "--driver-name = podman" option.



Switch back to the "molecule-blog" directory and initialize the new role "mywebapp" with the following command:



$ molecule init role mywebapp --driver-name=podman
--> Initializing new role mywebapp...
Initialized role in /home/ricardo/molecule-blog/mywebapp successfully.


Molecule creates our role structure in the "mywebapp" folder. Switch to this folder and see what is there:



$ cd mywebapp
$ tree
.
β”œβ”€β”€ defaults
β”‚   └── main.yml
β”œβ”€β”€ files
β”œβ”€β”€ handlers
β”‚   └── main.yml
β”œβ”€β”€ meta
β”‚   └── main.yml
β”œβ”€β”€ molecule
β”‚   └── default
β”‚       β”œβ”€β”€ converge.yml
β”‚       β”œβ”€β”€ INSTALL.rst
β”‚       β”œβ”€β”€ molecule.yml
β”‚       └── verify.yml
β”œβ”€β”€ README.md
β”œβ”€β”€ tasks
β”‚   └── main.yml
β”œβ”€β”€ templates
β”œβ”€β”€ tests
β”‚   β”œβ”€β”€ inventory
β”‚   └── test.yml
└── vars
    └── main.yml
 
10 directories, 12 files


Molecule puts its configuration files in the "molecule" subdirectory. When initializing a new role, there is only one script called "default". Later, you can add your scripts here to test various conditions. In this article, we will only use the "default" script.



Let's check the basic configuration in the file "molecule / default / molecule.yml":



$ cat molecule/default/molecule.yml 
---
dependency:
  name: galaxy
driver:
  name: podman
platforms:
  - name: instance
    image: docker.io/pycontribs/centos:7
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible


As we requested, this file states that the Podman driver is used for tests. This is where the default platform for the test instance is set, via the "docker.io/pycontribs/centos:7" container image, which we will change later.



Unlike Molecule v2, Molecule v3 does not define a default linter. Therefore, let's open the configuration file "molecule / default / molecule.yml" and add the lint configuration at the end:



$ vi molecule/default/molecule.yml
...
verifier:
  name: ansible
lint: |
  set -e
  yamllint .
  ansible-lint .


Save and close the file, and run the "molecule lint" command from the root folder of our project to run the linter throughout the project:



$ molecule lint


We get a few errors in the output, since the file "meta / main.yml" does not contain a number of required values. Let's fix this: edit the "meta / main.yml" file, add "author", "company", "license", "platforms" and remove the empty line at the end. For brevity, we will dispense with comments, and then our "meta / main.yaml" will look like this:



$ vi meta/main.yml
galaxy_info:
  author: Ricardo Gerardi
  description: Mywebapp role deploys a sample web app 
  company: Red Hat 
 
  license: MIT 
 
  min_ansible_version: 2.9
 
  platforms:
  - name: rhel
    versions:
    - 8 
  - name: ubuntu
    versions:
    - 20.04
 
  galaxy_tags: []
 
dependencies: []


Let's run the linter on the project again and make sure that there are no more errors.



$ molecule lint
--> Test matrix
    
└── default
    β”œβ”€β”€ dependency
    └── lint
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'
--> Executing: set -e
yamllint .
ansible-lint . 


So, our role is initialized and the basic configuration of the molecule is also in place. Now let's create a test instance.



Create a test instance



By default, Molecule defines only one instance, which is called "instance" and is created from the "Centos: 7" image. Our role, if you remember, should work on RHEL 8 and Ubuntu 20.04. Also, because it runs the Apache web server as a system service, we need a container image with "systemd" enabled.



Red Hat has an official Universal Base Image for RHEL 8 with "systemd" enabled:



β€’ registry.access.redhat.com/ubi8/ubi-init



There is no official "systemd" image for Ubuntu, so we'll use the image maintained by Jeff Geerling (Jeff Geerling) from the Ansible community:



β€’ geerlingguy / docker-ubuntu2004-ansible



To get instances with "systemd", let's edit the configuration file "molecule / default / molecule.yml" by removing the "centos: 7" instance from it and adding two new instances:



$ vi molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: podman
platforms:
  - name: rhel8
    image: registry.access.redhat.com/ubi8/ubi-init
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    capabilities:
      - SYS_ADMIN
    command: "/usr/sbin/init"
    pre_build_image: true
  - name: ubuntu
    image: geerlingguy/docker-ubuntu2004-ansible
    tmpfs:
      - /run
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    capabilities:
      - SYS_ADMIN
    command: "/lib/systemd/systemd"
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible
lint: |
  set -e
  yamllint .
  ansible-lint .


With these parameters we mount for each instance the temporary filesystem "/ run" and "/ tmp", as well as the volume "cgroup". In addition, we enable the "SYS_ADMIN" function, which is required to run containers with Systemd.



If you do everything smartly and run this example on a RHEL 8 machine with SELinux enabled, then you also need to set the boolean parameter "container_manage_cgroup" to true so that containers can run Systemd (see the RHEL 8 documentation for more details ):



sudo setsebool -P container_manage_cgroup 1


Molecule uses the Ansible Playbook to initialize these instances. Let's change and add the initialization parameters by modifying the "provisioner" dictionary in the "molecule / default / molecule.yml" configuration file.



It accepts the same configuration options as specified in the "ansible.cfg" configuration file. For example, let's update the provisioner configuration by adding a "defaults" section. Set the Python interpreter to "auto_silent" to disable warnings. Let's include the "profile_tasks", "timer" and "yaml" callback plugins so that the profiler information is included in the Playbook output. Finally, add the "ssh_connection" section and disable SSH pipelining as it does not work with Podman:



provisioner:
  name: ansible
  config_options:
    defaults:
      interpreter_python: auto_silent
      callback_whitelist: profile_tasks, timer, yaml
    ssh_connection:
      pipelining: false


Let's save this file and create an instance with the "molecule create" command from the root directory of our role:



$ molecule create


Molecule will execute an init playbook and create both of our instances. Let's check them with the "molecule list" command:



$ molecule list
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
rhel8            podman         ansible             default          true       false
ubuntu           podman         ansible             default          true       false


Let's also check that both containers are running in Podman:



$ podman ps
CONTAINER ID  IMAGE                                                   COMMAND               CREATED             STATUS                 PORTS  NAMES
2e2f14eaa37b  docker.io/geerlingguy/docker-ubuntu2004-ansible:latest  /lib/systemd/syst...  About a minute ago  Up About a minute ago         ubuntu
2ce0a0ea8692  registry.access.redhat.com/ubi8/ubi-init:latest         /usr/sbin/init        About a minute ago  Up About a minute ago         rhel8


When developing a role, Molecule uses the running instances to test it. If the test fails or some error leads to irreversible changes, because of which everything has to start over, you can kill these instances at any time with the command "molecule destroy" and recreate them with the command "molecule create".



Conclusion



If you are impatient and want to dig deeper into the topic of developing and testing roles, or the topic of Ansible automation, then we recommend the following resources:






All Articles