How to Set Up Application Load Balancer With Lambda

In less than ten minutes you can have a Lambda-powered API that is integrated with both API Gateway and Application Load Balancer. How? I will walk you through deploying a set of example services that I built that demonstrate using Application Load Balancer with your Lambda functions. We’ll first create an API with API Gateway, and then create an application load balancer with all of its pre-requisites (a VPC, etc), enabling you to call the same function using either the API Gateway endpoint or the application load balancer.

High-Level Steps

In this tutorial, we use examples that I’ve already created in my serverless training examples repo. When we’ve deployed those examples (it should take less than ten minutes if you just run the commands I provide here), you’ll be able to test your function using both API Gateway and Application Load Balancer. Once you have that done, I’ll explain some of the various pieces involved.

Here are the basic steps we’ll follow:

  1. Get set up by cloning my examples repo and installing dependencies
  2. Deploy a simple API using API Gateway
  3. Deploy a set of custom resources for use by our next stack
  4. Deploy an Application Load Balancer (ALB) and all its prerequisites. The ALB will call the same Lambda function we deployed in step one.

Okay, so let’s go:

Get the Examples Repo Installed

We’ll clone the the examples repo and install the dependencies to get started with.

Here are those commands so you can run them yourself:
git clone https://github.com/jthomerson/serverless-training-examples.git
cd serverless-training-examples
npm i

Deploy a Simple API Using API Gateway

Next, we’ll deploy our service that contains a simple API using API Gateway. This API will echo back to you in the response the event that it received containing the request. That will be especially helpful later when we have the same API being used by both the API Gateway and the Application Load Balancer.

Here are those commands so you can run them yourself:
cd services/echo-api/
../../node_modules/.bin/sls deploy

Deploy Custom Resources

Before we can deploy our application load balancer, we need to deploy some custom resources for use by CloudFormation. That’s needed because - at least for now - CloudFormation does not yet support associating a Lambda function with an application load balancer (technically what CloudFormation doesn’t support is the Lambda target type on the ALB’s target group). The custom resources we’re using are implemented in Silvermine’s CloudFormation custom resources repo (see ELBTargetGroup and ELBTargetGroupLambdaTarget).

To deploy our custom resources as a stack:

Here are those commands so you can run them yourself:
cd ../custom-resources/
npm i
../../node_modules/.bin/sls deploy

Deploy an Application Load Balancer With Lambda Integration

Finally we have all the pieces in place so that we can deploy our application load balancer with Lambda integrated:

Here are those commands so you can run them yourself:
cd ../application-load-balancer-lambda/
../../node_modules/.bin/sls deploy

It’s important to remember that the application load balancer will cost you money for every hour that it exists. Thus, whenever you’re done with the experiment and you don’t need it any more, remember to remove the stack that contains the application load balancer. I’ve included instructions for how to do this at the bottom of this article.

Experimenting With Your New API

Okay, so now you have a Lambda function for an API (in the echo-api service) that can be called by either its API Gateway endpoint or through your application load balancer. What should you do from here? Test it out! Here’s a quick example of how to get the URL to test the API from both environments:

Here are those commands so you can run them yourself:
# Get the URL for the API Gateway endpoint:
cd ../echo-api
../../node_modules/.bin/sls info

# Now get the URL for the ALB endpoint:
aws elbv2 describe-load-balancers --query 'LoadBalancers[0].DNSName' --output text
DNSNAME=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[0].DNSName' --output text)

# This will give you an example of the 404 response we configured on the ALB
# (that does not hit your Lambda function):
curl "http://${DNSNAME}/" | jq .

# And here's a response that does go to your Lambda function:
curl "http://${DNSNAME}/echo/" | jq .

Note: I use the jq command-line JSON parser for pretty-printing the response from those curl commands, but you don’t have to do that. You can also just open those URLs in your browser.

Further Experimenting

As you see in the screencast above, the API returns a JSON representation of the request event that was passed to the Lambda. Notice the differences in the event structure and content depending on if you open it from the API Gateway endpoint or the ALB endpoint. For more details on the exact differences between the events and response format for both API Gateway and Application Load Balancer, see my technical details article.

Go ahead and experiment with different input to your API. Try sending it different headers, and multi-valued headers. Try sending it different combinations of query strings, et cetera.

When you’re done with that, go ahead and start modifying the API code! Change the code in services/echo-api/src/handler.js and see the results, testing them in both API Gateway and ALB.

Where to Go From Here

Now that you have your load balancer up, I encourage you to look through the serverless.yml file in the example to get familiar with the pieces you need for deploying an application load balancer. Here’s a basic breakdown of those pieces:

  • VPC: The biggest prerequisite for deploying an ALB is that you need a VPC set up for it. If you’re already running EC2 instances (or services like managed ElastiCache), you’re familiar with a VPC. But if you’ve only run Lambda functions, DynamoDB, et cetera, you may have never set one up. In my example I included the complete setup for a small VPC with a subnet in two availability zones, an internet gateway (how machines in the private subnets talk to the internet), and a basic security group (like a firewall) that allows HTTP/HTTPS traffic in and nothing out.
    • Note: Normally you’d probably want to set up the VPC in a separate service / stack than the one you use for your ALB. That makes sense because the VPC could be shared / depended on by multiple stacks. In this example I set it up in the same stack just to make it simpler for you to deploy the example without yet-another-stack.
  • S3 Bucket for Logging: This is optional, but I set one up since it helps you a bit to already have some of the pieces put together. Application Load Balancer, like many other services (e.g. CloudFront, API Gateway), can log requests to an S3 bucket. In this stack you’ll see that we’re allowing ELB (Elastic Load Balancing is the service that offers Application Load Balancer) to write to the /alb/* prefix in your S3 bucket.
    • Note: As explained below, the logs that ALB writes to this bucket will prevent you from easily removing the stack; if you simply do sls remove, you’ll get an error about not being able to delete an empty bucket. The instructions below show you how to work through this.
  • Application Load Balancer: Ah, finally we get to the real point: we set up a load balancer. We have to configure it with references to the security group and subnets that we created earlier as part of the VPC.
  • Lambda Permission: You’ll see an AWS::Lambda::Permission resource in the stack. This is what grants the Elastic Load Balancing service permission to invoke the Lambda function(s) used by your ALB. If you have more than one Lambda function in your API, you’ll need to create one of these permission resources for each function.
  • Target Group and Target: On an application load balancer, you map certain paths (e.g. /api/*) to a “target group”. Until the integration with Lambda was announced, you could think of a target group as a group of resources - like EC2 instances - that could respond to the request. In the context of a Lambda function, a target group can have one Lambda function associated with it so that whenever the target group needs to respond to a request, the ALB will send your Lambda function a request object as the function’s event, and your function will respond with a response object. In the stack template, the Custom::ELBTargetGroupLambdaTarget is what associates your Lambda function with the Custom::ELBTargetGroup target group resource.
    • Note: You may have noticed that both of these resources are custom resources. That’s why in the example we had to deploy a “custom-resources” stack to support these two custom resources before we could deploy our ALB. At some point in the (hopefully near) future, I have no doubt that the CloudFormation team will add native support for Lambda-backed target groups. When they do, a) we won’t need to depend on these custom resources, and b) they are likely to combine the two resources into one - the AWS::ElasticLoadBalancingV2::TargetGroup resource. It was just easier for me to implement them as two resources than to combine them into one.
  • Listener(s): A listener is an ELB concept that essentially adds a port binding to the ALB that makes the ALB listen for network connections on the specified port. You determine what protocol (HTTP or HTTPS) the listener is listening on, and the port (generally 80 or 443, but you can choose any port). Your VPC security group must allow your users (generally anywhere on the internet, i.e. 0.0.0.0/0) to connect on that port, as we’ve done in our VPC security group.
    • Note: In this example, I only configured an HTTP listener because setting up an HTTPS listener would require also setting up an SSL certificate. Doing so is easy (if you’re using AWS ACM and CloudFormation’s AWS::CertificateManager::Certificate resource), but does require the stack to stall and wait for you to approve the cert, which is not something I wanted to have you do during this demo because you’re likely already familiar with doing that and it’s well-documented in many places on the internet.
    • Also note: you can add more than one listener to an ALB, so you can easily have a listener on HTTP and one on HTTPS. You could configure, for example, the HTTP listener to have a default action of redirecting to HTTPS, and then your HTTPS listener be the one that actually has the real actions and listener rules.
    • Final note: I encountered a weird issue when using an HTTPS listener with a Lambda target when HTTP/2 was enabled. Basically, when I made a request, the Lambda function was never invoked, the connection stayed open (I left it open for minutes one time), and finally when I killed the connection (e.g. Ctrl-C on the running curl command), ALB would invoke my Lambda. I need to investigate this more, and when I do, I’ll report back my findings.
  • Listener Rule(s): A listener rule is just what it sounds like: a rule that determines for each request on the listener which target group (in our case, Lambda function) the request should be routed to. In the example stack there’s just one rule that sends all requests for /echo/* to our single Lambda function. You can add other rules that send requests to the same or other functions (by making a target group and target for each function that needs to be invoked). If you’re integrating an ALB and some Lambda functions with other services that aren’t Lambda-based (e.g. some services you have running on EC2 or in ECS), you can have some rules that send to Lambda target groups and others that send to traditional target groups. Your rules can be based on either the path of the request (as our example is), and/or the domain of the request so that you can serve multiple domains / APIs off a single ALB.

So, with that overview in mind, go ahead and play around some more with the ALB. Add some new rules, or change the existing rules. Set up an HTTPS listener if you’d like. Change the default actions to add a redirect or change the fixed response that I built into the example. Get familiar with the functionality. Another nice thing about ALB is that configuration changes happen very quickly - the stack updates in seconds or minutes and the changes are visible immediately - unlike CloudFront, which can take 45 minutes to deploy an update.

Removing Your Application Load Balancer

As mentioned above, the application load balancer will cost you money for every hour that it exists. Thus, if you’re done with the experiment and you don’t need it any more, remember to remove the stack that contains the application load balancer.

Removing the stack is not as simple as running sls remove because we configured the load balancer to write its logs to an S3 bucket, and that bucket is part of this same stack. Thus, we need to empty the S3 bucket that contains the logs before we can remove the stack. Here’s how to do all that:

Here are those commands so you can run them yourself:
cd ../application-load-balancer-lambda/
# Note that you may need to replace "us-east-1" with whatever region you deployed this
# stack to if you deployed it to a different region.
aws s3 rm --recursive s3://alb-lambda-us-east-1-$(whoami)-logs/alb/
../../node_modules/.bin/sls remove

Note: the load balancer will flush its logs to the S3 bucket every few minutes (it seems like every five minutes). Thus, it’s possible that you can remove the logs and then try to remove the stack and still get an error from CloudFormation saying the bucket isn’t empty - that happens because the ALB wrote some logs after you purged them, but before the bucket was deleted. If this happens, just purge the logs again and then delete the stack; if the stack won’t delete using sls remove the second time, remove it through the CloudFormation console or via the AWS CLI tools.

Conclusion

Hopefully this quickstart example gave you the resources and confidence you need to set up the example application load balancer and hook it into the example Lambda function. If you’re now ready to take your experiment a step further and start modifying the Lambda function or hooking the ALB up to your own Lambda functions, read the next article in this series that outlines the technical differences between a Lambda that’s behind API Gateway and one that’s behind ALB:

API Gateway vs Application Load Balancer—Technical Details

In that article you’ll find the details of the exact differences in the request object (input event to your Lambda) and response (object you respond to from your Lambda).

As always, be sure to leave a comment if you have a question or enjoyed this article.

Stay Informed

Subscribe to the Serving of Serverless newsletter or follow me on Twitter. I’ll keep you current with the serverless world, new training resources, and I will never sell or share your contact information.