Michael

Ansible: a closer look

Ansible is a great tool for automating tasks executed either locally or remotely. Today we will talk about using Ansible in conjunction with Vagrant for setting up a local development environment.

All our Ansible playbooks can be split into 4 groups:

  • Pre-provision - executed on every VM bootup
  • Provision - executed when VM is being created, or when executing vagrant provision in the terminal
  • Virtual hosts - executed after VM was booted up, or when executing vagrant reload in the terminal
  • Cleanup - executed when VM is being shut down or destroyed

All of these playbooks are launched by Vagrant. We will use Provision scrips to walk you through the basics of what Ansible has to offer.

It all starts with provision/playbooks/site.yml.

You need to specify which hosts that playbook will run on (defined in an inventory file). We have specified the option "all", which is defined in provision/host.ini. More info on inventory is available here.

---
- hosts: all

You can define your own variables for later use in playbooks, and you can import a YAML file that already has all the variables defined. Variables are used like this - {{ sites_dir }}.

---
vars:
  sites_dir: /home/vagrant/sites
 
vars_files:
  - ../settings.yml

Roles is a great way of keeping your playbooks organised.

roles:
  - base
  - nginx
  - php
  - mysql
  - adminer
  - codeception
  - front

When Ansible is going through site.yml, the first role that is being executed is base. Ansible will automatically look for the file playbooks/roles/base/tasks/main.yml, which in its turn includes a bunch of other playbooks.

---
- include: aptget.yml
- include: bashrc.yml
- include: gitconfig.yml
- include: nfs.yml
- include: vimrc.yml

Out of the box Ansible supports a ton of modules, which significantly simplify writing playbooks. You can find a great example of installing packages in aptget.yml. Ansible loops through the list and makes sure that the packages are installed. Running commands as sudo is a breeze. Please note that if that package is already installed, Ansible skips it and goes to the next item. That saves heaps of time when you're re-provisioning for whatever reason.

---
- name: apt-get | apt-get update
  apt: update_cache=yes cache_valid_time=3600
  tags: aptget
  sudo: true
 
- name: apt-get | ensure core packages are installed
  apt: pkg={{ item }} state=installed
  tags: aptget
  sudo: true
  with_items:
    - atool
    - atop
    - curl
    - git
    - imagemagick
    - make
    - mc
    - python-software-properties
    - tig
    - tree
    - vim
    - unzip

You will be able to write most of the tasks using modules, however sometimes the actual terminal command works best.

- name: Git Config | Install git completion
  raw: curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash -o ~/.git-completion.bash
  tags: git

By using the template module, you can provide your own content for files.

- name: Git Config | add .gitconfig file
  template: src=gitconfig.j2 dest=/home/{{ user }}/.gitconfig
  tags: git
  sudo: true

Templates are processed by the Jinja2 templating language, and the great thing is that you can use your variables in templates, which makes templates dynamic.

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
port={{ mysql_port }}
max_allowed_packet={{ mysql_max_allowed_packet }}
 
{{ innodb_file_per_table }}
character_set_server={{ mysql_character_set_server }}
collation_server={{ mysql_collation_server }}
 
bind-address=0.0.0.0
 
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

Once in a while you will face situations when the next action depends on the result of the previous one. In our case, we check if drush has been installed, and depending on the output we either proceed with the installation or skip it. In the first task we capture the output of the shell command into a variable, and install drush when that output contains "command not found".

---
- name: Drush | Check if Drush is installed (ignore if fails)
  raw: drush --version
  register: drush_installed
  ignore_errors: true
  tags: drush
 
- name: Drush | install drush via composer
  command: composer global require drush/drush:6.*
  when: drush_installed.stdout.find('command not found') != -1
  tags: drush
 
- name: Drush | set drush to executable
  command: chmod u+x drush chdir=/home/{{ user }}/.composer/vendor/drush/drush
  when: drush_installed.stdout.find('command not found') != -1
  tags: drush
 
- name: Drush | link up the drush command so everyone can run it
  file: src=/home/{{ user }}/.composer/vendor/drush/drush/drush dest=/usr/bin/drush state=link
  when: drush_installed.stdout.find('command not found') != -1
  tags: drush
  sudo: true

As you can see, Ansible makes scripting a walk in the park, which is why we love it. 

For more information and other modules please check http://docs.ansible.com/.

You can also check out:

More blog posts by Michael Kudenko

When you're developing a system that is supposed to work with multiple content types (or any other data structures), polymorphism can be quite helpful. Today we'll talk about how to organise your database schema and use Eloquent to implement polymorphic relations between your models.
Previously we have talked about the benefits of using virtual machines for local development and how Vagrant and Ansible help you manage your VMs. In this post we will talk about the Vagrant part of our setup.
Here at Webscope we pay a great deal of attention to our development process. We are working hard to make it as smooth and efficient as possible. We find that developing locally, as opposed to remotely, provides greater benefits. In this post we'll talk about streamlining our local development with Vagrant and Ansible.