How to serve Lambda through an AWS Load Balancer


by Thomas Tran



You can use a Lambda function to process requests from an Application Load Balancer (ALB). Since Lambda is a short running piece of code, it executes quickly in milliseconds and dies out. As such, each Lambda function must do only a single task and nothing more.

The support of ALBs for Lambda (and serverless, in general) is to enable your lambda function to be available as a web service. Lambda functions, by default, can’t be invoked through the internet. This is unless you explicitly enable a function URL. Even then, your function will have a unique URL assigned that you can’t control. Configuring an ALB to your Lambda allows you to configure a custom URL (i.e. through Route 53).

In this article, we’ll use Terraform to configure a Lambda function. We’ll use an ALB and specify that Lambda as the target group. Finally, we’ll create an A record (“apex” domain) through Route 53 to route traffic that that ALB.

Lambda Function

First, you’ll need to create a Lambda function and compress the source code in a zip file. This step is out-of-scope for this article, so I’ll include a link for your reference.

Once you have that zip file ready to deploy, run terraform init to initialize a Terraform project. Next, create a main.tf file and add the following config:

resource "aws_lambda_function" "test_lambda" {
  filename      = "lambda_function_payload.zip"
  function_name = "lambda_function_name"
  role          = aws_iam_role.iam_for_lambda.arn
  handler       = "index.test"

  source_code_hash = filebase64sha256("lambda_function_payload.zip")

  runtime = "python3.x"

  environment {
    variables = {
      foo = "bar"
    }
  }
}

This piece of Terraform is telling AWS to create a Lambda function with the name lambda_function_name and use the zip file with the name lambda_function_payload.zip as the source code. The handler tells Lambda that it can find the “main” function in the index.py file under the test function.

The role property tells Lambda that this function has the IAM role specified in the iam_for_lambda role.

Let’s create that iam_for_lambda role:

resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

This role will attach the the Lambda function and will dictate what permissions the Lambda function has.

Application Load Balancer

Next, we’ll need to configure the application load balancer (ALB) and set the target group as the Lambda function, which will allow the load balancer to route traffic to our Lambda function.

First, let’s create the actual ALB:


resource "aws_lb" "alb-lambda" {
  name               = "lambda-load-balancer"
  internal           = false
  load_balancer_type = "application"
}

If you want your ALB to be public facing, you’ll need at a minimum a VPC with an internet gateway, two public subnets on different availability zones, and a security group. Don’t worry, though, because the default network in AWS has everything you’ll need for today.

You’ll then need to add a listener to that ALB to tell the load balancer what types of requests you want to listen to. By default your ALB isn’t listening for any traffic so you’ll have to tell it to.

resource "aws_lb_listener" "example" {
  load_balancer_arn = aws_lb.alb-lambda.id

  default_action {
    target_group_arn = aws_lb_target_group.example.id
    type             = "forward"
  }
}

The above listener will listen to traffic and forward requests to a target group.

Let’s create that target group, which will be the Lambda function:

resource "aws_lb_target_group" "lambda-example" {
  name        = "example-lb-tg"
  target_type = "lambda"
}

resource "aws_lb_target_group_attachment" "lambda-tg-attach" {
  target_group_arn = "${aws_lb_target_group.lambda-example.arn}"
  target_id        = "${aws_lambda_function.test_lambda.arn}"
  depends_on       = ["aws_lambda_permission.with_lb"]
}

… and that’s all we need for the ALB!

Creating a Domain

Creating a domain will allow you to hit the ALB through a custom URL that you specify. Let’s create a Route53 record to do that:

resource "aws_route53_record" "www" {
  zone_id = data.terraform_remote_state.dns.outputs.hosted_zone_id
  name    = "api.yourwebsite.com"
  type    = "A"

  alias {
    name                   = "${aws_lb.lambda-example.dns_name}"
    zone_id                = "${aws_lb.lambda-example.zone_id}"
    evaluate_target_health = true
  }
}

And that’s all you need to configure the public to call your Lambda through an ALB!