Django, Elastic Beanstalk, Apache Config, and Environmental Variables

I have a Django application running on AWS Elastic Beanstalk using Python 2.7. I am using the “64bit Amazon Linux 2014.09 v1.2.0 running Python 2.7″ AMI that Amazon provides as one of the options for Elastic Beanstalk.

You can customize the environment and some other aspects by using a YAML file such as “python.config” in the directory “.ebextensions” in the root directory of the project. If you are unfamiliar with this, Amazon has some good documentation on it here. You can also Google plenty of templates for getting started.

The problem:
I want to be able to customize some Apache settings and use environment variables. These might not sound mutually exclusive at first (and they shouldn’t be) but I had to use a workaround for both of these to function properly. Let’s start with customizing Apache settings.

To use custom Apache settings, I had to first create the Apache settings file, in this case as “wsgi.conf”. I had found the one Elastic Beanstalk uses as default and added the additional configurations I wanted.

  • LoadModule wsgi_module modules/
  • WSGIPythonHome /opt/python/run/baselinenv
  • WSGISocketPrefix run/wsgi
  • WSGIRestrictEmbedded On
  • <VirtualHost *:80>
  • Alias /static/ /opt/python/current/app/static/
  • <Directory /opt/python/current/app/static/>
  • Order allow,deny
  • Allow from all
  • </Directory>
  • WSGIScriptAlias / /opt/python/current/app/web/
  • <Directory /opt/python/current/app/>
  • Require all granted
  • </Directory>
  • WSGIDaemonProcess wsgi processes=1 threads=15 display-name=%{GROUP} \
  • python-path=/opt/python/current/app:/opt/python/run/venv/lib/python2.7/site-packages user=wsgi group=wsgi home=/opt/python/current/app
  • WSGIProcessGroup wsgi
  • RewriteEngine On
  • RewriteCond %{SERVER_NAME} !^(www\.)?example\.com$
  • RewriteRule ^ - [F]
  • RewriteRule .* - [F]
  • RewriteCond %{HTTP:X-Forwarded-Proto} !https
  • RewriteRule !/status https://%{SERVER_NAME}%{REQUEST_URI} [L,R]
  • </VirtualHost>

The main purpose of this is so that I can add rewrite conditions and rules. Here I force HTTPS as well as only allow traffic that goes through the domain name specified. I also add a separate configuration file (“enable_mod_deflate.conf”) to enable compression:

  • # mod_deflate configuration
  • <IfModule mod_deflate.c>
  • # Restrict compression to these MIME types
  • AddOutputFilterByType DEFLATE text/plain
  • AddOutputFilterByType DEFLATE text/html
  • AddOutputFilterByType DEFLATE application/xhtml+xml
  • AddOutputFilterByType DEFLATE text/xml
  • AddOutputFilterByType DEFLATE application/xml
  • AddOutputFilterByType DEFLATE application/xml+rss
  • AddOutputFilterByType DEFLATE application/x-javascript
  • AddOutputFilterByType DEFLATE text/javascript
  • AddOutputFilterByType DEFLATE text/css
  • AddOutputFilterByType DEFLATE image/png
  • AddOutputFilterByType DEFLATE image/gif
  • AddOutputFilterByType DEFLATE image/jpeg
  • # Level of compression (Highest 9 - Lowest 1)
  • DeflateCompressionLevel 9
  • # Netscape 4.x has some problems.
  • BrowserMatch ^Mozilla/4 gzip-only-text/html
  • # Netscape 4.06-4.08 have some more problems
  • BrowserMatch ^Mozilla/4\.0[678] no-gzip
  • # MSIE masquerades as Netscape, but it is fine
  • BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
  • <IfModule mod_headers.c>
  • # Make sure proxies don't deliver the wrong content
  • Header append Vary User-Agent env=!dont-vary
  • </IfModule>
  • </IfModule>

Ok, now that I have my configuration files (both in “.ebextensions/”), I can work on applying these changes to Apache on Elastic Beanstalk. I use the container_commands section in a .config file in .ebextensions.

  • container_commands:
  • 90wsgi:
  • command: "cp .ebextensions/wsgi.conf /tmp/wsgi.conf"
  • 92https:
  • command: "cp .ebextensions/ /opt/elasticbeanstalk/hooks/appdeploy/post/"
  • 93perms:
  • command: "chmod 755 /opt/elasticbeanstalk/hooks/appdeploy/post/"
  • 01_setup_apache:
  • command: "cp .ebextensions/enable_mod_deflate.conf /etc/httpd/conf.d"

Note: This only works on Amazon Linux 2014.09 v1.2.0. On older versions you will have to create the directory “/opt/elasticbeanstalk/hooks/appdeploy/post” in a separate container command. Newer versions might change the structure of this.

So I copy the Apache config files into their respective places and copy a script that is in my .ebextensions (shown below) into the post directory which Elastic Beanstalk will automatically run (as long as the permissions are correct).

  • #!/bin/bash
  • cp /tmp/wsgi.conf /etc/httpd/conf.d/wsgi.conf
  • rm -f /tmp/wsgi.conf
  • service httpd restart
  • exit 0

This will restart Apache and apply the custom configurations. Now this has an unexpected side effect of the application losing access to any environment variables that you may have defined either through the console or through “.elasticbeanstalk/optionsettings.*”. I found that the file with all the environment variables still exists at “/opt/python/current/env”. I tried numerous workarounds including adding a source command to the script above, but nothing worked. I ended up adding some code to the beginning of my Django settings file:

  • import os
  • import subprocess
  • command = ['bash', '-c', 'source /opt/python/current/env && env']
  • proc = subprocess.Popen(command, stdout=subprocess.PIPE)
  • for line in proc.stdout:
  • key, _, value = line.partition("=")
  • os.environ[key] = value[:-1]
  • proc.communicate()

This will let bash interpret the file instead of trying to use some crazy regex to read and parse the file. It then adds the environment variables so that the rest of the application can access them.

I hope I could help some people save some time in customizing their Django Elastic Beanstalk application!

Smash That Writer's Block

Smash That Writer's Block

WriteLab Tech Blog