Part of the Terraform for this site This Website
resource "aws_ecs_cluster" "server_cluster" {
name = "website_server_cluster"
setting {
name = "containerInsights"
value = "disabled"
}
}
resource "aws_cloudwatch_log_group" "cluster_logs" {
for_each = var.backend_tasks
name = "/ecs/webserver/${each.key}"
retention_in_days = 30
}
resource "aws_iam_role" "ecs_task_execution_role" {
name = "website_server_task_execution_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
role = aws_iam_role.ecs_task_execution_role.name
}
resource "aws_iam_role" "ecs_task_role" {
name = "website_server_task_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "assetaccess" {
name = "assetaccess"
role = aws_iam_role.ecs_task_role.name
policy = file("s3files.json")
}
resource "aws_ecs_task_definition" "site_definitions" {
for_each = var.backend_tasks
family = "${each.key}_task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
cpu = tostring(each.value.cpu)
memory = tostring(each.value.memory)
container_definitions = jsonencode([
{
name = "${each.key}_container_task"
image = "${aws_ecr_repository.site_containers[each.key].repository_url}:latest"
essential = true
readonlyRootFilesystem = false # I'll need to check this out later. The app couldn't write to cache when this was on.
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:${each.value.internal_port}/ || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
portMappings = [
{
containerPort = each.value.internal_port
# hostPort = 0
protocol = "tcp"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.cluster_logs[each.key].name
"awslogs-stream-prefix" = "ecs"
"awslogs-region" = aws_vpc.ecs_vpc.region
}
}
environment = [
{
name = "PORT"
value = tostring(each.value.internal_port)
},
{
name = "NEXT_PUBLIC_SITE_URL"
value = "https://${aws_lb.ECSWebServerLB.dns_name}"
},
{
name = "INTERNAL_API_URL"
value = "http://localhost:${each.value.internal_port}"
},
{
name = "HOSTNAME"
value = "0.0.0.0"
},
]
}]
)
}
resource "aws_ecs_service" "server_services" {
for_each = var.backend_tasks
name = "${each.key}_service"
cluster = aws_ecs_cluster.server_cluster.id
task_definition = aws_ecs_task_definition.site_definitions[each.key].arn
desired_count = each.value.desired_count
force_new_deployment = true
capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
weight = 99
base = 1
}
capacity_provider_strategy {
capacity_provider = "FARGATE"
weight = 1
base = 0
}
network_configuration {
subnets = [for s in aws_subnet.private_subnets : s.id]
security_groups = [aws_security_group.ecs_fargate_sg.id]
assign_public_ip = false # Tasks in private subnets
}
dynamic "load_balancer" {
for_each = {for k, v in var.frontend_apps : k => v if v.backend_key == each.key }
content {
target_group_arn = aws_lb_target_group.TG[load_balancer.key].arn
container_name = "${each.key}_container_task"
container_port = each.value.external_port
}
}
deployment_circuit_breaker {
enable = true
rollback = true
}
depends_on = [
aws_lb_listener.ECSWebServerListener,
aws_iam_role_policy_attachment.ecs_task_execution_role_policy,
]
lifecycle {
replace_triggered_by = [aws_subnet.private_subnets]
create_before_destroy = true
}
}
resource "aws_ecs_cluster_capacity_providers" "fargate" {
cluster_name = aws_ecs_cluster.server_cluster.name
capacity_providers = ["FARGATE", "FARGATE_SPOT"]
default_capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
weight = 10
base = 1
}
}
resource "aws_appautoscaling_target" "ecs_service" {
for_each = var.backend_tasks
max_capacity = 10
min_capacity = 1
resource_id = "service/${aws_ecs_cluster.server_cluster.name}/${aws_ecs_service.server_services[each.key].name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "ecs_service_cpu" {
for_each = var.backend_tasks
name = "${each.key}_cpu_scaling_policy"
resource_id = aws_appautoscaling_target.ecs_service[each.key].resource_id
scalable_dimension = aws_appautoscaling_target.ecs_service[each.key].scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_service[each.key].service_namespace
policy_type = "TargetTrackingScaling"
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 75.0
scale_in_cooldown = 300
scale_out_cooldown = 60
}
}
resource "aws_appautoscaling_policy" "ecs_service_memory" {
for_each = var.backend_tasks
name = "${each.key}_memory_scaling_policy"
resource_id = aws_appautoscaling_target.ecs_service[each.key].resource_id
scalable_dimension = aws_appautoscaling_target.ecs_service[each.key].scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_service[each.key].service_namespace
policy_type = "TargetTrackingScaling"
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
}
target_value = 75.0
scale_in_cooldown = 300
scale_out_cooldown = 60
}
}