Ansistrano, deploying Drupal with Ansible

Posted by alexmoreno on Mon, 04/04/2016 - 21:07

Last months have been pretty busy here at BBC Worldwide with some of the projects we've been involved in.

In my case, one of the main areas I’ve been trying to improve (apart of the daily busy routine) is our devops / CI approach. The previous approach was using Chef, but it was not really adopted by the developers (which meant that after some months, the system was broken) and we were lacking some fundamental things (like integration with the production system, a real CI flow, etc…).

On the other hand, my previous experience with puppet was that the system grew too much to be easily maintained, specially if you don’t have a big dedicated devops team. To sum up, during last months research I was pretty amazed with Ansible, it's amazingly simple, very flexible and a truly joy to maintain. So, to me, the way forward was Ansible.

So, we started rewriting some of the old broken features in Chef and, sooner than later, we found ourselves reinventing the wheel. Some features were custom things, like triggering "drush cc all”, executing grunt, phpcs, etc... but obviously lots others were not… well, we are not the first team in needing an apache and mysql database, are we?

And deployment is one of these things that I was not too keen to reinvent. It would be pretty simple with Ansible, to be honest, but why not use something that is already tested, works well and at the same time give some love back to the community? Enter Ansistrano.

Ansistrano is a deployment tool based in Ansible. The relation with Capistrano ends just here, with the name similarity (no code base was harmed during the process). For more information about Ansistrano please visit the official repository:

- Step 0. Install ansistrano (deploy and you’ll probably be interested in the rollback too):

ansible-galaxy install carlosbuenosvinos.ansistrano-deploy carlosbuenosvinos.ansistrano-rollback

- Step 0. Clone the deploy repo in the folder you’ll be using the app. Let’s say we’ll be working in ~/my-app-build :

git clone ansistrano-deploy

Notice that we are cloning the deploy repository.

We’ll be using the latest stable release, list tags:

vagrant@vagrant-ubuntu-trusty-64:~/my-app-build$ git tag

at the moment, the latest stable release is 1.4.1, so:

git checkout 1.4.1

Step 1. Now, the ‘tricky part’. We are going to write a file where we’ll execute all the magic. Let’s call it build-my-app.yml:

- name: Deploy example app to
  hosts: all
    ansistrano_deploy_to: "/var/www/myapp.local"
    ansistrano_keep_releases: 3
    ansistrano_deploy_via: "git"
    ## GIT pull strategy
    ansistrano_git_branch: develop
    ansistrano_git_identity_key_path: ""

    - { role: ansistrano-deploy }

Notice that the role we have at the end is just the deploy repository cloned from Ansistrano ( You can change the name, as I've done in this example (ansistrano-deploy) but you'll have to upgrade the roles variable in your yml (again, as shown in this example).  

Step 2. Run deploy:

ansible-playbook -i "localhost," -c local build-my-app.yml -vvv

Instead of specifying localhost you could also use a hosts file, as recommended in the official documentation. This is also going to give you the freedom of deploying your app not just in your local environment, but potentially in any remote server, i.e. aws, digital ocean, etc...

Step 3 (optional). From here, the limit is the sky. In our case, we have some complex deployments running grunt, drush, clearing cache… The advantage here is that ansible is pretty flexible, so you could for example add your own tasks in your deploy file (build-my-app.yml), i.e.:

  - include: goodfood/tasks/apt.yml

The right, official way of doing this is using the hooks that ansistrano itself offers you:

  # Hooks: custom tasks if you need them
  ansistrano_before_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-setup-tasks.yml"
  ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-setup-tasks.yml"
  ansistrano_before_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-update-code-tasks.yml"
  ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-update-code-tasks.yml"
  ansistrano_before_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-shared-tasks.yml"
  ansistrano_after_symlink_shared_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-shared-tasks.yml"
  ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-symlink-tasks.yml"
  ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-symlink-tasks.yml"
  ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-before-cleanup-tasks.yml"
  ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}/<your-deployment-config>/my-after-cleanup-tasks.yml"

where playbook_dir is the folder where your yml file lives.

That's it. Simple, elegant, and really powerful, I love it :-). Next episode, we'll talk about the rollback artifact.

Notes: Amended Step 0, as you don't need to install the playbook through the ansible galaxy, just cloning would do. Thank you Carlos Buenosvinos.