In this Ansible cheatsheet I have a page with all the playbooks, plus below I detail these files and the various options. Its best you use the Ansible documentation to see if any modules have been updated or any new modules have been created, below is just some of the highlights of what Ansible can do. You can also use the Ansible Galaxy website to see what others have created in regards to roles.
Ansible is an open-source software provisioning, configuration management, and application-deployment tool, it runs on many Unix-like systems, and can be configured for both Unix (Linux, Solaris, HPUX, etc) and Microsoft Windows. The diagram below shows the Ansible architecture

| Ansible Server | a system where Ansible is installed and configured to connect and execute commands on nodes |
| Node (Hosts) | a server that is controlled by Ansible |
| Inventory File | a file that contains information about the servers and groups Ansible controls, typically located at /etc/ansible/hosts |
| Play | a full Ansible run. A play can have several playbooks and roles, included from a single playbook that acts as entry point |
| Playbook | a file containing a series of tasks to be executed on a remote server. Playbooks exist to run tasks. |
| Tasks | combine an action (a module and its arguments) with a name and optionally some other keywords (like looping directives) |
| Action | An action is a part of a task that specifies which of the modules to run and which arguments to pass to that module |
| Role | a collection of playbooks and other files that are relevant to a goal such as installing a web server |
| Facts | are simply things that are discovered about remote nodes. While they can be used in playbooks and templates just like variables, facts are things that are inferred, rather than set. Facts are automatically discovered by Ansible when running plays by executing the internal setup module on the remote nodes |
| Modules | are the units of work that Ansible ships out to remote machines |
| Plugins | are a piece of code that expends the core functionality of Ansible. There are many useful plugins, and you also can write your own. |
| CMDB | is a type of repository which acts as a data warehouse for the IT installations |
| Cloud | is a network of remote servers on which you can store, manage, and process the data |
Ansible is agentless, temporarily connecting remotely via SSH or Windows Remote Management (allowing remote PowerShell execution) to do its tasks.

I will leave you to the web and other youtube videos on how to install the latest version of ansible.
Below is the instructions on how to setup a private/public key pair that can be used to allow the Ansible server to directly SSH into the the client. You should setup the root user to allow SSH to any client without a password. Start by setting up the clients first
| Client (Node) | ssh-keygen -t rsa # you can call it something and specify the directory, you can also use a passphase |
| Ansible Server | # copy the above clients public key to the Ansible server (you can use scp) cd ~/.ssh echo <client public key> >> authorized_keys # >> means append ssh <client> # now you should be able to login to the client without a password Notes ------------------------------------------------------------------------------------------------------ .ssh directory should have permissions of 700 and authorized_keys file should have permissions of 600 Its also a good ides to update the /etc/hosts file with the clients IP address |
The Inventory will list the ansible hosts plus can have some additional information (connection details). A file (ini or yaml) is used for the inventory, the default location /etc/ansible/hosts
| Inventory file (INI) | control.example.com ansible_connection=local # don't use SSH as control is same host [webservers] foo.example.com bar.example.com [dbservers] one.example.com two.example.com three.example.com |
| Inventory file (YAML) | all:
hosts:
control.example.com:
children:
webservers:
hosts:
foo.example.com:
bar.example.com:
dbservers:
hosts:
one.example.com:
two.example.com:
three.example.com:
Default groups |
Tasks are nothing more than a call to an ansible module. Tasks are made up of two parts the module and any arguments to that module. When you run a task, the Ansible server SSH into the client and then using the Python module runs the command you have requested (examples are ping, command, etc), all tasks have a return status. There are many modules, below is a list grouped by category, see Ansible docs for latest list

Plays are simply a set of hosts and tasks that need to be executed against those hosts, A Playbook is made up of plays. When running a playbook the display is in four parts

| Playbook example (basic (YAML)) | ---
- hosts: all
tasks:
- name: get server hostname
command: hostname |
There are many playbooks you can create to perform some of the below, generally you can go to the Ansible module page and then select the module that you want, it will give you details on how to use the module, options that are available, etc. You can then use that module inside your playbooks.
I will demostrate some of the modules below, first starting with installing packages.
| Packages (simple) | loadbalancer.yml
---------------------------------------------------------
---
- hosts: loadbalancer
become: true # use sudo command (you may or may not need)
tasks:
- name: install nginx
apt: name=nginx state=present update_cache=yes
## yum: name=nginx state=present # there is more to this but using yum instead
database.yml
---------------------------------------------------------
---
- hosts: database
become: true # use sudo command (you may or may not need)
tasks:
- name: install mysql-server
apt: name=mysql-server state=present update_cache=yes
## yum: name=mysql-server state=present # there is more to this but using yum instead |
| Packages (multiple) | ---
- hosts: webserver
become: true
tasks:
- name: install web components
apt: name={{item}} state=present update_cache=yes # {{item}} use the with_items list (using jinja syntax)
with_items:
- apache2
- libapache2-mod-wsgi
- python-pip
- python-virtualenv |
Next we look at services
| Services (start/enabled) | loadbalancer.yml
---------------------------
---
- hosts: loadbalancer
become: true
tasks:
- name: install nginx
apt: name=nginx state=present update_cache=yes
- name: ensure nginx started
service: name=nginx state=started enabled=yes # service module, enable means startup mode
database.yml
--------------------------
---
- hosts: database
become: true
tasks:
- name: install mysql-server
apt: name=mysql-server state=present update_cache=yes
- name: ensure mysql started
service: name=mysql state=started enabled=yes # service module, enable means startup mode |
| Service (stop/restart/start) | ---
# Bring stack down
- hosts: loadbalancer
become: true
tasks:
- service: name=nginx state=stopped
- hosts: webserver
become: true
tasks:
- service: name=apache2 state=stopped
# Restart mysql
- hosts: database
become: true
tasks:
- service: name=mysql state=restarted
# Bring stack up
- hosts: webserver
become: true
tasks:
- service: name=apache2 state=started
- hosts: loadbalancer
become: true
tasks:
- service: name=nginx state=started
|
| Service (handlers) | ---
- hosts: webserver
become: true
tasks:
- name: install web components
apt: name={{item}} state=present update_cache=yes
with_items:
- apache2
- libapache2-mod-wsgi
- python-pip
- python-virtualenv
- name: ensure apache2 started
service: name=apache2 state=started enabled=yes
- name: ensure mod_wsgi enabled
apache2_module: state=present name=wsgi
notify: restart apache2 # we notify the handler below (use handler name)
handlers: # handler won't do anything unless you notify it
- name: restart apache2
service: name=apache2 state=restarted |
Now lets take a look at files
| Files (copy) | # place this into the webserver playbook
- name: copy demo app source
copy: src=demo/app/ dest=/var/www/demo mode=0755 # destination directory will be created
notify: restart apache2 # we notify the handler to restart apache
- name: copy apache virtual host config
copy: src=demo/demo.conf dest=/etc/apache2/sites-available mode=0755
notify: restart apache2 # we notify the handler to restart apache |
| Files (using pip) | # place this into the webserver playbook
- name: setup python virtualenv
pip: requirements=/var/www/demo/requirements.txt virtualenv=/var/www/demo/.venv
notify: restart apache2 |
| Files (symlinks) | # place this into the webserver playbook
- name: de-activate default apache site
file: path=/etc/apache2/sites-enabled/000-default.conf state=absent # remove symlink
notify: restart apache2
- name: activate demo apache site
file: src=/etc/apache2/sites-available/demo.conf dest=/etc/apache2/sites-enabled/demo.conf state=link # create symlink
notify: restart apache2 |
| Files (template) | # place this into the loadbalancer playbook
- name: configure nginx site
template: src=templates/nginx.conf.j2 dest=/etc/nginx/sites-available/demo mode=0644
notify: restart nginx
nginx.conf.j2 (template file (jinja syntax))
--------------------------------------------------------------
upstream demo {
{% for server in groups.webserver %} # get all the hosts in the webserver group
server {{ server }};
{% endfor %}
}
server {
listen 80;
location / {
proxy_pass http://demo;
}
} |
| Files (inline) | # place this into the database playbook
- name: ensure mysql listening on all ports
lineinfile: dest=/etc/mysql/my.cnf regexp=^bind-address line="bind-address = 0.0.0.0" # subsitute a line in the my.cnf file
notify: restart mysql |
There are many modules for databases (mysql, postgres, mongodb, etc) which you can use to setup databases, user, etc. I demostrate the MySQL one below
| MySQL Module | database.yml file
----------------------------------------------------
---
- hosts: database
become: true
tasks:
- name: install tools
apt: name={{item}} state=present update_cache=yes
with_items:
- python-mysqldb # we need the python-mysqldb package
- name: install mysql-server
apt: name=mysql-server state=present update_cache=yes
- name: ensure mysql started
service: name=mysql state=started enabled=yes
- name: ensure mysql listening on all ports
lineinfile: dest=/etc/mysql/my.cnf regexp=^bind-address line="bind-address = 0.0.0.0"
notify: restart mysql
- name: create demo database
mysql_db: name=demo state=present # create a database called demo
- name: create demo user
mysql_user: name=demo password=demo priv=demo.*:ALL host='%' state=present # create a user called demo and grant permissions
handlers:
- name: restart mysql
service: name=mysql state=restarted |
The shell module is useful to retrieve information from the system for example get a directopry listing
| Shell Module | - name: get active sites
shell: ls -1 /etc/nginx/sites-enabled
register: active # save return output into variable
- name: de-activate sites
file: path=/etc/nginx/sites-enabled/{{ item }} state=absent # use below with_items array
with_items: active.stdout_lines # use above variable that was saved
when: item not in sites
notify: restart nginx |
A good idea is to create a playbook to check the status of the environment, this also highlights some of the other features that are available with Ansible. You can of course could use a monitoring tool as well.
| Status playbook | ---
- hosts: loadbalancer
become: true
tasks:
- name: verify nginx service
command: service nginx status # standard command execution to check nginx
- name: verify nginx is listening on 80
wait_for: host={{ ansible_eth0.ip4.address }} port=80 timeout=1 # test connection, wait for 1 second to get response (I will cover facts later)
- hosts: control # run from the control host (Ansible server)
tasks:
- name: verify end-to-end index response
uri: url=http://{{item}} return_content=yes # make sure we get a 200 status and also return the contents of the web page
with_items: groups.loadbalancer # run the above against the loadbalance group
register: lb_index # save the output so that we can use later (array)
- fail: msg="index failed to return content" # this will check the above output, using the fail module
when: "'Hello, from sunny' not in item.content" # check that this is in the web page
with_items: "{{lb_index.results}}" # we use the saved register from above task (lb_index)
- name: verify end-to-end db response
uri: url=http://{{item}}/db return_content=yes
with_items: groups.loadbalancer
register: lb_db
- fail: msg="db failed to return content"
when: "'Database Connected from' not in item.content"
with_items: "{{lb_db.results}}"
|
Roles are ways of automatically loading certain vars_files, tasks, and handlers based on a known file structure, allows for better scaling. Grouping content by roles also allows easy sharing of roles with other users. You can use a tool called Ansible Galaxy to scaffold the directory structure
| Galaxy (scaffolding) | mkdir /ansible/roles cd /ansible/roles ansible-galaxy init <directory name> # use something meaningful for the directory name ansible-galaxy init control # used for the Ansible server (for example) ansible-galaxy init nginx # used for nginx configuration (for example) ansible-galaxy init mysql # used for mysqld configuration (for example) |
The directory structure will look something like below, you can see that I have created a directory structure for each part of the project (control, nginx, mysql, app, etc), you can create what ever structure you like based on what you will be using Ansible for.

So now we can use the directory structure and roles for the playbooks, for example lets take the control, below i show the tasks but you will also need to change the handlers, templates, etc.
| /ansible/control/tasks/main.yaml | # some of the boiler code can be removed
---
- name: install tools
apt: name={{item}} state=present update_cache=yes
with_items:
- curl
- python-httplib2 |
| /ansible/control.yml | ---
- hosts: control
become: true
roles: # point to the control role
- control |
When you run the playbook you will now see the role name and the task

You can create a playbook that runs other playbooks, generally this is called site.yml
| site.yml | --- - include: control.yml # include a playbook to run - include: database.yml - include: webserver.yml - include: loadbalancer.yml |
Variables can be setup in Ansible that can be used with playbooks, Ansible provides dynamic variables called facts that can be used inside playbooks.
| Facts (IP address) | # we can use the ansible_eth0.ipv4.address fact to get the IP address
- name: ensure mysql listening on all ports
lineinfile: dest=/etc/mysql/my.cnf regexp=^bind-address line="bind-address = {{ ansible_eth0.ipv4.address }}"
notify: restart mysql |
You can use the specific defaults/main.yml (for each project playbook) file to create custom variables that can be used in other files
| defaults.yml | ---
db_name: myapp
db_user_name: dbuser
db_user_pass: dbpass
db_user_host: localhost
to use the variables
-----------------------------------------------
- name: create database
mysql_db: name={{ db_name }} state=present
- name: create user
mysql_user: name={{ db_user_name }} password={{ db_user_pass }} priv={{ db_name }}.*:ALL
host='{{ db_user_host }}' state=present |
you can also use the vars/main.yml
| vars/main.yml | --- some_var1: var1 some_var2: var2 |
You can loop through the elements of a hash using with_dict
| with_dict example | ---
sites: # top level
myapp: # key
frontend: 80 # value
backend: 80 # value
using with_dict in playbooks
--------------------------------
- name: configure nginx sites
template: src=nginx.conf.j2 dest=/etc/nginx/sites-available/{{ item.key }} mode=0644
with_dict: sites
notify: restart nginx
- name: activate nginx sites
file: src=/etc/nginx/sites-available/{{ item.key }} dest=/etc/nginx/sites-enabled/{{ item.key }} state=link
with_dict: sites
notify: restart nginx
using with_dict in templates
-------------------------------
upstream {{ item.key }} {
{% for server in groups.webserver %}
server {{ server }}:{{ item.value.backend }};
{% endfor %}
}
server {
listen {{ item.value.frontend }};
location / {
proxy_pass http://{{ item.key }};
}
} |
You can create a directory at the top level called /ansible/group_vars and then create a file called all, global variables can then be added to this file that can be used across all roles. You can create a file for each group if you wish. You can also create a file called /ansible/all/vars which would do the same thing.
| group varibles | # nothing new here file: group_vars/all --- db_name: demo db_user: demo db_pass: demo |
You may need to encrypt some variables and we can use Ansible Vault to do this, its better to create the vault file where the global variable file resides. You need to create a vault file see the commands section below
| Vault | # nothing new here
---
vault_db_pass: demo
use the vault file
----------------------------------
---
db_name: demo
db_user: demo
db_pass: "{{ vault_db_pass }}" |
Ansible has variable precedence as can be seen below which is taken from the Ansible documentation, the top of the list has the lowest priority and the bottom of the list has the highest priority. You can include variables inside the playbooks (site level, playbook top level, etc) but try to keep things simple.

This section covers the remaining bits and pieces of Ansible, performance improvements, optimizations and tidy ups.
| turn off fact gathering | gather_facts: false Note: might be a good idea to add when creating files and remove when you need to gather facts. |
| cache vaild time | tasks: -name: update apt cache apt: update_cache=yes cache_vaild_time=86400 # 24 hour cache valid time |
| limit option | ansible-playbook site.yml --limit <hostname> # will limit to just that host |
| tags | - name: install tools
apt: name={{item}} state=present update_cache=yes
with_items:
- curl
- python-httplib2
tags: [ 'package' ] # this task is now tagged with the package tag
ansible-playbook site.yml --list-tags # list all the tags that are available in the site.yml (and included)
ansible-playbook site.yml --tags "package" # run the playbook with the tag/s specified
ansible-playbook site.yml --skip-tags "package" # run the playbook but skip the specified tags |
| changed_when | tasks:
- name: verify nginx service
command: service nginx status
changed_when: false # we know the outcome so we set to false
change_when: "active.stdout_lines != site.keys()" # you can do complex code but result must be a boolean |
| ignore_errors | tasks:
- name: verify nginx service
command: service nginx status
changed_when: false # we know the outcome so we set to false
ignore_errors: true # ignore any errors at this stage and move on to the next step |
| debug option | - debug: var=active.stdout_lines # var will be printed to the console when playbook is run - debug: var=vars # print out all variables |
Some but not all of the commonly used Ansible commands:
| Directories and files | /etc/ansible/hosts # default inventory file /etc/ansible/ansible.cfg # ansible configuration file <own directory>/ansible.cfg # create own directory for all ansible file Configuration file options (ansible.cfg) ------------------------------------------- inventory = <some directory> # change the default directory of inventory file |
| Inventory (hosts, groups) | ansible --list-hosts all # list all hosts ansible -i <inventory file> --list-hosts all # list all hosts using specific inventory file ansible --list-hosts "*" # list all hosts using wildcard ansible --list-hosts <group name> # list all hosts in group ansible --list-hosts <hostname> # list all hosts with specific hostname ansible --list-hosts <string*> # list all hosts using search string and wildcard ansible --list-hosts <control[:|,]database> # list all hosts using multiple groups , : = old way and , = new way ansible --list-hosts <webserver[0]> # list all hosts using group and indexing ansible --list-hosts <\!webserver> # list hosts except anything in webserver group Options to inventory file ------------------------------------------------- ansible_connection=local # use local conneection as control is as as running host vault_password_file = <location> # set the vault password file location (text file that is locked down) Note: you can create a ansible.cfg in you own created directory and if you run commands it will use this cfg file. |
| Tasks (modules) | # useful tasks to check host connectivity ansible -m ping all # use ping module ansible -m command -a "hostname" all # run the hostname command on all hosts |
| Playbooks (plays) | ansible-playbook <playbook file> # run a playbook ansible-playbook <site.yml file> # run a site file that includes other playbooks ansible-playbook site.yml --limit <hostname> # will limit to just that host ansible-playbook site.yml --limit @<failed file name> # run playbook only on the failed hosts in the specified fail host file ansible-playbook --list-tasks # lists the tasks in the playbook ansible-playbook --start-at-task <task name> # run the playbook starting at task specified ansible-playbook site.yml --list-tags # list all the tags that are available in the site.yml (and included) ansible-playbook site.yml --tags "package" # run the playbook with the tag/s specified ansible-playbook site.yml --skip-tags "package" # run the playbook but skip the specified tags ansible-playbook site.yml --limit ? --tags ? --start-at-task ? # you can mix and match limit, start-at-task and tags ansible-playbook site.yml --step # ansible will prompt/ask each step in the playbook ansible-playbook --syntax-check <yaml file> # check the syntax of the yaml file ansible-playbook --check <yaml file> # perform a dry run but don't actually do anything (report only) ansible-playbook --ask-vault-pass <playbook> # allows you to enter vault password ansible-playbook --vault-password-file <location> # specify the file that has the vault password, text file that is locked down |
| Facts | ansible -m setup <hostname> # list facts for specific hostname in inventory |
| Vault | ansible-vault create <file name> # generally called vault, enter password to encrypt file ansible-vault edit <file name> # view and edit vault file |
| Performance | time ansible-playbook <playbook> # record how long a playbook takes to run (benchmark) |