Docker, Ansible and Vagrant

It's a great trio! I will try to explain why I think that combination of Ansible, Docker and Vagrant is a really good fit for your Development and Infrastructure team. It's all based on experience from an amazing project in Gamesys.

Ansible is a new cool kid in town. It's not as powerful customizable as Puppet or Chef, but in my opinion that is its biggest strengt. But automation engine is not enough. Docker provides you a platform for deploying your applications, but on it's own it doesn't solve all the problems. Finally Vagrant does amazing job in managing your local VMs.

When it comes to Docker there are many ways for building images - from plain Dockerfiles to solutions like Packer. Ansible gives you an option to do everything using the same tool. And that is Ansible's real power.

Why Ansible

Ansible is simple, and by simple I don't mean limited, I mean easy to understand and easy for developers to contribute to playbooks. It's a tool truly driven by DevOps philosophy. It empowers developers to contribute to automation scripts.

Having experience with Chef and basic experience with Puppet I can honestly say that Ansible was by far most kindly welcomed by developers. Biggest part of it is easy way to test it on local VMs and plain yaml task files and playbooks with no room for custom and complicated ruby modules.

I can not stress enough, how important developers involvment was in the process of building this project and I truly believe that it wouldn't be possible without Ansible.

Start Locally with Vagrant

I always start new project with Vagrant VM. It's simple to setup and allows all developers to work in the same environment. Shared folders allows you to edit source code in your favourite IDE and test/build/run it inside VM.

My standard folder tree for my Vagrant VMs project is always the same, and you can always download it from here.

├── ansible
│   ├── playbooks
│   ├── roles
│   └── vars
├── bin
│   └── vagrant-inventory
├── machines
│   └── web
│       ├── Vagrantfile
│       ├── ansible.cfg -> ../../ansible.cfg
│       └── ansible -> ../../ansible
├── vagrant
│   └── config.rb
└── ansible.cfg

The idea is very simple, each server or group of servers in your infrastructure has it's own folder inside machines folder aka vagrant VM. You should always be in a position where you can test any changes locally! ALWAYS!

Working with Ansible and Vagrant Locally

You (as an ops engineer) can use bin/vagrant-inventory which will update default inventory file (defined in ansible.cfg) which makes it really easy to use Ansible with vagrant VMs like ansible web -a uptime

For developers it's good idea to add support for multiple provisioning types using environment variables like MODE=configure vagrant provision which in this example will only (re)generate configuration files on your VM.
It's always easier for developers to remember simple mode rather then multiple Ansible vars, tags, etc.

if ENV['MODE'] == 'configure'
    config.vm.provision 'ansible' do |ansible|
        ansible.playbook = "ansible/playbooks/vagrant.yml"
        ansible.tags = "configure"
        ansible.extra_vars = {
            hosts: "default",
            root: "/var/code/",
            environment: "test01"
# Vagrant style
cd machines/web
MODE=configure vagrant provision

# Ansible style
ansible-playbook -e "hosts=web root=/var/code/ environment=test01" --tags=configure ansible/playbooks/vagrant.yml

Using Ansible to build Docker images

I know it may sound controversial but we use Ansible to build docker images. And we use it recursively. First run prepares Dockerfile and all artefacts needed for build including Ansible roles and plays. The first run also creates any files that may be needed inside Docker container during build process including configuration files or even application itself. The second run happens inside Docker container and it's using artefacts prepared in the first run.

The same Ansible plays can be used to configure Vagrant VM and to build Docker images. Our base Docker image has Ansible installed which makes our Dockerfiles really simple. An example

FROM gamesys/base

ADD artefacts /tmp/artefacts
RUN cd /tmp/artefacts &&\
    ansible-playbook provision.yml &&\
    cp / && chmod +x / &&\
    rm -rf /tmp/artefacts &&\
    yum clean all



About Wojtek

All aspects of web applications, focusing mainly on performance, automation and user experience has been one of my life’s passions. My interests cover the fields of continues integration and deployment, performance engineering / testing / monitoring, service-oriented applications, databases and distributed systems.