Ansible Playbooks are the heart of Ansible automation. They provide a simple way to define automation workflows using YAML. Playbooks consist of instructions (tasks) that Ansible executes on remote machines. They are easy to read and manage, making them ideal for automating IT processes such as configuration management, software deployment, and task execution.
In this guide, you’ll learn how to create, manage, and optimize Ansible Playbooks with examples.
Table of Contents
Understanding the Basics of Ansible Playbook
An Ansible Playbook is a YAML file containing a series of plays. Each play defines a set of tasks to run on a group of Ansible hosts. Playbooks can include variables, conditionals, and handlers to add flexibility.
Key Components of Playbooks:
- Hosts: These define the target machines on which the tasks are executed. The hosts field specifies which group or machine to apply the playbook to.
- Tasks: These are the actual steps executed by Ansible, such as installing software or restarting services.
- Handlers: Special tasks triggered when a change occurs, such as restarting a service after a configuration update.
- Variables: Dynamic values used in playbooks to make tasks reusable across multiple environments.
Writing Your First Ansible Playbook
Let’s start with a simple playbook to install NGINX on an Ubuntu server.
1. Create an inventory file inventory.yml:
[webservers]
server1 ansible_host=192.168.1.10 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa
2. Now create a playbook nginx_install.yml:
---
- name: Install NGINX on web server
hosts: webservers
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
- name: Install NGINX
apt:
name: nginx
state: present
3. Run the playbook:
# ansible-playbook -i inventory.yml nginx_install.yml
Using Loops and Conditional in Playbooks
You can use loops to repeat tasks, and conditionals to execute tasks only when certain conditions are met.
Here is a simple playbook that uses a loop to install multiple packages on target servers.
---
- name: Install multiple packages
hosts: webservers
tasks:
- name: Install necessary packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- curl
- git
You can also use when conditional in playbooks to install packages based on operating systems.
The following playbook installs the Nginx package only if the target host runs a Debian-based operating system.
- name: Install NGINX only if the host is Ubuntu
apt:
name: nginx
state: present
when: ansible_facts['os_family'] == "Debian"
Using include for Dynamic Task Execution
include_tasks allows you to include a separate task file dynamically. It’s useful when you need flexibility, such as including tasks conditionally or within a loop.
Example: Include Task Files Conditionally
Here, we will conditionally include different task files based on the operating system.
Main Playbook (site.yml):
---
- name: Configure Web Servers
hosts: webservers
tasks:
- name: Include tasks for Debian
include_tasks: debian.yml
when: ansible_facts['os_family'] == "Debian"
- name: Include tasks for RedHat
include_tasks: redhat.yml
when: ansible_facts['os_family'] == "RedHat"
Debian Task File (debian.yml):
---
- name: Install NGINX on Debian
apt:
name: nginx
state: present
RedHat Task File (redhat.yml):
---
- name: Install NGINX on RedHat
yum:
name: nginx
state: present
Using import for Static Task Execution
import_tasks is processed when the playbook starts, so it is static and executed unconditionally. It’s better for simpler scenarios where tasks don’t need to be dynamically included.
Import Task Files for Standard Tasks
In this example, we split common tasks into separate files and use import_tasks to include them.
Main Playbook (site.yml):
---
- name: Common Server Configuration
hosts: all
tasks:
- name: Import common tasks
import_tasks: common_tasks.yml
Common Task File (common_tasks.yml):
---
- name: Update package cache
apt:
update_cache: yes
- name: Ensure NGINX is installed
apt:
name: nginx
state: present
Reusing Tasks Across Playbooks
You can reuse the same set of tasks across multiple playbooks. This is useful for defining common actions, like setting up users or configuring security settings.
Reusing Tasks Across Playbooks
First Playbook (playbook1.yml):
---
- name: Web Server Setup
hosts: webservers
tasks:
- import_tasks: setup_users.yml
Second Playbook (playbook2.yml):
---
- name: Database Server Setup
hosts: dbservers
tasks:
- import_tasks: setup_users.yml
Task File (setup_users.yml):
---
- name: Create user John
user:
name: john
state: present
- name: Add John to sudo group
user:
name: john
groups: sudo
append: yes
Now, both playbook1.yml and playbook2.yml can reuse the setup_users.yml task file.
Using Variables and Facts in Playbooks
Ansible allows you to define variables using YAML syntax. Variables allow you to define values that can be reused across tasks, which helps avoid hardcoding values in multiple places. This improves playbook maintainability.
Ansible facts automatically gather data about the target hosts, such as OS, IP address, or hardware details. You can use facts to make decisions dynamically during playbook execution,.
Let’s look at an optimized playbook that installs different web servers (nginx or httpd) based on the host’s operating system. For flexibility, we’ll combine variables and Ansible facts.
- name: Install web server based on OS
hosts: all
vars:
web_package_debian: nginx
web_package_redhat: httpd
tasks:
- name: Install web server on Debian-based systems
apt:
name: "{{ web_package_debian }}"
state: present
when: ansible_facts['os_family'] == "Debian"
- name: Install web server on RedHat-based systems
yum:
name: "{{ web_package_redhat }}"
state: present
when: ansible_facts['os_family'] == "RedHat"
Ansible Playbook Options with Examples
When running a playbook, you can use several command-line options to control how the playbook executes. Understanding these options will help you customize playbook execution for different environments and scenarios.
Below are some frequently used options in the playbook.
1. Using –check Mode (Dry Run)
The –check option runs the playbook in “dry run” mode. It simulates the changes without applying them, allowing you to test the playbook’s logic and see what would change.
# ansible-playbook playbook.yml --check
Use this mode when you want to validate the playbook without making changes to the target system.
2. Using –diff for Configuration File Changes
The –diff option shows the differences between files before and after the playbook is applied. This is useful when you’re managing configuration files.
# ansible-playbook playbook.yml --diff
Use this when working with configuration files to see exactly what changes will be made.
3. Using –verbose for Detailed Output
Ansible provides verbosity levels through the -v, -vv, -vvv, or -vvvv flags. The more vs you use, the more detailed the output becomes. For example:
# ansible-playbook playbook.yml -vvv
Use this when you want to see detailed logs of each step in the playbook’s execution.
4. Using the debug Module
The debug module helps to print the value of variables or other information during playbook execution. This is useful for troubleshooting and ensuring that variables are set correctly.
- name: Debug variable value
hosts: all
tasks:
- name: Print OS family
debug:
var: ansible_facts['os_family']
- name: Print a custom message
debug:
msg: "Running on {{ inventory_hostname }} which is part of {{ ansible_facts['os_family'] }}"
Use the debug Ansible module when you want to inspect variables or outputs during playbook execution.
5. Using –start-at-task to Resume from a Specific Task
If a playbook fails or you want to test specific tasks, you can use the –start-at-task option to begin playbook execution from a particular task without starting from the beginning.
# ansible-playbook playbook.yml --start-at-task="Install NGINX"
Use this to save time when debugging a specific part of the playbook.
6. Using ansible-lint to Validate Playbooks
ansible-lint is a tool that checks your playbooks for best practices and potential issues. It helps ensure that your playbook follows coding standards and doesn’t have syntax or logic errors.
First, install ansible-lint:
# pip install ansible-lint
Then, run the linter:
# ansible-lint playbook.yml
Use ansible-lint regularly to validate your playbooks for common errors and best practices.
7. Using –limit to Limit Target Hosts
The -l option allows you to limit the playbook execution to specific hosts or groups.
Example:
# ansible-playbook playbook.yml -l webservers
This command limits the playbook execution to hosts in the webservers group.
8. Using –inventory to Specify Inventory File
By default, Ansible uses the inventory file specified in the default configuration. You can override it using the -i option.
Example:
# ansible-playbook my_playbook.yml -i /path/to/inventory.ini
This command specifies a custom inventory file for the playbook.
9. Using –ask-become-pass to Prompt for Sudo Password
If your playbook requires sudo privileges, you can use –ask-become-pass to prompt for the password.
Example:
# ansible-playbook playbook.yml --ask-become-pass
This command prompts you to enter the sudo password for tasks that require elevated privileges.
Conclusion
Ansible Playbooks provide a flexible way to automate IT operations. From simple tasks to complex workflows, playbooks can streamline configuration management and deployments. Start with simple playbooks and gradually build more complex, modular ones using roles and variables.
FAQs
1. Can I use conditionals in an Ansible Playbook?
Yes, conditionals can be applied using the when clause to run tasks only when specific conditions are met.
2. What is the purpose of the hosts keyword in playbook?
The hosts keyword specifies the target group of hosts on which the playbook tasks will be executed.
3. How do you handle errors in Ansible Playbooks?
You can handle errors using the ignore_errors directive or by using block and rescue sections for more advanced error handling.
4. What is the use of the become directive in Ansible Playbooks?
The become directive is used to escalate privileges, allowing tasks to run as a different user, such as root, when necessary.