Skip to content

Instantly share code, notes, and snippets.

@nicosingh
Last active October 8, 2024 05:34
Show Gist options
  • Save nicosingh/8b06190b8d675779617a8045b44f0582 to your computer and use it in GitHub Desktop.
Save nicosingh/8b06190b8d675779617a8045b44f0582 to your computer and use it in GitHub Desktop.
ECS using Terraform sample
*.tfbackup
.terraform/
*.tfstate
.terraform.tfstate.lock.info
# define ALB
resource "aws_alb" "alb" {
name = "${var.name_prefix}-alb"
subnets = ["${aws_subnet.public_subnet.*.id}"]
security_groups = ["${aws_security_group.load_balancer_sg.id}"]
}
resource "aws_alb_target_group" "alb_target_group" {
name = "${var.name_prefix}-alb-target-group"
port = "${var.container_port}"
protocol = "${var.alb_protocol}"
vpc_id = "${aws_vpc.main_network.id}"
target_type = "ip"
health_check {
healthy_threshold = "3"
unhealthy_threshold = "3"
timeout = "3"
interval = "5"
protocol = "${var.alb_protocol}"
matcher = "200"
path = "${var.healthcheck_path}"
}
}
resource "aws_alb_listener" "load_balancer_listener" {
load_balancer_arn = "${aws_alb.alb.id}"
port = "${var.container_port}"
protocol = "${var.alb_protocol}"
default_action {
target_group_arn = "${aws_alb_target_group.alb_target_group.id}"
type = "forward"
}
}
# define Autoscaling Target
resource "aws_appautoscaling_target" "autoscaling_target" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.ecs_cluster.name}/${aws_ecs_service.ecs_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "${var.ecs_autoscale_role}"
min_capacity = "${var.min_capacity}"
max_capacity = "${var.max_capacity}"
}
# define Outscaling Policy
resource "aws_appautoscaling_policy" "outscaling_policy" {
name = "${var.name_prefix}-outscaling-policy"
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.ecs_cluster.name}/${aws_ecs_service.ecs_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 3
metric_aggregation_type = "Maximum"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = 1
}
}
depends_on = ["aws_appautoscaling_target.autoscaling_target"]
}
resource "aws_cloudwatch_metric_alarm" "outscaling_metric_alarm" {
alarm_name = "${var.name_prefix}-outscaling-metric-alarm"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "1"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "60"
dimensions {
ClusterName = "${aws_ecs_cluster.ecs_cluster.name}"
ServiceName = "${aws_ecs_service.ecs_service.name}"
}
alarm_actions = ["${aws_appautoscaling_policy.outscaling_policy.arn}"]
}
# define Downscaling Policy
resource "aws_appautoscaling_policy" "downscaling_policy" {
name = "${var.name_prefix}-downscaling-policy"
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.ecs_cluster.name}/${aws_ecs_service.ecs_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 3
metric_aggregation_type = "Maximum"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = -1
}
}
depends_on = ["aws_appautoscaling_target.autoscaling_target"]
}
resource "aws_cloudwatch_metric_alarm" "downscaling_metric_alarm" {
alarm_name = "${var.name_prefix}-downscaling-metric-alarm"
comparison_operator = "LessThanOrEqualToThreshold"
evaluation_periods = "1"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "30"
dimensions {
ClusterName = "${aws_ecs_cluster.ecs_cluster.name}"
ServiceName = "${aws_ecs_service.ecs_service.name}"
}
alarm_actions = ["${aws_appautoscaling_policy.downscaling_policy.arn}"]
}
# where to save Terraform state file
terraform {
backend "s3" {
bucket = "tranque-terraform-state"
key = "ecs-dev/terraform.tfstate"
region = "us-east-1"
}
}
[
{
"cpu": ${fargate_cpu},
"essential": true,
"image": "${app_image}",
"memory": ${fargate_memory},
"name": "tranque-api",
"portMappings": [
{
"containerPort": ${app_port},
"hostPort": ${app_port}
}
]
}
]
# define Cluster
resource "aws_ecs_cluster" "ecs_cluster" {
name = "${var.name_prefix}-cluster"
}
# define Task
data "template_file" "template_container_definitions" {
template = "${file("container-definitions.json.tpl")}"
vars {
app_image = "${var.app_image}"
fargate_cpu = "${var.fargate_cpu}"
fargate_memory = "${var.fargate_memory}"
aws_region = "${var.aws_region}"
app_port = "${var.container_port}"
}
}
resource "aws_ecs_task_definition" "ecs_task" {
family = "${var.name_prefix}-task"
execution_role_arn = "${var.ecs_task_execution_role}"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "${var.fargate_cpu}"
memory = "${var.fargate_memory}"
container_definitions = "${data.template_file.template_container_definitions.rendered}"
}
# define Service
resource "aws_ecs_service" "ecs_service" {
name = "${var.name_prefix}-service"
cluster = "${aws_ecs_cluster.ecs_cluster.id}"
task_definition = "${aws_ecs_task_definition.ecs_task.arn}"
desired_count = "${var.min_capacity}"
launch_type = "FARGATE"
network_configuration {
security_groups = ["${aws_security_group.ecs_tasks_sg.id}"]
subnets = ["${aws_subnet.private_subnet.*.id}"]
assign_public_ip = true
}
load_balancer {
target_group_arn = "${aws_alb_target_group.alb_target_group.id}"
container_name = "${var.balanced_container_name}"
container_port = "${var.container_port}"
}
depends_on = ["aws_alb_listener.load_balancer_listener"]
}
# define Log Group
resource "aws_cloudwatch_log_group" "log_group" {
name = "/ecs/${var.name_prefix}"
tags {
Name = "tranque-api" #TODO check how this name matches the container/task/service/cluster/?
}
}
# define Log stream
resource "aws_cloudwatch_log_stream" "log_stream" {
name = "${var.name_prefix}-log-stream"
log_group_name = "${aws_cloudwatch_log_group.log_group.name}"
}
# get available AZs
data "aws_availability_zones" "available_azs" {}
# define VPC
resource "aws_vpc" "main_network" {
cidr_block = "172.17.0.0/16"
tags = {
Name = "${var.name_prefix}-vpc"
}
}
# define ${var.az_count} private subnets (one for each AZ)
resource "aws_subnet" "private_subnet" {
count = "${var.az_count}"
cidr_block = "${cidrsubnet(aws_vpc.main_network.cidr_block, 8, count.index)}"
availability_zone = "${data.aws_availability_zones.available_azs.names[count.index]}"
vpc_id = "${aws_vpc.main_network.id}"
map_public_ip_on_launch = true
tags = {
Name = "${var.name_prefix}-private-subnet-${count.index}"
}
}
# define ${var.az_count} public subnets (one for each AZ)
resource "aws_subnet" "public_subnet" {
count = "${var.az_count}"
cidr_block = "${cidrsubnet(aws_vpc.main_network.cidr_block, 8, var.az_count + count.index)}"
availability_zone = "${data.aws_availability_zones.available_azs.names[count.index]}"
vpc_id = "${aws_vpc.main_network.id}"
map_public_ip_on_launch = true
tags = {
Name = "${var.name_prefix}-public-subnet-${count.index}"
}
}
# define IGW
resource "aws_internet_gateway" "internet_gateway" {
vpc_id = "${aws_vpc.main_network.id}"
tags = {
Name = "${var.name_prefix}-igw"
}
}
resource "aws_route" "internet_access" {
route_table_id = "${aws_vpc.main_network.main_route_table_id}"
destination_cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.internet_gateway.id}"
}
# define NAT gateway for each private subnet
resource "aws_eip" "nat_gateway_eip" {
count = "${var.az_count}"
vpc = true
depends_on = ["aws_internet_gateway.internet_gateway"]
tags = {
Name = "${var.name_prefix}-nat-gateway-eip-${count.index}"
}
}
resource "aws_nat_gateway" "nat_gateway" {
count = "${var.az_count}"
subnet_id = "${element(aws_subnet.public_subnet.*.id, count.index)}"
allocation_id = "${element(aws_eip.nat_gateway_eip.*.id, count.index)}"
tags = {
Name = "${var.name_prefix}-nat-gateway-${count.index}"
}
}
# define route table for each private subnet
resource "aws_route_table" "private_route_table" {
count = "${var.az_count}"
vpc_id = "${aws_vpc.main_network.id}"
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = "${element(aws_nat_gateway.nat_gateway.*.id, count.index)}"
}
tags = {
Name = "${var.name_prefix}-nat-gateway-route-table-${count.index}"
}
}
# associate route tables with private subnets
resource "aws_route_table_association" "private_route_table_association" {
count = "${var.az_count}"
subnet_id = "${element(aws_subnet.private_subnet.*.id, count.index)}"
route_table_id = "${element(aws_route_table.private_route_table.*.id, count.index)}"
}
# get ALB DNS name
output "alb_hostname" {
value = "${aws_alb.alb.dns_name}"
}
# AWS configuration
provider "aws" {
shared_credentials_file = "$HOME/.aws/credentials"
profile = "default"
region = "${var.aws_region}"
}
# ALB security group
resource "aws_security_group" "load_balancer_sg" {
name = "${var.name_prefix}-alb-sg"
vpc_id = "${aws_vpc.main_network.id}"
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
# ECS tasks security group
resource "aws_security_group" "ecs_tasks_sg" {
name = "${var.name_prefix}-ecs-tasks-sg"
vpc_id = "${aws_vpc.main_network.id}"
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
security_groups = ["${aws_security_group.load_balancer_sg.id}"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "name_prefix" {
default = "dev-tranque"
}
variable "aws_region" {
default = "us-west-2"
}
variable "az_count" {
default = "2"
}
variable "healthcheck_path" {
default = "/"
}
variable "fargate_cpu" {
default = "1024"
}
variable "fargate_memory" {
default = "2048"
}
variable "ecs_task_execution_role" {
default = "arn:aws:iam::719699785587:role/ecsTaskExecutionRole"
}
variable "ecs_autoscale_role" {
default = "arn:aws:iam::719699785587:role/ecsAutoscaleRole"
}
variable "min_capacity" {
default = "2"
}
variable "max_capacity" {
default = "5"
}
variable "container_port" {
default = "80"
}
variable "alb_protocol" {
default = "HTTP"
}
variable "balanced_container_name" {
default = "tranque-api"
}
variable "app_image" {
default = "k8s.gcr.io/hpa-example:latest"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment