AWS Lambda Deployment Learning Guide
Deployment is often the biggest pain in software development. Hence, I’m here to share my learning strategies, steps to deploy, and working github templates for people who want to deploy their Python Flask and node express app on AWS Lambda functions.
Step 1: Download sam cli (command line for aws)
Step 2: Add credentials
- I recommend using “root user” rather than setting up the IAM role in the beginning. Permission problems can be annoying when while root user has all the permission.
- To add root users, go to security credentials, and add an access key.
Step 3: Start with a simple sample template from aws
Run on your terminal
sam init
Build your function and test if it is correct
sam build
Test your lint
cfn-lint template.yaml
This command is very helpful because we usually want to modify ourtemplate.yaml
file, and it gives us the exact line on our error.
Test your function
sam validate
Try to run it locally
sam local invoke
: You can invoke your local function. To specify a function when you have multiple functions, usesam local invoke {{your_function_name}}
sam local start-api
: You will receive an output API that you can test on.
Deploy on aws
sam deploy
: You will be able to see your function on cloud formation.
Step 4: Add your own functions
Python: Flask
Basic folder structure
├── README.md
├── __init__.py
├── events
│ └── event.json
├── hello_world
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
├── integration
│ ├── __init__.py
│ └── test_api_gateway.py
├── requirements.txt
└── unit
├── __init__.py
└── test_handler.py
The only thing we need to modify is the app.py
file to add flask, and template.yaml
to configure aws lambda.
My app.py
from flask import Flask
import json
app = Flask(__name__)@app.route('/')
def hello(event, context):
return {
"statusCode": 200,
"body": json.dumps({
"message": "Success"
})
}if __name__ == '__main__':
app.run(debug=True)
Corresponding template.yaml
file
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
FlaskAppLambda:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.hello
Runtime: python3.9
Events:
FlaskAppApi:
Type: Api
Properties:
Path: /
Method: get
Node: Express
While I was using zip as a package type of Python deployment, when you use Node, the aws template is naturally tied with the Docker container, but you can also choose to not use it.
Basic folder structure
.
├── README.md
├── events
├── hello-world
│ ├── Dockerfile
│ ├── app.mjs
│ ├── handler.mjs
│ ├── index.mjs
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ └── tests
├── samconfig.toml
└── template.yaml
Some useful command lines to see if your container is correctly connected with your Node.
- To see all containers:
docker ps -a
- To see only the running ones:
docker ps
- To run a specific container:
docker run --platform linux/amd64 -it helloworldfunction:nodejs18.x-v1
Here, you will need to modify several files: index.mjs
, template.yaml
. I bolded all the code that I added apart from the template.
The aws template comes with app.mjs
.
/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
* @param {Object} context
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/export const lambdaHandler = async (event, context) => {
try {
// Check if the HTTP method is POST
if (event.httpMethod !== "POST") {
return {
statusCode: 405, // Method Not Allowed
body: JSON.stringify({
error: "Method not allowed. Use POST method.",
}),
};
} // Parse the incoming request body
const requestBody = JSON.parse(event.body); // Your POST request handling logic here return {
statusCode: 200,
body: JSON.stringify({
message: "hello world",
}),
};
} catch (err) {
console.log(err);
return {
statusCode: 500,
body: JSON.stringify({
error: "Internal Server Error",
}),
};
}
};
Create a new file index.mj
import express from "express";
const app_test = express();app_test.get("/myfunction/get", (req, res) => {
console.log("Received a GET request to /myfunction");
res.json({ message: "Hello, World!" });
});// Handler for POST requests to /hello
app_test.post("/myfunction/post", (req, res) => {
console.log("Received a POST request to /myfunction/hell");
res.json({ message: "Received a POST request to /myfunction/hell" });
});const port = 3002;
app_test.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});export default app_test;
Modify template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
sam-node-template
Sample SAM Template for sam-node-template# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /
Method: post
Metadata:
DockerTag: nodejs18.x-v1
DockerContext: ./hello-world
Dockerfile: Dockerfile
# Define the functions section here
MyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/ #path to the function code
Handler: handler.hello
Runtime: nodejs18.x
Events:
MyGetFunction:
Type: Api
Properties:
Path: /myfunction/get
Method: GET
MyPostFunction:
Type: Api
Properties:
Path: /myfunction/post
Method: POSTOutputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${HelloWorldFunction}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
MyFunctionArn:
Description: "ARN of MyFunction"
Value: !GetAtt MyFunction.Arn
MyFunctionApi:
Description: "API of MyFunction"
Value: !Sub "https://${MyFunction}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
MyFunctionIamRole:
Description: "Implicit IAM Role created for My function"
Value: !GetAtt MyFunctionRole.Arn
Learning Strategies
Always remember to run it locally first.
- Run without lambda: You can run with your Python through
python3 app.py
ornode index.js
. - Run with lambda locally: You can build with
sam build
, test withsam validate
, and test your local API throughsam local start-api
. - Run lambda deployment: After
sam deploy
, look around your aws cloud console to gain bug reports. You can also directly add an API test on the console to prevent authentication or other potential problems.
Test the deployment environment first before integrating it with your code
This is the biggest learning I gained. Usually, I rush to integrate the deployment code with my existing codebase, but the bug becomes very complex because of too many moving parts involved. This time, I played around the code with Python and Node first to make sure I understood how to tweak it. I only integrate my Nodejs deployment code with my codebase when I’m confident that my get and post request both work on my deployment testing code.