Easy web deployments with Ansistrano

Easy web deployments with Ansistrano

At my previous work we've been using Capistrano for years and it's not particularly great. It does work well, but it really feels like messy Ruby scripting with messy documentation.

We use Capistrano for a specific kind of projects which are usually PHP or Node deployed on multiple non-autoscaled webservers.

Recently for school I had two Laravel projects to make. One of the requirements was to deploy them on a real webserver with a valid domain name, HTTPS, etc.

I've deployed PHP software countless times, even for myself, so this wasn't a hurdle. However after manually installing everything and doing git pull every time I wanted to "deploy" a new release, I figured I could do better, even if it was technically sufficient for my school project.

That was a good opportunity to try Ansistrano! Ansistrano is actually an Ansible role, but it's messy in its own way because IMHO it's not how Ansible is supposed to work.

Usually Ansible is supposed to be idempotent, which means if I run the same task twice, nothing will change the second time. Ansistrano is not idempotent in any way because it will make a new release every single time even if the source code didn't change. I don't mean to say this is a bad way of doing things though, because Ansistrano works really well. It's just that it feels a bit hacky (but it's still more pleasant to use than Capistrano).

The good thing about being an Ansible role though is that it integrates very well with existing Ansible configuration.

In my case, I had to deploy two Laravel projects on a single webserver. Luckily I already have a bunch of Ansible roles lying around so I was able to setup the whole middleware stack with Ansible!

That being done, I made another playbook for the application deployment with Ansistrano.

It's actually mostly variables:

---
- name: "Deploy {{ app_name }} laravel app"
  hosts: ffw
  vars:
    ansistrano_deploy_to: "/srv/{{ app_name }}"
    ansistrano_keep_releases: 3
    ansistrano_deploy_via: git
    ansistrano_git_repo: "https://github.com/fight-food-waste/{{ app_name }}.git"
    ansistrano_before_setup_tasks_file: "{{ playbook_dir }}/tasks/pre-deploy.yml"
    ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/tasks/post-deploy.yml"

  roles:
    - { role: ansistrano.deploy, tags: deploy }

My git repositories are public so it's even more straightforward. By the way Ansistrano supports many other sources such as SVN, rsync, HTTP, S3...

Then I'll deploy my app with:

ansible-playbook pb-laravel-app.yml -e app_name=collects

On the server, the folder architecture will look like that:

-- /srv/collects
|-- current -> /serv/collects/releases/20190512131539
|-- releases
| |-- 20190512131539
| |-- 20190509150741
| |-- 20190509145325
|-- shared

Every time the role is run, a new directory is created inreleases and the symlink to current is updated.

There are multiple phases during an Ansistrano deployement:

The Ansistrano workflow

You can import tasks files using variables, for example ansistrano_before_setup_tasks_file which will be the first step of all the workflow. In this case I install composer before setup and I run composer install after symlink (along with a bunch of other tasks). What's really cool is that you can use any Ansible task you want, which I find far better than using run to pass shell commands with Capistrano.

Overall I'm very satisfied with Ansistrano and I will definitely use it again if it fits the use case.

You can find my whole Ansible configuration example at fight-food-waste/deploy.

Stanislas
Author
Stanislas
I like building things with code and computers

Comments

1Atom feed
Markdown supported
  1. gawa

    I agree with you : it's not idempotent, not real ansible, hacky.

    In prehistoric times, sysadmins used ssh, ftp, cp, mv, tar, rsync and emacs/vim to deploy new releases.

    Then they used quick and dirty (and personnal) bash scripts to automate those things. Probably some sed and awk were thrown into the mix in order to edit the "documentRoot" path in apache confs and reload it, for the most sophisticated scripts, in order to minimize the downtime.

    Then came the versionning control systems (cvs, svn, git). Bash scripts evolved.

    But so did the websites, now called "web apps". With the web 2.0, CMS frameworks became a thing : don't write raw code anymore, use wordpress, symfony, ruby on rails, jango, you name it! And so came setup, pre-tasks and post-tasks related to installing dependancies, cache, warmup, and database schemas changes.

    Capistrano and ansistrano were invented to rationalize all this mess. That was clever, a framework for deploying vanilla CMS and execute common tasks, assuming one does not diverge from the defaults and follow good practices. It never happen though. "For the particularity of my very unique project not like others, I need a custom feature, a custom step!". Back to scripting. Except this time it's in ruby or python.

    In the end, we might well need comprehensive and documented libraries for very common tasks in popular scripting languages : remote copy (scp wrapper, handling all the security stuff) , local copy, symlink, run, run with timeout, run concurrently... Maybe scripts with simple building blocks would be less messy than a project trying to "deploy the whole symfony" with hidden default functions. What I describe is basicaly what we did with capistrano in my previous job : we mostly leveraged it only for the ssh abstraction.

    My guess is that no one will ever come with a satisfying KISS solution to that because the problem changed, again. "Web apps" are now "multiple micro stateless apps". All the efforts for graceful deployements are now on containers and containers orchestrators.