Deploy a Lambda with a static IP for FREE šŸ’ø

guillaumeduboc

Guillaume Duboc

Posted on October 11, 2023

Deploy a Lambda with a static IP for FREE šŸ’ø

Follow me on X (former twitter) šŸš€

TL;DR

In this article, we will learn how to assign a static IP to a Lambda for free using the AWS CDK šŸ’ø

The problem with Lambdas and static IPs

It's actually easy to assign a static IP to a Lambda, as shown by this article.

āŒ The only caveat is that we need to use a NAT Gateway and it is quite expensive ($30/month).

A few weeks ago I stumbled across this great article by Yan Cui. He explains how to outsmart AWS and exploit the resources they create in the background for us.

Basically :

  • every time you create a Lambda in a vpc, AWS creates an ENI (Elastic Network Interface)
  • we can programmatically extract the ENI ID
  • we can then attach an Elastic IP to the corresponding ENI

The idea in Yan's article is amazing but the Infrastructure as Code implementation is a bit complex. Here is how I was able to do it using the AWS CDK.

Deploying a Lambda with a static IP using ENIs

Step 1: Create a Lambda in a VPC

First, we need to create a Lambda in a VPC. It should be in a public subnet so that it can access the internet.

import * as cdk from 'aws-cdk-lib';

const vpc = new cdk.aws_ec2.Vpc(this, 'Vpc', {
  natGateways: 0,
});

// we'll see later why we need this
const sg = new cdk.aws_ec2.SecurityGroup(this, 'SecurityGroup', { vpc });

const func = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'TestFunc', {
  vpc,
  // cdk wants to make sure that the lambda can access the internet
  allowPublicSubnet: true,
  // the lambda needs to be in a public subnet
  vpcSubnets: { subnets: vpc.publicSubnets },
  securityGroups: [sg],
  // ...
});
Enter fullscreen mode Exit fullscreen mode

If your lambda already exists, make sure you move it to a public subnet.

Step 2: Extract the Elastic Network Interface ID

Now that we have a Lambda in a VPC, we need to find the ENI ID aws created automatically.

Let's try like this

const eni = func.connections.securityGroups[0].node.defaultChild as cdk.aws_ec2.CfnNetworkInterface;
Enter fullscreen mode Exit fullscreen mode

Well actually, it doesn't work. šŸ™€

The ENI is not created yet when the CDK is executed. The resource is created by the AWS Cloudformation service when the lambda is deployed. We need to find a way to extract the ENI ID after the lambda has been created during the rest of the deployment.

The solution is to use a custom resource. It allows us to execute a lambda during the deployment, and fetch the ENI ID using the AWS SDK. We can do that easily using the AwsCustomResource construct.

const cr = new cdk.custom_resources.AwsCustomResource(this, 'customResource', {
  onCreate: {
    physicalResourceId: cdk.custom_resources.PhysicalResourceId.of(
      // adds a dependency on the security group and the subnet
      `${sg.securityGroupId}-${vpc.publicSubnets[0].subnetId}-CustomResource`,
    ),
    service: 'EC2',
    action: 'describeNetworkInterfaces',
    parameters: {
      Filters: [
        { Name: 'interface-type', Values: ['lambda'] },
        { Name: 'group-id', Values: [sg.securityGroupId] },
        { Name: 'subnet-id', Values: [vpc.publicSubnets[0].subnetId] },
      ],
    },
  },
  policy: cdk.custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
    resources: cdk.custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE,
  }),
});
// adds a dependency on the lambda function
cr.node.addDependency(func);

// we can now extract the ENI ID
const eniId = cr.getResponseField('NetworkInterfaces.0.NetworkInterfaceId');
Enter fullscreen mode Exit fullscreen mode

In the snippet above we are calling describeNetworkInterfaces function of the AWS SDK. We only keep the ENIs attached to a Lambda, in my security group and my public subnet. Adding our lambda as a dependency ensures that the custom resource is executed after the lambda is created.

Step 3: Attach an Elastic IP to the ENI - (the easiest one)

Now that we have the ENI ID, we can attach an Elastic IP to it

// const eniId = cr.getResponseField("NetworkInterfaces.0.NetworkInterfaceId");

const eip =  new cdk.aws_ec2.CfnEIP(subnet, "EIP", { domain: "vpc" });
new cdk.aws_ec2.CfnEIPAssociation(this, "EIPAssociation", {
  networkInterfaceId: eniId
  allocationId: eip.attrAllocationId,
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Let's put it all together

We now have all the pieces we need to create a lambda with a static IP. We were able to do it for a single subnet, we just need to repeat the process for all the public subnets our lambda needs to be in.

You can find the full code here.

Key takeaways

  • We can use attach a static IP to a Lambda for FREE using ENIs šŸ’ø
  • We can use the AwsCustomResource construct to perform AWS SDK calls during the deployment

Make sure you enjoy your free static IP while it lasts. Elastic IPs will cost $3/month as of January 2024.

Although this is great for reducing your costs, you should not use it for production environments. AWS might change the way they manage ENIs in the future and break your infrastructure. Furthermore, if you're Lambda is not used in a while AWS will delete the ENI. To avoid this you can setup a cron job to keep the ENI.

šŸ’– šŸ’Ŗ šŸ™… šŸš©
guillaumeduboc
Guillaume Duboc

Posted on October 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related