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
- 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.
- 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.
- 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.
- 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
}