DEV Community

t3chflicks
t3chflicks

Posted on

☁️ 🚀 Cheaper than API Gateway — ALB with Lambda using CloudFormation

An alternative to API gateway is Application Load Balancer. ALB can be connected with Lambda to produce a highly performant, cost effective API. In this article, I demonstrate how to create an API using CloudFormation and Python.

API Gateway vs. Application Load Balancer

API Gateway and ALB are two different AWS services, however they can both be used to achieve the same thing: send network requests for a service to the service.

API Gateway is pay per request whereas ALBs have an hourly rate, therefore deciding which to use depends on traffic volume. For an in-depth price and feature comparison, I recommend this article, which shares this shocking statistic:

I expect to pay around $166 per month for ALB, whereas I’m paying $4,163 per month for the exact same service from API Gateway.

This is a massive saving! That said, the cost difference isn’t unmerited and you do get extra features from API gateway. Dougal Ballantyne, the Head of Product for Amazon API Gateway tweeted:

If you are building an API and want to leverage AuthN/Z, request validation, rate limiting, SDK generation, direct AWS service backend, use #APIGateway. If you want to add Lambda to an existing web app behind ALB you can now just add it to the needed route

Well, that’s exactly what we’re gonna do!

API with ALB and Lambda

I am going to build the following the system:

Architecture Diagram*

Architecture Diagram*

The complete CloudFormation templates can be found here, split into two templates:

  • vpc.yml — Configures the VPC. For simplicity, the VPC only contains two public subnets. You can read about a more complex VPC in our previous article.

  • service.yml— Configures both the ALB and Lambda function.

Lambda

To create a Lambda using CloudFormation, it is necessary to define a Lambda Function, a Role to run the Lambda and a Permission for the ALB to invoke the Lambda:

Lambda:
    Type: AWS::Lambda::Function
    Properties:
    Code:
        ZipFile: |
        def handler(event, context):
        response = {
            'isBase64Encoded': False,
            'statusCode': 200,
            'body': 'HELLO WORLD!'
        }
        return response
    Handler: lambda_function.handler
    Role: !GetAtt LambdaRole.Arn
    Runtime: python3.7

LambdaRole:
    Type: AWS::IAM::Role
    Properties:
    AssumeRolePolicyDocument:
        Statement:
        - Action: ['sts:AssumeRole']
            Effect: Allow
            Principal:
            Service: ['lambda.amazonaws.com']
    Path: /
    ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

LambdaFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
    FunctionName: !GetAtt Lambda.Arn
    Action: 'lambda:InvokeFunction'
    Principal: elasticloadbalancing.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

Application Load Balancer

The Load Balancer is placed across public subnets as it needs to be accessible from the internet. The ALB is configured to listen to HTTP traffic on port 80 and forward it to the Lambda:

LoadBalancerSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
    GroupDescription: Load balance allow port 80 traffic
    VpcId: !ImportValue VPCID
    SecurityGroupIngress:
        CidrIp: 0.0.0.0/0
        FromPort: 80
        IpProtocol: TCP
        ToPort: 80

LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
    SecurityGroups:
        - !Ref LoadBalancerSecGroup
    Subnets:
        - !ImportValue PublicSubnetA
        - !ImportValue PublicSubnetB

LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
    Protocol: HTTP
    LoadBalancerArn: !Ref LoadBalancer
    DefaultActions:
        - Type: forward
        TargetGroupArn: !Ref LoadBalancerTargetGroup
    Port: 80

LoadBalancerTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
    TargetType: lambda
    Targets:
        - AvailabilityZone: all
        Id: !GetAtt Lambda.Arn
Enter fullscreen mode Exit fullscreen mode

The complete code can be accessed here and can be deployed using the AWS CLI:

aws cloudformation create-stack --stack-name service --template-body file://template.yml --capabilities CAPABILITY_NAMED_IAM
Enter fullscreen mode Exit fullscreen mode

After a successful deployment, the DNS name of the ALB can be found in the EC2 section of the AWS console. It should look something like:

loadb-LoadB-R7RVQD09YC9O-1401336014.eu-west-1.elb.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

Now it is possible to make a request to this URL and get a response:

$ curl loadb-LoadB-R7RVQD09YC9O-1401336014.eu-west-1.elb.amazonaws.com'

HELLO WORLD!
Enter fullscreen mode Exit fullscreen mode

Photo by [Nghia Le](https://unsplash.com/@lephunghia?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)*

Photo by Nghia Le on Unsplash*

Cross Origin Response Sharing

In order to access this service from a browser webpage on a different domain, CORS must be enabled. This is done by setting headers:

Lambda:
    Type: AWS::Lambda::Function
    Properties:
    Code:
        ZipFile: |
        def handler(event, context):
        response = {
            'isBase64Encoded': False,
            'statusCode': 200,
            'body': 'HELLO WORLD!',
            'headers': {
                'access-control-allow-methods': 'GET',
                'access-control-allow-origin': '*',
                'access-control-allow-headers': 'Content-Type, Access-Control-Allow-Headers'
            }
        }
        return response
    Handler: lambda_function.handler
    Role: !GetAtt LambdaRole.Arn
    Runtime: python3.7
Enter fullscreen mode Exit fullscreen mode

Use a Domain Name

AWS provides an ugly Load Balancer address such as:

loadb-LoadB-R7RVQD09YC9O-1401336014.eu-west-1.elb.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

But it’s quite simple to use a custom domain using AWS. Firstly, transfer your DNS management to Route 53 and then create a new record set aliased to the load balancer.

Thanks For Reading

I hope you have enjoyed this article. If you like the style, check out T3chFlicks.org for more tech focused educational content (YouTube, Instagram, Facebook, Twitter).

Resources:

Top comments (0)