Create Lambda Functions in a VPC in AWS CDK

avatar
Borislav Hadzhiev

Last updated: Jan 26, 2024
5 min

banner

# Creating Lambda Functions in a VPC in AWS CDK

We are going to provision a Lambda function in a VPC and enable it to access the internet.

There are a couple of things we have to do to give Lambda functions created in a VPC internet access:

  • The function has to have permissions to create and manage elastic network interfaces (virtual network cards).
  • The function has to be placed in a private subnet with a route table rule. pointing to a NAT gateway or NAT instance. The NAT Gateway has to be provisioned in a public subnet and has to have a public IP address in order to access the internet through the VPC's Internet Gateway.
  • The function's security group has to allow the necessary outbound access.
The code for this article is available on GitHub

Let's create the VPC and lambda function:

lib/cdk-starter-stack.ts
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as cdk from 'aws-cdk-lib'; import * as path from 'path'; export class CdkStarterStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new ec2.Vpc(this, 'my-cdk-vpc', { ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), natGateways: 1, maxAzs: 3, subnetConfiguration: [ { name: 'private-subnet-1', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24, }, { name: 'public-subnet-1', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, ], }); const lambdaFunction = new lambda.Function(this, 'lambda-function', { runtime: lambda.Runtime.NODEJS_18_X, // ๐Ÿ‘‡ place lambda in the VPC vpc, // ๐Ÿ‘‡ place lambda in Private Subnets vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, }, memorySize: 1024, timeout: cdk.Duration.seconds(5), handler: 'index.main', code: lambda.Code.fromAsset(path.join(__dirname, '/../src/my-lambda')), }); } }

Let's go over the code snippet.

  1. We created a VPC with a PUBLIC and a PRIVATE_WITH_EGRESS subnet groups. We will launch our Lambda function in private subnets. Note that the VPC will provision 1 NAT Gateway, which will allow our Lambda to access the internet from a PRIVATE subnet
NAT Gateways have an hourly billing rate of about $0.045 in the us-east-1region.
  1. We created a Lambda function and placed it in the VPC. Note that we've narrowed down the subnets to PRIVATE_WITH_EGRESS.

Lambda functions provisioned in a PUBLIC subnet don't get assigned a public IP address and don't have access to the internet.

We didn't add a role to the Lambda function or edit its default role.

Lambda creates an Elastic network interface (virtual network card) for each (private) subnet in the VPC, so it has to have the necessary permissions to create, describe and delete network interfaces.

The necessary permissions are included in the AWSLambdaVPCAccessExecutionRole managed policy, which CDK attaches to lambdas launched in a VPC automatically.

We haven't explicitly provided a security group to the lambda function, so CDK will also create a default security group for us.

The default security group has a single inbound rule:

TypeProtocolPortSource
All TrafficAllAlldefault-SG-id

And the following outbound rule:

TypeProtocolPortSource
All TrafficAllAll0.0.0.0/0

The default security group allows all outbound traffic. Since security groups are stateful, an outbound request that we've made to the internet, automatically gets permissions for the response from the other direction.

In our case, the default security group will do. If you want to read more about creating security groups in CDK, check out my other article - Security Group Example in AWS CDK - Complete Guide.

Let's add the code for the lambda function at src/my-lambda/index.js:

src/my-lambda/index.js
const fetch = require('node-fetch'); async function main(event) { try { const res = await fetch('https://randomuser.me/api'); const resJson = await res.json(); console.log('api response ๐Ÿ‘‰', JSON.stringify(resJson, null, 4)); return {body: JSON.stringify(resJson), statusCode: 200}; } catch (error) { return {body: JSON.stringify({error})}; } } module.exports = {main};

The function makes a request to an API and returns the response. We've used the node-fetch package, so we have to install it:

shell
cd src/my-lambda npm init -y npm install node-fetch cd ../../

Let's deploy the Lambda function and the VPC and test if our function has access to the internet:

shell
npx aws-cdk deploy

After deployment, we can see that the Lambda has been launched in a VPC, and is associated with private subnets only.

lambda in VPC

The route tables associated with our private subnets have a route that points all non-intra-VPC traffic out to our NAT Gateway:

routes private subnet

CDK has also automatically attached the AWSLambdaVPCAccessExecutionRole managed policy to our lambda's role:

vpc lambda role

Our public subnets route all non-intra-VPC traffic to the Internet Gateway:

routes public subnet

Everything seems to be in place for our Lambda function launched in a VPC to have internet access. Let's test the function via the Lambda management console:

vpc lambda response

Our Lambda function successfully queried the remote API and got the response.

The CloudWatch logs are a bit more readable:

vpc lambda response cloudwatch

Either way, we successfully provisioned a Lambda function that can access the internet in a VPC.

The function is created in PRIVATE_WITH_EGRESS subnets of the VPC. The route tables associated with our private subnets have a route that points to a NAT Gateway, which enables our Lambda to access the internet.

CDK did quite a bit of the heavy lifting for us:

  • provided the glue logic for all of the VPC components
  • created a role for our lambda function and attached permissions to it that enable ENI creation and management
  • created a security group for our lambda function
If your Lambda function is placed in a VPC and doesn't need access internet access, you can set the allowPublicSubnet to true on the Function constructor to acknowledge the limitation. Placing your VPC Lambda in a public subnet without setting this prop to true returns a validation error.

# Clean up

To delete the resources we've provisioned, issue the destroy command:

shell
npx aws-cdk destroy
Don't forget to destroy the stack because we provisioned a NAT Gateway, which has an hourly billing rate.

I've also written an article on how to provision an AWS Lambda function outside a VPC in CDK.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev