# Phase 2: ECS Fargate + Application Load Balancer # File: infrastructure/services/main.tf terraform { required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.aws_region default_tags { tags = var.common_tags } } # Data sources to reference Foundation Layer outputs data "terraform_remote_state" "foundation" { backend = "s3" config = { bucket = "nvhi-atsila-microservice-terraform-state-c4ae0f80" key = "foundation/terraform.tfstate" region = var.aws_region } } # Enterprise naming convention with AWS resource limits locals { # Shortened base name for AWS resource naming limits base_name = "nvhi-atsila" # Computed names ensuring AWS limits compliance # Target Groups (32 char limit): "nvhi-atsila-blue-tg" = 17 chars ✅ blue_tg_name = "${local.base_name}-blue-tg" green_tg_name = "${local.base_name}-green-tg" # ALB name (32 char limit): "nvhi-atsila-alb" = 15 chars ✅ alb_name = "${local.base_name}-alb" # ECS names for consistency ecs_cluster_name = "${local.base_name}-cluster" ecs_service_name = "${local.base_name}-service" # ECR name ecr_repo_name = "${local.base_name}-app" # Common resource prefix for tagging and organization resource_prefix = local.base_name } # ECR Repository for container images resource "aws_ecr_repository" "app" { name = local.ecr_repo_name image_tag_mutability = "MUTABLE" force_delete = true # For demo purposes image_scanning_configuration { scan_on_push = true } tags = { Name = "${var.project_name}-ecr" Environment = var.environment Project = var.project_name } } # ECR Lifecycle Policy resource "aws_ecr_lifecycle_policy" "app" { repository = aws_ecr_repository.app.name policy = jsonencode({ rules = [ { rulePriority = 1 description = "Keep last 10 images" selection = { tagStatus = "tagged" tagPrefixList = ["v"] countType = "imageCountMoreThan" countNumber = 10 } action = { type = "expire" } }, { rulePriority = 2 description = "Delete untagged images older than 1 day" selection = { tagStatus = "untagged" countType = "sinceImagePushed" countUnit = "days" countNumber = 1 } action = { type = "expire" } } ] }) } # ECS Cluster resource "aws_ecs_cluster" "main" { name = local.ecs_cluster_name configuration { execute_command_configuration { logging = "OVERRIDE" log_configuration { cloud_watch_log_group_name = aws_cloudwatch_log_group.ecs_cluster.name } } } setting { name = "containerInsights" value = "enabled" } tags = { Name = "${var.project_name}-ecs-cluster" Environment = var.environment Project = var.project_name } } # CloudWatch Log Group for ECS resource "aws_cloudwatch_log_group" "ecs_cluster" { name = "/aws/ecs/${local.ecs_cluster_name}" retention_in_days = 7 # Free tier friendly skip_destroy = false tags = { Name = "${local.ecs_cluster_name}-logs" Environment = var.environment Project = var.project_name } } resource "aws_cloudwatch_log_group" "app" { name = "/aws/ecs/${local.ecr_repo_name}" retention_in_days = 7 # Free tier friendly skip_destroy = false tags = { Name = "${local.ecr_repo_name}-logs" Environment = var.environment Project = var.project_name } } # Application Load Balancer resource "aws_lb" "main" { name = local.alb_name internal = false load_balancer_type = "application" security_groups = [data.terraform_remote_state.foundation.outputs.alb_security_group_id] subnets = data.terraform_remote_state.foundation.outputs.public_subnet_ids enable_deletion_protection = false # For demo purposes tags = { Name = "${var.project_name}-alb" Environment = var.environment Project = var.project_name } } # ALB Target Group for Blue Environment resource "aws_lb_target_group" "blue" { name = local.blue_tg_name port = 8080 protocol = "HTTP" vpc_id = data.terraform_remote_state.foundation.outputs.vpc_id target_type = "ip" health_check { enabled = true healthy_threshold = 2 interval = 30 matcher = "200" path = "/health" port = "traffic-port" protocol = "HTTP" timeout = 5 unhealthy_threshold = 3 } deregistration_delay = 30 # Fast for demo tags = { Name = "${var.project_name}-blue-tg" Environment = var.environment Project = var.project_name } } # ALB Target Group for Green Environment (Blue/Green Deployment) resource "aws_lb_target_group" "green" { name = local.green_tg_name port = 8080 protocol = "HTTP" vpc_id = data.terraform_remote_state.foundation.outputs.vpc_id target_type = "ip" health_check { enabled = true healthy_threshold = 2 interval = 30 matcher = "200" path = "/health" port = "traffic-port" protocol = "HTTP" timeout = 5 unhealthy_threshold = 3 } deregistration_delay = 30 # Fast for demo tags = { Name = "${var.project_name}-green-tg" Environment = var.environment Project = var.project_name } } # ALB Listener (defaults to Blue) resource "aws_lb_listener" "main" { load_balancer_arn = aws_lb.main.arn port = "80" protocol = "HTTP" default_action { type = "forward" forward { target_group { arn = aws_lb_target_group.blue.arn } } } tags = { Name = "${var.project_name}-alb-listener" Environment = var.environment Project = var.project_name } } # IAM Role for ECS Task Execution resource "aws_iam_role" "ecs_task_execution" { name = "${var.project_name}-ecs-task-execution-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ecs-tasks.amazonaws.com" } } ] }) tags = { Name = "${var.project_name}-ecs-task-execution-role" Environment = var.environment Project = var.project_name } } # IAM Role Policy Attachment for ECS Task Execution resource "aws_iam_role_policy_attachment" "ecs_task_execution" { role = aws_iam_role.ecs_task_execution.name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } # Additional IAM Policy for ECR and CloudWatch resource "aws_iam_role_policy" "ecs_task_execution_custom" { name = "${var.project_name}-ecs-task-execution-custom" role = aws_iam_role.ecs_task_execution.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ] Resource = "*" }, { Effect = "Allow" Action = [ "logs:CreateLogStream", "logs:PutLogEvents" ] Resource = "${aws_cloudwatch_log_group.app.arn}:*" } ] }) } # IAM Role for ECS Task (application permissions) resource "aws_iam_role" "ecs_task" { name = "${var.project_name}-ecs-task-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ecs-tasks.amazonaws.com" } } ] }) tags = { Name = "${var.project_name}-ecs-task-role" Environment = var.environment Project = var.project_name } } # ECS Task Definition resource "aws_ecs_task_definition" "app" { family = local.ecr_repo_name network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] cpu = var.task_cpu memory = var.task_memory execution_role_arn = aws_iam_role.ecs_task_execution.arn task_role_arn = aws_iam_role.ecs_task.arn container_definitions = jsonencode([ { name = "app" image = "${aws_ecr_repository.app.repository_url}:${var.image_tag}" essential = true portMappings = [ { containerPort = 8080 protocol = "tcp" } ] logConfiguration = { logDriver = "awslogs" options = { awslogs-group = aws_cloudwatch_log_group.app.name awslogs-region = var.aws_region awslogs-stream-prefix = "ecs" } } environment = [ { name = "ENV" value = var.environment }, { name = "PROJECT_NAME" value = var.project_name } ] } ]) tags = { Name = "${var.project_name}-task-definition" Environment = var.environment Project = var.project_name } } # ECS Service (Blue Environment) resource "aws_ecs_service" "app" { name = local.ecs_service_name cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.app.arn desired_count = var.desired_count launch_type = "FARGATE" network_configuration { subnets = data.terraform_remote_state.foundation.outputs.app_subnet_ids security_groups = [data.terraform_remote_state.foundation.outputs.ecs_tasks_security_group_id] assign_public_ip = true # Required for public subnets without NAT gateway } load_balancer { target_group_arn = aws_lb_target_group.blue.arn container_name = "app" container_port = 8080 } depends_on = [aws_lb_listener.main] # Blue/Green deployment configuration deployment_maximum_percent = 200 deployment_minimum_healthy_percent = 50 deployment_circuit_breaker { enable = true rollback = true } enable_execute_command = true tags = { Name = "${var.project_name}-ecs-service" Environment = var.environment Project = var.project_name } } # CloudWatch Alarms for monitoring resource "aws_cloudwatch_metric_alarm" "high_cpu" { alarm_name = "${var.project_name}-high-cpu" comparison_operator = "GreaterThanThreshold" evaluation_periods = "2" metric_name = "CPUUtilization" namespace = "AWS/ECS" period = "300" statistic = "Average" threshold = "80" alarm_description = "This metric monitors ECS service CPU utilization" alarm_actions = [] # Add SNS topic ARN for notifications dimensions = { ServiceName = aws_ecs_service.app.name ClusterName = aws_ecs_cluster.main.name } tags = { Name = "${var.project_name}-high-cpu-alarm" Environment = var.environment Project = var.project_name } } resource "aws_cloudwatch_metric_alarm" "high_memory" { alarm_name = "${var.project_name}-high-memory" comparison_operator = "GreaterThanThreshold" evaluation_periods = "2" metric_name = "MemoryUtilization" namespace = "AWS/ECS" period = "300" statistic = "Average" threshold = "80" alarm_description = "This metric monitors ECS service memory utilization" alarm_actions = [] # Add SNS topic ARN for notifications dimensions = { ServiceName = aws_ecs_service.app.name ClusterName = aws_ecs_cluster.main.name } tags = { Name = "${var.project_name}-high-memory-alarm" Environment = var.environment Project = var.project_name } }