Fast Python deployments on Elastic Beanstalk
It should come as no surprise that some of the applications we build at Mirumee are hosted on Amazon’s architecture. Elastic Beanstalk gives us the ability to deploy and scale easily but the machines we choose for web workers are not necessarily great at compiling binaries. And compiling binaries is what inevitably happens when you install project’s dependencies from PyPI.
In our case it took 8 minutes to add a new machine to the load balancing pool. Here’s what we did to bring it down to 2 which is little more than it takes the machine to boot for the first time.
While compilation is necessary it’s only necessary once. Doing it on every machine and for every deployment not only wastes precious time but also introduces risk of two machines running slightly different code. We can do better than that. We can do all of this work ahead of the time and just unpack pre-built packages at install time. And it’s not that hard.
Get a custom package index
The first tool we need is a custom Python package server for pip to use. In our case we went with devpi but there are others. Whichever tool you choose, create a tiny EC2 instance and use the docs to get your server working there.
Note: while this solution requires you to maintain an extra machine, even a t2.nano instance should be enough to satisfy the requirements. In an auto-scaling environment the cost of such machine is nothing compared to other resources.
Build wheels of your dependencies
The second tool is the wheel package. It will allow pip to build wheels —pre-compiled versions of Python eggs. Make sure you’re using at least pip version 8 and have the latest versions of wheel and setuptools packages:
$ pip install --upgrade pip setuptools wheel
Once you have the packages installed, you can ask pip to build a package and all of its dependencies:
$ pip wheel pycrypto
For custom packages that contain a setup.py it’s equally easy:
$ python setup.py bdist_wheel
It’s up to you to choose whether you want to build all of your project’s dependencies (which may be a good idea for repeatable installs) or just the ones that contain compiled code (Pillow is a common first candidate).
Note: wheels are architecture-specific so make sure your development machine is running a binary-compatible version of Linux. Use Docker if you’re on a different operating system.
Upload wheel files to your package index
When you’re done building packages upload them to your package index. In case of devpi it’s as simple as:
$ devpi upload wheelhouse/*.whl
Make sure your machines have recent pip
At the time of this writing Elastic Beanstalk’s Python images come with pip version 7 which is not recent enough. Luckily this is quite easy to fix using .ebextensions:
commands:
update_pip:
command: "/opt/python/run/venv/bin/pip install --upgrade pip"
Have your machines use your package index
This one requires dropping in a pip configuration file to the root’s home directory and is also achieved using .ebextensions:
files:
"/root/.pip/pip.conf":
mode: "000744"
owner: root
group: root
content: |
[global]
index-url = http://example.com/your/package/index/
trusted_host = example.com
Note: remember to replace host names and paths with proper values.
Test it
Deploy the updated version of the app and tell Elastic Beanstalk to provision a new machine. You can use Elastic Beanstalk’s log viewer to confirm that the installation process is using your local package index and that it’s lightning-fast.