Couple of weeks ago I attended to serverless course. I’ve played with lambdas from time to time (basically when AWS forced me to use them) but without knowing exactly what I was doing. After this course I know how to work with the serverless framework and I understand better lambda world. Today I want to hack a little bit and create a simple Python service to obtain random numbers. Let’s start
We don’t need Flask to create lambdas but as I’m very comfortable with it so we’ll use it here. Basically I follow the steps that I’ve read here.
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=["GET"])
def hello():
return "Hello from lambda"
if __name__ == '__main__':
app.run()
And serverless yaml to configure the service
service: random
plugins:
- serverless-wsgi
- serverless-python-requirements
- serverless-pseudo-parameters
custom:
defaultRegion: eu-central-1
defaultStage: dev
wsgi:
app: app.app
packRequirements: false
pythonRequirements:
dockerizePip: non-linux
provider:
name: aws
runtime: python3.7
region: ${opt:region, self:custom.defaultRegion}
stage: ${opt:stage, self:custom.defaultStage}
functions:
home:
handler: wsgi_handler.handler
events:
- http: GET /
We’re going to use serverless plugins. We need to install them:
npx serverless plugin install -n serverless-wsgi
npx serverless plugin install -n serverless-python-requirements
npx serverless plugin install -n serverless-pseudo-parameters
And that’s all. Our “Hello world” lambda service with Python and Flask is up and running. Now We’re going to create a “more complex” service. We’re going to return a random number with random.randint function. randint requires two parameters: start, end. We’re going to pass the end parameter to our service. The start value will be parameterized. I’ll parameterize it only because I want to play with AWS’s Parameter Store (SSM). It’s just an excuse.
Let’s start with the service:
from random import randint
from flask import Flask, jsonify
import boto3
from ssm_parameter_store import SSMParameterStore
import os
from dotenv import load_dotenv
current_dir = os.path.dirname(os.path.abspath(__file__))
load_dotenv(dotenv_path="{}/.env".format(current_dir))
app = Flask(__name__)
app.config.update(
STORE=SSMParameterStore(
prefix="{}/{}".format(os.environ.get('ssm_prefix'), os.environ.get('stage')),
ssm_client=boto3.client('ssm', region_name=os.environ.get('region')),
ttl=int(os.environ.get('ssm_ttl'))
)
)
@app.route("/", methods=["GET"])
def hello():
return "Hello from lambda"
@app.route("/random/<int:to_int>", methods=["GET"])
def get_random_quote(to_int):
from_int = app.config['STORE']['from_int']
return jsonify(randint(from_int, to_int))
if __name__ == '__main__':
app.run()
Now the serverless configuration. I can use only one function, handling all routes and letting Flask do the job.
functions:
app:
handler: wsgi_handler.handler
events:
- http: ANY /
- http: 'ANY {proxy+}'
But in this example I want to create two different functions. Only for fun (and to use different role statements and different logs in cloudwatch).
service: random
plugins:
- serverless-wsgi
- serverless-python-requirements
- serverless-pseudo-parameters
- serverless-iam-roles-per-function
custom:
defaultRegion: eu-central-1
defaultStage: dev
wsgi:
app: app.app
packRequirements: false
pythonRequirements:
dockerizePip: non-linux
provider:
name: aws
runtime: python3.7
region: ${opt:region, self:custom.defaultRegion}
stage: ${opt:stage, self:custom.defaultStage}
memorySize: 128
environment:
region: ${self:provider.region}
stage: ${self:provider.stage}
functions:
app:
handler: wsgi_handler.handler
events:
- http: ANY /
- http: 'ANY {proxy+}'
iamRoleStatements:
- Effect: Allow
Action: ssm:DescribeParameters
Resource: arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:*
- Effect: Allow
Action: ssm:GetParameter
Resource: arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/random/*
home:
handler: wsgi_handler.handler
events:
- http: GET /
And that’s all. “npx serverless deploy” and my random generator is running.