Stephen Newey

Django app deployer

16 February 2015

Continuing on the theme of my previous posts, I’d like to give an overview of the automation processes I’ve built to deploy Django sites.

The tool I’ve built for my client is a simple Django application with models mapping clients, servers, projects and domains. Using Django’s admin app provides a convenient user interface to set things up. We run it on a dedicated EC2 micro instance and initiate all it’s actions from there.

Current configuration tool screenshot

A series of Django management commands provide host data to Ansible in JSON format, per-server Ansible playbooks for setting up projects, and configurations built from Django templates for supervisor, nginx and lsyncd.

There’s an Ansible playbook for creating an EC2 instance, configuring it with the latest nginx stable release and pointing an elastic IP address to it for handling non-www redirects (see Cheap, resilient hosting).

There are a series of Ansible playbooks for applying base configurations to manually created Linode and DigitalOcean instances. These install some base software requirements (Ubuntu packages for Python and various associated build tools), and install and configure MySQL and PostgreSQL, including replication. These playbooks are usually only run once when a new pair of host instances is configured.

Additionally, regularly used playbooks receive lists of projects for each server in the host data and create the various directories required for deployment (the project itself, static and media directories, a virtualenv). They also interact with Amazon Route 53 to dynamically set DNS entries.

These playbooks are usually executed during various tasks in the tool’s fabfile.

Fabric allows me to create step-by-step processes for grabbing a project’s source and deploying it to our servers. Ansible lets me create descriptions of how those servers should be configured that can be idempotently applied again and again.

The fabfile’s tasks include:

  • checking out the latest copy of the project’s specified branch
  • rsyncing it to both the primary and failover hosts
  • reading and installing the project’s Python requirements, both named packages and internal version controlled ones
  • populating nginx configuration on the app hosts and the redirect host
  • building the project environment (all credentials, paths, etc are specified in environment variables)
  • deploying a uWSGI vassal configuration (in JSON) for the project
  • creating the supervisor configuration to start lsyncd to keep media synced from the primary to failover host, and optionally run the Celery worker and beat process if required
  • running the various syncdb/migrate/collectstatic tasks required for most Django projects
  • writing cron jobs, defineable in the admin tool

In practice, using this tool as a developer to deploy a project entails running two commands. First, to get everything setup:

fab deploy_project:example

That takes anything up to about 5 minutes depending on the project dependencies.

And then, in most cases where a simple code update and syncdb/migrate/collectstatic and uWSGI reload are required:

fab fast_deploy_project:example

Which usually runs in about 30 seconds.

What’s next

In practice this tool has worked out well. It’s needed deploying a few projects to iron out the kinks and get it where it’s feature complete enough for our needs.

There are a few things that bug me about it though:

  • Storing configuration in the database makes it less portable than I’d like it to be. Working on it locally whilst making changes requires syncing over the database from the server we run it on. It’d be nice if configuration were in easily read files that could be kept in version control.
  • I have to manually create the DigitalOcean and Linode instances and do a small amount of setup work. It would be nice to use their APIs (and Ansible’s support them) to remove this step.
  • I don’t like that I run ansible-playbook manually for some steps. It would be nice to capture those into the Fabric file.
  • It’s somewhat tied to our deployment process and the kinds of projects we build. It would be nice to make it a little more generic, support multiple failover instances per project, etc.
  • It’s paid for by my client, so it’s not mine to open source and use elsewhere!

For all those reasons, especially the last one, I plan to start a new project from scratch to build a new deployment tool in the open. My next post will cover what I’d like to achieve with it.

Tags: development, hosting, python, deployment, devops, django