Skip to content

Instantly share code, notes, and snippets.

@tusharvikky
Last active January 15, 2022 23:05
Show Gist options
  • Save tusharvikky/4a6835b18d6e5a4902f81a5f22542b55 to your computer and use it in GitHub Desktop.
Save tusharvikky/4a6835b18d6e5a4902f81a5f22542b55 to your computer and use it in GitHub Desktop.
Deploying your nodejs to Fargate, AWS ECS/ECR via Terraform
resource "aws_lb" "main" {
name = "${var.name}-alb-${var.environment}"
internal = false
load_balancer_type = "application"
security_groups = var.alb_security_groups
subnets = var.subnets.*.id
enable_deletion_protection = false
tags = {
Name = "${var.name}-alb-${var.environment}"
Environment = var.environment
}
}
resource "aws_alb_target_group" "main" {
name = "${var.name}-tg-${var.environment}"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
healthy_threshold = "3"
interval = "30"
protocol = "HTTP"
matcher = "200"
timeout = "3"
path = var.health_check_path
unhealthy_threshold = "2"
}
tags = {
Name = "${var.name}-tg-${var.environment}"
Environment = var.environment
}
depends_on = ["aws_lb.main"]
}
# Redirect to https listener
resource "aws_alb_listener" "http" {
load_balancer_arn = aws_lb.main.id
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = 443
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
# Redirect traffic to target group
resource "aws_alb_listener" "https" {
load_balancer_arn = aws_lb.main.id
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.alb_tls_cert_arn
default_action {
target_group_arn = aws_alb_target_group.main.id
type = "forward"
}
}
resource "aws_appautoscaling_policy" "ecs_policy_memory" {
name = "memory-autoscaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs_target.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_target.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
}
target_value = 80
scale_in_cooldown = 300
scale_out_cooldown = 300
}
}
resource "aws_appautoscaling_policy" "ecs_policy_cpu" {
name = "cpu-autoscaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs_target.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_target.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 60
scale_in_cooldown = 300
scale_out_cooldown = 300
}
}
resource "aws_appautoscaling_target" "ecs_target" {
max_capacity = 4
min_capacity = 1
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.main.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_ecr_repository" "main" {
name = "${var.name}-${var.environment}"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = false
}
}
resource "aws_ecr_lifecycle_policy" "main" {
repository = aws_ecr_repository.main.name
policy = jsonencode({
rules = [{
rulePriority = 1
description = "keep last 10 images"
action = {
type = "expire"
}
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 10
}
}]
})
}
resource "aws_ecs_cluster" "main" {
name = "${var.name}-cluster-${var.environment}"
tags = {
Name = "${var.name}-cluster-${var.environment}"
Environment = var.environment
}
}
resource "aws_iam_role" "ecs_task_role" {
name = "${var.name}-ecsTaskRole-${var.environment}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_policy" "dynamodb" {
name = "${var.name}-task-policy-dynamodb-${var.environment}"
description = "Policy that allows access to DynamoDB"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:CreateTable",
"dynamodb:UpdateTimeToLive",
"dynamodb:PutItem",
"dynamodb:DescribeTable",
"dynamodb:ListTables",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:UpdateTable"
],
"Resource": "*"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "ecs-task-role-policy-attachment" {
role = aws_iam_role.ecs_task_role.name
policy_arn = aws_iam_policy.dynamodb.arn
}
resource "aws_ecs_service" "main" {
name = "${var.name}-service-${var.environment}"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.main.arn
desired_count = var.service_desired_count
deployment_minimum_healthy_percent = 50
deployment_maximum_percent = 200
health_check_grace_period_seconds = 60
launch_type = "FARGATE"
scheduling_strategy = "REPLICA"
network_configuration {
security_groups = var.ecs_service_security_groups
subnets = var.subnets.*.id
assign_public_ip = false
}
load_balancer {
target_group_arn = var.aws_alb_target_group_arn
container_name = "${var.name}-container-${var.environment}"
container_port = var.container_port
}
# we ignore task_definition changes as the revision changes on deploy
# of a new version of the application
# desired_count is ignored as it can change due to autoscaling policy
lifecycle {
ignore_changes = [task_definition, desired_count]
}
}
resource "aws_ecs_task_definition" "main" {
family = "${var.name}-task-${var.environment}"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.container_cpu
memory = var.container_memory
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
container_definitions = jsonencode([{
name = "${var.name}-container-${var.environment}"
image = "${var.container_image}:latest"
essential = true
environment = var.container_environment
portMappings = [{
protocol = "tcp"
containerPort = var.container_port
hostPort = var.container_port
}]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.main.name
awslogs-stream-prefix = "ecs"
awslogs-region = var.region
}
}
secrets = var.container_secrets
}])
tags = {
Name = "${var.name}-task-${var.environment}"
Environment = var.environment
}
}
resource "aws_iam_role" "ecs_task_execution_role" {
name = "${var.name}-ecsTaskExecutionRole-${var.environment}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-policy-attachment" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_nat_gateway" "main" {
count = length(var.private_subnets)
allocation_id = element(aws_eip.nat.*.id, count.index)
subnet_id = element(aws_subnet.public.*.id, count.index)
depends_on = [aws_internet_gateway.main]
tags = {
Name = "${var.name}-nat-${var.environment}-${format("%03d", count.index+1)}"
Environment = var.environment
}
}
resource "aws_eip" "nat" {
count = length(var.private_subnets)
vpc = true
tags = {
Name = "${var.name}-eip-${var.environment}-${format("%03d", count.index+1)}"
Environment = var.environment
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.name}-routing-table-public"
Environment = var.environment
}
}
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
resource "aws_route_table_association" "public" {
count = length(var.public_subnets)
subnet_id = element(aws_subnet.public.*.id, count.index)
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = length(var.private_subnets)
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.name}-routing-table-private-${format("%03d", count.index+1)}"
Environment = var.environment
}
}
resource "aws_route" "private" {
count = length(compact(var.private_subnets))
route_table_id = element(aws_route_table.private.*.id, count.index)
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = element(aws_nat_gateway.main.*.id, count.index)
}
resource "aws_route_table_association" "private" {
count = length(var.private_subnets)
subnet_id = element(aws_subnet.private.*.id, count.index)
route_table_id = element(aws_route_table.private.*.id, count.index)
}
resource "aws_security_group" "alb" {
name = "${var.name}-sg-alb-${var.environment}"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
ingress {
protocol = "tcp"
from_port = 443
to_port = 443
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "${var.name}-sg-alb-${var.environment}"
Environment = var.environment
}
}
resource "aws_security_group" "ecs_tasks" {
name = "${var.name}-sg-task-${var.environment}"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = var.container_port
to_port = var.container_port
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "${var.name}-sg-task-${var.environment}"
Environment = var.environment
}
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = element(var.private_subnets, count.index)
availability_zone = element(var.availability_zones, count.index)
count = length(var.private_subnets)
tags = {
Name = "${var.name}-private-subnet-${var.environment}-${format("%03d", count.index+1)}"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = element(var.public_subnets, count.index)
availability_zone = element(var.availability_zones, count.index)
count = length(var.public_subnets)
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-subnet-${var.environment}-${format("%03d", count.index+1)}"
Environment = var.environment
}
}
resource "aws_vpc" "main" {
cidr_block = var.cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.name}-vpc-${var.environment}"
Environment = var.environment
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.name}-igw-${var.environment}"
Environment = var.environment
}
}
provider "aws" {
profile = var.aws_profile
region = var.aws-region
version = "~> 2.0"
}
{
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<AccountID>:user/my-terraform-user"
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-terraform-backend-store"
}
]
}
# copy this file to secrets.tfvars and add your own secrets
# never commit the actual secrets.tfvars file
aws-access-key = "myAWSAccessKey"
aws-secret-key = "myAWSSecret"
application-secrets = {
"ENV" = "prod"
}
name = "my-project-name"
environment = "test"
aws_profile = "default"
availability_zones = ["eu-central-1a", "eu-central-1b"]
private_subnets = ["10.0.0.0/20", "10.0.32.0/20"]
public_subnets = ["10.0.16.0/20", "10.0.48.0/20"]
tsl_certificate_arn = "mycertificatearn"
container_memory = 512
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment