Ansible: Simple configuration & deployment

There’s a new kid on the block in the configuration management world that claims to be lean, simple and easy to understand. We’re using it internally for some exciting new projects, and have found that it lives up to it’s promise.

Ansible’s goal is to unify two similar but traditionally separate tools: configuration management and deployment. Instead of using a combination of Puppet + Capistrano or Chef + Fabric, you can now use a single tool to update your configuration, deploy new code or execute ad-hoc tasks against groups of servers. There is no requirement to setup any daemons or any software at all on the target servers - as long as you can reach them over SSH, you’re good. There is also very little setup on the machine running Ansible, since it only relies on a few standard Python libraries. There’s a detailed comparison page in the FAQ that compares Ansible to these various other tools.

To illustrate some of the features, we’ve created a playbook that sets up an Ubuntu 12.04 server as a multi-user LAMP developer environment. Our old method of creating a server would be to perform these steps once, document them on a wiki, and then anytime we want another similar server, clone the first one or repeat the steps manually. This procedure is fragile over time and in the case of cloning, virtualization-platform specific. It’s far better to create a reusable, automated set of steps that can be tweaked and improved, and executed against any fresh Ubuntu 12.04 server, running anywhere. The example is documented and fully functional, although it’s not 100% secured so be careful.

Playbooks

The tasks that need to be performed are recorded in YAML formatted Ansible playbooks, which are then executed against the target host. The main components of a ‘play’ are:

  1. Hosts against which the actions are performed.
  2. Variables that can be used in the play or in file templates.
  3. Actions that will be performed when the play runs.
  4. Handlers that respond to change events from the actions.

Here is an abbreviated example from our server build playbook file, setup.yml, that will apply php.ini and restart Apache if the file has changed:

setup.yml

---
- hosts: all
  user: root
  vars_files:
    - vars/settings-default.yml
  tasks:
    - name: PHP configuration file, php.ini
      action: template src=templates/etc-php5-apache2-php-ini.j2 dest=/etc/php5/apache2/php.ini
      notify: Restart Apache
  handlers:
    - name: Restart Apache
      action: service name=apache2 state=restarted

The first two directives, ‘host’ and ‘user’, are easy - they specify that the play should by default run against all hosts, as the root user. In the following sections we’ll cover what the others do.

Variables

The vars_files section lists files that will be imported into the current play. The variables specified in these files are then available to use as value substitution or logic control in both the play and in the templates.

In our case, the settings-default.yml file contains a list of configuration variables for the services like Apache, MySQL, etc. They’re all organized into their respective sections, but looking at the file as a whole we get a great birds-eye view of how the play will be modifying the default server configuration. Here is an excerpt:

settings-default.yml

---
# php.ini
php_max_execution_time: '90'
php_display_errors: 'On'
...
# my.cnf
mysql_max_allowed_packet: '128M'
mysql_character_set_server: 'utf8'
...

Any value that will be modified from it’s default out-the-box state is recorded in this one place. This makes changing settings really easy - no more grepping through thousands of lines of configuration in the master template file.

Tasks & Handlers

tasks:
  - name: PHP configuration file, php.ini
    action: template src=templates/etc-php5-apache2-php-ini.j2 dest=/etc/php5/apache2/php.ini
    notify: Restart Apache

These are the meat of the playbook. Each task is given a descriptive name and an action. In the example above, the action is ‘template’ and the parameters ‘src’ and ‘dest’ point to a source template on the local host, and a destination location on the target host, respectively.

To execute this step, the Ansible ‘template’ module will be invoked, passing the etc-php5-apache2-php-ini.j2 file through the Jinja2 templating engine, which performs variable substitutions in the appropriate place. For example, in this template we can insert the value of the variable php_max_execution_time (sourced from settings-default.yml above) in it’s correct place:

etc-php5-apache2-php-ini.j2 which becomes php.ini

; Maximum execution time of each script, in seconds
max_execution_time = {{ php_max_execution_time }}
...

The great thing about Ansible modules is that they’re idempotent, meaning no changes will be made if the new file is identical to the old one. And since we have this data available, we can trigger events when changes are actually made. That’s what the ‘notify’ section does - when Ansible detects this file has changed, it will call ‘Restart Apache’, which is defined at the end of the play and does exactly what you might think it does.

Modules

When Ansible executes a task, it creates an SSH connection to the target server and copies the required module over. The module is then executed on the target with the correct parameters, and all the module has to do is return a JSON object containing pass/fail and other optional status information.

This has several advantages: for one, modules can be written in any scripting language, as long as the target can execute that code from the shell. It also means your modules can be quite sophisticated, and since they’re running locally on the target server as opposed to sending individual commands over the wire, they run fast.

There are git, apt, yum and service modules, just to name a few. Developing extra modules is easy and there is a growing collection of ‘contrib’ modules that come from the community but are not part of core Ansible.

Command mode

In addition to creating playbooks, you can also execute ad-hoc tasks against your servers using the exact same modules and syntax. For example, I could execute from the command line:

ansible webservers -m shell -a '/sbin/reboot now'

That would reboot all servers defined in the ‘webservers’ pool. This shared syntax and configuration is one of Ansible’s strengths, we can reuse a single server cluster specification for all tasks.

Project Status

Michael DeHaan is the project lead, and he has a lot of experience in this area, having worked at Red Hat & Puppet Labs, co-created Func and developed Cobbler.

Ansible is still very new, having only been released this year. However, it’s already used in production and there is a strong community forming. Michael announced recently that he’d written almost zero code in the upcoming 0.5 release, a sure sign of good community momentum.

Resources

Check out the pedantically commented playbook example, that covers almost all the features in one place.
The Ansible mailing list is active and friendly.

Commenting on this Blog post is closed.

Comments

It doesn’t look like your playbook is actually idempotent. You execute those SQL commands through the shell with every run on every system, regardless of whether the MySQL root user has already been configured or not. You get away with it by embedding the root user password (in plaintext) in the root my.cnf, so that it won’t hang by prompting you for a password when you run the playbook again, but that’s kind of a hack. The lack of real puppet-style conditionals seems like a real weakness for ansible.

That’s correct, the playbook example is not 100% idempotent, the idea was to demonstrate what is possible with Ansible.

There’s a comment in there that says “TODO: Create a MySQL module to make this cleaner”, which is exactly what I’m going to be working on.

edit: The MySQL module is now available in the devel branch of Ansible.

We actually don’t lack conditionals.

There’s the fact based “only_if”, for excluding commands on certain hosts, and also, the command module takes a “creates” exactly like Puppet’s Exec.

Ansible is, for the most part, a distillation of lessons learned from Puppet and others (like the sane ordering from Chef), with the unneccessary parts taken out.