Last active
August 8, 2024 04:24
-
-
Save kuzemkon/fed98f7d5bf27fb553c228a34f73c6fa to your computer and use it in GitHub Desktop.
Terraform IaaC for AWS ECS with Fargate and AWS Application Load Balancer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
variable "env" { | |
type = string | |
default = "dev" | |
} | |
variable "api_host" { | |
type = string | |
default = "my.api.com" | |
} | |
variable "port" { | |
type = string | |
default = "3000" | |
} | |
# Obtain the AWS default VPC data | |
resource "aws_default_vpc" "default" { | |
enable_dns_support = true | |
enable_dns_hostnames = true | |
tags = { | |
Name = "Default VPC" | |
environment = var.env | |
} | |
} | |
# Get the AWS VPC's subnets data | |
data "aws_subnets" "default" { | |
filter { | |
name = "vpc-id" | |
values = [aws_default_vpc.default.id] | |
} | |
} | |
# Create a CloudWatch logs group for ECS cluster logs | |
resource "aws_cloudwatch_log_group" "backend_ecs_cluster" { | |
name = "backend-ecs-cluster-${var.env}" | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create the ECS cluster | |
resource "aws_ecs_cluster" "backend" { | |
name = "backend-${var.env}" | |
configuration { | |
execute_command_configuration { | |
logging = "OVERRIDE" | |
log_configuration { | |
cloud_watch_log_group_name = aws_cloudwatch_log_group.backend_ecs_cluster.name | |
} | |
} | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create the AWS Security Group for the AWS Load Balancer | |
resource "aws_security_group" "load_balancer" { | |
name = "allow_tls_${var.env}" | |
description = "Allow TLS inbound traffic" | |
vpc_id = aws_default_vpc.default.id | |
ingress { | |
description = "TLS from VPC" | |
from_port = 80 | |
to_port = 80 | |
protocol = "tcp" | |
cidr_blocks = ["0.0.0.0/0"] | |
ipv6_cidr_blocks = ["::/0"] | |
} | |
ingress { | |
description = "TLS from VPC" | |
from_port = 443 | |
to_port = 443 | |
protocol = "tcp" | |
cidr_blocks = ["0.0.0.0/0"] | |
ipv6_cidr_blocks = ["::/0"] | |
} | |
egress { | |
from_port = 0 | |
to_port = 0 | |
protocol = "-1" | |
cidr_blocks = ["0.0.0.0/0"] | |
ipv6_cidr_blocks = ["::/0"] | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create the AWS Load Balancer target group | |
resource "aws_lb_target_group" "microservice" { | |
name = "microservice-${var.env}" | |
port = 8080 | |
protocol = "HTTP" | |
target_type = "ip" | |
vpc_id = aws_default_vpc.default.id | |
health_check { | |
healthy_threshold = "3" | |
interval = "60" | |
protocol = "HTTP" | |
timeout = "30" | |
path = "/health" | |
unhealthy_threshold = "2" | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create AWS Load Balancer | |
resource "aws_lb" "backend" { | |
name = "backend-${var.env}" | |
internal = false | |
load_balancer_type = "application" | |
security_groups = [aws_security_group.allow_tls.id] | |
subnets = toset(data.aws_subnets.default.ids) | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create AWS Load Balancer Listener that prohibits all requests by default | |
resource "aws_alb_listener" "backend" { | |
load_balancer_arn = aws_lb.backend.id | |
port = "443" | |
protocol = "HTTPS" | |
ssl_policy = "ELBSecurityPolicy-2016-08" | |
certificate_arn = aws_acm_certificate.api.arn | |
default_action { | |
type = "fixed-response" | |
fixed_response { | |
content_type = "text/plain" | |
message_body = "FORBIDDEN" | |
status_code = "403" | |
} | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create AWS Load Balancer Listener for HTTP HTTPS redirect | |
resource "aws_lb_listener" "backend-https-redirect" { | |
load_balancer_arn = aws_lb.backend.id | |
port = "80" | |
protocol = "HTTP" | |
default_action { | |
type = "redirect" | |
redirect { | |
port = "443" | |
protocol = "HTTPS" | |
status_code = "HTTP_301" | |
} | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# Create AWS Load Balancer Listener rule to drive traffic to microservice | |
resource "aws_lb_listener_rule" "microservice" { | |
listener_arn = aws_alb_listener.backend.arn | |
priority = 100 | |
action { | |
type = "forward" | |
target_group_arn = aws_lb_target_group.microservice.id | |
} | |
condition { | |
host_header { | |
values = [var.api_host] | |
} | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# Microservice ECS Task Execution role | |
resource "aws_iam_role" "microservice_task_execution_role" { | |
name = "microservice_ecs_task_execution_role_${var.env}" | |
assume_role_policy = jsonencode({ | |
"Version" : "2008-10-17", | |
"Statement" : [ | |
{ | |
"Sid" : "", | |
"Effect" : "Allow", | |
"Principal" : { | |
"Service" : "ecs-tasks.amazonaws.com" | |
}, | |
"Action" : "sts:AssumeRole" | |
} | |
] | |
}) | |
tags = { | |
environment = var.env | |
} | |
} | |
# Microservice ECS Task Execution policy | |
resource "aws_iam_policy" "microservice_execution" { | |
name = "microservice_execution_${var.env}" | |
description = "ECS Task Permissions for Microservice ${var.env} environment" | |
policy = jsonencode({ | |
Version = "2012-10-17" | |
Statement = [ | |
{ | |
Effect = "Allow", | |
Action = [ | |
"ecr:GetAuthorizationToken", | |
"ecr:BatchCheckLayerAvailability", | |
"ecr:GetDownloadUrlForLayer", | |
"ecr:BatchGetImage", | |
"logs:CreateLogStream", | |
"logs:PutLogEvents" | |
], | |
Resource = "*" | |
} | |
] | |
}) | |
tags = { | |
environment = var.env | |
} | |
} | |
# Attach ECS Task Execution role to policy | |
resource "aws_iam_policy_attachment" "microservice_execution" { | |
name = "microservice_execution_${var.env}" | |
roles = [aws_iam_role.microservice_task_execution_role.name] | |
policy_arn = aws_iam_policy.microservice_execution.arn | |
} | |
# Create ECR repository to store Docker Images of the microservice application | |
resource "aws_ecr_repository" "microservice" { | |
name = "microservice-${var.env}" | |
image_tag_mutability = "MUTABLE" | |
tags = { | |
environment = var.env | |
} | |
} | |
# CloudWatch Log Group to store microservice logs | |
resource "aws_cloudwatch_log_group" "microservice" { | |
name = "${var.env}/microservice/service" | |
tags = { | |
environment = var.env | |
} | |
} | |
data "aws_region" "current" {} | |
# ECS task definition for microservice | |
resource "aws_ecs_task_definition" "microservice" { | |
family = "microservice-${var.env}" | |
network_mode = "awsvpc" | |
requires_compatibilities = ["FARGATE"] | |
cpu = 256 | |
memory = 512 | |
execution_role_arn = aws_iam_role.microservice_task_execution_role.arn | |
task_role_arn = aws_iam_role.microservice_task_execution_role.arn | |
container_definitions = jsonencode([ | |
{ | |
name = "microservice" | |
image = aws_ecr_repository.microservice.repository_url | |
cpu = 256 | |
memory = 512 | |
essential = true | |
portMappings = [ | |
{ | |
containerPort = var.port | |
hostPort = var.port | |
protocol = "tcp" | |
} | |
], | |
environment = [ | |
{ | |
name = "SOME_ENV" | |
value = "SOME_VALUE" | |
} | |
] | |
logConfiguration = { | |
logDriver = "awslogs", | |
options = { | |
"awslogs-group" : aws_cloudwatch_log_group.microservice.name, | |
"awslogs-region" : data.aws_region.current.name, | |
"awslogs-stream-prefix" : "ecs" | |
} | |
}, | |
healthcheck = { | |
command = ["CMD-SHELL", "curl -f http://localhost:${var.port}/health || exit 1"], | |
interval = 30, | |
retries = 3, | |
timeout = 5 | |
}, | |
} | |
]) | |
tags = { | |
environment = var.env | |
} | |
} | |
# AWS Security Group for microservice | |
resource "aws_security_group" "microservice" { | |
name = "microservice-ecs-service-${var.env}" | |
description = "Allow TLS inbound traffic" | |
vpc_id = var.vpc_id | |
ingress { | |
description = "TLS from VPC" | |
from_port = var.port | |
to_port = var.port | |
protocol = "tcp" | |
cidr_blocks = [var.vpc_cidr_block] | |
ipv6_cidr_blocks = ["::/0"] | |
} | |
egress { | |
from_port = 0 | |
to_port = 0 | |
protocol = "-1" | |
cidr_blocks = ["0.0.0.0/0"] | |
ipv6_cidr_blocks = ["::/0"] | |
} | |
tags = { | |
Name = "allow_tls" | |
environment = var.env | |
} | |
} | |
# ECS service for miroservice | |
resource "aws_ecs_service" "microservice" { | |
name = "microservice-${var.env}" | |
cluster = var.cluster_id | |
task_definition = aws_ecs_task_definition.microservice.arn | |
desired_count = var.desired_count | |
launch_type = "FARGATE" | |
network_configuration { | |
subnets = var.vpc_subnets | |
assign_public_ip = true | |
security_groups = [aws_security_group.microservice.id] | |
} | |
load_balancer { | |
target_group_arn = aws_lb_target_group.microservice.arn | |
container_name = "microservice" | |
container_port = var.port | |
} | |
tags = { | |
environment = var.env | |
} | |
} | |
# An autoscaling target for microservice ECS task | |
resource "aws_appautoscaling_target" "default" { | |
max_capacity = 5 | |
min_capacity = 1 | |
resource_id = "service/${var.cluster_name}/${aws_ecs_service.microservice.name}" | |
scalable_dimension = "ecs:service:DesiredCount" | |
service_namespace = "ecs" | |
} | |
# An autoscaling memory policy for ECS task | |
resource "aws_appautoscaling_policy" "memory" { | |
name = "dev-to-memory" | |
policy_type = "TargetTrackingScaling" | |
resource_id = aws_appautoscaling_target.default.resource_id | |
scalable_dimension = aws_appautoscaling_target.default.scalable_dimension | |
service_namespace = aws_appautoscaling_target.default.service_namespace | |
target_tracking_scaling_policy_configuration { | |
predefined_metric_specification { | |
predefined_metric_type = "ECSServiceAverageMemoryUtilization" | |
} | |
target_value = 80 | |
} | |
} | |
# An autoscaling CPU policy for ECS task | |
resource "aws_appautoscaling_policy" "cpu" { | |
name = "dev-to-cpu" | |
policy_type = "TargetTrackingScaling" | |
resource_id = aws_appautoscaling_target.default.resource_id | |
scalable_dimension = aws_appautoscaling_target.default.scalable_dimension | |
service_namespace = aws_appautoscaling_target.default.service_namespace | |
target_tracking_scaling_policy_configuration { | |
predefined_metric_specification { | |
predefined_metric_type = "ECSServiceAverageCPUUtilization" | |
} | |
target_value = 80 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment