Rebuilding Capistrano like deployment with Ansible

Probably you are familiar with Capistrano. It’s a deployment tool written in Ruby. I used it in several projects to deploy Rails applications. For the default Rails stack with a SQL Database it always worked fine. For a non default Rails stack, not always so good. This is how a capistrano deployment directory looks like on the server.

Screen Shot 2014-09-24 at 19.05.22

The current symlink links to a timestamped directory in the “releases” directory, which contains the actual application code. The “releases” directory looks like this:

Screen Shot 2014-09-24 at 19.05.49

Each subdirectory contains the timestamp of the deployment as name. The “shared” directory looks like this:

Screen Shot 2014-09-24 at 19.06.09

It contains directories which are shared over all “release” directories. For example the “log” directory for logging outputs or the “pid” directory which contains the pid of the current running ruby application server, for example unicorn.

Capistrano creates these directory structure automatically for you. And every time you perform a new deployment it creates a new timestamped directory under “releases” and if the deployment was successfull it links the “current” to the newest “release” directory under “releases”.

A couple months ago I learned Ansible, to automate the whole IT Infrastructure of VersionEye. Ansible is really great! I automated everything with it. But there was a break in the flow. Usually I executed an Ansible script to setup a new Server and then I had to execute capistrano to deploy the application. One day I thought why not implementing the whole deployment with Ansible as well? Why not just execute one single command which sets up EVERYTHING?

So I did. I implemented a capistrano like deployment with Ansible. This is how I did it.

First of all I ensure that the “log” and “pid” directories exist in the “shared” folder. The following commands will create them if they do not exist. These commands create the whole directory path if they do not exist. If they exist nothing happens.

- name: Create log directory
  file: >
    state=directory
    owner=ubuntu
    group=ubuntu
    recurse=yes
    path="/var/www/versioneye/shared/log"

- name: Create pids directory
  file: >
    state=directory
    owner=ubuntu
    group=ubuntu
    recurse=yes
    path="/var/www/versioneye/shared/pids"

The next part is a bit more tricky, because we need a timestamp as a variable. This is how it works with Ansible.

- name: Get release timestamp
  command: date +%Y%m%d%H%M%S
  register: timestamp

These command takes the current timestamp and registers it in the “timestamp” variable.  Now we can use the variable to create a new variable with the full path to the new “release” directory.

- name: Name release directory
  command: echo "/var/www/versioneye/releases/{{ timestamp.stdout }}"
  register: release_path

And now we can create the new “release” directory.

- name: Create release directory
  file: >
  state=directory
  owner=ubuntu
  group=ubuntu
  recurse=yes
  path={{ release_path.stdout }}

Allright. Now in the next step we can checkout our source code from git into the new “release” directory we just created.

- name: checkout git repo into release directory
  git: >
    repo=git@github.com:versioneye/versioneye.git
    dest="{{ release_path.stdout }}"
    version=master
    accept_hostkey=yes
    sudo: no

Remember. Ansible works via SSH tunneling. With the right configuration you can auto forward your SSH Agent. That means if you are able to check out that git repository on your localhost, you will be able to check it out on any remote server via Ansible as well.

Now we want to overwrite the “log” and the “pids” directory in the application directory and link them to our “shared” folder.

- name: link log directory
  file: >
    state=link
    path="{{ release_path.stdout }}/log"
    src="/var/www/versioneye/shared/log"
    sudo: no
- name: link pids directory
  file: >
    state=link
    path="{{ release_path.stdout }}/pids"
    src="/var/www/versioneye/shared/pids"
    sudo: no

Now let’s install the dependencies.

- name: install dependencies
  shell: cd {{ release_path.stdout }}; bundle install
  sudo: no

And pre compile the assets.

- name: assets precompile
  shell: cd {{ release_path.stdout }}; bundle exec rake assets:precompile --trace
  sudo: no

And finally update the “current” symlink and restart Unicorn, the ruby application server.

- name: Update app version
  file: >
    state=link
    path=/var/www/versioneye/current
    src={{ release_path.stdout }}
    notify: restart unicorn

That’s it.

This is how VersionEye was deployed for a couple months, before we moved on to Docker containers. Now we use Ansible to deploy Docker Containers. But that’s another blog post 😉