Ansible Test Environments
Introduction
Testing is a critical part of any development process, and Ansible automation is no exception. Ansible test environments allow you to validate your playbooks, roles, and modules before deploying them to production systems. This ensures that your automation works as expected and prevents potential issues that could impact your infrastructure.
In this guide, we'll explore how to set up and use different types of test environments for Ansible, along with best practices and tools that make testing more efficient.
Why Test Ansible Code?
Before diving into test environments, let's understand why testing Ansible code is important:
- Prevent Production Failures: Testing helps catch errors before they reach production systems.
- Validate Changes: Ensure that changes to playbooks and roles work as expected.
- Increase Confidence: Build confidence in your automation by verifying its behavior.
- Facilitate Collaboration: Enable team members to test changes without affecting others.
- Support CI/CD: Integrate testing into your continuous integration and deployment pipelines.
Types of Ansible Test Environments
There are several approaches to setting up test environments for Ansible. Let's explore the most common options:
1. Local Testing with Ansible Syntax Check
The simplest form of testing is using Ansible's built-in syntax checker to validate your playbooks before execution.
# Check syntax of a playbook
ansible-playbook --syntax-check playbook.yml
# Example output
playbook: playbook.yml
This simple check can catch basic syntax errors but doesn't verify the actual execution of your playbooks.
2. Virtual Machines with Vagrant
Vagrant is a popular tool for creating and managing virtual machine environments. It's ideal for testing Ansible playbooks in an isolated environment.
Here's a sample Vagrantfile
that creates a test environment for Ansible:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
# Enable provisioning with Ansible
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
ansible.verbose = "v"
end
end
To use this environment:
# Start the VM and run the Ansible playbook
vagrant up
# Re-run the Ansible playbook on the VM
vagrant provision
# Destroy the VM when done
vagrant destroy
3. Container-based Testing with Docker
Docker containers provide a lightweight alternative to virtual machines for testing Ansible playbooks.
Create a Dockerfile
to define your test environment:
FROM ubuntu:20.04
# Install SSH and Python (required for Ansible)
RUN apt-get update && \
apt-get install -y openssh-server python3 python3-pip sudo && \
mkdir /var/run/sshd
# Configure SSH
RUN echo 'root:password' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# Start SSH service
CMD ["/usr/sbin/sshd", "-D"]
Build and run the Docker container:
# Build the Docker image
docker build -t ansible-test-ubuntu .
# Run the container
docker run -d -p 2222:22 --name ansible-test ansible-test-ubuntu
Create an inventory file to target the Docker container:
# inventory.ini
[test]
localhost ansible_port=2222 ansible_user=root ansible_password=password ansible_host=127.0.0.1
Run your Ansible playbook against the container:
ansible-playbook -i inventory.ini playbook.yml
4. Molecule: Specialized Testing Framework for Ansible
Molecule is a framework designed specifically for testing Ansible roles. It provides a standardized way to test roles across different platforms and scenarios.
Install Molecule:
pip install molecule molecule-docker
Initialize a new role with Molecule:
molecule init role my_role --driver-name docker
This creates a directory structure with a molecule
directory containing test scenarios.
To test the role:
cd my_role
molecule test
Molecule will:
- Create a Docker container
- Apply your Ansible role
- Verify the results
- Destroy the container
Molecule's default test sequence includes:
dependency
: Install role dependencieslint
: Lint the playbookcleanup
: Clean up test resourcesdestroy
: Destroy test resourcessyntax
: Verify syntaxcreate
: Create test resourcesprepare
: Prepare test resourcesconverge
: Run the playbookidempotence
: Verify idempotenceside_effect
: Run side effectsverify
: Verify resultscleanup
: Clean up test resourcesdestroy
: Destroy test resources
Setting Up a Comprehensive Test Environment
Let's walk through setting up a comprehensive test environment using Molecule, which is considered the industry standard for testing Ansible roles.
Step 1: Install Required Tools
# Install Python packages
pip install molecule molecule-docker ansible-lint yamllint
# Ensure Docker is installed
# For Ubuntu/Debian:
apt-get install docker.io
Step 2: Create a New Role with Molecule
molecule init role my_web_server --driver-name docker
cd my_web_server
Step 3: Define the Role
Edit tasks/main.yml
to define your role:
---
# tasks/main.yml
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
become: true
- name: Start and enable Nginx
service:
name: nginx
state: started
enabled: yes
become: true
- name: Create custom webpage
copy:
content: "<html><body><h1>Hello from Ansible!</h1></body></html>"
dest: /var/www/html/index.html
become: true
Step 4: Configure Molecule
Edit molecule/default/molecule.yml
to define your test scenario:
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: ubuntu:20.04
pre_build_image: true
privileged: true
command: "/sbin/init"
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
environment:
ANSIBLE_USER: root
provisioner:
name: ansible
config_options:
defaults:
interpreter_python: auto_silent
verifier:
name: ansible
Step 5: Create Verification Tests
Create or modify molecule/default/verify.yml
:
---
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Check if Nginx is installed
command: which nginx
register: nginx_check
changed_when: false
failed_when: nginx_check.rc != 0
- name: Verify Nginx is running
command: systemctl is-active nginx
register: nginx_status
changed_when: false
failed_when: nginx_status.stdout != "active"
- name: Check webpage content
uri:
url: http://localhost
return_content: yes
register: webpage
failed_when: "'Hello from Ansible!' not in webpage.content"
Step 6: Run the Tests
molecule test
This will execute the complete test sequence. If any part fails, Molecule will stop and report the error.
Testing Strategy: A Practical Workflow
Let's put together a practical workflow for testing Ansible code:
-
Syntax Checking: Start with basic syntax validation.
bashansible-playbook --syntax-check playbook.yml
-
Lint Testing: Use ansible-lint to catch common mistakes and best practice violations.
bashansible-lint playbook.yml
-
Unit Testing with Molecule: Test individual roles in isolation.
bashcd roles/my_role
molecule test -
Integration Testing: Test multiple roles working together in a test environment.
bash# Using Vagrant for integration testing
vagrant up -
Manual Verification: Perform manual checks for critical functionality.
Let's visualize this workflow:
Best Practices for Ansible Test Environments
- Test Environment Parity: Make your test environment as similar to production as possible.
- Idempotence Testing: Ensure your playbooks can run multiple times without changing the result.
- Version Control: Keep your test configurations in version control alongside your Ansible code.
- Automated Testing: Integrate testing into your CI/CD pipeline.
- Test Coverage: Test multiple scenarios and edge cases.
- Isolation: Ensure tests don't interfere with each other or production systems.
Practical Example: Testing a Web Server Deployment
Let's walk through a complete example of testing an Ansible role that deploys a web server:
1. Create the Role Structure
molecule init role web_server --driver-name docker
cd web_server
2. Define the Role Tasks
Edit tasks/main.yml
:
---
- name: Install web server packages
package:
name:
- nginx
- curl
state: present
become: true
- name: Configure Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
become: true
notify: restart nginx
- name: Create web directory
file:
path: /var/www/html
state: directory
mode: '0755'
become: true
- name: Deploy sample website
template:
src: index.html.j2
dest: /var/www/html/index.html
become: true
3. Create Templates
Create templates/nginx.conf.j2
:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
}
Create templates/index.html.j2
:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to {{ ansible_hostname }}</title>
</head>
<body>
<h1>Hello from {{ ansible_hostname }}</h1>
<p>This server was configured using Ansible.</p>
</body>
</html>
4. Define Handlers
Edit handlers/main.yml
:
---
- name: restart nginx
service:
name: nginx
state: restarted
become: true
5. Configure Molecule
Edit molecule/default/molecule.yml
:
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: ubuntu:20.04
pre_build_image: true
privileged: true
command: "/sbin/init"
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
provisioner:
name: ansible
verifier:
name: ansible
6. Create Verification Tests
Edit molecule/default/verify.yml
:
---
- name: Verify
hosts: all
tasks:
- name: Check if Nginx is installed
command: which nginx
register: nginx_check
changed_when: false
failed_when: nginx_check.rc != 0
- name: Verify Nginx is running
command: systemctl is-active nginx
register: nginx_status
changed_when: false
failed_when: nginx_status.stdout != "active"
- name: Check website content
uri:
url: http://localhost
return_content: yes
register: webpage
failed_when: "'This server was configured using Ansible' not in webpage.content"
- name: Test idempotence of configuration
uri:
url: http://localhost
method: GET
status_code: 200
register: result
until: result.status == 200
retries: 3
delay: 5
7. Run the Tests
molecule test
When the tests pass, you'll see output confirming that each phase completed successfully.
Advanced Testing Techniques
Testing Across Multiple Platforms
Modify your molecule/default/molecule.yml
to test on multiple operating systems:
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu
image: ubuntu:20.04
pre_build_image: true
privileged: true
command: "/sbin/init"
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- name: centos
image: centos:8
pre_build_image: true
privileged: true
command: "/sbin/init"
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
provisioner:
name: ansible
verifier:
name: ansible
Testing Different Scenarios
You can create multiple test scenarios:
molecule init scenario alternative --driver-name docker
This creates a new scenario in molecule/alternative/
that you can customize for different test conditions.
Integration with CI/CD
Here's an example of integrating Molecule testing with GitHub Actions:
# .github/workflows/molecule.yml
name: Molecule Test
on: [push, pull_request]
jobs:
molecule:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install molecule molecule-docker ansible-lint yamllint
- name: Run Molecule tests
run: molecule test
Troubleshooting Common Issues
Docker Connection Issues
If you encounter connection issues with Docker containers:
# molecule/default/molecule.yml
platforms:
- name: instance
# Add these settings
published_ports:
- "0.0.0.0:8080:80/tcp"
networks:
- name: molecule_network
networks:
- name: molecule_network
Python Interpreter Issues
If you encounter Python interpreter issues:
# molecule/default/molecule.yml
provisioner:
name: ansible
config_options:
defaults:
interpreter_python: auto_silent
Idempotence Test Failures
If your idempotence tests are failing:
- Check for tasks that always report as changed
- Use
changed_when
to control change reporting - Ensure proper state definitions in your tasks
Summary
Setting up proper test environments for Ansible is essential for developing reliable automation. In this guide, we've explored:
- Different types of test environments (syntax checking, Vagrant VMs, Docker containers, and Molecule)
- A step-by-step process for setting up comprehensive test environments with Molecule
- Best practices for testing Ansible code
- A practical workflow for implementing a testing strategy
- Advanced testing techniques and troubleshooting tips
By implementing these testing approaches, you can ensure your Ansible code works correctly, is maintainable, and can be safely deployed to production environments.
Additional Resources
- Ansible Documentation on Testing
- Molecule Documentation
- Ansible Lint Documentation
- Testing Ansible Playbooks with Vagrant
Exercises
- Set up a basic test environment using Ansible's syntax check for an existing playbook.
- Create a Vagrant environment to test a playbook that installs and configures a database server.
- Use Molecule to test an Ansible role that deploys a web application across multiple operating systems.
- Implement an integration test for multiple roles working together.
- Add Molecule testing to a CI/CD pipeline using GitHub Actions or another CI platform.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)