It can feel daunting when you are tasked with managing complex systems, especially when you have a large infrastructure. The good news is that there are tools to help you automate the process. One of these tools is Ansible, an open-source automation engine. With Ansible, you can define the state of your systems and have the engine apply these states across your infrastructure, but the real power of Ansible comes from Playbooks. In this guide, you’ll learn what Ansible Playbooks are, how to write and use them, and everything in between.
What are Ansible Playbooks?
Ansible Playbooks are like a to-do list, but for machines. They are files that describe the tasks that Ansible needs to run on target systems. You write these files in YAML, a simple format easy for both humans and machines to read. Ansible then reads these files, connects to the target systems and executes the tasks one by one, in the order you wrote them. The main advantage here is that you don’t have to run the same commands repeatedly on every system.
Imagine that you have to install the same app on ten different servers, normally, you would connect to each one of them and run the commands. With Ansible and Playbooks, you just write the steps in one file, and Ansible does the hard work, saving you a lot of time, effort, and ensuring consistency across your systems.
Why Use Ansible Playbooks?
So, why would you choose Ansible Playbooks over just running manual commands? Well, there are several good reasons:
- Automation: Playbooks automate repetitive tasks. Instead of running the same commands on many servers, you can write them once in a playbook and run it on all of them.
- Consistency: Playbooks ensure that your systems are configured the same way every time. This reduces errors and inconsistencies.
- Version Control: Because playbooks are just files, you can keep them under version control (like Git). You can track changes, revert to previous configurations, and collaborate with your team.
- Idempotency: Playbooks are designed to be idempotent. This means you can run the same playbook multiple times, and the system will always end up in the same state. If a task has already completed, it won’t be run again unless there is a change.
- Orchestration: Playbooks can orchestrate complex workflows. You can define the order in which tasks should run, and you can use conditional logic.
Anatomy of an Ansible Playbook
Let’s break down the main parts of an Ansible Playbook. A typical playbook has the following structure:
- Plays: At the top level, a Playbook consists of one or more plays. Each play targets a specific set of hosts and executes a set of tasks.
- Hosts: Within a play, you specify the hosts on which the tasks should be executed. These are the target servers or devices you are managing.
- Tasks: Tasks are the actions that you want Ansible to perform. This can be things like installing a package, starting a service, or copying a file.
- Modules: Modules are the building blocks of tasks. They provide the logic for how Ansible should perform an action, and they do the work under the hood. For example, the
apt
module is used to manage packages on Debian-based systems. - Variables: Variables are used to store values that you want to reuse in your playbook. This helps keep your playbooks flexible and adaptable.
- Handlers: Handlers are special tasks that are executed only when a task notifies them. They are often used to restart services after making changes.
Getting Started: Writing Your First Playbook
Let’s jump in and create a basic playbook. We will write a playbook that installs the nginx
web server on a target machine. Here’s how you can structure this:
Step 1: Create a Playbook File
First, create a new file called install_nginx.yml
. The .yml
extension is for YAML files. Open the file in your preferred text editor and write the content.
Step 2: Add the Play
Inside the file, start with the plays, this tells Ansible what to do, and where to do it.
---
- name: Install and configure Nginx web server
hosts: webservers
become: true
---
: The three hyphens mark the start of a YAML document.name
: A description of the play, this is just for readability purposeshosts
: This specifies the group of hosts you are targeting, in this case,webservers
. You would have to set up thewebservers
group in your Ansible inventory file, which contains a list of your target hosts.become: true
: This is an indication to Ansible to use thesudo
command when executing the tasks that require root privileges.
Step 3: Add Tasks
Next, you must define the tasks that Ansible must execute on the target host.
tasks:
- name: Install Nginx package
apt:
name: nginx
state: present
- name: Start Nginx service
service:
name: nginx
state: started
tasks
: This section contains a list of tasks that you want to perform.name
: This specifies a description of what the task does.apt
: This uses the apt module to manage Debian-based system packages.name
: The name of the package that you want to install.state
: If set topresent
, it makes sure that the package is installed.
service
: This uses the service module to manage services.name
: The name of the service you want to manage.state
: If set tostarted
, it makes sure the service is running.
Step 4: The Complete Playbook
Here is the complete playbook, it includes the handler
to make sure the configuration of Nginx is updated.
---
- name: Install and configure Nginx web server
hosts: webservers
become: true
tasks:
- name: Install Nginx package
apt:
name: nginx
state: present
- name: Start Nginx service
service:
name: nginx
state: started
- name: Copy Nginx configuration file
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify:
- Restart Nginx
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
copy
: This module is used to copy files from the Ansible controller to the target host.src
: The path to the file that needs to be copied on the Ansible controller.dest
: The path where to copy the file on the target host.notify
: This notifies the handler to run when a change has been made in the task.handlers
: This section includes a list of actions that should be run if there is a change.name
: The name given to the handler, this must match the name in thenotify
sectionservice
: This is used to restart the Nginx service.
Step 5: Run the Playbook
To run the playbook, you can use the following command. Make sure that your nginx.conf
file is in the same directory as the install_nginx.yml
file, and that your inventory file has the webservers
group set up with the target host.
ansible-playbook install_nginx.yml
Ansible will then connect to the target servers and execute the tasks from the playbook. If everything goes as it should, Nginx will be installed and running.
Diving Deeper: Working with Modules
Ansible modules are the workhorses of your playbooks. They are reusable, standalone scripts that perform specific tasks. Ansible has a wide variety of built-in modules that cover a lot of the most common tasks, and there are even community-contributed modules, here are a few common modules that you might find useful:
apt
: Manages packages on Debian-based systems.yum
: Manages packages on Red Hat-based systems.service
: Manages services.copy
: Copies files from the control machine to remote hosts.template
: Copies template files to remote hosts.file
: Manages files and directories.user
: Manages user accounts.command
: Runs arbitrary commands.shell
: Runs shell commands.
Using the Command and Shell Modules
The command
module is used to execute commands on remote hosts. It’s simple but it doesn’t understand shell features like pipes (|
), or redirects (>
). The shell
module, on the other hand, runs commands using the shell environment. It has access to all the common features of the shell, which makes it more flexible but also potentially more dangerous.
Use the command
module if you need to execute a basic command. Use the shell
module if you need access to shell features. However, you need to be careful to always use the shell
module with proper care, and not to use it for simple commands that can be done with the command
module.
Using the Template Module
The template
module is useful when you have configuration files that need to change based on the environment. It uses the Jinja2 templating engine, which allows you to use variables, loops, and conditionals in your template files.
With this module, you can define template files that use placeholders for values. When you run the playbook, Ansible will replace these placeholders with the actual values. This is useful when you need to configure servers differently based on their role or environment, reducing the need for individual configuration files for each type of server.
Mastering Variables
Variables are a key part of Ansible, they allow you to parameterize your playbooks and make them more flexible. There are several ways to use variables in Ansible.
Defining Variables in Playbooks
You can define variables directly within your playbook. This is useful when you only need to use the variable within the scope of the playbook itself.
---
- name: Example using variables
hosts: all
vars:
package_name: nginx
tasks:
- name: Install package
apt:
name: "{{ package_name }}"
state: present
vars
: This section is where you define the variables.package_name
: This variable will hold the name of the package that you want to install"{{ package_name }}"
: This is how you reference the variable within the playbook.
Defining Variables in Inventory Files
You can also define variables in your inventory file. This is useful when you need to define variables that are specific to certain hosts or groups of hosts.
[webservers]
web1 ansible_host=192.168.1.101 server_type=web
web2 ansible_host=192.168.1.102 server_type=web
[database]
db1 ansible_host=192.168.1.103 server_type=database
Here, we are setting server_type
to web
for the webservers
group, and database
for the database
group.
Then, you can use these variables in your playbooks:
---
- name: Example using host variables
hosts: all
tasks:
- name: Print server type
debug:
msg: "This is a {{ server_type }} server"
The debug
module is used to print a message in the console, useful when troubleshooting, and using variables within messages.
Using Variable Files
Variable files let you keep your variable definitions separate from your playbooks and make them more organized. You can use the -vars_files
flag when you run the playbook to include these variable files. You can specify the type of file, and load multiple files with the same flag.
ansible-playbook my_playbook.yml --vars_files=global.yml --vars_files=environment.yml
Here, global.yml
contains variables that are the same regardless of the environment, and environment.yml
contains variables that change based on the environment.
Error Handling in Playbooks
Things don’t always go as planned. Your playbooks must be able to handle errors gracefully, without breaking everything. Ansible provides several ways to handle errors.
Ignoring Errors
Sometimes, you might want to ignore a specific error that is not critical. You can use the ignore_errors: true
attribute on a task to continue the playbook execution even if that task fails. It’s useful when a specific task is optional.
tasks:
- name: Install optional package
apt:
name: optional_package
state: present
ignore_errors: true
Retrying Failed Tasks
You can use the retries
attribute with the delay
attribute to retry a task if it fails. It’s good for operations that might fail temporarily, like network connections, and the until
keyword, in conjunction with the register
, are often used when you need to retry a task until it gets the proper result.
tasks:
- name: Check server status
command: systemctl status my_service
register: service_status
until: service_status.rc == 0
retries: 5
delay: 5
In this example, Ansible will execute the command to check the service status and it will retry it up to five times, and it will wait five seconds between retries, until the return code of the command is 0
.
Using the Block, Rescue and Always Features
You can use the block
, rescue
, and always
features to group tasks and handle errors in a more structured way.
block
: Is used to group tasks that should be executed together.rescue
: Is used to specify tasks that should be executed if a task inside the block fails.always
: Is used to define tasks that should be executed regardless of whether the block succeeded or failed, like cleaning resources.
tasks:
- block:
- name: Install service
apt:
name: my_service
state: present
- name: Start service
service:
name: my_service
state: started
rescue:
- name: Notify service failed
debug:
msg: "Service failed to install or start"
always:
- name: Clean resources
file:
path: /tmp/cleanup.txt
state: absent
In this example, the tasks inside the block
will be executed together. If any of the tasks fail, the tasks inside the rescue
block will be executed, and the tasks in the always
block will always run.
Working with Roles
Ansible roles are a way to organize and reuse your playbooks, tasks, variables, handlers, and other components in a more organized manner. Roles enable you to break down complex configurations into smaller, more manageable parts. Roles are often organized by features, so you can reuse them in other playbooks.
Creating a Role
A role is a directory that has a specific structure. You can create the directory structure for a new role with the following command:
ansible-galaxy init my_role
This command creates a folder named my_role
with the standard role structure, that includes these directories:
tasks
: Contains the main list of tasks for the role.handlers
: Contains handlers that are used by the tasks.vars
: Contains variables that are used by the tasks.defaults
: Contains default values for the variables, that are overridden by the values in thevars
directory.files
: Contains static files that you might want to copy to remote hosts.templates
: Contains Jinja2 templates.meta
: Contains metadata about the role.
Using a Role
You can use a role in your playbook by adding it to the roles
section.
---
- name: Example using roles
hosts: all
roles:
- my_role
Ansible will then look for a role named my_role
in the roles path and execute it when you run the playbook.
Sharing Roles
You can also share your roles with other people. If you want to share roles with the public, Ansible Galaxy is the place to go. Ansible Galaxy is a place where you can find many community-made roles that are ready to use.
You can install a role from Galaxy with the following command:
ansible-galaxy install username.role_name
Ansible will install that role into your roles folder, which by default is located in the same folder that your playbook is in.
Advanced Playbook Techniques
As you become more proficient with Ansible Playbooks, there are several advanced techniques that you can use to enhance your automation workflows.
Using Loops
Loops let you run the same task multiple times with different parameters. This saves you from having to repeat the same task multiple times, which is useful when you have to operate on multiple items.
tasks:
- name: Create multiple users
user:
name: "{{ item }}"
state: present
loop:
- user1
- user2
- user3
In this example, the user
task will be executed three times, each time using a different value from the loop.
Using Conditionals
Conditionals allow you to run tasks based on certain criteria, or depending on the current status of the target machine. This allows you to implement complex workflows in your playbooks. You can add conditionals to a task with the when
attribute.
tasks:
- name: Install package on Debian-based systems
apt:
name: my_package
state: present
when: ansible_os_family == "Debian"
- name: Install package on Red Hat-based systems
yum:
name: my_package
state: present
when: ansible_os_family == "RedHat"
Here, the apt
task will be executed only if the OS is Debian
, and the yum
task will be executed only if the OS is RedHat
. You can access all sorts of variables of the target system using Ansible facts, to create conditionals based on any property of the target machine.
Using Includes
Includes are a way to separate your playbook into smaller, more manageable files. This makes your playbooks easier to read and understand. You can include other playbooks or task files in your current playbook.
tasks:
- include: tasks/install.yml
- include: tasks/configure.yml
Here, Ansible will run the tasks from the install.yml
file first and then run the tasks from the configure.yml
file. You can also use include_tasks
and include_role
to include tasks and roles respectively.
Best Practices for Playbook Design
As you write more and more playbooks, some best practices will help you keep everything organized and efficient:
- Keep It Simple: Start small, and don’t overcomplicate your playbooks.
- Use Comments: Add comments to your playbooks to explain what the different parts do.
- Keep It Consistent: Adopt a consistent naming convention for your playbooks, roles, and variables.
- Use Version Control: Always keep your playbooks under version control.
- Test Thoroughly: Always test your playbooks in a test environment before running them in production.
- Use Roles: Use roles to organize your playbooks and make them more reusable.
- Parameterize: Use variables to parameterize your playbooks.
- Idempotency: Make sure that your tasks are idempotent.
- Error Handling: Plan and properly implement error handling in your playbooks.
Troubleshooting Common Issues
You may encounter some issues when working with Ansible Playbooks. Here are some common problems and how to solve them:
- Connection Issues: Check if the target machine is reachable from the Ansible control machine, and that the SSH connection is configured correctly.
- Syntax Errors: Use a YAML linter to check for syntax errors in your playbooks.
- Task Failures: Check the error messages in the Ansible output to find out why a task failed.
- Idempotency Issues: Ensure that your tasks are idempotent, meaning that they will only make changes if necessary.
- Permissions Issues: Make sure that the user that Ansible is using has the required permissions to perform tasks on the target host.
- Module Errors: Check the Ansible module documentation for the specific error.
The Value of Ansible Playbooks
Ansible Playbooks are a key technology when it comes to automating infrastructure, they provide a simple, powerful, and reliable way to manage your systems. Playbooks are written in an easy-to-read format, and have a wide range of features that let you implement complex automation workflows. With Ansible Playbooks, you can ensure consistency across all of your systems, and save a lot of time by automating repetitive tasks. If you are working with infrastructure, Ansible playbooks are an important tool to learn and master, they will greatly improve the way you manage your environments.