Playing with Ansible
I’ve been working with Ansible for several weeks now. It’s a very powerful tool for automating server deployment (like Chef or Puppet). It’s very easy to use, the only thing that it expects from the server is a working SSH access. Also it has a pretty good documentation.
Ansible is modular - in fact it uses modules for doing all the heavy lifting.
There are a lot of modules, and you can
also write your own. All modules are idempotent, which means that they first
check what needs to be done and only do their thing if it’s really needed.
Troubleshooting is easy - the modules are simple and quite low-level
that you always know what is going. On the other hand, the lack of abstractions is a double-edged
sword: Ansible playbooks are usually bound to the specific environment they were
written for, e.g. you can’t just take any playbook written for Debian and use it
in Centos, because they use completely different packaging systems and Ansible
has specialized modules for installing packages - in this case
The thing that makes Ansible very powerful is the separation of “what” from “where”. Playbooks specify what you want to do (e.g. install a package, generate a config file from a template, start the service), but they don’t know where will they actually run - because they can run anywhere. The information about “where” is stored in inventory files. They are simple text files that contain hostnames or IP addresses of hosts where the playbooks shall run. Entries in the inventory file can be grouped - for instance, you can have a group for www servers and a group for loadbalancers.
You can have many inventory files for various use cases, for example an inventory files for production servers and another one for virtual machines used for development.
Real Example (well, almost)
But this is just talking, so let’s try something real. Say we want to deploy this simple web application to a virtual machine running CentOS 7 and put HAProxy in front of it. How do we do that?
Create a virtual machine
First, create a virtual machine running Centos 7 so we have something to work with. Install VirtualBox or something similar and create yourself one. Also make sure you can SSH to it with an SSH key.
Clone the Flask repo
Then clone the official Flask repo, it contains our example web application (checkout the last stable version):
git clone https://github.com/mitsuhiko/flask.git /path/to/flask.git cd /path/to/flask.git && git checkout -b v0.10.1 0.10.1
Now we can create the deployment procedure. We will use the directory layout from Ansible Best Practices, because it makes things nicely organized and has a couple of convenient properties like not having to type full file paths in playbooks.
First, we create the inventory file
inventory/hosts. Put the IP address of
your virtual machine to it, my VM had
[webservers] 192.168.56.170 [haproxy] 192.168.56.170
Now we can create the playbooks. Create the
site.yml with the following contents:
--- - include: webservers.yml - include: haproxy.yml
This is a very simple Ansible playbook, it just includes two other playbooks for setting up the webserver and haproxy. This is just a convenience file we will use later for deploying the whole “site”.
The contents of
webservers.yml is more interesting:
--- - hosts: webservers remote_user: root roles: - webserver
Here we say that the playbook operates on a group of hosts called
and fulfills the role of “webserver”.
By convention, roles are stored in subdirectories in
roles/. Tasks for
setting up the webserver role are stored in file
--- - name: create user user: name="" shell="/sbin/nologin" state="present" - name: install python-flask yum: name="python-flask" state="present" - name: install rsync yum: name="rsync" state="present" - name: copy flaskr synchronize: src="/" dest="/" recursive="yes" delete="yes" rsync_opts="--exclude *.swp, --exclude *.swo" owner="no" group="no" notify: - restart flaskr - name: generate flaskr.cfg template: src="flaskr.cfg.j2" dest="/flaskr.cfg" owner="flaskr" group="flaskr" mode="644" notify: - restart flaskr - name: generate flaskr.service template: src="flaskr.service.j2" dest="/etc/systemd/system/flaskr.service" owner="root" group="root" mode="644" notify: - reload systemd - name: start flaskr service: name="flaskr" state="started"
It’s pretty self-explanatory, isn’t it? It will create a user that the app will run under, install Flask, copy the app over, generate config and start the app. Easy.
Ansible can use Jinja2 templates to generate
config files. So no more ad-hoc awk`ing of config files and wondering why it
doesn’t work. Templates for the “webserver” role are usually stored in
role/webserver/templates/. For example, a systemd service file for the flaskr
flaskr.service.j2 looks like this:
[Unit] Description=Flaskr - a minimal blog application [Service] Type=simple Environment="FLASKR_SETTINGS=/flaskr.cfg" ExecStart=/usr/bin/python /flaskr.py User= Group= Restart=always [Install] WantedBy=multi-user.target
You can use any Jinja2 syntax there, including control flow statements. Config files can have logic now!
The values of variables can come from several places, I recommend looking at
Playbook Variables docs to
get the idea. In this case we have a YAML file
contains the variables for the hosts group
--- flaskr_user: flaskr flaskr_group: flaskr flaskr_home: /home/flaskr
haproxy role is very similar, there’s no need to cut-n-paste it here. If
you are intersted you can find the complete example on
After you have everything set, you can run the
ansible-playbook site.yml -i inventory/hosts -e "flaskr_dir=/path/to/flask.git/examples/flaskr/"
So to sum it up - I would say that the initial effort put into writing playbooks pays off very quickly. Also you should store the deployment procedure in a git repo, so you can easily keep track of all configuration changes.