Python’s Flask framework is an easy and excellent tool for writing web applications. Its in-built features and ecosystem of supporting packages let you create extensible web APIs, handle data and form submissions, render HTML, handle websockets, set-up secure account-management, and much more.
It’s no wonder the framework is used by individuals, small teams and all the way through to large enterprise applications. A very simple, yet still viable, Flask app with a couple of endpoints looks as follows.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
@app.route('/<name>')
def greet(name):
return 'Hello, ' + name
Flask apps like this can easily be deployed to a server (e.g. a VPS) or to an app-delivery service (e.g. Heroku, AWS Elastic Beanstalk, and Digital Ocean App Platform). In these scenarios, the server/provider often charges the developer for each hour the app is running. Additionally, as traffic increases or reduces, the provider can automatically scale up and down the resources powering your app in order to meet demand. However this scaling can sometimes be a slow process and also means that the developer is charged even when the app is not being used.
If you want to set-up your app so that it can automatically scale from 0 to thousands of concurrent users almost instantly, where you are not charged when users aren’t using your app, where it is highly-available (keep up your uptime to meet SLAs), and where there is no server set-up or maintenance required (and there is nothing for bad actors to try and SSH into), then migrating to a more serverless architecture might be of interest to you.
Also, given that most providers offer a pretty generous free tier for serverless apps, you may not end up paying much at all (up to a few dollars max a month) until you start generating enough traffic.
Note: in this article I use Flask as an example, however the same should apply to any WSGI-compatible framework, such as Bottle and Django, too.
What is a serverless web app?
“Serverless” is the generic term for a family of cloud-based execution models where the developer does not need to worry about provisioning, managing, and maintaining the servers that run their application code. Instead, the developer can focus on writing the application and can rely on the cloud provider to provision the needed resources and ensure the application is kept highly-available.
Although services such as Heroku and Digital Ocean App Platform can be considered “serverless” too (in that there is no server to configure by the developer), I refer more to delivery via function as a service as the particular serverless model of interest in this article, since this offers the benefits listed at the end of the previous section.
“Function as a service” (FaaS) - as its name suggests - involves writing functions, which are deployed to a FaaS provider and can then be invoked. Such systems are event-driven, in that the functions are called as a result of a particular event occurring - such as on a periodic schedule (e.g. a cron job) or, in the web application case, an HTTP request.
There are many FaaS providers, such as Azure Functions, Google Cloud Functions, Cloudflare Workers, and IBM Cloud Functions.
Probably the most famous (and first major) FaaS provider offering is AWS Lambda. In this article I will focus on using Lambda as the tool for deploying Flask apps, but many of the concepts discussed are generic across providers.
Serverless apps written using AWS Lambda usually also involve Amazon API Gateway, which handles the HTTP request/response side of things and passes the information through as code to the Lambda function. The event
argument received by the function describes - among other things - the information about the request that can be used to generate an appropriate response, which is then returned by the function.
import json
def lambda_handler(event, context):
name = event['queryStringParameters']['name']
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"Hello": name})
}
As long as your function(s) return a valid object to generate a response from API Gateway, applications on Lambda can use a separate function for each request path and method combination, or use one function for all invocations from API Gateway and use the event
parameter and code logic to provide the needed actions.
Either way, this is a different pattern to how Flask structures its functions, requests, and responses. As such, we can’t simply deploy our Flask app as-is to Lambda. I’ll now talk about how we can do it without too much extra work.
Using Serverless framework to describe a basic app
The Serverless framework, along with its extensive library of plugins, is a well-established tool for provisioning serverless applications on a number of providers. It bundles your code and automates the deployment process, making it easy to create a serverless app.
Configuration of apps deployed using Serverless is done through the serverless.yml
file. The example configuration below would, when deployed, create an API Gateway interface and a Lambda function using the code in app.py
, and would invoke the lambda_handler
function (above) each time a GET
request is made to /hello
:
service: my-hello-app
provider:
name: aws
runtime: python3.8
region: eu-west-1
memorySize: 512
functions:
hello:
handler: app.lambda_handler
events:
- http:
path: hello
method: get
Deploying an existing Flask app to AWS Lambda
The good news is that we can also leverage the Serverless framework to deploy Flask apps - and without needing much change to the existing project. This section assumes that you have an AWS account already that you can use. If not, then you can sign-up from their website.
First off, we need to install the Serverless framework itself. This can be achieved through NPM: npm install -g serverless
.
Next, we need to configure credentials that will allow Serverless to interact with your AWS account. To do so, use the IAM manager on the AWS console to generate a set of keys (an access key and secret access key) and then use the following command to configure Serverless to use them:
serverless config credentials --provider aws --key <ACCESS_KEY> --secret <SECRET_ACCESS_KEY>
While you should try and restrict access as much as possible, the fastest (yet riskiest) approach is to use an IAM user with Administrator Access permissions. If you want to configure more security I recommend reading the Serverless docs.
Once the above groundwork has been completed, you can proceed to create a new serverless.yml
file in the root of your Flask project:
service: my-flask-app
provider:
name: aws
runtime: python3.8
plugins:
- serverless-wsgi
functions:
api:
handler: wsgi_handler.handler
events:
- http: ANY /
- http: ANY {proxy+}
custom:
wsgi:
app: app.app
Don’t worry too much about the wsgi_handler.handler
and events
parts - essentially these ensure that all HTTP requests to the service will get routed through to your app via a special handler that Serverless will setup for us.
This setup assumes your root Flask file is named app
and that your Flask instance within this file is also named app
(in the custom.wsgi
attribute above), so you may need to change this if it doesn’t match your project setup.
Another thing to note is the new plugins
block. Here we declare that our application requires the serverless-wsgi
plugin, which will do much of the heavy lifting.
To make use of the plugin, you’ll need to add it to your project as a dependency by running serverless plugin install -n serverless-wsgi
. As long as your Flask project dependencies are listed in a requirements.txt
file, you can now deploy your app by simply running serverless deploy
. After a few minutes, the framework will complete the deployment and will print out the URL to your new service.
Tweaking the deployment
There are various ways to adjust the environment of your deployed service. For example, you can change the amount of memory assigned to your function, make use of environment variables (e.g. for database connection strings or mail server URLs), define roles for your functions to work with other AWS services, and much more.
I recommend taking a look at the Serverless documentation to understand more about what options are available.
If you want to use a custom domain for your service, then you can either set this up yourself in API Gateway through the AWS console or by using the serverless-domain-manager
plugin. Either way you will need to have your domain managed using Route 53.
Serverless caveats
Whilst the benefits offered by serverless delivery are strong, there are also some things to bear in mind - particularly when it comes to avoiding unexpected costs. Lambda functions bill per 100 milliseconds of execution time, and so long-running functions may be cut short (unless you tweak the duration allowance on the Lambda function, which can be up to 5 minutes long).
Additionally, if your Flask app makes use of concurrency (e.g. if you use threads to background longer-running tasks, like email-sending), then this may not play nicely with Lambda, since the function may get terminated once a response is generated and returned.
I outlined some extra things to watch out for in a recent article, so take a look through that if you want to read more on these.
Generally speaking, however, serverless apps are quite a cheap and risk-free way to experiment and get early prototypes off the ground. So, if you’re familiar with Flask (or other WSGI frameworks) and want an easy and scalable way to deploy your app, then perhaps this approach could be useful for your next project.