Ansible¶
Ansible is used extensively for automation.
It has the ability to run tasks on a list of remote hosts.
Ansible uses Playbooks, written in yaml
, to define a list of tasks to run on a list of hosts.
Inside the Playbooks, you use modules to define how Ansible will complete tasks.
Also see:
Other Ansible Tools¶
- AWX: A web UI and orchestration platform for Ansible with an API.
- AAP: Ansible automation platform
- Ansible Tower: A GUI framework accessible from a browser or a REST API
Table of Contents¶
- Ansible Playbooks
- Tags in Tasks
- Getting Help with Ansible Modules
- Ad-hoc Commands using Ansible
- Handlers
- Ansible Roles
- Loops in Ansible
- Updating in Batches (Serial)
- Defining an Inventory using a Hosts File
- Save the Output of a Task in Ansible
- Blocks in Ansible
- Ansible Configuration Files
- Resources
- Questions
Ansible Playbooks¶
Ansible playbooks are yaml
files that can do several things:
- Declare configurations for systems
-
Automate and organize the steps of a manual process, running them in a specific
order on one or more machines. -
Run tasks either synchronously or asynchronously.
Ansible playbooks are read and executed top to bottom, procedurally.
The yaml
in Ansible should use 2-space indentation throughout the whole file.
That's the most common and is considered a best practice.
Run an Ansible Playbook¶
-i
: Specifies the inventory file.
Debugging an Ansible Playbook¶
Run playbooks with -v
for 'verbose' output.
You can use up to 3 levels of verbosity (-vvv
).
Items in Playbooks¶
When a line starts with a -
(hyphen), it represents an item in a list.
- name: Install and Configure nginx
hosts: webserver
become: yes
tasks:
- name: install nginx
apt:
name: nginx
state: present
- name: start nginx
service:
name: nginx
state: started
- The first hyphen starts a
play
in the playbook.- A
play
is a set of instructions applied to specified hosts.
- A
- The subsequent hyphens start tasks within that
play
.- Each
task
defines a specific action to perform.
- Each
Playbook-level keys:
- name: Install and configure NGINX
- Provides a descriptive name for the
play
(or playbook).
- Provides a descriptive name for the
hosts: webserver
: Specifies the target machine or group of machines where the play will run.webserver
: Refers to a group defined in theinventory.yml
file.
become: yes
: Indicates that the tasks in the play should run with elevated privileges.- Some consider it better form to use
true
orfalse
instead ofyes
orno
.
- Some consider it better form to use
tasks:
: Defines the set of actions to perform on the target hosts.- Tasks are the core of a playbook.
Tasks¶
Tasks are defined by a name, then a module.
- name: Example play
hosts: some-inventory-group
become: yes # Become root user to perform tasks
tasks:
- name: Example Task
copy: # The 'copy' Ansible module
dest: ~/random_file.txt
content: "This is what will be written to the file"
mode: 0755 # Setting file permissions
Module actions in tasks can be one-liners as well.
Task keywords¶
Most commonly used task keywords:
name
: A descriptive name for the task.vars
: Define variables that are only available to the task.become
: Execute tasks with elevated privileges (root)-
args
: Provide parameters to the module in a dictionary format.
-
environment
: Set environment variables for the task. ignore_errors
: Continue execution even if the task fails.loop
: Interate over a list or sequence of items (see loops).with_items
: Deprecated version ofloop
.
-
loop_control
: Customize the behavior of theloop
(see loops).- E.g., Setting variables for indices or limiting output.
- E.g., Setting variables for indices or limiting output.
-
notify
: Trigger a handler task after the task completes successfully. -
register
: Save the output of the task to a variable. -
until
: Retry the task until a condition is met. Used withretries
anddelay
. delay
: Set a delay before retying the task. Used withretries
-
retries
: Number of retries for a task if it fails. Used withuntil
. -
when
: Specify a condition that must be met for a condition to run. become_user
: Execute the task as a different user (default isroot
).-
timeout
: Set a timeout for tasks (in seconds). -
debugger
: Enable thetask debugger
for the specific task. -
delegate_to
: Execute the task on a different host than the one in the inventory. -
tags
: Assign tags to the task for selective execution. (see tags) -
action
: Explicitly defines the module and its parametes (rarely needed, modules are usually defined dicrectly)
Playbook Keywords¶
See Ansible docs on playbook keywords.¶
Modules¶
Modules are essentially "tools" that perform individual tasks.
Each module has its own purpose and set of arguments.
Ansible provides many modules out of the box to handle administrative and
configuragion management tasks.
ansible-doc -l # List all modules available
ansible-doc module-name # View docs for a specific module
Ansible recommends using the FQDN of modules inside playbooks (fully qualified domain name).
E.g.:
# Short name
- name: Example task
copy:
src: /path/to/local/file
dest: /path/to/remote/file
mode: '0755'
# Long name (FQDN)
- name: Example task
ansible.builtin.copy:
src: /path/to/local/file
dest: /path/to/remote/file
mode: '0755'
Common Modules¶
These are some common ansible.builtin
modules.
apt
: Managing packages on Debian-based systems.setup
: Gathering facts about remote hosts.stat
: Getting information about files and directories.copy
: Copying files to remote hosts.file
: Managing files and directories.command
: Executing commands on the remote host.cron
: Managing cron jobs on the remote host.find
: Finding files on the remote host.fetch
: Fetching files from the remote host.- Works like
copy
, except in reverse.
- Works like
groups
: Managing groups on the remote host.ping
: Pinging remote hosts and verifying a usable Python.shell
: Executing shell commands on the remote host.- Ansible recommends using
ansible.builtin.command
for one-liners. - Use this module when you actually need a shell (e.g., for redirections, pipes, etc)
- Ansible recommends using
script
: Running local scripts on a remote host.reboot
: Rebooting remote hosts.lineinfile
: Managing text in files (linewise).- Good for single line changes.
- For multiple line changes, check
ansible.builtin.replace
. - For modifying blocks of lines
ansible.builtin.blockinfile
.
General Structure of a Task using a Module¶
Each module has its own set ofarguments
.Refer to the module docs for the list of arguments a module will accept.
Tags in Tasks¶
Tags allow you to label tasks so you can control which tasks are executed during a play.
Using tags allows you to filter and skip tasks based on tags:
ansible-playbook
:ansible-playbook my_playbook --tags "setup" # only run tasks with the `setup` tag.
ansible-playbook my_playbook --skip-tags "setup" # skip all tasks with the `setup` tag.
Getting Help with Ansible Modules¶
ansible-doc
is the help command used to view documentation on Ansible modules.
options
you can use for a module in a playbook.It will also show you the types that the options should have.
ansible-doc apt # Show options for the `apt` Ansible module
ansible-doc -l # List all modules available
To see a snippet of how to use a module in a playbook, use -s
/--snippet
.
ansible-doc -s module-name
ansible-doc -s apt # Show snippet for the `apt` Ansible module
ansible-doc -s stat # Show snippet for the `stat` Ansible module
ansible-doc -s setup # Show snippet for the `setup` Ansible module
Ad-hoc Commands using Ansible¶
Ad-hoc commands are used to run tasks on a remote host without creating a playbook.
-m
: The module to use.-a
: Arguments to pass to the module.
ansible servers -i /hosts/file -m setup -a 'filter=ansible_distribution'
ansible servers -i /hosts/file -m setup -a 'filter=ansible_date_time'
Create a Directory on Remote Hosts¶
To create a new directory on a list of hosts:
Copy File from Local Machine to Remote Hosts¶
To copy files into that new directory:
Copy file from Remote Host to Local Machine¶
Use the fetch
command to bring down files from remote hosts to the local machine.
Modifying a Line in a File on Remote Hosts¶
To modify a line in a file:
ansible servers -i /inventory/file -m lineinfile -a "path=/home/user/new_dir/file.txt regexp='^var1' line='var1=1000"
Then you specify the new line you want to replace it with.
Handlers¶
Handlers are defined at the play level under handlers
.
A handler
is just like a task but only runs when notified (using notify
in a task).
- name: Configure webservers
hosts: webservers
tasks:
- name: Copy nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Handlers are executed at the end of the play.
If serial
is used, handlers are executed after all tasks in the current batch.
Ansible Roles¶
See roles in Ansible.
Loops in Ansible¶
Also see playbook loops in ansible docs.¶
There are several ways to iterate over a list of items using loop
.
When loop
is being used, each item is stored in the item
variable.
You can use a conditional to loop through a list of items.
tasks:
- name: Loop through a list of items with a conditional
debug:
msg: "The current item is {{ item }}. "
loop:
- nginx
- apache2
- python3
- "some other item"
when: item != 'apache2'
- name: Loop over a range of numbers
debug: msg="Number is {{ item }}"
loop: "{{ range(0, 10) }}"
Controlling Loops¶
Also see Loop control in the Ansible docs¶
You can use loop_control
to change the behavior of a loop.
loop_control
Examples¶
You can use the label
keyword under loop_control
to set a label for the loop.
The label is shown in the output on every iteration of the loop.
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- vim
- git
- curl
loop_control:
label: "Installing {{ item }}"
- name: Break out of a loop when a condition is met (version 2.18+)
vars:
my_var: "MATCH ME"
debug: msg="Currently checking {{ item }}"
loop:
- "This won't match"
- "Neither will this"
- "MATCH ME"
- "This won't get printed"
loop_control:
break_when: # Only version 2.18+
- item is my_var
Setting an index_var
will set a 1-based index variable, available throughout the task.
This can be used to dynamically name files or track the position in the loop.
- name: Create files
copy:
dest: /tmp/file_{{ loop_index }}
content: "This is file number {{ loop_index }}"
loop:
- one
- two
- three
loop_control:
index_var: loop_index
label: "Creating file {{ loop_index }}"
/tmp/file_1
, /tmp/file_2
, and /tmp/file_3
.
Updating in Batches (Serial)¶
In Ansible, serial
refers to the number of hosts to run tasks on simultaneously during a play.
It controls how Ansible processes a play across a group of hosts in chunks or "batches."
The serial
keyword is defined in a playbook to specify the batch size for
processing hosts.
- name: Run tasks on hosts in batches
hosts: webservers
serial: 2
tasks:
- name: Example task
debug:
msg: "Running on {{ inventory_hostname }}"
webservers
in the inventory, tasks will be executedon 2 hosts at a time.
The play will process the first 2 hosts in parallel, then the next 2 hosts, etc.
ansible_play_batch
contains the list of active hosts in the current batch being
processed.
If you're processing hosts in batches of 2, then ansible_play_batch
will contain
the first 2 hosts in the inventory, then the next 2, as they're being processed.
Defining an Inventory using a Hosts File¶
See inventory files in Ansible for more details.
You can use a hosts file to define a group of hosts.
By default, Ansible looks for /etc/ansible/hosts
to specify an inventory.
Specify an inventory file by using the -i
option when running an Ansible command.
The file can be in ini
format (most common) or yaml
format.
-
hosts.ini
:
-
hosts.yaml
:
Save the Output of a Task in Ansible¶
You can save the output of a task using the register
keyword.
df_output
is the name of the variable that will hold the output of the task.
Accessing The Registered Variable¶
The output of a task is a dictionary that holds the values:
stdout
: The output of the command (standard output).stderr
: Any errors that occurred (standard error).rc
: The return code of the command.changed
: Whether the task caused any changes to the remote host.
You can use Jinja2
dot-notation to access the values in the dictionary.
.stdout
: This is the actual output of the command.- Variables created from
register
are dictionaries themselves. - The
stdout
is a key in the dictionary that contains the standard output of the command.
- Variables created from
Accessing the Registered Variable in a Separate Play in the Same Playbook¶
If you define a variable in a playbook that has several plays, you can access your
defined variables using the hostvars
variable.
This is a dictionary that holds all the variables and facts about every host in the
inventory.
For example, if your hosts file contains a host named host1
, you can access the
variables collected on that host in tasks from other hosts by using:
hostvars['host1'].ansible_host # The IP address of host1
hostvars['host1'].ansible_user # The username used on host1
Capturing the output of Debug Messages¶
Using register
with debug
messages is a good way to save variables to use later.
- name: Get the hostname for the local host
ansible.builtin.debug:
msg: "{{ ansible_hostname }}"
register: localHostName
- name: Print the hostname that was captured
ansible.builtin.debug:
msg: {{ localHostName.msg }}
msg
variable is the output of the debug message.
This can also be done with echo
from ansible.builtin.command
, but you'd
use the stdout
key to access it instead of msg
.
Getting the IP Address of any Host in a Playbook¶
Get the ip address of a REMOTE host using the hostvars['host-name'].ansible_host
variable.
Using this on the localhost (in hosts: localhost
) will result in localhost being returned (127.0.0.1
).
Blocks in Ansible¶
Blocks are a way to logically group tasks in Ansible.
They allow you to apply common attributes (like become
and when
) to multiple
tasks at once, as well as handle errors using rescue
and always
sections for
tasks within the block.
A block is defined with the block
keyword, and tasks are listed inside it.
It can also include rescue
(for error recovery) and always
(for tasks that should
always run).
- Tasks under
rescue
only run if a task (any task) inblock
fails. - Tasks under
always
run no matter what happens inside theblock
.
Basic Block Example¶
- name: Install and configure a web server
block:
- name: Install nginx
apt:
name: nginx
state: present
- name: Start nginx
service:
name: nginx
state: started
when: ansible_os_family == 'Debian'
Debian
.The
when
condition applies to the entire block, so you don't need to repeat it for
each task.
Block Error Handling with rescue
and always
¶
- name: Install packages with error handling
block:
- name: Install apache
apt:
name: apache2
state: present
- name: Start apache
service:
name: apache2
state: started
rescue:
- name: Roll back by removing Apache
apt:
name: apache2
state: absent
- name: Notify admin of failure
debug:
msg: "Failed to install or start Apache!"
always:
- name: Ensure cleanup is done
file:
path: /tmp/install_logs
state: absent
block
attempts to install and start Apache.The
rescue
runs if any task in the block
fails.The
always
section runs no matter what happens in the block
.
Ansible Configuration Files¶
Default config file locations:
/etc/ansible/ansible.cfg
: Global config file, used if it exists.~/.ansible.cfg
: User config file. Overrides the default config if it exists../ansible.cfg
: Local config file (in current working directory).- This file is assumed to be 'project specific' and overrides the rest if present.
If the ANSIBLE_CONFIG
environment variable is set, it will override all others and
use the file specified in the variable.
- This should point to the config file you want to use.
Check the current config file:
Generate an Ansible config file with all the defaults:
View the ansible config:
Using set_fact
to Persist Variables Across Plays¶
You can have multiple plays inside a playbook.
But, when you define a variable inside of a play, it is local to that play.
It will not be available in other plays, even within the same playbook.
- name: Collect information from one system
hosts: localhost
tasks:
- name: Get the actual network IP for the current machine
ansible.builtin.shell:
cmd: 'hostname -I | awk "{ print $1 }"'
register: master_node_ip
- name: Save IP to facts
ansible.builtin.set_fact:
MASTER_NODE_IP: "{{ master_node_ip.stdout | trim }}" # Trim whitespace
- name: Different play to test fact
hosts: servers
tasks:
- name: Show the information gathered on localhost
ansible.msg.debug:
msg: "Localhost IP: {{ hostvars['localhost']['MASTER_NODE_IP'] }}"
Jinja2 Filters¶
Also see Ansible builtin filters and jinja2 builtin filters.
Filters can be used on any variable using the pipe (|
) character.
Syntax:
Common, useful filters:
trim
: Trim whitespace from the value.upper
: Converts a value to uppercase.lower
: Converts a value to lowercase.capitalize
: Converts the first character to uppercase, and the rest to lowercase.pprint
: Pretty-print the value.replace
: Perform a substitution on a value.replace("hello", "goodbye")
: Replaces all instances ofhello
withgoodbye
.replace("hello", "goodbye", 1)
: Replaces1
instance ofhello
withgoodbye
.
Prompt for Passwords¶
Password for become
¶
Use the -K
option when running a playbook to prompt for a become
password.
This is the same as --ask-become-pass
.
That way you don't have to set it anywhere on the system.
Password for SSH Connection¶
Use the -k
(lowercase) option to prompt for the password needed to connect to the
remote machine.
Patching Servers with Ansible¶
Remember: Always check if all hosts are up by doing a pre-check.
You can use a wildcard *
in the package
module for your package manager.
An example (from het_tanis) to temporarily remove excludes from yum.conf
.
- name: Back up the original yum.conf
copy:
src: /etc/yum.conf
dest: /etc/yum.conf.{{ansible_date_time.date}}
- name: Ensure that exclude is not there so patching completes
ansible.builtin.lineinfile:
path: /etc/yum.conf
regexp: '^exclude'
line: "#exclude"
- name: Patch all servers to most current version of installed software
yum:
name: "*"
state: latest
- name: Restore original yum.conf
copy:
src: /etc/yum.conf.{{ansible_date_time.date}}
dest: /etc/yum.conf
Patching servers can sometimes break things. Check these things:
- Always check that
/var/cache
has enough space. rpm
database may be broken.rpmdb --rebuilddb
Use jinja templates to generate reports.
Resources¶
- Intro to Playbooks
- Ansible builtin modules
- Ansible Cheatsheet
- Ansible Modules
- Ansible Basics Cheatsheet
- Ansible Installation Guide
- The Copy Module
- Ansible Configuration
- Playbook Variables, Hostvars and Facts
- Using Filters to Manipulate Data
- Ansible Builtin Filters
- jinja2 builtin filters
Practicing with Ansible¶
Vagrant is a tool that allows us to create virtual machines.
Proxmox is a virtualization platform that allows us to create
virtual machines and containers.
These tools are very useful for testing and practicing Ansible, as you can set up a
bunch of hosts to run playbooks on.
Questions¶
How do conditionals work in ansible?
What is an ansible collection? Ansible collections
What are 'blocks' in ansible?
What are 'handlers' in ansible? Handlers
What about Terraform?¶
Ansible and Terraform serve very different purposes.
Ansible is more automating server configuration & tasks, where Terraform is automating infrastructure.
You'd use Terraform to spin up an EC2 instance, you'd use Ansible to install nginx on that EC2 instance.
Generally speaking, TF spins up your infrastructure, Ansible configures it.