Ansible IV (Organizing playbooks)

Alex Izuka
6 min readOct 26, 2023

I began a series discussing what you need to know about Ansible, you can check the last series here. In this article, we will look at organizing our playbooks, gathering facts about target hosts, roles, and their usage, and template creation using Jinja2.

Using variables in Ansible

Variables in Ansible are essential for making playbooks more dynamic and adaptable to various scenarios. They allow you to store and reuse values or configurations, making your automation more flexible. Below is how to use variables in Ansible.

  1. Defining Variables: You can define variables at different levels in Ansible:
  • Inventory Variables: You can set variables in your inventory file, either for individual hosts or for groups of hosts. For example, in your inventory file (inventory.ini):
[web]
webserver1 ansible_host=192.168.1.10

[db]
dbserver1 ansible_host=192.168.1.20

[web:vars]
web_var1=value1
web_var2=value2

[db:vars]
db_var1=value3
  • Playbook Variables: You can define variables directly in your playbook using the vars section:
---
- name: Playbook Name
hosts: web
vars:
playbook_var1: value1
playbook_var2: value2
tasks:
# ...
  • Separate Variable Files: It’s a good practice to store variables in separate YAML files (e.g., vars.yml) and include them in your playbooks using vars_files:
---
- name: Playbook Name
hosts: web
vars_files:
- vars.yml
tasks:
# ...

2. Using Variables in Playbooks: You can use variables within your playbooks as follows:

  • Referencing Variables: To reference a variable, use double curly braces {{ }}. For example:
- name: Task Name
debug:
msg: "This is a message with a variable: {{ playbook_var1 }}"
  • Conditional Statements: You can use variables in conditional statements to control task execution based on variable values:
- name: Conditional Task
command: "echo 'Task executed because condition is true'"
when: playbook_var2 == 'value2'
  • Looping with Variables: You can use variables in loops for tasks that need to be executed multiple times:
- name: Loop Task
command: "echo 'Loop iteration {{ item }}'"
with_items:
- 1
- 2
- 3

Gathering facts about target hosts

Facts provide information about the remote systems, which can be used for various tasks, conditional statements, or reporting. Ansible automatically collects these facts when you run a playbook. Stated below is how to gather facts about target hosts.

  1. Use the gather_facts Option: By default, Ansible gathers facts about target hosts when you run a playbook. The gather_facts option is set to true by default in playbooks. However, you can explicitly set it to false to skip facts gathering if needed. See its usage below.
---
- name: Gather Facts Example
hosts: webserver
gather_facts: true
tasks:
# Your tasks here

To disable facts gathering for a specific playbook or task, set gather_facts to false.

2. Accessing Facts: Facts are collected and made available as variables that you can use within your playbook. To access facts, use the ansible_facts variable. For example, to access the hostname of a target host:

- name: Access Hostname Fact
debug:
msg: "The hostname of the target host is {{ ansible_facts['ansible_hostname'] }}"

Common facts you can access include ansible_hostname, ansible_os_family, ansible_distribution, ansible_os_version, and many more.

3. Viewing Facts: If you want to view the facts for a target host without running tasks, you can use the setup module in an ad-hoc command. For example:

ansible webserver -m setup

This command will display detailed facts about the webserver host, including information about hardware, network, and operating system.

4. Caching Facts: Ansible has a fact caching mechanism that can store facts on the control machine to speed up playbook execution. You can configure the fact cache in your Ansible configuration file (ansible.cfg). Common options include json, yaml, and memory as the fact caching plugin. Caching is particularly useful when you run multiple plays or playbooks against the same target hosts. For example, to enable fact caching in a ansible.cfg file:

[defaults]
fact_caching = yaml
fact_caching_connection = /path/to/cache/directory

Roles and their usage

The concept of roles in Ansible is a way to organize and modularize your automation code. Roles allow you to group related tasks, variables, and files into reusable components that can be easily integrated into multiple playbooks. This modular approach promotes code reusability and simplifies the management of complex automation projects. See its usage below.

Creating an Ansible Role

  1. Role Directory Structure: Roles follow a specific directory structure to organize tasks, variables, templates, files, and other resources. You can create a role using the ansible-galaxy command or manually create the directory structure. Below is a manually created directory structure for an example role called "webserver".
roles/
└── webserver/
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
├── templates/
├── files/
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
└── meta/
└── main.yml

We explain each of the various sections below.

2. Role Tasks: The tasks/main.yml file contains the tasks specific to the role. For example, you can have tasks to install Nginx, configure the virtual host, and manage files. Here's a simplified example:

---
- name: Install Nginx
apt:
name: nginx
state: present

- name: Copy Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf

3. Role Handlers: The handlers/main.yml file contains handlers that are triggered when tasks notify them. Handlers are typically used for restarting services or performing other actions in response to changes.

---
- name: Restart Nginx
service:
name: nginx
state: restarted

4. Role Templates and Files: The templates and files directories contain template files and static files, respectively, that the role tasks may use.

5. Role Variables: The vars/main.yml file in the vars directory can define variables specific to the role. These variables can be overridden by playbook or inventory variables.

--- 
webserver_port: 80

6. Role Default Variables: The defaults/main.yml file in the defaults directory can set default values for the role variables. These defaults can be overridden at the playbook or inventory level.

--- 
webserver_port: 80

7. Role Meta: The meta/main.yml file in the meta directory provides metadata about the role, such as its dependencies.

---
dependencies:
- { role: common, some_parameter: 'value' }

Using an Ansible Role

Once you’ve created a role, you can use it in your playbooks. To include a role, use the roles keyword in your playbook as shown below:

---
- name: My Playbook
hosts: webserver
roles:
- webserver

The webserver role is included in the playbook. When you run the playbook, Ansible will execute the tasks defined in the role on the specified target hosts.

Template creation using Jinja2

Creating templates using Jinja2 in Ansible allows you to generate configuration files, scripts, or any other text-based output with dynamic content. Jinja2 is a powerful and flexible template engine that is widely used in Ansible to customize configurations for different hosts or situations. See its usage below.

  1. Template Files: You can start by creating template files with the .j2 extension. These files contain a mixture of static text and dynamic content enclosed within double curly braces {{ }}. For example, a simple template file named my_template.j2 might look like this:
# This is a template file for {{ application_name }} on {{ ansible_hostname }}

Listen {{ webserver_port }}
ServerName {{ website_domain }}

DocumentRoot {{ document_root }}

In this template, {{ application_name }}, {{ ansible_hostname }}, {{ webserver_port }}, and {{ website_domain }} are placeholders for dynamic content.

2. Variables in Templates: Variables in templates are typically provided through Ansible. You can include variables directly in the template file using {{ variable_name }}. When you render the template using Ansible, these variables will be replaced with their corresponding values.

3. Looping and Conditionals: Jinja2 also allows you to use control structures like loops and conditionals within your templates. For example, you can loop through a list of items or apply conditional logic to generate different content based on specific conditions.

{% for item in items %}
Item: {{ item }}
{% endfor %}

{% if condition %}
This content is generated when the condition is true.
{% else %}
This content is generated when the condition is false.
{% endif %}

4. Template Rendering: To render a template and replace variables with their values, you can use the template module in Ansible. Below is an example of how to use it in a playbook:

---
- name: Render Template
hosts: webserver
tasks:
- name: Create configuration file from template
template:
src: /path/to/my_template.j2
dest: /etc/myapp/myapp.conf

In this playbook, the template module copies the template file my_template.j2 to the destination /etc/myapp/myapp.conf, and during this process, it replaces the variables (e.g., {{ application_name }}, {{ ansible_hostname }}, etc.) with their actual values.

5. Running the Playbook: Run the playbook as usual with the ansible-playbook command:

ansible-playbook my_playbook.yml

The template will be rendered, and the resulting configuration file will be placed at the destination you specified.

Conclusion

We have looked at organizing playbooks, gathering facts about target hosts, roles, and their usage, and template creation using Jinja2.

--

--