How to create an AWS Lamba environment with AWS DocumentDB - MongoDB and serverless in 2021

What do you need for the whole setup ?

AWS Lambda, AWS EC2, AWS API Gateway, AWS DocumentDB, VS 2019 AWS Extension, MongoDB local if you want to manage/explore the DocDB from your PC (MongoCompass)

Quick overview about the steps needed:

  • Create a DocumentDB cluster
  • Create .Net Lambda function (same VPC) with API Gateway connection (with sample code)
  • Upload your .Net code for the function (the sample overwriten)

Done! you can reach the function by the API

Let's go step-by-step:

Let's start with a simple Lambda function first:

  • AWS>Lambda> Create function (upper right yellow button)
  • "Author from scratch"
  • Enter a function name - in this example "functest"
  • Runtime: .NET Core 3.1 (c#...)
  • At the networking section, please select an existing vpc (if any) (it's important to be in the same VPC (network) as other services if you are using more than this)
  • Subnets: select all the VPC related subnets.
  • Security groups: Select the default sec group (you will see the inbound rules in the table below)
  • CREATE FUNCTION

Create AWS Lambda function

After the creation, you can test it - before the first test, you need to enter a sample event with sample data, just give a name for the event "test1", in the data just enter an empty json "{}".
With the new test profile you can test the Lamda, as it's a factory "hello world", the test will be ok, and you will see something like this:

 

How to test AWS Lambda function from AWS Console

 

At this point you have a working serverless Lamda function in .Net, 

What's next ?

We need an API endpoint to use the function. This done by the "Triggers" - so we add a trigger to the function

Add lambda trigger

Trigger configuration: API Gateway
API: Create and API
API Type: Rest API
Security: Open

Add lambda trigger config

After the Add, you have the API endpoint for the func, to see the endpoint address , click on the API Gateway box, and on the table below, at the details, there is the public endpoint address:

AWS Lambda API Gateway details

Click on the endpoint, and you will get the working .net serverless Lambda result:

WellDone!

Install VS 2019 Extension - Push to AWS

For easier deployment from VS2019 , you need the "AWS Toolkit for Visual Studio 2017/2019" extension, with this toolkit you get the "Publish to AWS Lambda" right click menu and many other nice stuff

Upload your own code

Let's create a simple function from own code, VS2019 > New Project > AWS Lambda Project (.Net Core c#) > Empry function

It's a very simple function, try to Publish it to the AWS, Solution explorer > "Publish to AWS Lamda"

This screen has very important settings and dependency with the AWS Console Lambda settings

 

The Lambda function handler at the upload screen must match the Handler settings at the AWS Console,
if it's not matching, you will get this error: (1st screen from the VS AWS Publisher test, 2nd, from the AWS Console Lambda test)

Error at VS test

To solve this, goto the AWS Console > Lambda › Runtime settings, and correct the handler

 

Create DocumentDB (Mongodb compatible)

AWS Console>Amazon DocumentDB>Create

Don't forget to change the instance class to smaller size (t3.medium) - to avoid large bill :-)

For testing purpose the number of instances:1 (larger number will multiply the price)

Show advanced settings: Check the VPC , is it the same as the EC2 (probably yes, if you have one VPC)

At the bottom part you will see the hourly cost for the cluster (play with the instance number, and with the instance class, and you will see how the bill will look like)

 

Turn off SSL

It's an important step, without this, you can't use from .net with SSL enabled.

To disable the TLS configuration, create a new custom Amazon DocumentDB cluster parameter group. Set the tls parameter to disabled, and then modify the cluster to use the new cluster parameter group.

AWS Document  DB turn off SSL with parameter groups

after creation, go into the newly created param group, edit the tls parameter, and set it to false

Go to the cluster -> configuration tab, and modify the config, and set the param group to the new param group.

On the next screen schedule the modification to "Apply immediately" › Modify cluster

Check the DocDB cluster endpoint (Config tab contains the cluster endpont too), you need it for the connections:

Connection to DocumentDB

The AWSDocumentDB  instance be be accessed from the same VPC only, so if you want to access it from your PC or outsie of AWS, you need to "proxy" - ssh tunnel your request with an EC2 instance (which must be located in the same VPC as the DocDB) , regarding this, if you want to use the DocDB from your Lamda, you need to put the Lamda into the same VPC (during function creation, or you can move it later).

SSH Tunneling

This step is optional, only needed if you want to explore the DocumentDB from your PC.

For SSH tunneling you need a virtual machine inside the AWS VPC, that's an EC2 instance, so let's create it.

AWS Console > EC2 › Launch instance > Select the "Amazon Linux 2 AMI (HVM), SSD Volume Type" › Instance type: t3.nano - it's perfectly enough for this usecase (quite interesting, the t3.nano vs t2.nano price - bigger is cheaper), 

Step 3: Configure Instance Details: Leave evething in default

Step 4: Add storage: 8GiB (default size)

Step 5: Tags - no change

Step 6: Configure Security Group: Select an existing security group (select your default VPC security group)

Review and launch

Step 7: Review › Launch : At this point you need to generate a key pair for the SSH login, select the "create new key pair" (if you don't have any) , give it a friendly name, and "Download key pair" (save it to a safe place :-) - you need it later, and can't download again )

Launch instance

Go back to the instances, and check the details of this new instance, search for the IPv4 DNS entry, that will be the "EC2 tunnel endpoint", what you need later, during the "SSH Tunneling"

Launch EC2 instance for the SSH tunneling

Connect from a machine outside AWS using an SSH tunnel

based on: https://aws.amazon.com/premiumsupport/knowledge-center/documentdb-cannot-connect/

1.    Set up an SSH tunnel from your local computer to an EC2 instance that is running in the same VPC as your Amazon DocumentDB cluster. For more information, see Connecting to an Amazon DocumentDB cluster from outside an Amazon VPC.

ssh -i "keypairfile.pem" -L 27017::27017 ec2-user@ -N

This allows you to run commands directly to the local host (27017). The commands are then sent to the Amazon DocumentDB cluster. As a best practice, avoid using this method for production environments or if you use the DocumentDB cluster as a replica set for your connections. For more information, see Connecting to Amazon DocumentDB as a replica set.

After this step, you can use your mongodb compass to explore the DocumentDB data, just connect to your localhost, and it will be tunneled to the AWSDocDB.
The MongoDB Compass connection string will be something like this: mongodb://username:password@localhost:27017/

Performance tests

I made couple of test during the whole experiment, nearly all of them were below 5-6 sec with cold start.
Did many different test scenarios, to see what's going on

  .Net 
Hello World
.Net
S3 PresignUrl
.Net
DocDB Insert
Nodejs
HelloWorld
NodeJs
DocDB Insert
Python
HelloWorld
Cold start 400-1100ms 4400ms 4900-5200ms 500-600ms 4300ms 500-600ms
Hot start 60-70ms 40-100ms 50-90ms 80-100ms 150-500ms 80-90ms

 

Cold start: At least 10-15 minutes after the last usage, the first call to the Lamda

Hot start: 2nd or further call on the same endpoint

Typical cooldown time: 5-10 min,
Regarding the warmup time. all the functions are in different environment, so they are independent from each other.

The reason for the S3 and DocDB longer startup-up time is the related system (S3, DocDB) warmup time

The "Cold Start" situation can be initiated by a code upgrade - like IIS Apppool recycle

Pricing:

When it's stopped, there is no cost on the EC2 and for the DocumentDB

Important: you can stop the DocumentDB for 7 days maximum, after 7 days it will start automatically (remember this, if you left an instance in stopped state during development)

Problems found during development:

Can't reach the DocumentDB - timeout

  • probably the Lambda is not in the same VPC (or not in VPC) as the DocDB
  • the clusterendpoint is not correct (i made this mistake many times, when recreated the DocDB cluster, the address changed)
  • TLS/SSL must be turned off

Permission problems

If you are moving the function into the VPC after creation, you need VPC managment permission, the system will indicate what you need, just goto the IAM and add the policy to the account.

Assembly not found

SSL/TLS with .Net: i can't solve the connection with SSL to DocDB, because when i try to import the amazon *.pem i got an error message: the store is readonly

No Internet connection in the Lamda

For example: Can't reach the External API.
This is normal, if the Lambda running inside a VPC, with the default settings, there is no internet connection.

Solution: You need a NAT Gateway on a public subnet, and on the private subnet route the trafic 0.0.0.0/0 to the NAT Gateway. and put your lambda into the private subnet(s).
Important: the NAT Gateway must be on a public subnet (public = has an internetgateway).
2nd NOTE: Don't know why, but it's not enough to put the Lambda into a subnet with Internet gateway.

There are many article on the net with "lambda internet access",
here's the simpliest: https://dev.to/reggi/how-to-setup-aws-lambda-function-to-talk-to-the-internet-and-vpc-bgf

and AWS Doc: https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/

Other concerns

During the development phase we can use the MongoDB local version, but regarding the Lambda enviroment, it's hard to replicate locally

MongoDB data structure changes

Two scenario

1., Delete a property from the c# model - (or add an extra property at the Mongo side) - so Mongo has more property than the c# model, in this casem, the MongoDriver will thrown an exception:

to solve this, you can use the [BsonIgnoreExtraElements] attribute on the model class:

 

2., If the model has more property than MongoDB (new property added) - on the next change (update), the new property will be created (added) to the structure in the MongoDB