Building Serverless Microservices with Zappa and Flask

by Rich Jones on Mar 29, 2016.
_________________

Today, I'm going to show you how to write and deploy serverless microservices using Flask and Zappa. If you're new to Flask, you'll see just how easy is. However, if you prefer Pyramid, Bottle, or even Django, you're in luck, because Zappa works with any WSGI-compatible framework!

Zappa is super, super easy.

With serverless deployments, the web application only exists during the span of a single HTTP request. The benefit of this is that there's no configuration required, no server maintenance, no need for load balancers, and no cost of keeping a server online 24/7. Plus, it's incredibly easy and fun!

This demonstration will start with the most trivial example, and build to an example of a nearly-useful image thumbnailing service. In the next part, we'll look at setting up our service on a domain with an free SSL certificate using Let's Encrypt.

The Simplest Example

Before you begin, make sure you have a valid AWS account, your AWS credentials file is properly installed.

Flask makes it super easy to write simple web services and APIs, and Zappa makes it trivially easy to deploy them in a serverless way to AWS Lambda and AWS API Gateway. The simplest example even fits in a single .gif!

First, you'll need to set up your "virtual environment" and install Flask and Zappa into it, like so:

$ virtualenv env
$ source env/bin/activate
$ pip install flask zappa

Now, we're ready to make our application. Open a file called my_app.py and write this into it:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return "Hello, world!", 200

# We only need this for local development.
if __name__ == '__main__':
    app.run()

The code is basically self-explanatory. We make a Flask object, use the 'route' decorator functions to define our paths, and call a 'run' function when we run it locally (which you can confirm by calling python app.py and visiting localhost:5000 in your browser.)

Okay, so now let's deploy! Open a new file called zappa_settings.json where we'll load in our Zappa configuration.

{
    "dev": {
        "s3_bucket": "your_s3_bucket",
        "app_function": "my_app.app"
    }
}

This defines an environment called 'dev' (later, you may want to add 'staging' and 'production' environments as well), defines the name of the S3 bucket we'll be deploying to, and points Zappa to a WSGI-compatible function, in this case, our Flask app object.

Now, we're ready to deploy. It's as simple as:

$ zappa deploy dev

And our serverless microservice is alive! How cool is that?!

Adding File Uploads

Okay, now let's make our application a bit more interesting by turning it into a thumbnailing service. We'll take an uploaded image, cut a thumbnail, and host the thumbnail on S3.

First, we'll need to add a few more packages from pip:

$ pip install boto3 Pillow

You used to have to manually compile PIL/Pillow if you wanted to use it on AWS Lambda, but since Zappa automatically uses Lambda-compatible packages via lambda-packages, we don't have to worry about.

Then, we'll update our code to add new imports:

import base64
import boto3
import calendar
import io

from datetime import datetime, timedelta
from flask import Flask, request, render_template
from PIL import Image

s3 = boto3.resource('s3')
BUCKET_NAME = 'your_public_s3_bucket'

and a new route:

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        new_file_b64 = request.form['b64file']
        if new_file_b64:

            # Decode the image
            new_file = base64.b64decode(new_file_b64)

            # Crop the Image
            img = Image.open(io.BytesIO(new_file))
            img.thumbnail((200, 200))

            # Tag this filename with an expiry time
            future = datetime.utcnow() + timedelta(days=10)
            timestamp = str(calendar.timegm(future.timetuple()))
            filename = "thumb.%s.jpg" % timestamp

            # Send the Bytes to S3
            img_bytes = io.BytesIO()
            img.save(img_bytes, format='JPEG')
            s3_object = s3.Object(BUCKET_NAME, filename)
            resp = s3_object.put(
                Body=img_bytes.getvalue(),
                ContentType='image/jpeg'
                )

            if resp['ResponseMetadata']['HTTPStatusCode'] == 200:

                # Make the result public
                object_acl = s3_object.Acl()
                response = object_acl.put(
                    ACL='public-read')

                # And return the URL
                object_url = "https://{0}.s3.amazonaws.com/{1}".format(
                    BUCKET_NAME,
                    filename)
                return object_url, 200
            else:
                return "Something went wrong :(", 400

    return render_template('upload.html')

You'll notice that we're also using "render_template" now, so download this template as a file called 'upload.html' in a 'templates' directory within your project.

The other thing to notice here is that we're base64 encoding our binary data on the client, then decoding it server-side. AWS API Gateway can't yet handle binary data through the Gateway, so we have to encode it for now. Quite frankly, you probably don't want your data to go through API Gateway anyway, and you should just upload directly to S3 and then pass the key name to your service, but both ways work and it's easy enough to get our data for this example.

Finally, you'll also notice that we're appending an 'expiry' time into the filename of our thumbnail. Because our service is completely serverless, we don't use a database. So, we'll have to use other ways of storing information. If this service was part of a larger microservice deployment, we probably wouldn't worry about this here, but since we're still standing alone, we have to use what resources we have available to us for storing little bits of data that we might want. So, filenames and 'Tags' on S3 objects are super useful to us!

Now, you just have to update your code:

$ zappa update dev

And you've got a serverless thumbnailing service! Hooray! Browse to https://{{your_apigw_path}}/dev/upload to try it out!

Wrapping Up

So, now we've seen how trivially easy it is to build serverless microservices with Flask and Zappa. This combo is great for image processing services, text-processing, number-crunching, or even hosting fairly complex web applications if that's your goal.

In the next guide, we'll learn how to deploy our microservice to a subdomain with a free SSL certificate using Let'sEncrypt. Stay tuned!

Learn and Earn!

Sign up for great tutorials, guides, rants, raves and opportunities to earn more money!