Deploy Sever-less Application using Terraform (AWS API Gateway, Lambda and DynamoDB)


Nowadays, A popular approach to running "serverless" web applications is to implement the application functionality as one or more functions in AWS Lambda and then expose these for public consumption using Amazon API Gateway without having to think about infrastructure.

With my style, I am going through a procedure to deploy such a web application using terraform.

Note: In order to follow this guide you will need an AWS account and to have Terraform installed. Configure your credentials so that Terraform is able to act on your behalf.
                            or
you can simply clone my repo and place your access key in terraform.tfvars file.

Before moving onto the deployment refer below figure to understand how AWS full architecture works.

Our ideas is simple, User fille the feedback form and click submit, on submission write to the DynamoDB (post).

Refer the form below
Steps involved in the deployment
1.    Create DynamoDB
2.    Create AWS IAM Role to assume on behalf you
3.    Create a Lamda function
3.1. Create put Item function 
4.    Create Api Gateway
4.1. Create api  Resourse
4.3  Create api method 
4.4. Create invoke function to invoke lamda
5.   Deploy API and Allowing API Gateway to Access Lambda.
6. Copy the API ARN and paste to the form post URL to test (POSTMAN can also be used)

Note find the full repo here

1. Create DynamoDB
Amazon DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability. – from AWS 
Create DynamoDB with primary as email
[fayasak@controller]$ cat dynamo.tfresource "aws_dynamodb_table" "dynamodb"{ name="mywebsite" hash_key="email" attribute=[ { name="email" type="s" } ] }
2. Create AWS IAM Role to assume on behalf of you
Before specifying the Lambda Functions we have to create permissions for our functions to use. This makes sure that our lambda function has permission to access DynamoDB resources.
[fayasak@controller]$ cat iam.tf#create a role for lamda access aws resoucesresouce "aws_iam_role" "lamda-iam"{ #role name name="lamda-dynamodb" #assume role to a services assume_role_policy =<<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "lambda.amazonaws.com" }, "Effect": "Allow", "Sid": ""    } ] }EOF}# create policy for role to assume dynamodbresource "aws_iam_role_policy" "dynamodb-lambda-policy"{ name = "dynamodb_lambda_policy" role = "${aws_iam_role.lamda-iam.id}"
policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:*" ], "Resource": "${aws_dynamodb_table.dynamodb.arn}" } ] } EOF}

Note: I have created a policy full access policy for dynamoDB. if you want you can change policy as per you want.
  EX: "dynamodb:Scan","dynamodb:BatchWriteItem","dynamodb:PutItem"

3.    Create a Lamda function
 In my case, I want sned data to DynamoDB though I've created Lambda is used to post or send the coding tips to the database.
[fayasak@controller]$ cat postlambda.tf# postresource "aws_lambda_function" "lambda_test"{ function_name = "lambda_post" filename = "./post.zip/index.js" role = "${aws_iam_role.lamda-iam.arn}" handler = "index.handler" runtime = "nodejs8.10"}
The above code used to create lambda function and /post.zim/index.js contains post lambda function
3.1. Create put Item function 
post.zip/index.js contains the lamda function or handler to post data to dynamodb
[fayasak@controller]$ cat postlambda.tfconsole.log('function starts') const AWS = require('aws-sdk') AWS.config.apiVersions = { dynamodb: '2012-08-10', // other service API versions region: 'eu-west-1' }; const dynamodb = new AWS.DynamoDB.DocumentClient() exports.handler = async(event, context) =>{ const feedbackitem = {}; feedbackitem.Item = { "email": event.email, "fname": event.fname, "lname": event.lname, "country": event.country, "message": event.message }; feedbackitem.TableName: "mywebsite"; try { const data = dynamodb.put(feedbackitem).promise(); console.log(data); } catch(err) { console.log(err); } }
you can refer the page to write lambda function here
Note: I referred some AWS Article to make my code more convenient- DocumentClient and Promises
4.    Create API Gateway
[fayasak@controller]$ cat api_gateway.tf#create an aws apiresouce "aws_api_gateway_rest_api" "lamda-api-gateway"{ name= "lamda-api" description = "api access to lamda"}
Note:All incoming requests to API Gateway must match with a configured resource and method in order to be handled. I implemented my API resource and method in postlabmda.tf instead of api_gateway.tf. 
Let's say you want to create get method in this case  you only have to create another getlabmda.tf simply have to change HTTP-METHOD to GET.
4.1. Create API  Resource
[fayasak@controller]$ cat postlabmda.tfresource "aws_api_gateway_resource" "lamda-resource"{ rest_api_id = "${aws_api_gateway_rest_api.lamda-api-gateway.id}" parent_id   = "${aws_api_gateway_rest_api.lamda-api-gateway.root_resource_id}" path_part   = "lamda-resource"}
4.3  Create API method 
[fayasak@controller]$ cat postlabmda.tfresource "aws_api_gateway_method" "api-method" { rest_api_id = "${aws_api_gateway_rest_api.lamda-api-gateway.id}" resource_id = "${aws_api_gateway_resource.lamda-resource.id}" http_method = "POST" authorization = "NONE" }
4.4. Create invoke function to invoke the lambda
Each method on an API gateway resource has an integration which specifies where incoming requests are routed. Add the following configuration to specify that requests to this method to send the Lambda function
[fayasak@controller]$ cat postlabmda.tf#api gate way invoke lambda (every client post invoke lambda)resource "aws_api_gateway_integration" "api-invoke-lambda" {   rest_api_id = "${aws_api_gateway_rest_api.lamda-api-gateway.id}"   resource_id = "${aws_api_gateway_method.api-method.lamda-resource}"   http_method = "${aws_api_gateway_method.api-method.http_method}"   integration_http_method = "POST"   type                    = "AWS_PROXY"   uri                     = "${aws_lambda_function.lambda_test.invoke_arn}" }


5. Deploy API and Allowing API Gateway to Access Lambda.
By default, any two AWS services have no access to one another until access is explicitly granted.
[fayasak@controller]$ cat postlabmda.tf # create an API Gateway "deployment" in order to activate #the configuration and expose the API at a URL resource "aws_api_gateway_deployment" "expose-deployment" { depends_on = ["${aws_api_gateway_integration.api-invoke-lambda}"] rest_api_id = "${aws_api_gateway_rest_api.lamda-api-gateway.id}" stage_name = "test" } #allowing API Gateway to Access Lambda resource "aws_lambda_permission" "api-gw" { statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = "${aws_lambda_function.lambda_test.arn}" principal = "apigateway.amazonaws.com" # The "/*/*" portion grants access from any method on any resource # within the API Gateway REST API. source_arn = "${aws_api_gateway_rest_api.expose-deployment.execution_arn}/*/*" }
  6. Copy the API ARN and paste to the form post URL to test 
[fayasak@controller]$ cat /website/feedback.html <form action="https://kfb9dlbm36.execute-api.us-west-2.amazonaws.com/prod/lamda-resource" method="post"> <div class="row"> <div class="col-25"> <label for="fname">First Name</label> </div> <div class="col-75"> 

Find the full code here
, , , , , , ,

No comments:

Post a Comment