When building an application with AWS Lambda, you may need to host your Lambda function in a VPC. The most common reason for this is because your Lambda function will use other resources which aren't accessible from the public internet, such as a relational database or Redis instance.
Since the improvement of VPC cold starts in 2019, hosting a Lambda function inside a VPC is more feasible even for user-facing workflows. However, by default, your Lambda function in a VPC won't have access to the public internet. This is fine for many use cases, as you may have an HTTP endpoint that uses your database in the VPC and responds to the user without making any public internet calls.
But even a single endpoint like this can be a pain if you're using a service like Amazon CloudWatch Metrics to store metrics about your function's execution. Like other AWS services, the CloudWatch Metrics API is a public API that requires public internet access to publish metric data from your Lambda function.
In this post, we'll see three ways to use AWS services from your Lambda function in a VPC:
- Give your Lambda function public internet access with a NAT Gateway
- Create a VPC endpoint for your desired service
- Use tools like CloudWatch Metric's Embedded Metric Format or Lambda Destinations to interact with services
In the sections below, we'll review the pros and cons of each approach and walk through the steps to set it up. This post uses Amazon SNS as a guiding example, but most of the principles apply to other services.
Want code examples for the methods below? Check out the Serverless VPC examples repo on GitHub.
Let's get started!
Give your Lambda function public internet access with a NAT Gateway
The first way to use an AWS service from a Lambda function that's in a VPC is to give your Lambda function access to the public internet. This is the most common way -- it's been available for a while and has some official AWS guidance on how to do it. It's also my least favorite way because there's an always-on cost of doing it, plus you need to get deep into the weeds of VPC networking.
To give public internet access to your Lambda function, you'll need to add a NAT gateway in a public subnet. Then you'll need to add a route to the route table of your private subnet that points to the NAT gateway.
Not a networking wizard? Me neither. Let's walk through how to set this up using the Serverless Framework.
Head here to find a full example of a Lambda in a VPC with a NAT Gateway configured.
After you deploy the service, you'll have the following architecture:
It will contain the following resources:
- A VPC;
- Two public subnets and two private subnets. The Lambda functions will use the private subnets, but the NAT Gateways will be in the public subnets.
- An Internet Gateway and a VPC Gateway Attachment to connect the Gateway to the VPC. The Internet Gateway will allow public internet access for the public subnets.
- RouteTables and Subnet Route Table Associations to associate each subnet with a route table.
- Routes in the public subnet route tables that direct traffic through the Internet Gateway.
- Two Elastic IP Addresses that will be assigned to our NAT Gateways.
- Two NAT Gateways that will be used to connect our private subnets to the public internet.
- Routes in the private subnet route tables to route traffic through the NAT Gateways.
- A Security Group to give to our Lambda function.
Oof, that's a lot of networking minutia!
NAT Gateways have an hourly cost of $0.045 (about $33 per month) as well as a data processing cost of $0.045 per GB. A NAT Gateway lives in a single availability zone. If you want to have two NAT Gateways for redundancy in case an AZ goes down, you're looking at a base cost of $70/month to give internet access to your Lambda functions.
The great thing about this setup though is that it will work for any resource you need to access on the public internet, not just AWS services. If you're using Auth0, Twilio, Algolia, or another third-party service, adding a NAT Gateway will let you access them.
Set up a VPC endpoint for your AWS service
The NAT gateway approach is flexible but can be a pain if you're only using a single AWS service in your Lambda function. Further, it can be quite expensive if you want a redundant setup.
The second approach to using AWS services from a Lambda in a VPC is to set up a VPC endpoint in your VPC. VPC endpoints allow communication with AWS services from your VPC without requiring access to the public internet. Traffic to the configured service will be routed through the endpoint directly to the service without hitting the public internet.
There are two types of VPC endpoints: interface endpoints and gateway endpoints. Gateway endpoints are simply a route in your subnet's route table that directs traffic directly to the given service. Gateway endpoints are great because they don't cost you anything to run. Unfortunately, gateway endpoints are only supported for Amazon S3 and DynamoDB. If you want other services, you're out of luck.
Interface endpoints support a much broader menu of services, including Amazon CloudWatch, Amazon SNS, Amazon SQS, and Amazon Kinesis. Interface endpoints use AWS PrivateLink to route your network traffic to the given service. Unfortunately, interface endpoints do have an associated cost -- $0.01 per hour per endpoint per AZ (~$7.50 per month) plus a $0.01 fee per GB of data processed. If you have three AZs in your VPC, you're looking at a base cost of $22 per month in addition to the data processing charges.
Let's see how to configure a VPC endpoint, again using the Serverless Framework.
Adding a VPC endpoint with the Serverless Framework
Want the full thing? Here's a walkthrough of deploying a Lambda function in a VPC with a VPC endpoint configured for Amazon SNS.
We will need to use CloudFormation to add a VPC endpoint to our service with the Serverless Framework.
To set up a VPC endpoint, we will use the AWS::EC2::VPCEndpoint resource in CloudFormation. The resources
section of your serverless.yml
file should look as follows:
# serverless.yml
resources:
Resources:
SNSVPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: True
SecurityGroupIds:
- !GetAtt VpcEndpointSecurityGroup.GroupId
ServiceName: "com.amazonaws.us-east-1.sns"
SubnetIds:
- <yourFirstSubnetId>
- <yourSecondSubentId>
VpcEndpointType: Interface
VpcId: <yourVpcId>
VpcEndpointSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupDescription: "Security group for VPC Endpoint"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !GetAtt VpcEndpointLambdaSecurityGroup.GroupId
VpcEndpointLambdaSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: <yourVpcId>
GroupDescription: "Security group for VPC Endpoint Lambda"
We're creating three resources here:
- A VPC interface endpoint connected to Amazon SNS
- A security group for the VPC interface point that allows inbound TCP traffic on port 443 (HTTPS) from our Lambda's security group
- A security group for our Lambda function
The security group resources are pretty straightforward, but let's walk through the different properties for the VPC endpoint here.
The PrivateDnsEnabled
property configures private DNS in our VPC for the public DNS name to route to the private IP addresses for the service. In our situation, this means sns.us-east-1.amazonaws.com
will route to our VPC endpoint. This will make it a lot easier to use the interface endpoint in our Lambda function -- we can use the standard SDKs to make calls to our AWS service without needing to specify our own endpoint.
The ServiceName
property is the name of the service for which you're configuring an endpoint. Unfortunately, you can't just use the name of the AWS service -- you need to find the VPC endpoint name. To look at available options, you can use the AWS cli:
aws ec2 describe-vpc-endpoint-services
This will list the available services. Most of them are clear though some of them, like CloudWatch (aka monitoring
), need some deciphering.
There are three properties you'll need to fill out with your own values. They are:
SecurityGroupIds
: The security groups that will be associated with the interface. This is the security group you've configured that allows inbound TCP traffic over port 443.SubnetIds
: The subnets in which the interface will be available. These should be the same subnets you use for your Lambda functions. Note that a subnet is in a single Availability Zone, and you pay per availability zone for a gateway interface. The more subnets you use, the higher your baseline cost.VpcId
: This is the ID of the VPC used.
You can deploy this VPC endpoint in your Serverless service using the serverless deploy
command. Once the VPC endpoint is configured, all requests to SNS in your Lambda function will use the interface endpoint to communicate with SNS.
Your system architecture will look as follows:
Your Lambda functions are functionally treated as being in the private subnets of your VPC. They're able to use the configured VPC endpoints to reach out to Amazon SNS.
Service-specific ways of communication within a VPC
The two methods above are generic methods of interacting with a variety of AWS services within a VPC. However, both of them cost money and require a fair bit of configuration.
For a limited subset of other services, you can use service-specific ways of interacting with them inside a VPC. There are two main examples here:
- Using the Embedded Metric Format to log metrics to CloudWatch
- Using Lambda Destinations to send payloads to SQS, SNS, or EventBridge.
Let's take a look at each of those.
Embedded Metric Format for CloudWatch
For the price-conscious among us, paying for a VPC endpoint might be too much to swallow if your metric needs are low. It seems crazy to pay $22/mo plus processing just to have the ability to send a few metrics to CloudWatch. Fortunately, there's a free way to handle this.
In late 2019, AWS announced the Embedded Metric Format for CloudWatch. You can log metrics to CloudWatch Logs in the Embedded Metric Format, and CloudWatch Metrics will allow charting and querying against the metrics. Because your Lambda function already logs any output to CloudWatch Logs, this won't require any additional infrastructure or cost (beyond the cost of CloudWatch Logs and querying your metrics).
There's a complex spec that defines the Embedded Metric Format, but handcrafting your own metrics is a hassle. Fortunately, AWS has created some client libraries for making it easier to log in the Embedded Metric Format. There are currently libraries for Node.js and Python, and hopefully additional languages will be added soon.
Lambda destinations
A second service-specific tool is the new Lambda Destinations feature. Like the CloudWatch Embedded Metric Format, it works in a narrow set of use cases. If those use cases fit your needs, you can avoid the hassle of configuring & paying for NAT Gateways or VPC Endpoints.
With Lambda functions, you often are transforming and enriching some data, then sending it into another system like SQS, SNS, or EventBridge for processing. This process involves adding the AWS SDK into your Lambda function package, calling the proper service, and handling failure scenarios.
Lambda Destinations are designed to avoid those downsides. With Lambda Destinations, you can send the result of asynchronous Lambda function invocations to a specific destination in SQS, SNS, or EventBridge. Not only does this save you from adding the AWS SDK to your package and handling failure, but it also could save you from configuring internet access for your functions in a VPC.
There are some limitations around Lambda Destinations -- they only work for asynchronous invocations, you can only send to a few AWS services, and it can only be used with messages that are sent at the end of your function invocation. Further, the wonderful crew at Trek10 has written up some surprising edges around Lambda Destinations. That said, this pattern covers a nice chunk of AWS Lambda use cases and can be a nice solution to the VPC internet access problem.
Summary and conclusion
In this post, we walked through a few different ways to use AWS Services in our Lambda function that is in a VPC. Let's do a quick review of the methods we covered:
Method | Works for | Cost |
---|---|---|
NAT Gateway | - Any AWS service or third-party service | - $0.045/hr (~$33.50 monthly) - $0.045/GB processing |
VPC gateway endpoint | - Amazon S3 - Amazon DynamoDB | None |
VPC interface endpoint | - 66 different AWS services, including Amazon CloudWatch, Kinesis Firehose, SNS, SQS, and SSM. | - $0.01/hr per endpoint per AZ (~$7.45 monthly) - $0.01/GB processing |
Amazon CloudWatch Embedded Metric Format | - Amazon CloudWatch only | None |
Lambda Destinations | - Amazon SNS - Amazon SQS - Amazon EventBridge But: only for asynchronous Lambda invocation results | None |
Personally, I'd love to see more services supported by VPC gateway endpoints to provide easy, free access to AWS services. However, it looks as though AWS is more focused on building out support for more services in VPC interface endpoints for the time being.
If you have questions or comments on this piece, feel free to leave a note below or email me directly.