Ansible Overview
Updated: 2019-04-07
Published: 2017-08-17
Intro
Ansible is a 'radically simple', open source, IT automation platform.
Architecture
Control Node The Ansible control node is the central point of management for managed nodes. Configuration is pushed to the managed nodes from the control node.
Managed Node A managed node is a device that will be configured by an ansible control node. Ansible supports many types of operating systems including network operating systems from Juniper and linux distributions such as RedHat.
Playbook A playbook is a list of 'plays' that will be executed on the managed nodes. Plays are a list tasks that will be performed such as a yum update or copying a file.
Inventory The inventory is a list of managed nodes that playbooks will be run against. Managed nodes in the inventory can be combined into groups, for example; Sydney and Melbourne.
Install
Requirements Linux managed nodes require python 2.4 or later. If the python version is less than 2.5 python-simplejson is required.
Control Node The Control node requires python 2.4 or later. Ansible is only required to be installed on the control node. Neither ansible or any agents are required on the managed nodes.
Python Ansible can be installed via pip, python 3 support is in beta as of Ansible version 2.2. I prefer to install ansible within a virtual environment.
mkdir ~/envs
virtualenv ~/envs/ansible-env
source ~/envs/ansible-env/bin/activate
pip install ansible
Yum RPM's are available in the EPEL release for Yum based systems such as RedHat and Centos.
Apt Apt based systems such as Ubuntu and Debian can install via a PPA.
More details can be found in the ansible installation documentation.
Configuration
Ansible configuration can be defined in multiple locations. The following search paths will be checked for an ansible.cfg file from highest to lowest preference.
- $ANSIBLE_CONFIG environment variable.
- ./ansible.cfg current working directory.
- ~/.ansible.cfg users home directory.
- /etc/ansible/ansible.cfg default location.
When a playbook is run the first file found will be the configuration that is used, configuration files will not be merged. The ansible.cfg file uses the .ini format. Sections are defined with square brackets [defaults]. Variables are defined like so inventory=/etc/ansible/hosts.
The ansible configuration documentation has details regarding all configuration options.
Inventory
Ansible inventory files define managed nodes that playbooks can be run against. Inventories can be statically created or dynamically built from another inventory system such as CMDB.
Static Inventory
Static inventory files are similar to .ini file format. There are three types of host definitions.
- test.lab.local singular host
- [sydney] group of hosts
- [australia:children] group of groups
Example static inventory
test.lab.local
[sydney]
host1.lab.sydney
host2.lab.sydney
[melbourne]
host1.lab.melbourne
host2.lab.melbourne
[australia:children]
sydney
melbourne
Dynamic Inventory
A dynamic inventory script can be written in any language as long as it returns valid JSON. The script must support two arguments: --list and --host and must be executable.
The --list argument must return a key/value pair of group to host mappings.
[user@control-node ~]$ ./inventoryscript --list
{
"sydney" : [ "host1.lab.sydney", "host2.lab.sydney" ],
"melbourne" : [ "host1.lab.melbourne", "host2.lab.melbourne" ]
}
The --host argument must return a key/value pair of host to variable mappings or an empty dictionary.
[user@control-node ~]$ ./inventoryscript --host host1.lab.sydney
{
"ntp_server" : "ntp.lab.sydney",
"syslog_server" : "syslog.lab.sydney"
}
Inventory Usage
The inventory that will be used can be defined in the following two ways from highest to lowest preference.
- -i /path/to/inventory argument at playbook runtime
- inventory=/path/to/inventory variable in the ansible.cfg file
The default inventory location is /etc/ansible/hosts.
Multiple inventory files can be used if the parameter used with the -i argument or the inventory variable defined in the ansible.cfg file is a directory.
If using a directory the files will be read in alphabetical order. The order of files is important if a group of groups in one file refers to a group in another file.
See the ansible inventory documentation for more details.
Playbook
Playbooks can describe a desired configuration state and/or the steps to implement change on a managed host.
A playbook defines a list of 'plays' that will be executed against managed nodes. Each play contains one or more tasks which will be executed against the defined hosts group. Task utilise modules to perform the actions on the managed nodes.
Playbooks are writen in YAML which is an easy to read and write key/value data serialization language.
Playbooks have four sections
- hosts host or host group in an inventory file
- vars variables that will be used in templates
- tasks will to be executed against hosts
- handlers perform actions like restarting services
Example Playbook
---
- hosts: sydney
vars:
ssh_port: 22
tasks:
- name: ensure openssh is at the latest version
yum: name=openssh-server state=latest
- name: write the openssh config file
template: src=~/playbooks/templates/sshd.j2 dest=/etc/ssh/sshd_config
notify:
- restart sshd
handlers:
- name: restart sshd
service: name=sshd state=restarted
Find out more about ansible playbooks in the official documentation.
Modules
Modules are where the magic happens in ansible. Modules are invoked with tasks in playbooks or with the ansible ad hoc command -m argument.
There are three types of modules in ansible.
- Core ship with anisble, maintained by core ansible team
- Extras ship with ansible, maintained by community
- 3rd Party anisble galaxy, maintained by community
An example is the yum module that can be used to perform yum related task such as yum install.
Example yum module usage in playbook
tasks:
- name: ensure openssh is at the latest version
yum: name=openssh-server state=latest
When utilizing the ansible ad hoc command the m module_name [-a module_args] is used to specify a module. Note the [-a module_args] is an optional parameter.
The setup module which is used to collect facts about a device is automatically run on hosts during playbook execution.
Refer to the ansible modules documention for more details.
Variables
Variables are technique for managing dynamic values throughout your code. Ansible variable names must start with a letter and only contain letters, numbers or underscores.
Variables in ansible have three scopes
- Global set from the command line or defined in anisble.cfg file
- Play set in plays vars and include_vars
- Host set via host groups or on hosts by the inventory, facts or registered tasks
Within each scope there are many places a variable can be defined.
Variable Precedence If a variable with the same name is defined in multiple places, the variable with the higher precedence will be used.
From ansible 2.x the variable precedence is as follows from most to least preferred
- -e extra vars argument with ansible ad-hoc command
- task vars task scope only
- block vars task block scope
- role vars and include_vars
- set_facts module
- registered vars in a play
- vars_files in a play
- vars_prompt in a play
- vars in a play
- facts gathered about a host
- host_vars in a playbook
- group_vars in a playbook
- host_vars in an inventory
- group_vars in an inventory
- vars in a static or dynamic inventory
- defaults file for a role
More on variable precedence can be found in the official documentation.
Facts When a playbook is run against a host, there is an implicit module named the setup module that gathers facts about a device. Fact gathering can be disabled by setting the gather_facts: false argument.
Example facts variables
"ansible_all_ipv4_addresses": [
"REDACTED IP ADDRESS"
],
"ansible_all_ipv6_addresses": [
"REDACTED IPV6 ADDRESS"
],
...
long list of values
...
"ansible_virtualization_role": "guest",
"ansible_virtualization_type": "VMware"
Variable Usage
Depending on where a variable is being defined determines the format to set the variable. Below are some examples of how to define a variable.
- {{ inventory_hostname }} templates and playbooks
- ansible_hostname=host1.lab.sydney inventory files
- hostname: host1.lab.sydney var_files and vars argument in playbook
When a variable is defined in a playbook with the {{ ... }} syntax and it starts an argument, it must be enclosed in quotes. For example: template: src='{{ base_path }}/motd.j2'
More information regarding variables can be found in the documentation.
Templates
Templates in ansible utilize the Jinja2 template language. Templates allow for the re-use of files in a DRY (dont repeat yourself) fashion. For example a template can be written for a MOTD banner that has variables that will be substituted for the inventory hostname.
Jinja2 templates are basically text files that have control structures and variables that get manipulated by the templating engine on rendering. By convention jinja2 files should end with a .j2 extension, but this is not required.
Delimiters
Jinja2 templates have the following delimiters which define functionality to be executed by the templating engine.
- {% ... %} statements eg: for, if and include
- {{ ... }} variables and expressions
- {# ... #} comments
- {# ... #} line statements (not used in ansible afaik)
Templates are used in ansible playbooks via the template module
Example template
This is a MOTD file for {{ inventory_hostname }}
{% if ansible_all_ipv4_addresses %}
{% for ip in ansible_all_ipv4_addresses %}
{{ ip }}
{% endfor %}
{% endif %}
Example template usage in playbook
tasks:
- template: src=templates/motd.j2
dest=/etc/motd
owner=root
group=root
mode="0600"
See the official Jinja2 docs for the full gamut of options available. More about the template module can be found in the official ansible docs.
Roles
Ansible roles allow for the abstraction of tasks, making them more portable and re-usable. Rather than having a playbook that builds out a web app, you could seperate the tasks into roles, for example, a role to install and configure the database, another role to install and configure the web server. You could then potentially use these roles to build out any kind of application.
Roles can be uploaded to ansible galaxy and shared with the community. To create a role there is a nice helper command anisble-galaxy init <i>role_name</i> which will build out the directory structure required for a role.
Example Role Directory Structure
roles/example_role/
|-- README.md
|-- defaults
| `-- main.yml
|-- files
|-- handlers
| `-- main.yml
|-- meta
| `-- main.yml
|-- tasks
| `-- main.yml
|-- templates
|-- tests
| |-- inventory
| `-- test.yml
`-- vars
`-- main.yml
Ansible Galaxy
3rd party modules can be uploaded to ansible galaxy for re-use by others. Ansible galaxy roles are tied to a github account. Starting at ansible version 1.8 it is possible to use git sources other than ansible galaxy.
The ansible-galaxy <i>args</i> cli command can be used to search, list, initialize, install and remove ansbile roles.
ansible-galaxy --help
Usage: ansible-galaxy [delete|import|info|init|install|list|login|remove|search|setup] [--help] [options] ...
ansible-galaxy search junos
Found 2 roles matching your search:
Name Description
---- -----------
Juniper.junos Network build automation of Junos devices.
reloadzhang.junos-stdlib Network build automation of Junos devices.
A comprehensive guide to getting started with ansible galay can be found at the ansible galaxy about.
Ansible Vault
Ansible provides a 'vault' to securely stores secrets. The ansible-vault <i>args</i> command is used to create, edit, encrypt and decrypt vault files. Ansible uses a python library to symetrically encrypt a file with AES 256 bit encryption using a password as the key. The default editor for ansible vault is vi, this can be changed by exporting the $EDITOR environment variable. Ansible vault files use YAML syntax for key:value pairs.
ansible-vault --help
Usage: ansible-vault [create|decrypt|edit|encrypt|rekey|view] [--help] [options] vaultfile.yml
It is recommended to name the ansible vault file vault and store it under the group_vars/ directory. Vault variable names should be prefixed withvault_.
There are three methods to tell ansible to access the vault during playbook execution:
- --ask-vault-pass provide key at runtime.
- --vault-password-file <i>/path/to/file</i> path to a file containing the key.
- ANSIBLE_VAULT_PASSWORD_FILE via an environment variable.
More on ansible vault can be found in the official docs.
Ansible Tower
Ansible tower provides a nice web interface for controlling ansible managed nodes with enterprise features.
- Role base access control
- LDAP integration
- Centralized logging
- Playbook scheduler
Anisble tower is build on a Django web stack with a postgres backend. An ansible playbook is used to install ansible tower after an initial wizard is used to define the configuration variables.
There are three installation methods for ansible tower
- Single machine, internal database
- Single machine, external database
- Active/Passive, multiple machines, external database. API only available on primary node
More on ansible tower can be found in the official documentation.