I have seen great posts about Elixir release deployments lately and I would like to share my experience deploying Elixir releases to production. After testing different approaches (from git cloning code in the target machine to building Docker images), I decided to go with, in my opinion, a simpler approach.
Deployment is one of the most important things in a project. It needs to be easy, fast and painless to rollback when things go wrong. Although it’s critical part of a project, it is sometimes overlooked. It’s not hard to find teams that deploy to a production-like environment only when they will release the project to the public, after months of development and thousands of commits.
Delaying a project deployment until you need to expose your project to the world, brings many unnecessary risks, like:
All those risks can be mitigated if the deployment starts early, is automated and it’s performed by everyone throughout the development phase.
The goals when defining the deployment strategy of this project, was to offer a really simple and fast way of getting a new release shipped. The TODO list while researching and implementing it was:
Before we get into the deployment, I think it’s important to give you some context about the project development cycle, since it has a direct impact on how the deploy is done. The numbers on the image means:
The infrastructure that powers this project is quite standard, but let’s take a look on it into more details:
This project is accessed both by users (using a browser) and by the mobile applications (iOS and Android), which consume the API. As highlighted by the item 1 on the image above, both of them can perform requests to the HTTP load balancer.
The load balancer spreads the load to all the machines available at a given time (as highlighted by the item 2 on the image).
The infrastructure has its creation and setup automated by Ansible. All the information about the load balancers, web servers, databases, caches, etc are stored and managed by Ansible.
For that reason, using Ansible also for deployments seemed to be a really good fit for our case.
For a post about “deployment”, we have spent a considerable amount of time talking about different things, but not really getting into the main topic… deployment.
As mentioned earlier, we used Ansible for setting all the machines used by this project. Deploying a new version of the project, means running an Ansible Playbook, that contains all the tasks to be applied to the target machines.
That’s the command that someone (developer, web, Slack bot) runs in order to deploy a new version:
Let’s break this command down into smaller parts:
--ask-vault-pass means we are going to use information stored inside one of the Ansible vaults that belongs to the project. Ansible vaults allow the storage of confidential information (passwords, keys, tokens) inside our repository, without exposing it;
version=[COMMIT] is the commit version of the project that we would like to deploy;
-i [ENV] is the Ansible inventory option. It contains information about the environment we are going to deploy to. In that project, the inventories available are:
When this command runs, several tasks are applied to the machines present in the environment inventory. Using the image above, let’s see what happens in each stage:
The first thing that Ansible does, is to remove the server where the deployment is going to happen from the load balancer pool (item 1). It prevents incoming requests being handled by that server;
After Ansible successfully removes the server from the load balancer pool, it performs a number of actions (item 2):
Migratoris a helper module) (
bin/project rpc Elixir.Project.Migrator migrate);
As soon as the new version is running and the smoke test has successfully passed, then Ansible puts the server back in the load balancer pool (item 3), making it available to new incoming requests;
It repeats the same process (except the migration part, that is only executed once) to all the machines inside the load balancer pool.
Thanks to Elixir releases (and Exrm), the entire deployment process takes less than 30 seconds per machine (when not dealing with long-running migrations) and it’s pretty straightforward.
Are you deploying Elixir to production? What’s your approach? Feel free to send me a message.
Today I released "Versioned APIs with Phoenix" free book. It covers three different strategies on API versioning with Phoenix...
This is the second part of the API versioning series. This post shows how to achieve API versioning using the Accept header...
API versioning allows you to response with different content, based on the information sent by the client. Let's see how to do that with Phoenix...