Collections: Making Your Playbooks Modular and Scalable
This exercise builds on your previous experience with Ansible by focusing on collections. Collections offer an efficient way to package and distribute automation content, including roles, modules, plugins, and playbooks, all within a single unit. You will develop a collection that installs and configures Apache (httpd), demonstrating how to structure content for modular and reusable automation.
Understanding Ansible Collections
Ansible collections are the preferred way to organize, distribute, and reuse automation content. They group together various components—like roles, modules, and plugins—so developers can manage and share automation resources more efficiently. Collections allow you to store related content in one place and distribute it via Ansible Galaxy, Automation Hub, or private Automation Hub within your organization.
Each collection can include the following components:
Modules
Small programs that perform specific automation tasks on local machines, APIs, or remote hosts. Modules are usually written in Python and include metadata defining how, when, and by whom the task can be executed. Modules can be used across various use cases like cloud management, networking, and configuration management.
Example modules:
-
package: Installs or removes packages with the systems package manager.
-
service: Manages system services (start, stop, restart).
-
command: Executes commands on a target system.
Roles
Roles are modular bundles of tasks, variables, templates, and handlers. They simplify automation workflows by breaking them into reusable components. Roles can be imported into playbooks and used across multiple automation scenarios, reducing duplication and improving manageability.
Cleaning up the Environment
Before we build the collection, let’s clean up any previous Apache installations. Create a file called cleanup.yml in your ~/lab_inventory directory:
touch ~/lab_inventory/cleanup.yml
Then add the following content to cleanup.yml:
---
- name: Cleanup Environment
hosts: all
become: true
vars:
package_name: httpd
tasks:
- name: Remove Apache from web servers
ansible.builtin.package:
name: "{{ package_name }}"
state: absent
when: inventory_hostname in groups['web']
- name: Remove firewalld
ansible.builtin.package:
name: firewalld
state: absent
- name: Delete created users
ansible.builtin.user:
name: "{{ item }}"
state: absent
remove: true
loop:
- alice
- bob
- carol
- Roger
- name: Reset MOTD to an empty message
ansible.builtin.copy:
dest: /etc/motd
content: ''
mode: '0644'
Now run the playbook to clean the environment
ansible-playbook -i hosts cleanup.yml
Building an Apache Collection
-
Create the Collection Structure
Use
ansible-galaxyto initialize the collection structure:ansible-galaxy collection init webops.apache --init-path ./collections/ansible_collectionsThis creates the following structure:
. └── collections └── ansible_collections └── webops └── apache ├── docs ├── galaxy.yml ├── meta │ └── runtime.yml ├── plugins │ └── README.md ├── README.md └── roles -
Create the
apacherole within yourwebops.apachecollection:cd collections/ansible_collections/webops/apache/roles ansible-galaxy role init apache -
Define Role Variables:
Add Apache-specific variables in
roles/apache/vars/main.yml:--- apache_package_name: httpd apache_service_name: httpd -
Create Role Tasks:
Add the following tasks to
roles/apache/tasks/main.ymlto install and configure Apache:--- - name: Install Apache web server ansible.builtin.package: name: "{{ apache_package_name }}" state: present - name: Ensure Apache is running and enabled ansible.builtin.service: name: "{{ apache_service_name }}" state: started enabled: true - name: Install firewalld ansible.builtin.package: name: firewalld state: present - name: Allow HTTP traffic on web servers ansible.posix.firewalld: service: http permanent: true state: enabled when: inventory_hostname in groups['web'] notify: Reload Firewall -
Add Handlers:
Create a handler to reload the firewall in
roles/apache/handlers/main.yml:--- - name: Reload Firewall ansible.builtin.service: name: firewalld state: reloaded -
Create a Custom Webpage Template:
Add a Jinja2 template for the web page in
roles/apache/templates/index.html.j2:<html> <head> <title>Welcome to {{ ansible_hostname }}</title> </head> <body> <h1>Hello from {{ ansible_hostname }}</h1> </body> </html> -
Deploy the Template:
Add the template deployment task to
roles/apache/tasks/main.yml:- name: Deploy custom index.html ansible.builtin.template: src: index.html.j2 dest: /var/www/html/index.html mode: '0644'
Using the Collection in a Playbook
Create a playbook named deploy_apache.yml within ~/lab_inventory directory to apply the collection to the web group:
---
- name: Deploy Apache using Collection
hosts: web
become: true
roles:
- webops.apache.apache
Create a requirements.yml file
A requirements.yml file is the standard way to declare collection dependencies in Ansible projects. Rather than expecting collaborators to know which collections to install manually, you ship this file with your project so dependencies are explicit and reproducible. Create a requirements.yml file under ~/lab_inventory with the following content:
collections:
- name: ansible.posix
Install the declared collections with:
cd ~/lab_inventory
ansible-galaxy collection install -r requirements.yml -f
Summary
In this exercise you:
-
Learned how Ansible collections organize roles, modules, plugins, and playbooks into reusable units
-
Cleaned up a previous environment to prepare for a fresh deployment
-
Built a custom
webops.apachecollection with roles, tasks, handlers, and templates -
Created a playbook that uses the collection to deploy Apache
-
Declared collection dependencies using
requirements.ymlfor portability
In the next exercise you will run this playbook inside an Execution Environment using ansible-navigator.