Terraform Hands-On: Deploy a serverless application on AWS
In this project, I demonstrate how to deploy a serverless application on AWS using Terraform. The application utilizes API Gateway for REST APIs, Lambda functions for compute, and DynamoDB for database storage.
Introduction:
Step-by-Step Guide
Create the Terraform Files.
-
The provider.tf file specifies the AWS provider configuration, including the region where resources will be deployed.
provider "aws" {
region = var.region
}
-
The main.tf file defines AWS resources including API Gateway, Lambda function, DynamoDB table, IAM roles, and their configurations using Terraform's declarative syntax.
resource "aws_iam_role" "lambda_exec_role" {
name = "lambda_exec_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
},
},
],
})
}
resource "aws_iam_policy_attachment" "lambda_policy_attachment" {
name = "lambda_policy_attachment"
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
roles = [aws_iam_role.lambda_exec_role.name]
}
resource "aws_dynamodb_table" "my_table" {
name = var.dynamodb_table_name
hash_key = "id"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "id"
type = "S"
}
}
resource "aws_lambda_function" "my_lambda" {
filename = "lambda_function.zip"
function_name = var.lambda_function_name
role = aws_iam_role.lambda_exec_role.arn
handler = "index.handler"
runtime = "nodejs14.x"
environment {
variables = {
DYNAMODB_TABLE = aws_dynamodb_table.my_table.name
}
}
source_code_hash = filebase64sha256("lambda_function.zip")
}
resource "aws_api_gateway_rest_api" "my_api" {
name = var.api_gateway_name
description = "API Gateway for my serverless application"
}
resource "aws_api_gateway_resource" "api_resource" {
rest_api_id = aws_api_gateway_rest_api.my_api.id
parent_id = aws_api_gateway_rest_api.my_api.root_resource_id
path_part = "items"
}
resource "aws_api_gateway_method" "api_method" {
rest_api_id = aws_api_gateway_rest_api.my_api.id
resource_id = aws_api_gateway_resource.api_resource.id
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "api_integration" {
rest_api_id = aws_api_gateway_rest_api.my_api.id
resource_id = aws_api_gateway_resource.api_resource.id
http_method = aws_api_gateway_method.api_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.my_lambda.invoke_arn
}
resource "aws_lambda_permission" "apigw_lambda" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.my_lambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.my_api.execution_arn}/*/*"
}
resource "aws_api_gateway_deployment" "api_deployment" {
depends_on = [aws_api_gateway_integration.api_integration]
rest_api_id = aws_api_gateway_rest_api.my_api.id
stage_name = "prod"
}
-
The variables.tf file declares input variables used in the configuration, such as AWS region and resource names.
variable "region" {
description = "AWS region to deploy resources"
type = string
default = "us-west-2"
}
variable "lambda_function_name" {
description = "Name of the Lambda function"
type = string
default = "MyServerlessFunction"
}
variable "api_gateway_name" {
description = "Name of the API Gateway"
type = string
default = "MyServerlessAPI"
}
variable "dynamodb_table_name" {
description = "Name of the DynamoDB table"
type = string
default = "MyServerlessTable"
}
-
The outputs.tf file defines output values that are useful for interacting with or verifying the deployed infrastructure.
output "api_url" {
description = "Base URL of the API Gateway endpoint"
value = aws_api_gateway_deployment.api_deployment.invoke_url
}
To create the lambda_functon.zip file in Codespaces, follow these steps:
Create the Lambda Function Code:
Create a directory for your Lambda function code, if it doesn't exist. Inside the directory, create an index.js file with your Lambda function code. Zip the Lambda Function Code:
Use the zip command to create a zip file containing your Lambda function code. Here's how you can do this step by step:
Step 1: Create the Lambda Function Code Navigate to your project directory in Codespaces and create the index.js file:
mkdir -p lambda_function
cd lambda_function
echo 'const AWS = require("aws-sdk"); const dynamo = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => { let response; try { const body = JSON.parse(event.body); const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: body.id, data: body.data } };
await dynamo.put(params).promise();
response = { statusCode: 200, body: JSON.stringify({ message: "Item inserted successfully" }), }; } catch (error) { response = { statusCode: 500, body: JSON.stringify({ error: error.message }), }; } return response; };' > index.js
Step 2: Create the Zip File Navigate back to your Terraform project directory and create the zip file:
cd .. zip lambda_function.zip -j lambda_function/index.js
Verifying on AWS Console API Gateway:
Go to the API Gateway service in the AWS Management Console. Find the API with the name specified in api_gateway_name (default: MyServerlessAPI). Verify the resources (items) and methods (POST) are configured correctly. Lambda Function:
Navigate to the Lambda service in the AWS Management Console. Find the Lambda function with the name specified in lambda_function_name (default: MyServerlessFunction). Check the function code and its integration with API Gateway. DynamoDB Table:
Visit the DynamoDB service in the AWS Management Console. Locate the DynamoDB table with the name specified in dynamodb_table_name (default: MyServerlessTable). Review the table structure and ensure it matches the expected schemaThis setup will deploy a serverless application on AWS using API Gateway for REST APIs, Lambda functions for compute, and DynamoDB for database storage. The outputs.tf file will provide you with the API Gateway endpoint URL once deployed.