Your containerized application with IAC on AWS — Pt.2

Paloma Lataliza
11 min readJan 4, 2024

Hi everyone! We’ll see how to create our terraform modules in this blog article. Next, we’ll publish our application to AWS Fargate using the terraform modules we created here and also terragrunt.

TERRAFORM

We will establish our directory structure and our terraform module scripts in this blog article. We will set everything up and utilize terraform in conjunction with Terragrunt in part 3.

DIRECTORIES

Our codes must be organized at the directory level in order to use terraform and terragrunt:

app
modules
├── amazon_vpc
├── aws_loadbalancer
├── aws_fargate
├── aws_roles
├── aws_ecs_cluster
└── aws_targetgroup
└── aws_certificate_manager



terragrunt
└── dev
└── us-east-1
├── aws_ecs
│ ├── cluster
│ └── service
├── aws_loadbalancer
├── amazon_vpc
├── aws_targetgroup
├── aws_roles
├── aws_certificate_manager
└── terragrunt.hcl
  • app: This is our infrastructure’s primary directory.
  • modules: Each unique AWS resource or service has a subdirectory within this directory. The modules will be inserted here, arranged according to resources like VPC, load balancers, ECS, etc.
  • Terraform subdirectories: Module-specific Terraform files are located in subdirectories like amazon_vpc and aws_loadbalancer.
  • Terragrunt: Terragrunt configurations are kept in this directory.
  • dev: Stands for the configuration of the development environment.
  • us-east-1: Configurations unique to the AWS region “us-east-1”.
  • Terragrunt subdirectories: Environment- and region-specific options for individual services may be found in the aws_ecs, aws_loadbalancer, amazon_vpc, etc. folders.
  • terragrunt.hcl: This is our Terragrunt configuration file, where we will include backend configurations as well as those that apply to all services in the “us-east-1” area of the development environment.

Modules have three files: variables.tf, main.tf, and _outputs.tf in each of the subdirectories. Roles will make use of a _data.tf

  • main.tf: The main.tf file, which defines and configures AWS resources, is the hub of the module.
  • variables.tf: Allows for module customisation and reuse by defining variables that the module will use.
  • _outputs.tf: Indicates which module outputs — information — will be accessible to other modules or the Terraform project in its whole.
  • _data.tf: To consult and look up information on already-existing resources or services, we shall utilize data.

RESOURCES

The following are the AWS resources that we will use:

  • VPC
  • SUBNETS
  • ROUTE TABLE
  • INTERNET GATEWAY
  • NAT GATEWAY
  • ELASTIC IP
  • ECR
  • SECURITY GROUP
  • APPLICATION LOAD BALANCER
  • FARGATE
  • ROUTE53
  • ACM

TERRAFORM MODULES

VPC

Let’s get started with VPC module creation. It will be necessary for each and every one of our apps’ network connections.

modules
├── amazon_vpc

main.tf

// Creat VPC
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr_block
enable_dns_hostnames = true
enable_dns_support = true

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-vpc"
},
var.tags,
)
}


// Creat public subnet1 for VPC
resource "aws_subnet" "public_subnet1" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.public_subnet1_cidr_block
availability_zone = var.availability_zone1

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-public-subnet1"
},
var.tags,
)
}


// Creat public subnet2 for VPC
resource "aws_subnet" "public_subnet2" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.public_subnet2_cidr_block
availability_zone = var.availability_zone2

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-public-subnet2"
},
var.tags,
)
}


// Creat private subnet1 for VPC
resource "aws_subnet" "private_subnet1" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.private_subnet1_cidr_block
availability_zone = var.availability_zone1

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-private-subnet1"
},
var.tags,
)
}


// Creat private subnet2 for VPC
resource "aws_subnet" "private_subnet2" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.private_subnet2_cidr_block
availability_zone = var.availability_zone2

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-private-subnet2"
},
var.tags,
)
}


// Create Internet gateway
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id

tags = merge(
{
"Name" = "${var.env}-${var.project_name}"
},
var.tags,
)
}


// Creat route IGW VPC default rtb
resource "aws_default_route_table" "vpc_default_rtb" {
default_route_table_id = aws_vpc.vpc.default_route_table_id

# Internet gtw route
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-vpc-default-rtb"
},
var.tags,
)
}


// Associate a public subnet1 with VPC
resource "aws_route_table_association" "public_subnet1_rtb_association" {
subnet_id = aws_subnet.public_subnet1.id
route_table_id = aws_default_route_table.vpc_default_rtb.id
}

# Associate public subnet2 with VPC
resource "aws_route_table_association" "public_subnet2_rtb_association" {
subnet_id = aws_subnet.public_subnet2.id
route_table_id = aws_default_route_table.vpc_default_rtb.id
}

# Create custom private route table 1
resource "aws_route_table" "private_rtb1" {
vpc_id = aws_vpc.vpc.id

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-rtb1"
},
var.tags,
)
}


// Creat custom private route table 2
resource "aws_route_table" "private_rtb2" {
vpc_id = aws_vpc.vpc.id

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-rtb2"
},
var.tags,
)
}


// Creat EIP for nat1
resource "aws_eip" "eip1" {
domain = "vpc"

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-eip1"
},
var.tags,
)
}


// Creat EIP for nat2
resource "aws_eip" "eip2" {
domain = "vpc"

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-eip2"
},
var.tags,
)
}


// Creat NAT GTW1
resource "aws_nat_gateway" "nat_gtw1" {
allocation_id = aws_eip.eip1.id
subnet_id = aws_subnet.public_subnet1.id

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-nat-gtw1"
},
var.tags,
)
}


// Creat NAT GTW2
resource "aws_nat_gateway" "nat_gtw2" {
allocation_id = aws_eip.eip2.id
subnet_id = aws_subnet.public_subnet2.id

tags = merge(
{
"Name" = "${var.env}-${var.project_name}-nat-gtw2"
},
var.tags,
)
}


// Configure natgtw route private route table 1
resource "aws_route" "private_rtb1_nat_gtw1" {
route_table_id = aws_route_table.private_rtb1.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_gtw1.id
}



// Configure nat gtw route private route table 2
resource "aws_route" "private_rtb2_nat_gtw2" {
route_table_id = aws_route_table.private_rtb2.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_gtw2.id
}



// Associate private subnet1 VPC
resource "aws_route_table_association" "private_subnet1_rtb_association" {
subnet_id = aws_subnet.private_subnet1.id
route_table_id = aws_route_table.private_rtb1.id
}



// Associate private subnet2 VPC
resource "aws_route_table_association" "private_subnet2_rtb_association" {
subnet_id = aws_subnet.private_subnet2.id
route_table_id = aws_route_table.private_rtb2.id
}



resource "aws_security_group" "default" {
name = "${var.env}-${var.project_name}-sg-vpc"
description = "Default security group to allow inbound/outbound from the VPC"
vpc_id = "${aws_vpc.vpc.id}"

ingress {
from_port = "0"
to_port = "0"
protocol = "-1"
self = true
}

egress {
from_port = "0"
to_port = "0"
protocol = "-1"
self = "true"
}
}

variables.tf

variable "vpc_cidr_block" {
}

variable "public_subnet1_cidr_block" {
}

variable "public_subnet2_cidr_block" {
}

variable "private_subnet1_cidr_block" {
}

variable "private_subnet2_cidr_block" {
}

variable "availability_zone1" {
}

variable "availability_zone2" {
}

variable "project_name" {
}

variable "env" {
}

variable "tags" {
type = map(string)
}

_outputs.tf


output "vpc_arn" {
value = aws_vpc.vpc.arn
}

output "vpc_id" {
value = aws_vpc.vpc.id
}

output "vpc_main_rtb" {
value = aws_vpc.vpc.main_route_table_id
}

output "vpc_cidr_block" {
value = aws_vpc.vpc.cidr_block
}


output "public_subnet1_id" {
value = aws_subnet.public_subnet1.id
}

output "public_subnet1_cidr_block" {
value = aws_subnet.public_subnet1.cidr_block
}

output "public_subnet1_az" {
value = aws_subnet.public_subnet1.availability_zone
}

output "public_subnet1_az_id" {
value = aws_subnet.public_subnet1.availability_zone_id
}


output "public_subnet2_id" {
value = aws_subnet.public_subnet2.id
}

output "public_subnet2_cidr_block" {
value = aws_subnet.public_subnet2.cidr_block
}

output "public_subnet2" {
value = aws_subnet.public_subnet2.availability_zone
}

output "public_subnet2_az_id" {
value = aws_subnet.public_subnet2.availability_zone_id
}


output "private_subnet1_id" {
value = aws_subnet.private_subnet1.id
}

output "private_subnet1_cidr_block" {
value = aws_subnet.private_subnet1.cidr_block
}

output "private_subnet1_az" {
value = aws_subnet.private_subnet1.availability_zone
}

output "private_subnet1_az_id" {
value = aws_subnet.private_subnet1.availability_zone_id
}


output "private_subnet2_id" {
value = aws_subnet.private_subnet2.id
}

output "private_subnet2_cidr_block" {
value = aws_subnet.private_subnet2.cidr_block
}

output "private_subnet2_az" {
value = aws_subnet.private_subnet2.availability_zone
}

output "private_subnet2_az_id" {
value = aws_subnet.public_subnet2.availability_zone_id
}


output "igw_id" {
value = aws_internet_gateway.igw.id
}

output "default_rtb_id" {
value = aws_default_route_table.vpc_default_rtb.id
}

IAM PERMISSIONS

We need to create permissions for our services.

modules
├── aws_roles

_data.tf

data "aws_iam_policy_document" "ecs_service_role" {
statement {
actions = [
"application-autoscaling:DeleteScalingPolicy",
"application-autoscaling:DeregisterScalableTarget",
"application-autoscaling:DescribeScalableTargets",
"application-autoscaling:DescribeScalingActivities",
"application-autoscaling:DescribeScalingPolicies",
"application-autoscaling:PutScalingPolicy",
"application-autoscaling:RegisterScalableTarget",
"autoscaling:UpdateAutoScalingGroup",
"autoscaling:CreateAutoScalingGroup",
"autoscaling:CreateLaunchConfiguration",
"autoscaling:DeleteAutoScalingGroup",
"autoscaling:DeleteLaunchConfiguration",
"autoscaling:Describe*",
"ec2:CreateNetworkInterface",
"ec2:DescribeDhcpOptions",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVpcs",
"ec2:AssociateRouteTable",
"ec2:AttachInternetGateway",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CancelSpotFleetRequests",
"ec2:CreateInternetGateway",
"ec2:CreateLaunchTemplate",
"ec2:CreateRoute",
"ec2:CreateRouteTable",
"ec2:CreateSecurityGroup",
"ec2:CreateSubnet",
"ec2:CreateVpc",
"ec2:DeleteLaunchTemplate",
"ec2:DeleteSubnet",
"ec2:DeleteVpc",
"ec2:Describe*",
"ec2:DetachInternetGateway",
"ec2:DisassociateRouteTable",
"ec2:ModifySubnetAttribute",
"ec2:ModifyVpcAttribute",
"ec2:RunInstances",
"ec2:RequestSpotFleet",
"codebuild:BatchGetBuilds",
"codebuild:StartBuild",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject",
"s3:PutObjectAcl",
"s3:ListBucket",
"es:ESHttpPost",
"ecr:*",
"ecs:*",
"ec2:*",
"sqs:*",
"cloudwatch:*",
"logs:*",
"iam:PassRole",
"elasticloadbalancing:Describe*",
"iam:AttachRolePolicy",
"iam:CreateRole",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:GetRole",
"iam:ListAttachedRolePolicies",
"iam:ListRoles",
"iam:ListGroups",
"iam:ListUsers",
"iam:ListInstanceProfiles",
"elasticfilesystem:*",
"secretsmanager:GetSecretValue",
"ssm:GetParameters",
"ssm:GetParameter",
"ssm:GetParametersByPath",
"kms:Decrypt",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query",
"dynamodb:Scan",
]

sid = "1"
effect = "Allow"
resources = ["*"]
}
}

main.tf

// Creat policy
resource "aws_iam_policy" "ecs_service_policy" {
name = "${var.env}-${var.project_name}-policy"
path = "/"
policy = data.aws_iam_policy_document.ecs_service_role.json
}

// Creat IAM Role
resource "aws_iam_role" "ecs_service_role" {
name = "${var.env}-${var.project_name}-role"
force_detach_policies = "true"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"Service": [
"ecs.amazonaws.com",
"ecs-tasks.amazonaws.com",
"codebuild.amazonaws.com",
"codepipeline.amazonaws.com",
"ecs.application-autoscaling.amazonaws.com",
"ec2.amazonaws.com",
"ecr.amazonaws.com"
]
}
}
]
}
EOF

tags = merge(
{
"Name" = "${var.env}-${var.project_name}"
},
var.tags,
)
}

resource "aws_iam_policy_attachment" "ecs_service_role_atachment_policy" {
name = "${var.env}-${var.project_name}-policy-attachment"
roles = [aws_iam_role.ecs_service_role.name]
policy_arn = aws_iam_policy.ecs_service_policy.arn
}

variables.tf

variable "env" {
}

variable "project_name" {
}


variable "tags" {
type = map(string)
default = {}
}

_outputs.tf

output ecs_role_arn {
value = aws_iam_role.ecs_service_role.arn
}

AWS CERTIFICATE MANAGER

modules
├── aws_certificate_manager

We will also need a domain already configured in a zone hosted on AWS. With the domain created, we will create a valid TLS certificate within our account.

main.tf


// creat the certificate
resource "aws_acm_certificate" "cert" {
domain_name = "*.${var.domain_name}"
validation_method = "DNS"

tags = merge(
{
"Name" = "${var.env}-${var.project_name}"
},
var.tags,
)
lifecycle {
create_before_destroy = true
}
}


// validation certificate
resource "aws_route53_record" "record_certificate_validation" {
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}

allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = "Z08676461KWRT5RHNLSKS"
}

variables.tf

variable "env" {
}

variable "domain_name" {
}

variable "project_name" {
}

variable "tags" {
type = map(string)
default = {}
}

_outputs.tf

output "acm_arn" {
value = aws_acm_certificate.cert.arn
}

AWS LOAD BALANCER

Here, we will create an application load balancer that will handle the balancing of our applications.

modules
├── aws_loadbalancer

main.tf

// Creat AWS ALB 
resource "aws_lb" "alb" {
load_balancer_type = "application"
internal = var.alb_internal
name = "${var.env}-alb-${var.project_name}"
subnets = ["${var.subnet_id_1}", "${var.subnet_id_2}"]
drop_invalid_header_fields = var.alb_drop_invalid_header_fields

security_groups = [
aws_security_group.alb.id,
]

idle_timeout = 400

dynamic "access_logs" {
for_each = compact([var.lb_access_logs_bucket])

content {
bucket = var.lb_access_logs_bucket
prefix = var.lb_access_logs_prefix
enabled = true
}
}

tags = {
Name = "${var.env}-alb-${var.project_name}"
}
}



//Creat SG ALB
resource "aws_security_group" "alb" {
name = "${var.env}-sg-alb-${var.project_name}"
description = "SG for ECS ALB"
vpc_id = var.vpc_id

revoke_rules_on_delete = "true"

ingress {
description = "TLS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]

}

ingress {
description = "HTTP from VPC"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]

}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}


tags = {
Name = "${var.env}-alb-${var.project_name}"
}
}

//Creat default TG - ALB
resource "aws_alb_target_group" "target_group" {
name = "${var.env}-tg-default-alb"
port = 80
protocol = "HTTP"
target_type = "ip"
vpc_id = var.vpc_id

lifecycle {
create_before_destroy = true
}

tags = merge(
{
"Name" = "${var.env}-tg-${var.project_name}"
},
var.tags,
)
}


// Creat HTTPS listener
resource "aws_alb_listener" "listener_ssl" {
load_balancer_arn = aws_lb.alb.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.certificate_arn

default_action {
target_group_arn = aws_alb_target_group.target_group.arn
type = "forward"
}
depends_on = [
aws_alb_target_group.target_group
]
}


resource "aws_alb_listener_rule" "ssl_listener_rule" {
action {
target_group_arn = aws_alb_target_group.target_group.arn
type = "forward"
}

condition {
host_header {
values = ["default.${var.domain_name}"]
}
}

priority = var.priority_listener_rule
listener_arn = aws_alb_listener.listener_ssl.arn

depends_on = [
aws_alb_listener.listener_ssl,
aws_alb_target_group.target_group
]
}


// Creat HTTP listener
resource "aws_lb_listener" "listener_http" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"

default_action {
type = "redirect"

redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}

}

variables.tf

variable "alb" {
default = true
}

variable "alb_http_listener" {
default = true
}

variable "alb_sg_allow_test_listener" {
default = true
}

variable "alb_sg_allow_egress_https_world" {
default = true
}

variable "alb_only" {
default = false
}

variable "alb_ssl_policy" {
default = "ELBSecurityPolicy-2016-08"
type = string
}

variable "alb_internal_ssl_policy" {
default = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
type = string
}

variable "alb_drop_invalid_header_fields" {
default = true
type = bool
}

variable "lb_access_logs_bucket" {
type = string
default = ""
}

variable "lb_access_logs_prefix" {
type = string
default = ""
}

variable "vpc_id" {
type = string
default = ""
}

variable "subnet_id_1" {
type = string
default = ""
}

variable "subnet_id_2" {
type = string
default = ""
}

variable "project_name" {
type = string
default = ""
}

variable "env" {
type = string
default = ""
}

variable "alb_internal" {
type = bool
default = false
}

variable "certificate_arn" {
type = string
default = ""
}

variable "tags" {
type = map(string)
default = {}
}

variable "priority_listener_rule" {
}

variable "domain_name" {
}

outputs.tf


output "alb_arn" {
value = aws_lb.alb.arn
}

output "alb_dns_name" {
value = aws_lb.alb.dns_name
}


output "alb_secgrp_id" {
value = aws_security_group.alb.id
}


output "alb_arn_suffix" {
value = trimspace(regex(".*loadbalancer/(.*)", aws_lb.alb.arn)[0])
}

output "listener_ssl_arn" {
value = aws_alb_listener.listener_ssl.arn
}

AWS TARGET GROUP

Moving forward, let’s look at the codes that will comprise our TG.

modules
├── aws_targetgroup

main.tf

//Creat Target Group
resource "aws_alb_target_group" "target_group" {
name = "${var.env}-tg-${var.project_name}"
port = 80
protocol = "HTTP"
target_type = "ip"
vpc_id = var.vpc_id

health_check {
matcher = "200-299"
path = var.health_check_path
port = var.container_port
protocol = "HTTP"
unhealthy_threshold = 8
timeout = 10
}


lifecycle {
create_before_destroy = true
}

tags = merge(
{
"Name" = "${var.env}-tg-${var.project_name}"
},
var.tags,
)
}



// Creat HTTPS listener rule
resource "aws_alb_listener_rule" "ssl_listener_rule" {
action {
target_group_arn = aws_alb_target_group.target_group.arn
type = "forward"
}

condition {
host_header {
values = ["${var.host_headers}"]
}
}

priority = var.priority_listener_rule
listener_arn = var.listener_ssl_arn

}

variables.tf

variable "project_name" {
}

variable "env" {
}

variable "certificate_arn" {
}

variable "tags" {
description = "Mapa de tags para serem aplicadas aos recursos."
type = map(string)
default = {}
}

variable "vpc_id" {
}

variable "subnet_id_1" {
}

variable "subnet_id_2" {
}

variable "listener_ssl_arn" {
}

variable "priority_listener_rule" {
}

variable "host_headers" {
}

variable "health_check_path" {
}

variable "container_port" {
}

_outputs.tf

output "tg_alb_arn" {
value = aws_alb_target_group.target_group.arn
}

output "tg_arn_suffix" {
value = regex(".*:(.*)", aws_alb_target_group.target_group.arn)[0]
}

ECS and ECR

All of the container configurations will be made here. We will build an ECS cluster first, and then a fargate service with all the necessary components. To host our application image, we will construct a repository in ECR in addition to the cluster and service.

ECS CLUSTER

modules
├── aws_cluster

main.tf

// Creat ECS cluster ECS 
resource "aws_ecs_cluster" "ecs" {
name = "${var.env}-${var.project_name}"

setting {
name = "containerInsights"
value = var.container_insights ? "enabled" : "disabled"
}

lifecycle {
ignore_changes = [
tags
]
}

}

variables.tf

variable "project_name" {
type = string
default = ""
}

variable "env" {
type = string
default = ""
}

variable "container_insights" {
type = bool
default = false
}

_outputs.tf

output "cluster_name" {
value = aws_ecs_cluster.ecs.name
}

output "cluster_arn" {
value = aws_ecs_cluster.ecs.arn
}

FARGATE

modules
├── aws_fargate

main.tf

//Creat ECR repositpry
resource "aws_ecr_repository" "ecs_cluster_ecr" {
name = "${var.env}-${var.project_name}"

tags = merge(
{
"Name" = "${var.env}-${var.project_name}"
},
var.tags,
)
}

//Creat Route53 record
resource "aws_route53_record" "record_sonic" {
zone_id = "Z08676461KWRT5RHNLSKS"
name = "${var.host_headers}"
type = "CNAME"
ttl = 300
records = [var.alb_dns_name]
}

//Creat Task Definition
resource "aws_ecs_task_definition" "ecs_task_definition" {
family = "${var.env}-task-def-${var.project_name}"

container_definitions = <<DEFINITION
[
{
"name": "${var.env}-${var.project_name}" ,
"image": "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.env}-${var.project_name}:latest",
"essential": true,
"memoryReservation": 64,
"portMappings": [{
"containerPort": ${var.container_port}
}],
"environment": [
{
"name": "ENV_PORT",
"value": "${var.container_port}"
},
{
"name": "ENVIRONMENT",
"value": "${var.env}"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "ecs-${var.env}-${var.project_name}",
"awslogs-region": "${var.region}",
"awslogs-create-group": "true",
"awslogs-stream-prefix": "${var.env}-${var.project_name}"
}
}
}
]

DEFINITION


requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
task_role_arn = var.ecs_role_arn
execution_role_arn = var.ecs_role_arn
cpu = var.container_vcpu
memory = var.container_memory
}


//Creat Fargate Service
resource "aws_ecs_service" "ecs_service" {
name = "${var.env}-${var.project_name}-service"
cluster = "${var.cluster_arn}"
task_definition = aws_ecs_task_definition.ecs_task_definition.arn
desired_count = var.instance_count
launch_type = "FARGATE"

load_balancer {
target_group_arn = var.target_group_arn
container_name = "${var.env}-${var.project_name}"
container_port = var.container_port
}

network_configuration {
security_groups = [aws_security_group.sg_ecs.id]
subnets = ["${var.subnet_id_1}", "${var.subnet_id_2}"]
assign_public_ip = "false"
}

deployment_minimum_healthy_percent = 50
deployment_maximum_percent = 400

tags = merge(
{
"Name" = "${var.env}-${var.project_name}"
},
var.tags,
)
}



///Creat SG to ECS
resource "aws_security_group" "sg_ecs" {
name = "${var.env}-sg-ecs-${var.project_name}"
description = "SG for ECS"
vpc_id = var.vpc_id
revoke_rules_on_delete = "true"

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "${var.env}-sg-ecs-${var.project_name}"
}
}

// SG rule ALB
resource "aws_security_group_rule" "rule_ecs_alb" {
description = "from ALB"
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1"
security_group_id = aws_security_group.sg_ecs.id
source_security_group_id = var.sg_alb
}

// SG rule ECS
resource "aws_security_group_rule" "in_ecs_nodes" {
description = "from ECS"
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1"
security_group_id = aws_security_group.sg_ecs.id
source_security_group_id = aws_security_group.sg_ecs.id
}

variables.tf

variable "env" {
}

variable "region" {
}

variable "project_name" {
}

variable "container_port" {
}

variable "instance_count" {
}

variable "container_vcpu" {
}

variable "container_memory" {
}

variable "vpc_id" {
}

variable "subnet_id_1" {
}

variable "subnet_id_2" {
}

variable "aws_account_id" {
}

variable "tags" {
type = map(string)
default = {}
}

variable "ecs_role_arn" {
}

variable "target_group_arn" {
}

variable "sg_alb" {
}

variable "cluster_arn" {
}


variable "host_headers" {
}

variable "alb_dns_name" {
}

_outputs.tf

output "sg_ecs" {
value = aws_security_group.sg_ecs.id
}

output "service_name" {
value = aws_ecs_service.ecs_service.name
}

Our modules are ready, and in the next section, we will create the hcl for Terragrunt and also apply our code. See Ya!

--

--

Paloma Lataliza

Apaixonada por cloud computing e tecnologia. Fã de cavaleiros do zodíaco. AWS Community Builder, líder do AWS User Group MG. Também escrevo sobre ☁️