Deploying an Application

Before using Empire successfully it's important to understand its design and architecture. It's suggested you first read the Features & Design Goals document.

There are a few key things which your applications, and overall architecture, will need to keep in mind in order to be successful with Empire. Below is an exhaustive set of criteria your applications must meet in order to work with Empire. It's not that long and by no means locks you in to Empire. Structuring your application in this way provides many benefits, only one of which is being able to be managed by Empire.

For the impatient, there are two absolutes:

  • You must create a Procfile which defines how to run your application
  • If your application answers requests over the network, it must listen on port 8080, or better, on the $PORT environment variable. It's strongly suggested that you use the $PORT variable as port 8080 could change in future releases.

To see an example application, you can look at remind101/acme-inc

Procfile

Empire was modeled after the Heroku API. As such, many concepts and commands are similar. One concept which has bled over and which is a requirement is that of a Procfile. For your application to actually work, you must include a Procfile at the root level of your application.

Empire supports two Procfile formats; standard and extended. The standard format is probably what you're used to if you've used Heroku before and simply maps a process name to a command to run:

worker: celery -A tasks worker --loglevel=info

The extended format is Empire specific and allows you to define additional configuration for processes, like exposure settings (http/https/tcp/ssl), health checks, and more. An example of the Procfile above in the extended format would simply be:

worker:
  command: celery -A tasks worker --loglevel=info

The extended Procfile format is documented here.

Whichever format you use, the file would be named Procfile, and live at the directory root for your application.

$ tree .
.
├── Dockerfile
├── Procfile
├── myapplication/

Empire application types

Empire treats "web" and "non-web" services different. Here, a "web" process is defined as anything which needs to expose a port. The way to differentiate the two types of processes is easy.

Non-web processes

Let's start with non-web processes first since they're much simpler. If your service doesn't expose a port it doesn't need to be discovered by any other system. With that, all you need to do it deploy your application and let Empire manage it. The act of discovering other services is up to you (database, caches, etc.)

Web processes

If your service is going to be used by other systems, you'll need to run some type of server which exposes a port. In this scenario you'll need to name it web in your Procfile:

web: node server.js

or, using Django as an example:

web: python manage.py runserver

For web applications, Empire does a few things. There are multiple layers of routing, none of which are extremely complex.

Assume we have deployed a container named mycompany/awesome-app.

The routing to your application is handled as such inside of the VPC:


     http://awesome-app/
              +
              |
              |
              v
             ELB: port 80
              +
              |
              |
              v
            Minion: port [9000-10000]
              +
              |
              |
              v
          Container: port $PORT

There are various things going on...let's break them down:

  • Empire creates an internal HostedZone CNAME record for awesome-app. This CNAME points at an ELB which Empire also creates, specifically for this application. If you emp deploy mycompany/another-app, yet another CNAME and ELB would be created for another-app.
  • The ELB created is managed by Empire. As you scale your application up, instances running your application are placed into the ELB. Likewise, as you scale your application down, instances are removed from the ELB. The ELB listens on port 80 and maps to a random port between 9000 and 10000 on the minion instances running your application.
  • The ELB runs a health check to determine whether your application is healthy. It will simply perform a tcp ping to your application...if your app doesn't respond, you will end up in a state where there are no healthy instances behind the ELB.
  • The container running on a minion will map a random port between 9000 and 10000 to the $PORT environment variable in your application. Currently, $PORT defaults to 8080. The random port in the 9000-10000 range is managed by Empire.

There are a few other environment variables which Empire will set for your running container, but $PORT is the most important for now. It's strongly suggested that you use $PORT rather than the default of 8080. There are various ways to get your application listening on $PORT. One such way is to run your application from a shell script, which is in turn used in your Procfile

run.sh

#!/bin/bash

gunicorn -w 2 --bind=:$PORT app:app

Dockerfile

RUN mkdir /code
ADD . /code/

Procfile

web: /code/run.sh

Scheduled processes

NOTE: This feature is currently experimental, and requires the CloudFormation backend.

Processes that should run at scheduled times can be configured in the extended procfile by setting a cron expression. The builtin support for running scheduled tasks has multiple advantages over using a process that runs cron inside the container:

  1. Scheduled tasks will not be killed during emp deploy or emp restart. If you have a long running task (e.g. a couple hours), those tasks will continue to run until completion.
  2. Memory and CPU constraints can be controlled on a per process basis, just like any other process in the Procfile.
  3. Scheduled tasks will show up in emp ps while they're running.

To use scheduled processes, simply add a cron: key when using the extended Procfile format:

web:
  command: ./bin/web
scheduled-job:
  command: ./bin/scheduled-job
  cron: '0/2 * * * ? *' # Run once every 2 minutes

Like other non-web processes, scheduled processes are disabled by default. To enable a scheduled job, simply scale it up:

$ emp scale scheduled-job=1

If you want to run more than 1 instance of the process when the cron expression triggers, you can scale it to a value greater than 1:

$ emp scale scheduled-job=5 # Run 5 of these every minute

To disable a scheduled job, simply scale it back down to 0:

$ emp scale scheduled-job=0

When the cron expression triggers, and a task is started, you'll be able to see it when using emp ps:

$ emp ps
v54.scheduled-job.9b649d34-b4f5-4fb7-bfe2-889d80dbd3c9  1X  RUNNING  11s  "./bin/scheduled-job"
v54.web.fd130482-675f-4611-a599-eb0da1879a10            1X  RUNNING   9m  "./bin/web"

Refer to http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/ScheduledEvents.html for details on the cron expression syntax.

Run only processes

When using emp run, if the command you provide matches a process within the Procfile, it will invoke the command defined inside the process. For example, you might define a migrate process inside the Procfile, which users would use to run migrations:

#!/bin/bash

dir=${1:-up}
exec bundle exec rake db:migrate:$dir
migrate:
  command: ./bin/migrate

Now, users can invoke the migrate process like so:

$ emp run migrate up
$ emp run migrate down

You can add the noservice: true flag to tell Empire to not create any AWS resources for the process, for extra re-assurance that the process won't be scaled up.

migrate:
  command: ./bin/migrate
  noservice: true

Environment variables

TODO