Start Learning Ansible With One Line And No Files

Ansible is a free “configuration management” tool. It’s what you use when you’re tired of copying commands by hand from Digital Ocean tutorials into terminal windows. Or when you want to set up identical servers that are actually identical.

I really like Ansible. If you’ve never used its older cousins Chef or Puppet — or if, having used them, you’d prefer to never use them again — I recommend you give Ansible a try. These tools are more alike than different, and once you learn one it’s easy to switch to another, but Ansible makes it especially quick to go from a series of shell commands:

#!/bin/bash

sudo apt-get update
sudo apt-get install php-xhprof
sudo echo "[xhprof]\n\txhprof.output_dir=/tmp/xhprof" > /etc/php5/php.ini 

… to a series of Ansible commands:

---
- hosts: localhost
  connection: local
  tasks:
  - name: "Install the latest xhprof profiler for PHP."
    sudo: yes
    apt:
      pkg: php-xhprof
      state: latest
      update_cache: yes

  - name: "Configure xhprof to write to /tmp/xhprof."
    sudo: yes
    ini_file:
      dest: /etc/php5/php.ini
      section: xhprof
      option: xhprof.output_dir
      value: /tmp/xhprof

Like the shell script, this Ansible playbook is just a series of tasks that are performed in sequence, and a simple sequence fits entirely in one file. Unlike the shell script, you can easily run these tasks on other computers, not just the one the script is running on, by editing the list of hosts and removing the connection: local specification:

---
- hosts: staging-1.example.com, web-17.aws.example.com
  tasks:
    ...

You can also make use of Ansible’s intelligent modules, like the ini_file module shown above. It’s actually quite difficult to edit a .ini file using bash. You could spend hours hacking away with tricky Unix tools like sed, awk, or m4. More typically, you could do what I did in my bash example above: Write something quick, simple, and obviously buggy, commit it, and hope that no one ever notices. Having tried both alternatives, I have to recommend Ansible.

The Shortest Path To Using Ansible

Setting up a configuration management tool can be painful. There’s a bunch of files to edit, in special formats, in a specially-arranged directory. Sometimes there’s a bunch of special servers to be launched. At some point, every programmer is tempted to give up.

So let’s try Ansible without any setup at all.

The catch is: We have to install it first. There’s no getting out of that.

If you’re running Mac OS X or Linux, you can just install Ansible on your own computer (following the instructions).

A Windows version of Ansible is in progress, but it’s not ready, and the examples below wouldn’t make sense in Windows anyway. So if you’re using Windows I recommend that you find a Linux machine. You could launch one at Digital Ocean or Linode or AWS. Even better (but at the expense of more work), you could install Vagrant and Virtualbox, launch an Ubuntu virtual machine, and install Ansible on that.

Mac and Linux users might also want to use Vagrant. But I actually install Ansible directly on every machine I use. Having gotten used to it, I never want to be without it.

A One-Line Configuration

Here’s the shortest Ansible command that I know how to write:

$ ansible all -i 'localhost,' -c local -m ping

Type that into the shell and it should respond with:

localhost | success >> {
    "changed": false, 
    "ping": "pong"
}

Congratulations, now you’re an Ansible user.

Wait, What Did That Line Actually Do?

Let’s take our one-liner apart. Could we make it shorter or simpler?

  • ansible is the command which runs one task at a time.
  • all tells Ansible to run this task on all the hosts in the inventory.
  • -i localhost, …is a trick, to avoid the need to make an inventory file. -i means “here is the pathname of the inventory file”. But ansible has a poorly-documented bonus feature: Instead of a path, we can choose to provide a list of host names, each of which names a computer on the network. And our list can have just one entry, localhost. (The weird extra comma after localhost is vital; do not leave it out. It tells Ansible that this is a list of hosts and not a pathname.)
  • -c local is shorthand for --connection=local. It tells Ansible not to try to use SSH to contact the hosts, but to run tasks on our local computer instead. Ansible uses SSH by default, because usually it’s running tasks on faraway machines in the cloud. However, it’s common to encounter problems when connecting to your own computer via SSH, and fixing these problems without impairing your security is tricky and not worth the effort.
  • Finally, -m means “use this Ansible module”, and ping is the name of the module. The ping module contacts the host and proves that it’s listening.

We can’t make this one-liner much shorter without risking failure. It might be less confusing, though, to just make an inventory file instead of using the -i localhost, hack. Ansible inventory files can just be lists of host names:

localhost
my-web-server.example.com
my-other-web-server.example.com

In our case we only need a one-line inventory:

localhost

Save this line in a text file, `/tmp/my-inventory’, and we can run Ansible like this:

$ ansible all -c local -i /tmp/my-inventory -m ping

And if we edit our inventory file to include an option for localhost:

localhost ansible_connection=local

we can leave out the -c local on the command line:

$ ansible all -i /tmp/my-inventory -m ping

This is what all ansible commands will look like:

  1. ansible
  2. A list of hosts to operate on (which can be “all”)
  3. A path to the inventory where Ansible can find detailed connection information about the hosts.
  4. A module, specified with -m for “module”.
  5. Arguments to the module, specified with -a for “arguments”

Here is a module that takes arguments, command. It just runs a command on the target hosts:

$ ansible all -i '/tmp/my-inventory' -m command -a 'echo "Hello World"'

localhost | success | rc=0 >>
Hello World

If you leave out the -m portion, Ansible will assume that command was the module you wanted, so we could make our last example shorter:

$ ansible all -i '/tmp/my-inventory' -a 'echo "Hello World"'

localhost | success | rc=0 >>
Hello World

One-Liners That Are Actually Useful

Frankly, these one-liners, which you have to type in over and over, are not the big reason people use Ansible. And soon we will be working with playbooks, and roles, and fancier inventories, and other powerful features.

But every Ansible tutorial begins with one-liners because they’re the essence of Ansible’s design:

  • Take a task, consisting of a module, arguments, and options.
  • Execute that task on a group of hosts, in parallel, over the network via SSH.

This, all by itself, is a very powerful feature. Suppose you have fifty web servers in production, and you have reason to suspect that one of them isn’t running nginx as it should be. Assuming you have all fifty servers listed in your inventory file, in the “webs” group, you can check them all with one line [1]:

$ ansible webs -i 'production/inventory' -u ubuntu -s --forks=50 -a 'service nginx status'

and back come fifty responses:

web-37.example.com | success | rc=0 >>
 * nginx is running
web-76.example.com | success | rc=0 >>
 * nginx is not running
web-3.example.com | success | rc=0 >>
 * nginx is running
...

Or you could try to fix some weird inconsistency on your test cluster by gracefully reloading the nginx configuration:

$ ansible webs -i 'mytestcluster/inventory' -u ubuntu -s -a 'service nginx reload'

As an Ansible beginner, though, your most common one-liner will be the setup module. This confusingly-named module asks a host to list all of its facts: Useful variables which describe its status. Give it one hostname at a time, and brace yourself for the flood of information:

$ ansible all -i 'localhost,' -c local -m setup

localhost | success >> {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.0.2.15", 
            "192.168.33.11"
        ], 
        "ansible_all_ipv6_addresses": [
            "fe80::a00:27ff:fe9c:73f6"
        ], 
        "ansible_architecture": "x86_64", 
        "ansible_bios_date": "12/01/2006", 
        "ansible_bios_version": "VirtualBox", 
        "ansible_cmdline": {
            "BOOT_IMAGE": "/vmlinuz-3.13.0-34-generic", 
            "quiet": true, 
            "ro": true, 
            "root": "/dev/mapper/trusty64--distro--vg-root"
        },
...
 

These facts can be used in Ansible templates or to configure optional features, but that’s another tutorial for another day.


  1. Here, -u ubuntu means “connect as the Ubuntu user” and -s means “use sudo to execute this task as root”. --forks=50, which tells Ansible to try contacting up to fifty servers in parallel, might be just a little too metal, but the docs claim otherwise: "some [users] set this to 500 or more". ↩

Questions or problems with this article? Write me at [email protected].

For a thorough head-to-head comparison of four modern configuration management tools including Ansible, Puppet, and Chef, I recommend Taste Test, by Matt Jaynes. Spoiler Alert: Ansible has a delicious flavor.

Want to use Ansible to deploy entire applications? Sign up for the mailing list below, and get new tutorials as they’re published.