Tuesday, January 5, 2016

Infrastructure automation with Ansible and Docker: part 1

Up until now, I've been using Packer along with a bunch of shell provisioning scripts to set up development and production environments, but as the complexity of environments (no. of packages/tools/dependencies, configurations and data) increased along with the need for rapid creation/deletion of these environments I decided to give some automation tools out there a try.
I'm sure you've already heard enough about Docker and it's offerings. I have been using it for a few specific cases where it really came in handy for deploying and experimenting with different apps in isolated containers while keeping the host machine clean and tidy. I also used the Gradle Docker plugin to package my .war artifacts into Docker Tomcat images for deployment into the Docker host machine. However, moving on I feel that should be job for Jenkins.
The interesting bit is all about Ansible, which you've also probably heard of. Now, although it might seem quite young as compared to it's heavyweight competitors like Puppet and Chef, it caught my attention early on for one simple yet very important reason: simplicity.
Ansible, at it's core uses SSH, which comes natural to most of us when it comes to managing infrastructure. Don't be fooled by the simplicity factor, as when used in conjunction with the composability of "playbooks", can make your life a lot easier!
With the help of forks, it can be quite handy, when it comes to running otherwise cumbersome tasks such as upgrading software/packages on tens or even thousands of machines (see how twitter uses it for managing thousands of machines).
Now, as for the using Ansible and Docker together, I came to the conclusion that building Docker images should be left out of Ansible, which is what Michael Dehaan, one of the creators of Ansible recommends. So as I mentioned before, leave that job to your build automation system.
To get started, here is a simple Ansible playbook for installing docker on a local VM.
The installation steps for Docker have been taken from Docker documentation. You can find the steps for installing Ansible here.
---
# file: ubuntu_docker.yml
# Playbook for setting up docker

-
  hosts: all

  sudo: True

  user: vagrant

  tasks:
    - name: Check that the server is live
      action: ping

    - name: Add docker gpg key
      apt_key:
        keyserver: "hkp://p80.pool.sks-keyservers.net:80"
        id: "58118E89F3A912897C070ADBF76221572C52609D"
        state: present

    - name: Add docker repo
      apt_repository:
        repo: "deb https://apt.dockerproject.org/repo ubuntu-trusty main"
        state: present

    - name: Install the required packages
      apt: pkg={{ item }} state=present update_cache=yes
      with_items:
        - linux-image-extra-{{ ansible_kernel }}
        - docker-engine

    - name: Update Ubuntu firewall to allow forwarding
      lineinfile:
        dest: /etc/default/ufw
        regexp: DEFAULT_FORWARD_POLICY=
        line: DEFAULT_FORWARD_POLICY="ACCEPT"
        state: present

    - name: Reload ufw
      ufw:
        state: reloaded

    - name: Check docker daemon running
      service:
        name: docker
        state: started 
  
For running this playbook:
ansible-playbook -i local --ask-pass ubuntu_docker.yml
Note that you either need a file in the same directory with your target machines or an ANSIBLE_INVENTORY environment variable pointing to a file where you have stored list of your target machines.
For this example I used a file called "local" with the following contents:
dev-vm ansible_ssh_host=192.168.100.100 
Also, the --ask-pass flag would ask for the target machine password when the playbook is run. 

For further information on Ansible playbook best practices and discovering playbook syntax read here and here.