DEV Community

Cover image for Deploy a Lambda with a static IP for FREE 💸
Guillaume Duboc for Serverless By Theodo

Posted on

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.

Top comments (6)

Collapse
 
junhyuk_leeneosarchizo profile image
Junhyuk Lee (Neosarchizo)

I think using lightsail with static IP is better

Collapse
 
mrmit007 profile image
Mitkumar Patel

Any idea how can I use AWS SDK with Google App Script? I want to call a lambda without AWS gateway constraint of 29 second. Please help if you know.

Collapse
 
rcoundon profile image
Ross Coundon

I'm not familiar with Google App Script but depending on your requirements and need for authentication, you could use function URLs - docs.aws.amazon.com/lambda/latest/...

Alternatively you could use the API Gateway call to start your process (potentially by writing a message to a queue or triggering an event which are hooked up to the lambda that actually does the work) and return. Then you would poll a status lambda for the progress of the job.

Both would give you up to 15 minutes of runtime. In the 2nd example, you could split this up over multiple lambda invocations according to your use case giving you an unlimited amount of time to complete

Collapse
 
guillaumeduboc profile image
Guillaume Duboc

Tanks @rcoundon for the answer
I totally agree

Collapse
 
florian_sabani_feee256072 profile image
Florian Sabani

Wow! Lovely post.

Any idea on how that could be used for ECS containers running on specify IP as well?

Collapse
 
guillaumeduboc profile image
Guillaume Duboc

I build serverless application most of the time so I'm no expert on EC2.
The EIPAssociation I'm using can be used on an ec2 instance directly and you won't need to use ENIs.

Let me know if it works for you