david winter

Introduction to Ansible

I’ve been using Ansible now for just under six months. I love it. I used to use Puppet religlously, but with a change of job, I needed something simple to setup, to work with a range of different servers, and be easy for others to learn.

Puppet is very powerful, and I was very happy with it. I still use it for some of my projects, because it just doesn’t make sense to switch them over to Ansible unless there is a specific reason that warrants rewriting the manifests.

You may think Puppet will win over people who manage multiple servers, and that maybe Ansible can’t handle this*. You’d be absolutely wrong to make that assumption. Ansible is known to be used in setups managing in the range of 20,000 nodes. Ansible does’t require a complex master-slave setup, as Puppet does, that takes in itself hours to setup. And it’s coped perfectly with any setup I’ve encountered so far.

Provide Ansible with a list of machines to provision, and the specifics and it’ll just go ahead and do it.

Lets dive in.

Note: I’m using Vagrant for this demo. Though, all of the Ansible specific parts are not dependent on it.

Also, at time of writing, I’m using Vagrant 1.3.5 and Ansible 1.3.4.

Install Ansible with:

sudo easy_install pip
sudo pip install -U ansible

Get a base Ubuntu machine up and running:

mkdir ansible-demo
cd ansible-demo
vagrant init precise64 http://files.vagrantup.com/precise64.box

We’ll want to give the machine a dedicated IP address that we can have Ansible connect to, so in your Vagrantfile, ensure you have the following line:

config.vm.network :private_network, ip: "192.168.33.20"

config.vm.provision :ansible do |ansible|
  ansible.playbook = "private/ansible/site.yml"
  ansible.inventory_path = "private/ansible/development"
end

We’ve also told Vagrant that we want to use Ansible to provision our box.

Let’s create the directory structure for Ansible:

mkdir -p private/ansible

Now to tell Ansible about the machine we want it to provision. Add the following file private/ansible/development and add the following:

[development]
192.168.33.20

This adds our new Vagrant machine to the development group. We specify it’s IP address so Ansible knows how to connect to it.

Note: An inventory file isn’t actually required for Vagrant—it would create one automatically for us if we hadn’t—however, it’s a core part of Ansible, so worth using it with an explanation.

Now we’ll tell Ansible how to provision our Vagrant machine. Create the file private/ansible/site.yml and inside it add:

---
- hosts: all
  sudo: yes
  tasks:
    - name: Update Apt cache
      apt: update_cache=yes
      
    - name: Install Apache
      apt: pkg=apache2 state=present

Lets get the machine up and running:

vagrant up

After a while, you should see some output like:

[default] Running provisioner: ansible...

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************
ok: [192.168.33.20]

TASK: [Update Apt cache] ******************************************************
ok: [192.168.33.20]

TASK: [Install Apache] ********************************************************
changed: [192.168.33.20]

PLAY RECAP ********************************************************************
192.168.33.20              : ok=3    changed=1    unreachable=0    failed=0

If you now visit http://192.168.33.20 in your browser, you should see the amazing “It works!” Apache default host file.

If you hadn’t noticed already, site.yml is written in YAML. To run through the site.yml:

- hosts: all

This allows you to target specific groups for Anible to apply rules to. We’ve used the development group in the private/ansible/development file. You could also have webservers, databases, etc. For our introduction, we’ll just tell Ansible to target all hosts.

As we’re installing packages on Ubuntu, we need root privileges, so we tell Ansible to use sudo.

Next up we define a bunch of tasks to run. Tasks use Ansible modules to apply things to the machine. Each task you give a descriptive name which is used in the output when Ansible runs. Be as descriptive as possible.

We use the apt module. First of all, we tell Apt to update it’s cache so that it can install new packages.

Then we get the apache2 package installed. We tell Ansible to ensure it’s present so that on subsequent runs, it doesn’t need to try and install it again.

So… the Apache ‘It works!’ page isn’t that much fun. Lets get PHP up and running.

Create a file called test.php in the directory private/ansible/files with the contents:

<?php phpinfo();

At the bottom of site.yml we’ll include some more tasks:

- name: Add test php file
  copy: src=files/test.php dest=/var/www/test.php

- name: Install PHP
  apt: pkg=libapache2-mod-php5 state=present

We have two new tasks here. The first will copy the local test.php file to the remote machine into the specified destination. We then install the PHP module for Apache.

If we were to provision this now with Ansible, the packages would be installed and the file would be copied, however, we need to restart Apache so that it loads the PHP module.

We do this in Ansible with handlers.

At the bottom of site.yml add the following:

handlers:
  - name: Restart Apache
    service: name=apache2 state=restarted

We then want to reference this handler when we install the PHP module. Update the PHP apt task to be:

- name: Install PHP
  apt: pkg=libapache2-mod-php5 state=present
  notify: Restart Apache

We’ve added the notify part to the task. This will tell Ansible to run the Restart Apache handler when PHP is installed, which in turn will restart the Apache server making PHP become available.

At this stage it’s safe to run vagrant provision.

Once this has run, you’ll be able to visit http://192.168.33.20/test.php and you should see a PHP info page.

That’s nice. But we’ll be a bit more exciting.

In your Vagrantfile lets mount out Vagrant project to the virtual machine so that we can use files in our project. Add this below the :private_network line:

config.vm.synced_folder ".", "/vagrant", :nfs => true

This mounts our Vagrant project directory, denoted by the period . and on the virtual machine, mounts it to /vagrant.

Create a new file called wordpress.conf and put it inside the private/ansible/files directory with the following contents:

<VirtualHost *:80>
	DocumentRoot /vagrant/public
	ServerName wordpress.dev
</VirtualHost>

Go to WordPress.org and download the latest version. Extract the files and move the wordpress directory into your project and rename it to public.

Now add the following to your site.yml file below where we installed PHP:

- name: WordPress virtualhost
  copy: 
    src: files/wordpress.conf 
    dest: /etc/apache2/sites-available/wordpress.conf

- name: Enable WordPress virtualhost
  file:
    src: /etc/apache2/sites-available/wordpress.conf
    dest: /etc/apache2/sites-enabled/wordpress
    state: link
  notify: Restart Apache

- name: Disable default Apache virtualhost
  file:
    dest: /etc/apache2/sites-enabled/000-default
    state: absent

- name: Install MySQL
  apt: pkg=mysql-server state=present

- name: Install python-mysqldb
  apt: pkg=python-mysqldb state=present

- name: Install PHP MySQL bindings
  apt: pkg=php5-mysql state=present
  notify: Restart Apache

- name: Setup WordPress database
  mysql_db: name=wordpress state=present

It should be pretty clear what this does by just reading the name titles for each task! However, I’ll explain two of them.

We disable the default Apache virtual host so that we don’t have to worry about setting up a hostname in our /etc/hosts file. This is something you’ll more than likely do when you have a few different projects on your machine, but we’ll skip that for this demo.

We also install the python-mysqldb package. This is installed so that Ansible can then create databases for us with the mysql_db task. It’s an Ansible dependency that we have to include so that we get to use Ansible more.

Because we’ve changed the Vagrantfile we can’t just do a vagrant provision - we need to actually reboot the machine so that Vagrant can mount the /vagrant directory share. Because we’re using NFS for this, it’ll need sudo privileges, so you may be prompted for your password.

vagrant reload

Once the machine is running again, provision with Ansible:

vagrant provision

You can now visit http://192.168.33.20 and you should be preented with a WordPress installer. That’s it. Very little effort. You can use the default MySQL credentials to setup Wordpress:

Note: We’re not worrying about securing MySQL. This is just a development virtual machine, so we don’t have to worry about it. That’s why we use the root account with no password.

That’s a very quick, brief introduction to Ansible. In my next post, I’ll talk more about reusable Ansible tasks - roles.


Some very helpful websites to have in your arsenal when working with Ansible:


Update: Michael DeHaan—creator of Ansible—thought I should clarify my original sentence. Quite rightly so, as it could have been misinterpreted that I was suggesting Ansible couldn’t handle complex/large/master-slave setups. It can do all of them. The point I was trying to make is that it’s something Puppet users may think it can’t, and that they could make decisions not to use Ansible because of a misunderstanding.

November 23, 2013

Questions? Comments? Your 2¢'s? → @davidwinter