Provisioning Infrastructure using Terraform Modules for AWS Lambda, API Gateway, and API Integration

In today’s digital era, deploying applications efficiently is paramount for staying competitive. AWS Lambda and API Gateway have transformed how developers build and deploy serverless applications, offering scalability, flexibility, and cost-effectiveness. When paired with Terraform, an infrastructure as code tool, the deployment process becomes even smoother. But what if you want to deploy an application using a container image stored in Amazon Elastic Container Registry (ECR)? Let’s explore how to deploy an application using AWS Lambda with API Gateway and Terraform modules, incorporating ECR images into the mix.

Why Use ECR Images?

Amazon Elastic Container Registry (ECR) is a fully managed container registry service that makes it easy to store, manage, and deploy Docker container images. By utilizing ECR, you can securely store your container images and seamlessly integrate them into your deployment workflows. This allows for consistency between development, testing, and production environments, streamlining the deployment process and enhancing collaboration among team members.

Project Structure Overview:

Before we dive into the deployment process, let’s take a quick look at the directory structure of our Terraform project:

├── main.tf
├── modules
│   ├── api_gateway
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   ├── lambda_api_integration
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   └── lambda_functions
│   ├── main.tf
│   ├── outputs.tf
│   └── variables.tf
└── variables.tf

In this structure, we have organized our Terraform configurations into modules for API Gateway, Lambda functions, and Lambda API integration. Each module contains its own set of Terraform configuration files (main.tf, variables.tf, output.tf) for better modularity and maintainability.

Lets first start with lambda function

lambda_function_module
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf

Overview Of main.tf

  1. IAM Role Definition (lambda_role):
    • This part creates an IAM role named “lambda_role” for Lambda functions.
    • It specifies that the Lambda service is allowed to assume this role using the “sts:AssumeRole” action.
  2. Lambda Execution Policy (lambda_execution_policy):
    • An inline policy named “lambda_execution_policy” is attached to the IAM role.
    • This policy grants Lambda functions permission to invoke other Lambda functions using the “lambda:InvokeFunction” action.
    • The “*” wildcard in the “Resource” field allows this permission for all Lambda functions.
  3. ECR Access Policy (ecr_access_policy):
    • Another inline policy named “ecr_access_policy” is attached to the IAM role.
    • This policy grants permissions required for accessing Amazon ECR (Elastic Container Registry) resources, including actions like “ecr:GetAuthorizationToken” and “ecr:BatchGetImage”.
    • The “*” wildcard in the “Resource” field allows access to all ECR resources.
  4. Lambda Basic Execution Role Attachment:
    • Lastly, the AWS managed policy “AWSLambdaBasicExecutionRole” is attached to the IAM role.
    • This policy provides basic execution permissions for Lambda functions, enabling them to write logs to CloudWatch Logs and access other necessary resources.
Create a lambda function
resource "aws_lambda_function" "Prod_lamda" {
  for_each = var.functions

  function_name = each.key
  image_uri     = each.value.image_uri
  memory_size   = each.value.memory_size
  timeout       = each.value.timeout
  role          = each.value.lambda_role
  package_type  = "Image"  # Used package type as Image for container-based Lambda functions
}

Overview of variables.tf

variables for lambda function

variable "functions" {
  type = map(object({
    image_uri   = string
    memory_size = number
    timeout     = number
    lambda_role = string
  }))
}

Overview of output.tf

outputs of all the created resources and its values

output "function_arns" {
  description = "ARNs of the created Lambda functions"
  value       = [for key, value in aws_lambda_function.Prod_lamda : value.arn]
}
now create api_gateway

api_gateway
   │   ├── main.tf
   │   ├── output.tf
   │   └── variables.tf

Overview of main.tf

data "aws_region" "current" {}

resource "aws_api_gateway_rest_api" "Prod_API" { 
  name = "Prod_API"
  description = "Prod API Gateway"
  endpoint_configuration {
    types = ["REGIONAL"]
 }
}
#Create resources in api:

resource "aws_api_gateway_resource" "order_check" {
  rest_api_id = aws_api_gateway_rest_api.Prod_API.id
  parent_id   = aws_api_gateway_rest_api.Prod_API.root_resource_id
  path_part   = "order_check"
}
#Now attach method to existing resources:

resource "aws_api_gateway_method" "order_check_method" {
  rest_api_id   = aws_api_gateway_rest_api.Prod_API.id
  resource_id   = aws_api_gateway_resource.order_check.id
  http_method   = "POST"  # Update with your desired HTTP method
  authorization = "NONE"
}

Overview of output.tf

# Define outputs
output "api_gateway_arn" {
  value = aws_api_gateway_rest_api.Prod_API.arn
}
Now Create lambda_api_integration module

├── lambda_api_integration
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf

Overview of main.tf

#Create api integration with variables:


resource "aws_api_gateway_integration" "order_check_integration" {
  rest_api_id             = var.rest_api_id
  resource_id             = var.resource_id
  http_method             = var.http_method_order_check
  integration_http_method = "POST"  # Update with your desired integration HTTP method
  type                    = "AWS_PROXY"
  passthrough_behavior    = "WHEN_NO_MATCH"
  uri                     = var.uri

}

Overview of variables.tf

#Create variables for all used variable in integration code:


variable "http_method_save_menu" {
  type = string
  default = "POST"
}

Now we will create a main directory called “main.tf” where we will add the modules we have created.

#first we will add lambda module:

module "lambda_functions" {
  source = "./modules/lambda_functions"
  functions = {
    Prod_Order_Check = {
      image_uri    = "00000.dkr.ecr.us-east-2.amazonaws.com/prod-lamda-test:latest"
      memory_size  = 128
      timeout      = 200
      lambda_role  = module.lambda_functions.lamda_role_arn
    }
}
}



#second we will add api gateway module:

module "api_gateway" {
  source = "./modules/api_gateway"
}


#Third we will add api integration module

module "order_check_integration_with_api" {
  source = "./modules/lambda_api_integration"
  rest_api_id = module.api_gateway.rest_api_id
  resource_id = module.api_gateway.resource_order_check
  uri = module.lambda_functions.lambda_function_invoke_uris["Prod_Order_Check"]
}

#Now we will create resources for deployment


resource "aws_api_gateway_deployment" "deployment" {
  rest_api_id = module.api_gateway.rest_api_id
  stage_name = var.stage_name
  depends_on = [
    module.menu_subscriber_integration_with_api,
    module.order_check_integration_with_api
  ]

}
Overview of main directory variables.tf

variable "lambda_function_arns" {
  description = "Map of Lambda function identifiers to ARNs"
  type        = map(string)
  default     = {}
}


variable  "api_gateway_arn"{
  type =  string
  default = "module.API_gateway.output.api_gateway_arn"
}

variable "lambda_function_invoke_uris" {
  description = "URIs for invoking the Lambda functions through API Gateway"
  type        = list(string)
  default = [ "module.lambda_functions.lambda_function_invoke_uris" ]
  # Add any additional constraints or validation rules if needed
}
variable "stage_name" {
  default = "Dev"
  type    = string
}

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top