Ansible seems to be the simplest tool to centrally manage systems of any kind, Docker containers, AWS, Google Compute, Rackspace, OpenStack instances, VMware VMs, etc… There isn’t any dependencies on the managed system apart from Python 2.6. Ansible isn’t using any database or daemon and won’t install anything on the managed system, all operations are executed using SSH.
In this article, I’ll details how to install it will introduce the main concepts and terminologies. You’ll then understand why companies like Evernote, Twitter, Nasa, Rackspace or Atlassian are all using this Configuration Management, deployment and orchestration tool Compared to other solutions like Chef, Puppet or SaltStack, Ansible is designed to be minimal in nature with low learning curve.
Last but not least, Ansible seems to be inspired by Ursula K. Le Guin communication device capable of instantaneous or superluminal communication.
SSH Keys
Ansible use SSH by default to communicate with remote hosts. So if you don’t have any SSH keys at hand, generate yours now
$ ssh-keygen -t rsa -C "your_email@somedomain.com"
And copy them to the node you want to manage
$ ssh-copy-id -i ~/.ssh/id_rsa.pub root@<remote host IP addr>
Most of the cloud services can inject your public key at provisioning time, we’ll be using this mechanism instead.
Ansible doesn’t add another authentication layer (PKI) on top of the managed system, so no added complexity, which is great. It’s using what’s already there for day to day operation, nothing more, nothing less !
Installation
Ansible can easily be installed from pre-built packages, but to get the latest version and the greatest features let’s install it from source. This article assume you’re using a Debian based OS.
Python Dependencies
To install Ansible required Python packages, let’s use pip, so first make sure it’s there or install it
# apt-get install python-pip
Use pip to install the following required modules
# pip install paramiko PyYAML jinja2 httplib2 six
Installation from source
Clone the Git repositoty (19MB)
$ git clone git://github.com/ansible/ansible.git --recursive
If you prefer you can checkout a specific version using
$ git tag
$ git checkout v1.6.6
If you want to stay at the bleeding of Ansible, don’t forget to pull the devel branch. You can first check the diff.
$ git diff devel origin/devel
$ git pull
But you can also review the diff after the pull.
$ git diff HEAD@{1}
But this isn’t not a Git article, so let’s continue on.
$ cd ./ansible
run env-setup
if you don’t have root access on your system to use it from the repository itself.
$ source ./hacking/env-setup
Or install it on your system [require python-dev]
$ sudo make install
If the above installation fails, install more dependencies and retry the above command
$ sudo apt-get install build-essential libssl-dev libffi-dev python-dev
Host inventory
/etc/ansible/hosts
is an Hosts Inventory file that you have to create to start playing with Ansible, run the following commands as root to create this file:
# echo "127.0.0.1" >> /etc/ansible/hosts
This file will be used by Ansible to know the hosts where it can act.
If you don’t have root access on your system, you can store it in your homedir by setting ANSIBLE_HOSTS
$ export ANSIBLE_HOSTS=~/ansible_hosts
We’ll see later how to use Dynamic Inventory plugins instead of such a static INI style configuration file.
You can now run your first test which will check that Ansible can communicate with your inventoried nodes, localhost in our case.
$ ansible all -m ping --ask-pass
You should get
127.0.0.1 | success >> {
"changed": false,
"ping": "pong"
}
Easy isn’t it ?
Ansible upgrade
If you want to upgrade from source:
# git pull
# git checkout v1.8.4
# git submodule update --init --recursive
# make install
Check it worked
# ansible --version
Other installation methods
You’ll find other ways to install your Ansible control node in the official documentation.
Inventory
/etc/ansible/hosts
is the default path of Ansible Host inventory. It contains an INI formated configuration.
[lb]
haproxy.yet.org
[web]
wiki1.yet.org
wiki2.yet.org
[web:vars]
listen_port=80
[database]
db1.yet.org:2020 ansible_ssh_user=admin ansible_ssh_private_key_file=/home/admin/.ssh/id_rsa
db2.yet.org:2020
[yet:children]
web
database
ansible_ssh_user=admin
variables can be set with hosts declaration.
:2020
is used to change SSH Port to connect to on that host.
[web:vars]
declare groups variables
:children
syntax declare a group of groups.
Tasks
Playbook Tasks are the smallest operation Ansible can do, it’s expressed in YAML. Tasks calls Modules, currently Ansible have over 230 of them that do anything from installing packages to restart services.
tasks:
- name: Ensure Apache is installed
apt: pkg=apache2 state=latest
- name: wait user confirmation
pause: prompt="Press ENTER if you are ready to update Apache config and restart or CTRL-C to quit."
- name: configure apache
copy: src=files/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify: restart apache
- name: Wait for Apache to start
wait_for: port=80 state=started
pause
instruct the Playbook to wait for user confirmation and can also be used to wait for some seconds (seconds=30 instead of prompt)
wait_for
continuously poll for the specified TCP Port and wait it respond before continuing Playbook execution.
Modules
Modules are small program that run on remote host and ensure the given host is in a particular state.
You can get the full list of supported modules with -l
$ ansible-doc -l
ansible-doc <MODULE NAME>
will give you a detailled information about a module. Ansible docs is great by the way.
$ ansible-doc nova_compute
Playbooks
Playbooks are the Ansible way of expressing the configurations using human readable and machine parseable YAML file.
Playbooks combine the execution of multiple Ansible modules to perform application rollouts.
To execute a Playbook run
ansible-playbook -k -K boostrap.yml
-k (--ask-pass)
ask for a password instead of assuming ssh-agent key based authentication
-K (--ask-sudo)
ask for a password for sudo access
Playbooks are composed of three sections:
---
# This playbook deploys YET Blog on an OpenStack cloud.
- name: Provision a Ubuntu 14.04 Instance on OpenStack
hosts: localhost
connection: local
gather_facts: false
roles:
- { role: bootstrap, tags: [ 'bootstrap' ] }
- name: secure the instance and install yet.
hosts: yet-blog
user: "{{ ansible_ssh_user | default(root) }}"
gather_facts: false
sudo: yes
roles:
- base
- yet
- hosts: the Target section defines the hosts targeted by this Playbook. You can also define SSusername and SSH related config: sudo(sudo needed ?), user, sudo_user, connection(local/ssh), gather_fac(false, to avoid gathering facts if you’ve done it in an earlier Playbook)
- vars: the Variable section defines the variable available to this Playbook.
- tasks: the Task section list all modules in the order of execution.:
You don’t see all of this in our example above, we are instead leveraging Roles instead.
Roles
Roles brings a way to put together a set of configuration, templates, variables, … all in one little reusable package for different kind of things.
apache.yml
roles
web
defaults
files
apache2.conf
httpd.conf
handlers
main.yml
meta (reference to other roles)
tasks
main.yml
templates
vars
ubuntu.yml
centos.yml
They are stored by default in the /etc/ansible/roles
directory.
For Rails developpers around, it should remind you the notion of convention over configuration. From now on, within your Playbooks you won’t need to specify where you store the different components as soon as you respect the above directory structure for your roles.
Now you can use them in your main infrastructure Playbook like this
---
- hosts: all
roles:
- security
- monitoring
- fileshare
- hosts: webservers
roles:
- nginx
- php
- drupal
- hosts: database
roles:
- postgres
Iterate
You can easily repeat a module several times using with_items which create a variable called item
- name: create directory structure
file: path=/var/{{ item.path }} mode={{ item.mode }} owner="www-data" group="www-data" state=directory
with_items:
- { path: 'www', mode: '0775' }
- { path: 'www/yet', mode: '0775' }
with_fileglob works the same way but iterate over a pattern matching file glob.
Conditions
a when clause condition the execution of the module to the value of it. The module will only execute if the Python expression evaluate to True.
Variables
Variables can be defined from facts or inventory variable using the following syntax:
{{ ansible_eth0.ipv4.address }}
{{ innodb_buffer_pool_size_mb|default(128) }}
Use vars_files: to load them from a file like this
vars_files:
/conf/dc.yml
/conf/rack.yml
/conf/web.yml
Use prompt
to wait for user input
vars_prompt:
- name: 'password'
prompt: 'Please type the root Password'
private: yes
By using private
, you make sure that the password won’t be printed on the screen.
Handlers
This section use the same syntax as the Tasks one, Handlers are only run when called from a task that changed something
handlers:
- name: restart dhcp
service: name=httpd state=restarted
Tags
Some tasks can take quite some time, so tags are handy to only run subsection of your Playbooks.
- apt: name={{ item }} state=installed
with_items:
- apache
- memcached
tags:
- packages
You can then run only this section like this
ansible-playbook example.yml --tags "configuration,packages"
Or everything else
ansible-playbook example.yml --skip-tags "notification"
Jinja2 templates
Jinja2 is a full featured template engine for Python which use the following syntax
{# #} encloses comments.
{% %} encloses conditional logic and other Python.
{% if %} {% endif %}
{% for %} {% endfor %}
[% %] encloses system facts according to directive from the first line of the template file.
To use templates you just have to call the template module. You aren’t supposed to use complex logic in your templates, prefer to use set_fact
set_fact: innodb_buffer_pool_size_mb="{{ ansible_memtotal_mb / 2 }}"
Using Vagrant with Ansible
Vagrant is great if you want to test your overall infrastructure on your local machine. You just have to define a provisioner, give it a Playbook and an Inventory File or it will use /etc/ansible/hosts
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yaml"
ansible.inventory_path = "inventory-vagrant"
end
Using Ansible on Rackspace cloud
First install pyrax
which is the client binding for Rackspace Cloud.
pip install pyrax
Now store your Rackspace cloud auth credentials in ~/.rackspace_cloud_credentials (other location can be specified in RAX_CREDS_FILE shell variable)
[rackspace_cloud]
username = <username>
api_key = <apikey>
Provisioning an Instance using Rax module
Here is an example of YAML file which will create a new instance on Rackspace cloud
---
- name: Launch an Instance on Rackspace Cloud
hosts: localhost
connection: local
gather_facts: False
tasks:
- name: Server build request
local_action:
module: rax
name: yet-nanoc
group: yet-blog
credentials: ~/.rackspace_cloud_credentials
flavor: 2
image: bb02b1a3-bc77-4d17-ab5b-421d89850fca
wait: yes
state: present
region: DFW
key_name: seebackupforprivate-dallas
networks:
- public
register: rax
We are adding yet-blog
in the group metadata to easily target this host using dynamic inventory (see below).
Dynamic Inventory
For convenient access and to potentialy run multiple inventory script at the same time, cp rax.py
and all your needed scripts into an inventory folder.
mkdir ~/inventory
cp ~/ansible/plugins/inventory/rax.py ~/inventory
You can use the Inventory plugin like this
~/inventory/rax.py --list
~/rax.py --host yet-nanoc1
And you can now run a Playbook using Rackspace dynamic inventory
ansible-playbook -i ~/inventory yet-followup.yaml
The above instance can easily be targeted using the following line in any Playbook.
Hosts: yet-blog
Vault Encrypted Files
Not all information should be stored as clear text, Ansible 1.5 brings encryption to its core with Vault. You can encrypt :
- group_vars/ or host_vars/ inventory variables
- variables loaded using include_vars or vars_files
- Role or default variables
- or passed to Ansible using -e @file.yml
To encrypt an existing file use
ansible-vault encrypt cloud_id.yml
You can then run the playbook like this, all encrypted files need to use the same password.
ansible-playbook yet.yml --ask-vault-pass
Use the following commands to edit, change the password or decrypt it
ansible-vault edit cloud_id.yml
ansible-vault rekey cloud_id.yml
ansible-vault decrypt cloud_id.yml
Note: make sure you set your EDITOR environment variable or the edit won’t work.
Summary
I’ve been playing with Chef for quite some time, but I’m always curious about new things. I’m quite happy with Ansible so far, it differentiate from any other Configuration Management tool with some unique key concepts:
- It’s pretty small
- It’s Agentless
- No central server
- No configuration database
- Configurations are human readable YAML files
- It’s using SSH for communicating with managed nodes.
For all these reason I think you should spend some time with Ansible, you won’t regret it.
Links
- Ansible Best Practice
- Ansible Configuration Management- a 92 pages Packt book
- Ansible for DevOps - a 147 pages leanpub book, half complete. Check his recorded DevOps for Humans presentation.
- Ansible Weekly Newsletter
- Ansible Rackspace Roles - great roles published by Rackspace
- Ansible Rackspace (Rax) module documentation
- Ransack, an Application Built on Ansible’s API for Rackspace – AnsibleFest NYC 2014
- Ansible Google Compute Demo