Home   About Me   Blog  

Migrating a python app to AWS lambda as is; without code change.(02 June 2018)

Intro
AWS Lambda lets you run code without provisioning or managing servers.
In this article, we will go through an example of how to migrate a python application to AWS lambda.
Of importance to note is that; we will not make any significant code changes to our app in order to migrate it.
You may have heard of Python frameworks that help you write python code targeted for running on AWS lambda. for example; Chalice or Zappa.
A drawback of those frameworks - in my opinion - is that they require you to change the way you architect your applications.
They require you to make code changes to suit their way of things.
In this post we wont do any of that, you will keep your familiar architect/code and still get to run your app on lambda.

- We will create a small python/django app from the ground up, using the same old familiar django pattern.
- We will then run that app on AWS lambda.

Create django app.
The app we will build is a simple one. It is a website uptime checker.
It makes a http request to google.com and twitter.com, checks whether they are up or not and reports the same.
We'll create a new directory for our app, create a virtualenv, install django and also create a requirements file with the installed packages.


mkdir uptime-checker && \
cd uptime-checker && \
virtualenv .uptime-checker && \
source .uptime-checker/bin/activate && \
pip install django && \
pip freeze >> requirements.txt
                

Create a settings.py file with the following contents:

import os

def here(*args):
    return os.path.join(os.path.abspath(os.path.dirname(__file__)), *args)

PROJECT_ROOT = here('')
def project_root_joiner(*args):
    return os.path.join(os.path.abspath(PROJECT_ROOT), *args)

ALLOWED_HOSTS = '*'
SECRET_KEY =  'A-random-and-secure-secret-key'
ROOT_URLCONF = 'urls'
WSGI_APPLICATION = 'wsgi.application'
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)
STATIC_ROOT = project_root_joiner('', 'static/')
STATIC_URL = '/static/'
                

Create a wsgi.py file with contents;

import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
application = get_wsgi_application()
                

And urls.py;

from django.conf.urls import url
import views
urlpatterns = (
    url(r'^$', views.home, name='home'),
)
                

We also need the manage.py file.
Create manage.py file with content;

#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)          
                

We created urls.py and the code in there is referring to views, but we haven't created views yet.
So lets do just that, create views.py whose content is;

from django.http import HttpResponse
def home(request):
    return HttpResponse("Hello. Welcome to Python and AWS lambda.") 
                     

And this been python, we need an empty __init__ file, so create it.

touch __init__.py
                    

So your final directory structure looks like;

uptime-checker/
    __init__.py
    manage.py
    settings.py
    urls.py
    views.py
    wsgi.py
                

But does the app really work? There's only one way to find out; let's run it.

python manage.py runserver 0.0.0.0:8080
                

When you visit http://localhost:8080/ with your favorite browser, you see that you get the Hello. Welcome to Python and AWS lambda. greeting.

We were not making a greetings app, we want a website uptime checker. So lets add the logic in our views to check on the uptime of our two websites; google and twitter.
The modified views.py becomes;

import urllib
from django.http import HttpResponse
def home(request):
    g = urllib.urlopen("http://www.google.com")
    google_status_code = g.getcode()
    g.close()
    t = urllib.urlopen("http://www.twitter.com")
    twitter_status_code = t.getcode()
    t.close()
    return HttpResponse("""Uptime Checker. google.com status={0}. twitter.com status={1}""".format(
        google_status_code,
        twitter_status_code)) 
                


There, our app is working as per spec. It is not pretty or the most useful app, but it is working albeit on our machines.
What we want to do now is deploy this app - as is - to AWS lambda.

Deploying django app to AWS lambda.
1. First we need to install up.
up bills itself thus;
Up focuses on deploying "vanilla" HTTP servers there's nothing new to learn, just develop with your favorite existing frameworks such as Express, Koa, Django, Golang net/http or others. -- https://github.com/apex/up (emphasis is mine.)

up can be installed via curl;

curl -sf https://up.apex.sh/install | sh
                

You can make sure that up has installed succesfully by checking it's help command.

up --help
                


2. Next we need to make sure that our AWS setup is ok.
- make sure that your aws credentials are okay and stored in the right place/s. For my case, I have a ~/.aws/credentials file that looks like;

[apex-up-profile]
aws_access_key_id = myAccessId
aws_secret_access_key = myAccessKey
region = eu-west-1
                

- also make sure that you create this AWS policy in your AWS account.

3. There's only one change that you have to make to your app files in order to satisfy up, we have to rename our manage.py file to app.py; but the contents of that file remain the same.

mv manage.py app.py
                

The reason we have to do that is because up can be used to deploy apps created using other programming languages/runtimes, and the way up is able to tell which runtime it is you want to deploy is by looking for specific file names.
The file name that tells up you are using a python runtime is app.py

4. In the uptime-checker directory, create an up.json file whose contents are;

{
    "name": "uptime-checker",
    "profile": "apex-up-profile",
    "proxy": {
        "command": "python app.py runserver 0.0.0.0:$PORT"
    }
}
                

where the value of the profile json key is the same as the profile name in your aws config( ~/.aws/credentials for my case)
up.json is up's configuration file. See this link for more about the config file.

5. to deploy your app to AWS lambda, run the following command;

up deploy
                

You will see an output like;

build: 4,753 files, 14 MB (4.427s)
deploy: commit bc51169 (28.903s)
stack: complete (47.398s)
                

To see the domain/url that your app was deployed to, run the command;

up url
                

You can copy and paste the url you get, into your browser to access your app.
This is what I get; uptime-checker staging
If you want to debug/introspect what is going on, you can always look at the logs of your app via;

up logs -fv
                

Up also sends your logs to AWS cloudwatch, so you can search for them there. uptime-checker cloudwatch logs

Anything you log to stdout in your app, will show up in the logs. So you can use a python logging config like;

import logging
logging.basicConfig(
    format="%(message)s\n",
    stream=sys.stdout,
    level=logging.DEBUG,
)
                


Conclusion
There are lot more bells in up, you can do log querying/filtering from your command line, use custom domains for your app, use secrets, deploy to multiple AWS regions, deploy to multiple stages(test/staging/prod etc) and so much more.
Before this article turns into a marketing article for up(up is open source, though they also have a paid plan) let me invite you to go check it out for yourselves; https://github.com/apex/up

We have deployed a Python/django app to AWS lambda as is, without changing the way we write our app.

uptime-checker on lambda

If you want to tear down everything that we just created,

up stack delete
                

All the code in this blogpost can be found here:- https://github.com/komuw/uptime-checker