Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Pattern: AWS Lambda REST API behind Amazon ALB with resource-path Listener Rule (Python & SAM) #2027

Merged
Merged
13 changes: 13 additions & 0 deletions alb-lambda-rest-api-sam-py/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
aws-lambda-powertools = {version = "*"}

[dev-packages]
aws-lambda-powertools = {extra = ["all"], version = "*"}

[requires]
python_version = "3.9"
154 changes: 154 additions & 0 deletions alb-lambda-rest-api-sam-py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# AWS Lambda RESTful API with Amazon ALB and Path Based Listener Rules

This configuration pattern creates an Application Load
Balancer with path-based listener rules, paired with an AWS Lambda RESTful API function as the target. It uses Python 3.9 and the Serverless Application Model (SAM) CLI.

Learn more about this pattern
at [Serverless Land Patterns](https://serverlessland.com/patterns/alb-lambda-rest-api-sam-py).

Important: this application uses various AWS services and there are costs associated with these services after the Free
Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any
AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already
have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls
and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (
AWS SAM) installed
* [Python 3 installed](https://www.python.org/downloads/)
* [AWS Lambda Powertools for Python](https://docs.powertools.aws.dev/lambda/python/latest/)
* [Docker](https://www.docker.com/products/docker-desktop/)

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```
cd alb-lambda-rest-api-sam-py
```
3. Install dependencies and build:
```
pipenv install
bb245 marked this conversation as resolved.
Show resolved Hide resolved
```
4. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
```
sam validate && sam build && sam deploy --guided
```
5. During the prompts:
bb245 marked this conversation as resolved.
Show resolved Hide resolved
* Enter a stack name
* Enter the desired AWS Region
* Enter VPC ID
* Enter comma seperated Subnet IDs for e.g, subnet-1,subnet-2
* Allow SAM CLI to create IAM roles with the required permissions.

6. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for
testing.

## How it works
bb245 marked this conversation as resolved.
Show resolved Hide resolved

This setup orchestrates the deployment of an Application Load Balancer, configures path-based routes directing traffic
to a Python-based AWS Lambda function, and leverages
the [AWS Lambda Powertools for Python](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#application-load-balancer)
library. The Lambda function, serving as the target, records details of the incoming ALB event, along with the API and
context objects, logging them to an Amazon CloudWatch Logs log group and Amazon X-Ray.

Note: ALB has no authentication or authorization and should only be used for demo purposes.

## Testing

### SAM CLI for Local API Testing

The SAM CLI seamlessly extends the capabilities of the AWS CLI, introducing essential features for constructing and validating Lambda applications. Leveraging the power of Docker, it orchestrates the execution of functions within an Amazon Linux environment aligned with the Lambda runtime specifications, all sourced from [sam/build-python3.9](https://gallery.ecr.aws/sam/build-python3.9).
This emulation replicates the Lambda environment.

To simulate an ALB (Application Load Balancer) event, the CLI relies on the configuration stored in [event.json](./events/event.json). This file corresponds to an ALB event, and it is generated by the SAM command: `sam local generate-event alb request`.

Now, to build and execute your function, execute the following command:


```commandline
pipenv install
pipenv requirements > requirements.txt

sam build
sam local invoke -e events/event.json
```

### Deploy and Test API in AWS

Once the application is deployed, retrieve the Application Load Balancer endpoint value from CloudFormation Outputs.

```commandline
curl --request GET --header "Client-Correlation-Id:bb245" --url http://{ALB_ID}.{REGION}.elb.amazonaws.com/hello'
```

Alternatively, you can use [xh utility](https://github.com/ducaale/xh), too as:

```commandline
xh http://{ALB_ID}.{REGION}.elb.amazonaws.com/hello Client-Correlation-Id:bb245
```

Response:

```commandline
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 37
Content-Type: application/json
Server: awselb/2.0

{
"message": "Hi from API behind ALB"
}

```

When an invalid resource (/nf-test) is used as:

```commandline
curl --request GET --header "Client-Correlation-Id:not-found-test-1" --url http://{ALB_ID}.{REGION}.elb.amazonaws.com/nf-test'
```
_OR_

with [xh utility](https://github.com/ducaale/xh) as:

```commandline
xh http://{ALB_ID}.{REGION}.elb.amazonaws.com/nf-test Client-Correlation-Id:'not-found-test-1'
```

Response:

```commandline
HTTP/1.1 400 Bad Request
Connection: keep-alive
Content-Length: 27
Content-Type: text/plain; charset=utf-8
Date: Wed, 27 Dec 2023 23:13:43 GMT
Server: awselb/2.0

404 Error!!! Page Not Found

```

## Cleanup

1. Delete the stack
```bash
sam delete
```
2. Confirm the stack has been deleted
```bash
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus"
```

----
Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions alb-lambda-rest-api-sam-py/events/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"body": "null",
"resource": "/{proxy+}",
"path": "/hello",
"httpMethod": "GET",
"isBase64Encoded": true,
"queryStringParameters": null,
"pathParameters": {
"proxy": "/hello"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "application/json;v=1",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.us-east-1.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https",
"Client-Correlation-Id": "bb245"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"accessKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"path": "hello",
"resourcePath": "/{proxy+}",
"httpMethod": "GET",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}
76 changes: 76 additions & 0 deletions alb-lambda-rest-api-sam-py/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"title": "AWS Lambda REST API and Amazon ALB with Listener Rules",
"description": "This pattern creates an Application Load Balancer with route-based listener rules, paired with an AWS Lambda RESTful API function as the target. It uses Python 3.9 and the Serverless Application Model (SAM) CLI.",
bb245 marked this conversation as resolved.
Show resolved Hide resolved
"language": "Python",
"level": "200",
"framework": "SAM",
"introBox": {
"headline": "How it works",
"text": [
"This setup orchestrates the deployment of an Application Load Balancer, configures path-based routes directing traffic to a Python-based AWS Lambda function, and leverages the [AWS Lambda Powertools for Python](https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/) library. The Lambda function, serving as the target, records details of the incoming ALB event, along with the API and context objects, logging them to an Amazon CloudWatch Logs log group and Amazon X-Ray."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/alb-lambda-rest-api-sam-py",
"templateURL": "serverless-patterns/alb-lambda-rest-api-sam-py",
"projectFolder": "alb-lambda-rest-api-sam-py",
"templateFile": "template.yaml"
}
},
"resources": {
bb245 marked this conversation as resolved.
Show resolved Hide resolved
"bullets": [
{
"text": "AWS CLI",
"link": "https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html"
},
{
"text": "SAM CLI",
"Link": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html"
},
{
"text": "Python 3",
"Link": "https://www.python.org/downloads/"
},
{
"text": "AWS SAM build image for Python 3.9",
"Link": "https://gallery.ecr.aws/sam/build-python3.9"
},
{
"text": "Docker Desktop",
"Link": "https://www.docker.com/products/docker-desktop/"
},
{
"text": "Using AWS Lambda with an Application Load Balancer",
"Link": "https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html"
},
{
"text": "Powertools for AWS Lambda (Python)",
"Link": "https://docs.powertools.aws.dev/lambda/python/latest/"
}
]
},
"deploy": {
"text": [
"sam validate && sam build && sam deploy --guided"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>sam delete</code>."
]
},
"authors": [
{
"name": "Balasubrahmanya Balakrishna",
"image": "https://github.com/bb245/balabala/blob/main/balabala.png",
"bio": "Balasubrahmanya Balakrishna is a Senior Lead Software Engineer with Capital One Services, LLC.",
"linkedin": "bala~bala"
}
]
}
28 changes: 28 additions & 0 deletions alb-lambda-rest-api-sam-py/src/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json
from http import HTTPStatus

from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler import ALBResolver, Response, content_types
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.logging import correlation_paths

log = Logger()
app = ALBResolver()


@app.get("/hello")
def sample_get() -> Response:
client_correlation_id = app.current_event.get_header_value(name="Client-Correlation-Id")
log.info(f"client_correlation_id: {client_correlation_id}")
response = {
"message": "Hi from API behind ALB"
}
return Response(status_code=int(HTTPStatus.OK), body=json.dumps(response),
content_type=content_types.APPLICATION_JSON)


@log.inject_lambda_context(correlation_id_path=correlation_paths.APPLICATION_LOAD_BALANCER, log_event=True,
clear_state=True)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
log.debug(event)
return app.resolve(event, context)
Loading