Skip to content

Commit a67fff3

Browse files
authored
Merge pull request aws-samples#1645 from SoufanConsulting/main
[Follow up PR] New pattern submission: Lambda Elastic IP without NAT Gateway
2 parents b1fca77 + 8e95918 commit a67fff3

5 files changed

Lines changed: 50 additions & 14 deletions

File tree

lambda-elastic-ip-no-nat-gateway-cdk/README.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,38 @@ Important: This application uses various AWS services and there are costs associ
5151
```
5252

5353
## Use Case
54-
You have a Lambda function that requires internet access to make API calls to 3rd party service but you need a dedicated IP to be whitelisted by the 3rd party vendors.
54+
This solution will help you, if your use case tick the following bullet points:
55+
- You are trying to connect to a 3rd party API
56+
- 3rd party API requires whitelisting a static IP of its clients
57+
- You want to use Lambda functions
58+
59+
#### Current Industry Standard Solution
60+
Place your Lambda in *private* subnet. Place a NAT Solution (NAT Gateway/NAT Instance) in a *public* subnet and attach an AWS Elastic IP to the NAT Solution.
61+
62+
This solution is actually great and offers HA (with NAT Gateways) and scalable. Exactly what you need for production environments
63+
64+
Solution is explained in details [here](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/generate-a-static-outbound-ip-address-using-a-lambda-function-amazon-vpc-and-a-serverless-architecture.html)
65+
###### *However...*
66+
67+
NAT Gateways come at a cost (**~$33/month per gateway**). To make NAT Gateway Highly Available & Scalable you need to provision 1 NAT Gateway per subnet.
68+
So, if you have 3 subnets the cost will be:
69+
70+
**3 * $33 = ~$100/month**
71+
Now this is nearly an unavoidable expense for your production environments, as scalability & HA are requirements.
72+
73+
However, if you have 3 non-prod environments (DEV, QA, STAGE...) the cost of these environments will be **3 accounts * $100 = ~$300/month**. High Availability is not probably a requirement for these environments.
74+
75+
So, This solution will help you save on NAT costs when scalability & HA are not required.
5576

5677
## How it works
5778

58-
This pattern allows you to assign your Lambda function a static public IP address that you can use to interact with APIs that require whitelisted IPs without the need to provision a NAT Gateway. Therefore, this pattern will save almost **$33/month** in NAT Gateway costs.
79+
This pattern kick things off by provisioning an Elastic IP in your account.
80+
As you may know, AWS manages the provisioning of ENIs for each Lambda provisioned within an AWS VPC. Using CDK code we won't have access to that ENI. However, once that ENI is provisioned it can be accessed. So the pattern will create a Custom Resource that taps into that ENI and make a CLI call (using the SDK) to attach it to the Elastic IP.
81+
82+
Now that the lambda has an elastic IP associated to its network interface, you can copy that Elastic IP and whitelist it with your 3rd party vendors so the lambda can connect to it.
83+
84+
#### Main Benefit:
85+
Saving on NAT Gateway costs **$33/month per subnet per environment-account** when the solution does not need to be very scalable or highly available
5986

6087
##### **NOTE:** This pattern is best suited for non-production environments since it is not multi-AZ nor highly scalable.
6188

@@ -65,7 +92,22 @@ The following resources will be provisioned:
6592
- An Elastic IP to associate with the Lambda function
6693
- A custom resource with Lambda function to associate the Elastic IP with the test lambda's ENI
6794

68-
Since AWS manages the provisioning of any Lambda ENI, we cannot access that ENI in CDK code. Therefore, to automate the process, we have to associate the Elastic IP with the ENI in a custom resource after the deployment occurs and the ENI is provisioned.
95+
96+
97+
##### **NOTE:** This pattern is best suited for non-production environments since it is not multi-AZ nor highly scalable.
98+
99+
### Limitations To Be Aware Of
100+
101+
- Elastic IPs: 5 per Region [VPC Quotes](https://docs.aws.amazon.com/vpc/latest/userguide/amazon-vpc-limits.html)
102+
- Network interfaces: 5,000 per Region
103+
- HENIs (Hyperplane ENIs) soft limit of 250 per VPC and the hard limit is 350 HENIs per VPC [link](https://aws.plainenglish.io/dealing-with-you-have-exceeded-the-maximum-limit-for-hyperplane-enis-for-your-account-223147e7ab64#b6c5)
104+
- Hyperplane ENI is a special type of ENI used by AWS Lambda to cut off on the start up time of ENI provisioning and provide the capability of sharing the underlying network hardware for all Lambdas in *subnet+securityGroup* combination (for more [info](https://aws.amazon.com/blogs/compute/announcing-improved-vpc-networking-for-aws-lambda-functions/))
105+
106+
- There is a limit of 65K on connections for a single Hyperplane ENI. So, if group of Lambdas in the same Subnet+SecurityGroup combination create more than 65K connections at the same time, AWS will provision a new ENI for the connection number 65001 [Lambda ENI](https://docs.aws.amazon.com/lambda/latest/dg/foundation-networking.html#foundation-nw-eni-create)(Thanks to Yan Cui for pointing that out in the [comment on this video](https://www.youtube.com/watch?v=yV1TGDYR3qU&t=92s&ab_channel=YanCui))
107+
108+
### Pricing Notes
109+
You are still charged for data transfer expenses in and out of the ENI (~$0.09/GB in each direction) [data-transfer pricing](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer)
110+
69111

70112
## Testing
71113

lambda-elastic-ip-no-nat-gateway-cdk/cdk/lib/cdk-stack.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ interface AssociateLambdaToElasticIpCRProps {
1212
vpc: cdk.aws_ec2.IVpc;
1313
publicSubnet: cdk.aws_ec2.ISubnet;
1414
securityGroup: cdk.aws_ec2.ISecurityGroup;
15-
functionName: string;
1615
}
1716
export class LambdaElasticIpStack extends cdk.Stack {
1817
constructor(scope: Construct, id: string, props: LambdaElasticIpStackProps) {
@@ -57,11 +56,10 @@ export class LambdaElasticIpStack extends cdk.Stack {
5756
vpc,
5857
publicSubnet,
5958
securityGroup,
60-
functionName: publicFunction.functionName,
6159
});
6260
}
6361

64-
private associateLambdaToElasticIpCR({ elasticIP, publicSubnet, securityGroup, vpc, functionName }: AssociateLambdaToElasticIpCRProps) {
62+
private associateLambdaToElasticIpCR({ elasticIP, publicSubnet, securityGroup, vpc }: AssociateLambdaToElasticIpCRProps) {
6563
const associateElasticIpFunctionCR = new cdk.aws_lambda_nodejs.NodejsFunction(this, 'Associate-Elastic-IP-CR', {
6664
memorySize: 128,
6765
handler: 'handler',
@@ -98,7 +96,6 @@ export class LambdaElasticIpStack extends cdk.Stack {
9896
availabilityZone: publicSubnet.availabilityZone,
9997
allocationId: elasticIP.attrAllocationId,
10098
staticIp: elasticIP.ref,
101-
functionName,
10299
date: new Date(),
103100
}),
104101
},

lambda-elastic-ip-no-nat-gateway-cdk/cdk/src/lambdas/associate-lambda-elastic-ip-cr.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ export const handler: Handler = async (event: AssociateLambdaElasticIpEvent) =>
4040
Name: 'status',
4141
Values: ['in-use'],
4242
},
43-
{
44-
Name: 'description',
45-
Values: [`AWS Lambda VPC ENI-${event.functionName}*`],
46-
},
4743
],
4844
AllocationIds: [allocationId],
4945
MaxResults: 10,
@@ -58,7 +54,9 @@ export const handler: Handler = async (event: AssociateLambdaElasticIpEvent) =>
5854
console.log('DescribeNetworkInterfacesCommandResponse:', JSON.stringify(DescribeNetworkInterfacesCommandResponse, null, 2));
5955
const returnedNetworkInterfaces = DescribeNetworkInterfacesCommandResponse.NetworkInterfaces;
6056
if (!returnedNetworkInterfaces?.length) {
61-
throw new Error(`No network interface is associated with this lambda function ${event.functionName}`);
57+
throw new Error(
58+
`No network interface is associated with this subnet + securityGroup combination (${event.subnetId}, ${event.securityGroupId})`,
59+
);
6260
}
6361

6462
console.log('Associating Lambda to Elastic IP...');

lambda-elastic-ip-no-nat-gateway-cdk/cdk/src/types/associate-lambda-elastic-ip-event.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ export type AssociateLambdaElasticIpEvent = {
55
allocationId: string;
66
staticIp: string;
77
availabilityZone: string;
8-
functionName: string;
98
};

lambda-elastic-ip-no-nat-gateway-cdk/example-pattern.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
},
4343
"cleanup": {
4444
"text": [
45-
"Delete the stack: <code>cdk destroy</code>."
45+
"Delete the stack: <code> cdk destroy </code>."
4646
]
4747
},
4848
"authors": [

0 commit comments

Comments
 (0)