Zappa - A WSGI Wrapper for Python

Run Existing Django Apps on AWS Lambda

Posted by Ryan S. Brown on Fri, Mar 11, 2016
In Mini-Project
Tags: python, lambda, django, api gateway

In the past few weeks, Rich from gun.io released a shim library that maps API Gateway requests to Python’s WSGI interface. Python’s WSGI is a specification that maps HTTP requests to an interface to be consumed by Python web apps. Django, Flask, Pyramid, and just about every other web framework depend on WSGI to talk to the outside world.

Zappa provides a WSGI interface to be run inside of the Lambda runtime and receive API Gateway requests.

In this post, we’ll take the Django version of Zappa for a spin with a demo app I wrote to compare the major Python web frameworks. Next time, we’ll add RDS and make our Lambda VPC-enabled.

Getting Started

To view the initial version of the application, clone the repo and view the v1.0 tag.

$ git clone TODO
$ cd TODO
$ git checkout v1.0

Optionally, you can try running it locally to see how it works. There’s no magic here: it’s a form that takes your name and what you had for lunch.

$ pip install -r requirements.txt
$ python manage.py runserver
# go to the page at http://localhost:5000/

In your browser, you should see something like this.

screenshot of running application

The application is ready, and you have the dependencies, now let’s upgrade this to a project that can run without servers.

Using Django-Zappa

To work with django-zappa, we need to make a few changes to our app’s configuration, but no code changes will be necessary. In Amazon lingo, this is a “lift and shift” style migration.

First, we need the django-zappa module installed, to do so add django-zappa to the requirements.txt file and re-run pip install -r requirements.txt.

Next we have to add the custom middleware at the head of the queue so it gets the first crack at modifying incoming requests. This is critical because no other middleware is going to be able to understand an incoming API Gateway event, and it’s up to django-zappa to translate it into the request object Django is used to reading. Replace the existing MIDDLEWARE_SETTINGS and INSTALLED_APPS variables in hello_django/settings.py with this:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'wut4lunch',
    'django-zappa', # lets you use the django-zappa commands like `deploy`
)

MIDDLEWARE_CLASSES = (
    'django_zappa.middleware.ZappaMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

The changes above make it possible to use django-zappa, but we still need to define environments where the app can be deployed to. For now, we’ll just define a “testing” environment. In a production application, you’ll want to have separate test, stage, and prod environments.

ZAPPA_SETTINGS = {
    'testing': {
       's3_bucket': 'tmp.serverlesscode.com',
       'settings_file': '~/code/django-zappa-example/hello_django/settings.py',
    }
}

We’re almost ready to deploy now, all we need is a database. The SQLite3 database I have committed to the repo should do - obviously it won’t survive in Lambda but that’s ok for now.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': '/var/task/test.db',
    }
}

The /var/task is where the deploy zipfile is extracted, so that’s where our SQLite database will be by default.

Deploying

Now it’s time to deploy!

$ ./manage.py deploy testing
Creating ZappaLambdaExecution IAM..
Packaging project as zip..
Uploading zip (9.8MiB)...
Creating API Gateway routes..
Deploying API Gateway..
Your Zappa deployment is live!: https://REDACTED-api.us-east-1.amazonaws.com/testing

Open the URL in your browser and you should be greeted with a page to share what you had with your friends. Note how long it takes for that first page load, for me the first page load takes 1.6 seconds but subsequent calls are just 280 milliseconds. The slower first call is the time it takes for Lambda to warm up and get the new code.

The downside here is, of course, that the app doesn’t actually work. If you try to save your name and food, you get an error like this.

wut4lunch error about read-only database connection

Next Steps

In the next post, we’ll cover using Lambda’s recent support for VPC connections and RDS to make our data persistent.

Get the code so far on the sqlite-db repo branch and check out Django-Zappa on Github. Keep up with future posts via RSS. If you have suggestions, questions, or comments feel free to email me, ryan@serverlesscode.com .


Tweet this, send to Hackernews, or post on Reddit