Serverless architecture offers developers a variety of advantages that prove attractive in the development of large, scalable applications. Let's go over 3 top benefits:
- It offers the ability to write code and deploy to the cloud without worrying about the infrastructure.
- It enhances the economic sense of paying for what you use or execution-only billing.
- The ability to write an application in a language/framework of your choosing with a fast turnaround to production-ready setup.
Integration of third-party services is an inevitable part of the development lifecycle. If you do work with security-conscious third-party services then a common requirement that arises is whitelisting of an IP to avail these services.
In this two-part tutorial series, we will walk through the creation of an AWS lambda function with some additional AWS resources that will allow you to provide third-party services with a static IP for whitelisted communication
Let’s begin with Part 1 of this tutorial where you will:
- Use the serverless framework with webpack to create a serverless application and all the necessary AWS resources that go with it.
- Integrate a NAT Gateway with Elastic IP for static IP.
In the next part (i.e. Part 2 ) of this series, you will,
- Use GitHub Actions as a CD pipeline to verify and deploy to AWS.
The Architecture
This tutorial assumes you have an expert level understanding of the following AWS services:
We will also be using the serverless framework to create, set up, test locally and deploy the application. The serverless framework is a great tool to get started with serverless architecture and systems.
Please visit the link https://www.serverless.com/framework/docs to learn more.
Our setup will look like this:
In this tutorial, we will help you get through the deployment of a Lambda function with the proper connections to have an elastic IP associated.
Let's start building
Starter Project
Just a quick intro to what all we have. The most important part of the setup is the serverless.yml file. In it you will find:
Service name: Currently, it reads from the env file. Feel free to use one of your choosing.
Plugins:
serverless-webpack plugin for bundling the functions, the dependencies and more. Check out the webpack config here.
serverless-offline plugin for testing locally.
We’ll break this part of the tutorial into two sections:Create the AWS resources
Addition of Lambda functions
Creating the AWS Resources
Step 1 - Update the serverless file
Add the following lines below the last lines of the serverless.yml file:
...
functions: ${file(./resources/functions.yml)}
resources:
- ${file(./resources/iam.yml)}
- ${file(./resources/vpc.yml)}
- ${file(./resources/security-groups.yml)}
- ${file(./resources/internet-gateway.yml)}
- ${file(./resources/elastic-ip.yml)}
- ${file(./resources/nat-gateway.yml)}
- ${file(./resources/route-private.yml)}
- ${file(./resources/route-public.yml)}
Here we are pointing to the functions (lambdas) and the resources (AWS infrastructure) that we will need to setup all this. We will add these files along the way. Exciting much?
`YAML syntax maybe problematic for some people.
Please take the help of lint plugin or a service
like http://www.yamllint.com/`
Step 2 - Adding the VPC
vi resources/vpc.yml
Let's add the resources. First, create a vpc.yml file in the resources folder. This is where you will create an AWS vpc resource. Copy and paste the following code into the vpc.yml. Don’t forget to check the indentation and change the names, tags as you want.
Resources:
ServerlessVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "10.0.0.0/16"
Tags:
- Key: 'Name'
Value: 'ServerlessVPC'
Pretty basic stuff, right? We have a resource type and a CIDR block (a range of IP addresses).
We will need to come back to this file in a bit. Let's move on.
Step 3 - Adding the Elastic IP and Internet Gateway
We will create two files called internet-gateway.yml and elastic-ip.yml in the resources folder. Add the below resources to the files as mentioned in the elastic-ip.yml
vi resources/elastic-ip.yml
## Elastic IP
Resources:
ElasticIpLambda:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Now let's create the internet-gateway.yml file.
vi touch resources/internet-gateway.yml
## Elastic IP
Resources:
ElasticIpLambda:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
We created two more resources. An Internet Gateway that allows us to connect to the outside internet from AWS VPC and an Elastic IP is the pubic static IP that will be given to third parties as our service IP address. The domain is a field that indicates whether the Elastic IP address is for use with instances in a VPC (which it is!).
At this point your folder structure will look like this:
Step 4 - Adding a NAT Gateway Resource and Subnets
vi touch resources/nat-gateway.yml
Create a nat-gateway.yml file in the resources. Add the following resources.
#nat-gateway.yml
Resources:
ServerlessNatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- ElasticIpLambda
- AllocationId
SubnetId:
Ref: ServerlessPublicSubnet1
ServerlessPublicSubnet1:
DependsOn:
- ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: ServerlessVPC
CidrBlock: '10.0.2.0/24'
AvailabilityZone: ${self:provider.region}a
Tags:
- Key: Name
Value: ServerlessPublicSubnet1
ServerlessPrivateSubnet1:
DependsOn:
- ServerlessVPC
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: ServerlessVPC
CidrBlock: '10.0.1.0/24'
AvailabilityZone: ${self:provider.region}a
Tags:
- Key: Name
Value: ServerlessPrivateSubnet1
The NAT gateway is the star of the show. NAT is a service that allows instances inside your vpc to connect to outside resources or systems but external connections to internal vpc systems are prohibited. AllocationId is a function that gets the AllocationId of the Elastic IP resource we created. The Nat has a public subnet that it connects to. Look at the figure for the architecture.
The other resources are subnets. A private one that connects to resources in the vpc. A public one that will reroute and connect to Internet Gateway. Read more about the subnet here.
Step 5 - Route Tables
We will have two route tables as part of this setup. One for the private subnet and another for the public subnet. Create two files route-private.yml and route-public.yml and add the following resources correctly.
Let's work on the route-private first
vi resources/route-private.yml
#route-private.yml
Resources:
DefaultPrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: ServerlessVPC
Tags:
- Key: Name
Value: DefaultPrivateRouteTable
DefaultPrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: DefaultPrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: ServerlessNatGateway
SubnetRouteTableLambdaAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: DefaultPrivateRouteTable
SubnetId:
Ref: ServerlessPrivateSubnet1
Now the route public file and resources
vi resources/route-public.yml
#route-public.yml
Resources:
DefaultPublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: ServerlessVPC
Tags:
- Key: Name
Value: DefaultPublicRouteTable
DefaultPublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: DefaultPublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: SlsTutorialIGW
IGWRouteTableLambdaAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: DefaultPublicRouteTable
SubnetId:
Ref: ServerlessPublicSubnet1
A route table is a set of rules that establishes where network traffic is directed. It's can be associated with a subnet. It has a destination and target gateway. For the private route table, we add a route table rule that routes all traffic through the NAT Gateway. We also create an association between the route table and our private subnet.
This is how a route table looks after creation. Don't worry we will reach there.
The public route table also follows mostly the same pattern. The only difference is that its association is with the IG we created in Step 2.
Step 6 - VPC Gateway attachment
Now let's get back to the vpc resource down the line. It's that time. Go back to the vpc.yml file and add the following lines
vi resources/vpc.yml
#vpc.yml
.
.
.
ServerlessVPCGA:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: ServerlessVPC
InternetGatewayId:
Ref: SlsTutorialIGW
This attaches the Internet Gateway to the vpc enabling communication with the internet through the vpc.
Remember this is a new resource within the vpc file. I know some people don’t like code pictures, but I wanted to try carbon. So vpc.yml will look like this:
Step 7 - Adding an IAM resource
vi resources/iam.yml
Resources:
TestRoleForSLSNATGateway:
Type: AWS::IAM::Role
Properties:
Description: This is an example role for SLS NAT Gateway
RoleName: ${self:service.name}-nat-gateway-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
We are adding an IAM role that will allow us to access the lambda and the associated lambda logs in CloudWatch. Now that we have all the resources. All the file structures should look like this.
Step - 8: Add a security group
We will add a security group for our architecture setup.
vi resources/security-groups.yml
#security-groups.yml
Resources:
ServerlessSecurityGroup:
DependsOn:
- ServerlessVPC
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for Serverless Functions
VpcId:
Ref: ServerlessVPC
Tags:
- Key: 'Name'
Value: 'sls-tutorial-sg'
We add a security group and add it to the VPC we created using the VpcId key in the template. This is important to control traffic to and from the subnets. By default, it has a set of rules associated with the traffic that flows through the group.
Adding the functions
Step 1: Add a function resource
We will add a function or a lambda in this step. Create a file called functions.yml in the resources folders and add the following code to it. This just points to a function that we will add, nothing fancy.
vi resources/functions.yml
#functions.yml
slsNatTutorialFunction:
handler: functions/tutorial-function/index.handler
role: TestRoleForSLSNATGateway
events:
- http
method: GET
path: /say-hello
cors: true
We have the name of the function and the handler it points to.
Step 2 Add the function
Inside the functions folder, create a folder called tutorial-function and an index.js. Add the following function to the handler
mkdir -p functions/tutorial-function
vi touch functions/tutorial-function/index.js
import { apiSuccess, apiFailure } from '@utils';
import axios from 'axios';
exports.handler = async (event, context, callback) => {
console.log(JSON.stringify(event));
try {
const response = await axios.get('https://httpbin.org/ip');
const data = response.data;
console.log(data);
return apiSuccess(callback, data);
} catch (error) {
return apiFailure(callback, error);
}
};
This function is very basic with the idea being to just hit an outside service that returns the IP of the server from which the request was made. This will help us see that we have assigned a NAT Gateway Elastic IP to the lambda.
Step 3 - Attaching the resources to the function
This is where it all comes together. We have created a lot of resources, we need to piece it all together so that the lambda we created has these resources attached. We do that in the serverless.yml file.
vi serverless.yml
.
.
.
versionFunctions: false
vpc:
securityGroupIds:
- Fn::GetAtt:
- ServerlessSecurityGroup
- GroupId
subnetIds:
- Ref: ServerlessPrivateSubnet1
We should add the lines starting from the vpc to the file. Make sure you get the indent correct. We are attaching our Lambda functions with the vpc, security group, and the private subnet. Remember the Lambda rests in the private subnet.
Step 4 - Testing this locally
Now as part of this set up, we do have a very interesting way to test our functions locally. We have added a plugin called serverless-offline to get this started up locally rather easily.
To get started, go to your working directory with your setup and run the following command.
yarn start-offline
This should start up a server using webpack, that exposes the following API’s.
You can see a GET method exposed by the server. To verify the API’s you could now just go to a API testing resource like postman and try to hit this endpoint.
#here is a cURL for you to copy paste.
curl --location --request GET
'http://localhost:3000/local/say-hello'
The result should be something like this.
Nice right? Now let’s get this deployed to AWS so that the whole world can say hello to your API. That was bad. Onwards we go.
Where to go from here?
This got a bit long, didn’t it ? If at any point you get stuck or want to refer to the resources, please feel free to refer to the finished setup available here
We have certainly made great strides in creating all these resources and linking all of them. If you reached all the way to the end here, great job!
Let’s wrap this up and deploy all the resources we created using GitHub Actions in Part 2 of this tutorial series. Will see you there!
Liked what you see? Found it helpful? Feel Free to share it.
We'd love to hear what you think, Tweet at us here.
Originally appeared on https://www.wednesday.is/writing-tutorials/when-less-is-more-serverless-nat-gateway-part-1
About the Author
Vishnu Prasad is a Software Engineer
at Wednesday Solutions. If not thinking about creating great
experiences on the web he is probably re-watching episodes of the Office or listening to 90's Malayalam Music
Top comments (0)