Hi, devops fans
Welcome to the 4th and last part of tutorial, where we will speak about the TG (Target Group) terraform module and final implementation of fargate deployment from the terraform code side. The 1st 3 parts can be found within the next links:
- Part 1 – basic AWS network components, general architecture diagram.
- Part 2 – AWS ALB terraform realization
- Part 3 – terraform fargate module
We will start, as always, from physical structure file’s overview:

Here is terraform code for target group:
# tg.tf
resource "aws_lb_target_group" "this" {
name = format("%s-%s", local.name_prefix, var.tg_name)
port = var.tg_port
protocol = var.tg_protocol
vpc_id = var.vpc_id
target_type = "ip"
deregistration_delay = var.deregistration_delay
health_check {
port = var.health_check_port
protocol = var.health_check_protocol
enabled = var.health_check_enabled
interval = var.health_check_interval
path = var.health_check_path
timeout = var.health_check_timeout
healthy_threshold = var.health_check_threshold
unhealthy_threshold = var.health_check_unhealthy_threshold
matcher = var.health_check_matcher
}
}
resource "aws_lb_listener_rule" "host" {
listener_arn = var.listener_443_arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.this.arn
}
condition {
host_header {
values = var.host_headers
}
}
}
So, in the module we define aws_lb_target_group terrafrom resource, where we set its name, vpc, port and protocol. As we are working with fargate, we must to set target_type as “ip”, because our container would be registered at awsvpc network with getting own IP address. There is also very interesting property, which is called deregistration_delay. It determines how long the load balancer waits before fully deregistering a target (Fargate task) that has been marked for removal. When deploying new versions of your application or scaling down your services, the deregistration delay ensures that active sessions or requests are not abruptly terminated. In our learning example, the application is very simple, so it does not matter a lot what you will set here, though in real life it is essential to adjust it to application requirements.
Then we have health check options and there is aws_lb_listener_rule, where we pass our ssl listener arn. We want to forward all traffic at our ECS Fargate cluster when host matches the value we pass as variable.
Then we have list of files that does not required a lot of explanations:
# alarms.tf
resource "aws_cloudwatch_metric_alarm" "unhealthy_instance_count" {
alarm_name = format("%s-%s-%s", local.name_prefix, var.tg_name, "unhealthy-instances")
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "UnHealthyHostCount"
namespace = "AWS/ApplicationELB"
period = "120"
statistic = "Average"
threshold = "1"
datapoints_to_alarm = "1"
dimensions = {
LoadBalancer = var.alb_arn_suffix
TargetGroup = aws_lb_target_group.this.arn_suffix
}
alarm_actions = [data.aws_sns_topic.alarm_topic.arn]
}
# locals.tf
locals {
name_prefix = format("%s-%s", var.project, var.env)
common_tags = {
Env = var.env
ManagedBy = "terraform"
Project = var.project
}
}
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.21"
}
}
required_version = "~> 1.6"
}
# outputs.tf
output "tg_arn" {
value = aws_lb_target_group.this.arn
}
# sns.tf
data "aws_sns_topic" "alarm_topic" {
name = var.alarm_sns_topic_name
}
# variables.tf
variable "tg_name" {
type = string
description = "TG Name"
}
variable "tg_port" {
type = number
description = "TG Port"
}
variable "tg_protocol" {
type = string
description = "TG Protocol"
}
variable "vpc_id" {
type = string
description = "VPC ID"
}
variable "deregistration_delay" {
type = number
description = "TG deregistration delay. AWS default: 300"
}
variable "health_check_port" {
type = number
}
variable "health_check_protocol" {
type = string
}
variable "health_check_enabled" {
type = bool
}
variable "health_check_interval" {
type = number
}
variable "health_check_path" {
type = string
}
variable "health_check_timeout" {
type = number
}
variable "health_check_threshold" {
type = number
}
variable "health_check_unhealthy_threshold" {
type = number
}
variable "health_check_matcher" {
type = string
}
variable "listener_443_arn" {
type = string
description = "Listener for the TG (443)"
}
variable "host_headers" {
type = list(string)
description = "Host headers for Listener rule"
}
variable "alb_arn_suffix" {
type = string
}
variable "alarm_sns_topic_name" {
type = string
}
variables-env.tf – as always the same we had it at previous parts 🙂
So, now it is time to have a look at final implementation example:
# fargate-app/main.tf
terraform {
backend "s3" {
bucket = "terraform-state-fargate"
dynamodb_table = "terraform-state-fargate"
encrypt = true
key = "stage-fargate-app.tfstate"
region = "eu-central-1"
}
}
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "terraform-state-fargate"
key = "dev-network.tfstate"
region = var.region
}
}
data "terraform_remote_state" "alb" {
backend = "s3"
config = {
bucket = "terraform-state-fargate"
key = "dev-alb.tfstate"
region = var.region
}
}
provider "aws" {
allowed_account_ids = [var.account_id]
region = var.region
}
locals {
app_name = "app"
ssm_secret_path_prefix = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/${var.project}/${var.env}/${local.app_name}"
}
module "fargate" {
source = "../../modules/fargate"
account_id = var.account_id
env = var.env
project = var.project
region = var.region
fargate_cpu = 256
fargate_memory = 512
# Available X86_64, ARM64; ARM64 - much more cheaper option, especially if take saving compute plan
# image build for specific platrofm must be used
runtime_platform = "X86_64"
#runtime_platform = "ARM64"
app_name = local.app_name
app_image = "004571937517.dkr.ecr.eu-central-1.amazonaws.com/udemy-dev-app:latest"
app_port = 5000
app_count = 1
app_environments = [
{"name": "MODE_TYPE", "value": "app"},
{"name": "AWS_DEFAULT_REGION","value": var.region},
]
app_secrets = [
{"name": "AWS_ACCESS_KEY_ID", "valueFrom": "${local.ssm_secret_path_prefix}/AWS_ACCESS_KEY_ID"},
{"name": "AWS_SECRET_ACCESS_KEY", "valueFrom": "${local.ssm_secret_path_prefix}/AWS_SECRET_ACCESS_KEY"},
]
fargate_subnets = data.terraform_remote_state.network.outputs.subnets_public
fargate_ecs_task_sg = data.terraform_remote_state.network.outputs.sg_ecs_fargate_task
tg_arn = module.target_group.tg_arn
listener_443_arn = data.terraform_remote_state.alb.outputs["listener_443_arn"]
}
# fargate-app/tg.tf
module "target_group" {
source = "../../modules/tg-fargate"
account_id = var.account_id
env = var.env
project = var.project
region = var.region
vpc_id = data.terraform_remote_state.network.outputs.vpc["id"]
tg_name = "fargate-app"
tg_port = 80
tg_protocol = "HTTP"
deregistration_delay = 60
health_check_enabled = true
health_check_port = 5000
health_check_protocol = "HTTP"
health_check_path = "/ping"
health_check_matcher = "200-399"
health_check_interval = 30
health_check_timeout = 5
health_check_threshold = 2
health_check_unhealthy_threshold = 2
listener_443_arn = data.terraform_remote_state.alb.outputs["listener_443_arn"]
host_headers = [
"flask.sergiitest.website"
]
alb_arn_suffix = data.terraform_remote_state.alb.outputs["arn_suffix"]
alarm_sns_topic_name = "udemy-dev-alerts"
}
I believe this tutorial will be a valuable resource for anyone looking to enhance their skills in cloud deployment. Whether you’re a beginner or have some experience, you’ll find useful information to take your projects to the next level.
Thank you for the attention.
P.S. If you want to pass all material at once in fast and convenient way, with detailed explanations, then welcome to my course: “AWS Fargate DevOps: Autoscaling with Terraform at practice”, here you may find coupon with discount.