/dev/random

Vagrant and Ansible (part 2)

In my previous post, Vagrant and Ansible (part1), I wrote about the Vagrant configuration. Let's now move on to the Ansible part.

Basic Ansible configuration

Ansible needs at least two configuration files, as you can see in the Vagrantfile:

  • an inventory
  • a playbook

The inventory assigns machine IPs to groups, the playbook configures the hosts or the groups.

Inventory file

This is our inventory file (provision/ansible/development):

[development]
192.168.33.20

Quite simple. It just assigns our guest IP (192.168.33.20) to a group we call development.

Playbook

The playbook is where the fun begins. Here it is (provision/ansible/site.yml):

{% raw %}

- hosts: all
  sudo: yes

  handlers:
    - name: restart ssh
      service: name=ssh state=restarted
    - name: restart apache
      service: name=apache2 state=restarted

  tasks:
    - name: Update Apt cache
      apt: update_cache=yes
    - name: Update Installed packages
      apt: upgrade=yes

    - name: Install Apache+PHP
      apt: pkg={{ item }} state=latest
      with_items:
        - libapache2-mod-php5
        - php5-mysql
        - php5-sqlite
        - php5-cli
        - php5-json
        - php5-gd
        - php5-curl

    - name: Disable unused apache modules
      command: a2dismod autoindex cgi env

    - name: Enable required apache modules
      command: a2enmod rewrite

    - name: copy apache vhost file for SITE1
      template: src=guest/etc/apache2/sites-available/site_1
                dest=/etc/apache2/sites-available/site_1
                owner=root
                group=root
                mode=0644

    - name: copy apache vhost file for SITE2
      template: src=guest/etc/apache2/sites-available/site_2
                dest=/etc/apache2/sites-available/site_2
                owner=root
                group=root
                mode=0644

    - name: enable apache2 site
      command: /usr/sbin/a2ensite site_1 site_2
      notify:
        - restart apache

{% endraw %}

It's in YAML format. The hierarchy is defined by indentation level.

Let's split it to understand what it does (but it should be clear enough).

- hosts: all
  sudo: yes

This begins defining what we want to configure (all of our hosts: just one!) and telling ansible it needs to prepend sudo to every command, as we don't have direct access as root to the target guest.

handlers:
  - name: restart ssh
    service: name=ssh state=restarted
  - name: restart apache
    service: name=apache2 state=restarted

This defines a couple of “services”. Consider them like some kind of function you can call later to do something on the target. In our case we have two functions: one to restart ssh and one to restart apache.

tasks:

This row defines the beginning of the operations we want to execute on the target while provisioning it.

- name: Update Apt cache
  apt: update_cache=yes
- name: Update Installed packages
  apt: upgrade=yes

The first rule uses the apt module included in ansible to access apt-get on the target and update its cache. Simply put: apt-get update.

The name row defines what appears on the screen while provisioning.

The second rule upgrades all the installed packages, even those in the base image.

{% raw %}

- name: Install Apache+PHP
  apt: pkg={{ item }} state=latest
  with_items:
    - libapache2-mod-php5
    - php5-mysql
    - php5-sqlite
    - php5-cli
    - php5-json
    - php5-gd
    - php5-curl

{% endraw %}

This defines which packages to install on the target. It uses Ansible's apt module to install the packages, and the items cycle to allow us to install all the packages in a single run, instead of creating a configuration section for each one of them.

It also makes sure that each package gets updated if it's already installed (state=latest) if we provision the target again.

The packages list naturally includes the packages your application needs, and the dependencies are resolved as usual by Apt.

- name: Disable unused apache modules
  command: a2dismod autoindex cgi env

- name: Enable required apache modules
  command: a2enmod rewrite

Here we can see how to launch arbitrary commands on the target machine. We are using it to disable unused/dangerous apache modules and install the ones we need.

- name: copy apache vhost file for SITE1
  template: src=guest/etc/apache2/sites-available/site_1
            dest=/etc/apache2/sites-available/site_1
            owner=root
            group=root
            mode=0644

- name: copy apache vhost file for SITE2
  template: src=guest/etc/apache2/sites-available/site_2
            dest=/etc/apache2/sites-available/site_2
            owner=root
            group=root
            mode=0644

Ansible can copy files from your host machine to the target. We are using it to copy the Apache's Virtualhost configuration files in the sites-available directory and setting the correct permissions.

Remember that our guest machine is a Ubuntu 12.04 Linux installation. If you are using a different distribution, the dest paths could change.

- name: enable apache2 site
  command: /usr/sbin/a2ensite site_1 site_2
  notify:
    - restart apache

Finally we are using the command feature again to enable the Virtualhosts, or make sure they are enabled. We are also using the notify check to call a function (see above) when the command finishes, so that, in this case, we restart Apache after the Virtualhosts are enabled.

Apache Virtualhosts

The Apache Virtualhost configuration are no different from the standard. For example:

<VirtualHost *:80>
    ServerAdmin root@example.com
    ServerName site1.example.com
    ServerAlias www.site1.example.com

    DocumentRoot /var/www/sites/site_1/

    <Directory /var/www/sites>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    </Directory>

    CustomLog ${APACHE_LOG_DIR}/site1-access.log combined
    ErrorLog ${APACHE_LOG_DIR}/site1-error.log

    # Possible values include: debug, info, notice, warn, error, crit,
    # alert, emerg.
    LogLevel warn

    # supress php errors
    php_flag display_startup_errors off
    php_flag display_errors off
    php_flag html_errors off

    # enable PHP error logging
    php_flag  log_errors on
    php_value error_log /var/log/site1-PHP-errors.log
</VirtualHost>

In our case, we are targeting a site inside the directory exported from our host to the guest by Virtualbox (see Vagrantfile: config.vm.synced_folder).

One More Thing

This is the directory structure for our whole project. It could help understand it better:

~/Vagrant/Dev/
 |- Vagrantfile
 |- provision/
     |- ansible/
         |- development
         |- site.yml
         |- guest/
             |- etc/
                 |- apache/
                     |- sites-available/
                         |- site_1
                         |- site_2

It's not mandatory. There is no imposed structure. You could flatten it or expand it as needed. For example, you could have guest_development, guest_staging and guest_production instead of just guest if you want to have different configurations for three servers. Or call them huey, dewey and louie if you so prefer.

That's All, Folks!

This is a good start point to create one or more virtual machines to test your sites locally cloning the production server's configuration without the need to install a different OS on your developemnt machine.

But it can be used to create and deploy the staging or the production machine (or machines).

During the provision phase you could copy the application code in the correct directory, install MySQL, PostgreSQL, MongoDB, Redis, etc., and copy data from a dump or from another server.

Have fun!