Skip to content

Instantly share code, notes, and snippets.

@kuzemkon
Last active August 8, 2024 04:24
Show Gist options
  • Save kuzemkon/fed98f7d5bf27fb553c228a34f73c6fa to your computer and use it in GitHub Desktop.
Save kuzemkon/fed98f7d5bf27fb553c228a34f73c6fa to your computer and use it in GitHub Desktop.
Terraform IaaC for AWS ECS with Fargate and AWS Application Load Balancer
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