I have one lambda inside a VPC. It has 2 subnets (A & B). I need to call an external api inside the lambda. I've been reading about NatGatway and RouteTables but I'm not able to provide internet access (output, from Lambda to the API) to the lambda. I'm using serverless framework.
Here is my serverless file:
serverless.yml
...
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
memorySize: 256
iamRoleStatements:
- Effect: 'Allow'
Resource: !GetAtt ReceiveOrderQueue.Arn
Action:
- 'sqs:SendMessage'
- Effect: 'Allow'
Action:
- 'lambda:InvokeFunction'
Resource: '*'
environment:
NODE_ENV: ${opt:stage, 'dev'}
DB_NAME: ${self:custom.DB_NAME}
DB_USER: ${self:custom.DB_USERNAME}
DB_PASS: ${self:custom.DB_PASSWORD}
DB_PORT: ${self:custom.DB_PORT}
DB_HOST: ${self:custom.PROXY_ENDPOINT}
DATABASE_URL: 'mysql://${self:custom.DB_USERNAME}:${self:custom.DB_PASSWORD}@${self:custom.PROXY_ENDPOINT}:${self:custom.DB_PORT}/${self:custom.DB_NAME}'
plugins:
- serverless-offline
- serverless-plugin-include-dependencies
resources:
- ${file(resources/vpc.yml)}
- ${file(resources/routing.yml)}
- ${file(resources/rds.yml)}
- ${file(resources/rds-proxy.yml)}
- ${file(resources/receive-order-queue.yml)}
custom:
...
functions:
refresh-api:
name: update-order-status-${opt:stage, 'dev'} // This is the lambda that needs to call the api
handler: update-order-status/main.handler
timeout: 30
vpc:
securityGroupIds:
- !Ref LambdaSecurityGroup
subnetIds:
- !Ref SubnetA
- !Ref SubnetB
events:
- httpApi:
method: ANY
path: /{proxy+}
order-receiver:
name: order-receiver-${opt:stage, 'dev'}
handler: order-receiver/main.handler
timeout: 120
vpc:
securityGroupIds:
- !Ref LambdaSecurityGroup
subnetIds:
- !Ref SubnetA
- !Ref SubnetB
events:
- sqs:
arn: !GetAtt ReceiveOrderQueue.Arn
batchSize: 10
package:
patterns:
- '!node_modules/.prisma/client/libquery_engine-*'
- 'node_modules/.prisma/client/libquery_engine-linux-arm64-*'
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node'
- '!node_modules/prisma/libquery_engine-*'
- '!node_modules/@prisma/engines/**'
Here are my resources related to VPC and Routing (I don't think that all others be needed to solve this trouble)
routing.yml
Resources:
RouteTablePublic:
DependsOn: VPCGA
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: 'Name'
Value: 'RouteTablePublic'
RoutePublic:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref RouteTablePublic
RouteTableAssociationSubnetA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePublic
SubnetId: !Ref SubnetA
NatGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGatewayA:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGatewayEIP.AllocationId
SubnetId: !Ref SubnetA
NatGatewayB:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGatewayEIP.AllocationId
SubnetId: !Ref SubnetB
RouteTableAssociationSubnetB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePublic
SubnetId: !Ref SubnetB
And my vpc.yml
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: ${self:custom.VPC_CIDR}.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: 'Name'
Value: 'VPC'
SubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: ${self:provider.region}a
CidrBlock: ${self:custom.VPC_CIDR}.0.0.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: 'Name'
Value: 'SubnetA'
SubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: ${self:provider.region}b
CidrBlock: ${self:custom.VPC_CIDR}.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: 'Name'
Value: 'SubnetB'
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: 'Security group for Lambdas'
Tags:
- Key: 'RefreshLambdaSecurityGroup'
Value: 'LambdaSecurityGroup'
RDSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: 'Security group for RDS'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 0
ToPort: 65535
CidrIp: '0.0.0.0/0'
Tags:
- Key: 'RefeshRDSSecurityGroup'
Value: 'RDSSecurityGroup'
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: 'Name'
Value: 'InternetGateway'
VPCGA:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
SQSEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.sqs'
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref LambdaSecurityGroup
SubnetIds:
- !Ref SubnetA
- !Ref SubnetB
VpcEndpointType: Interface
Thanks in advance <3
This shows that you are creating 2 Subnets and 2 NAT Gateways in them. Both are public subnet and their default gateway points at Internet gateway as expected. When it comes to Lambda, you will need to create Private Subnets (i.e. 2 more subnets) and point their default gateway towards the NAT Gateways.
Sharing updated file sections below: (excuse any typo's)
vpc.yml Adding 2 private subnets too
SubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: ${self:provider.region}a
CidrBlock: ${self:custom.VPC_CIDR}.0.2.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: 'Name'
Value: 'SubnetC'
SubnetD:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: ${self:provider.region}b
CidrBlock: ${self:custom.VPC_CIDR}.0.3.0/24
MapPublicIpOnLaunch: false
Tags:
- Key: 'Name'
Value: 'SubnetD'
routing.yml Getting Routes setup for 2 private subnets. Since you have 2 NAT Gateways, we will need 2 Private route tables, one for each AZ
RouteTablePrivateAzA:
DependsOn: VPCGA
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: 'Name'
Value: 'RouteTablePrivateAzA'
RouteTablePrivateAzB:
DependsOn: VPCGA
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: 'Name'
Value: 'RouteTablePrivateAzB'
RoutePrivateAzA:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref NatGatewayA
RouteTableId: !Ref RouteTablePrivateAzA
RoutePrivateAzB:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref NatGatewayB
RouteTableId: !Ref RouteTablePrivateAzB
RouteTableAssociationSubnetC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePrivateAzA
SubnetId: !Ref SubnetC
RouteTableAssociationSubnetD:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTablePrivateAzB
SubnetId: !Ref SubnetD
serverless.yml Creating Lambda in Private Subnets
order-receiver:
name: order-receiver-${opt:stage, 'dev'}
handler: order-receiver/main.handler
timeout: 120
vpc:
securityGroupIds:
- !Ref LambdaSecurityGroup
subnetIds:
- !Ref SubnetC
- !Ref SubnetD
With the above flow will be:
Lambda [Private Subnet] > NAT GW [Public Subnet] > IGW > Internet