diff --git a/services/terraform/remote/service_backup.tf b/services/terraform/remote/service_backup.tf --- a/services/terraform/remote/service_backup.tf +++ b/services/terraform/remote/service_backup.tf @@ -12,6 +12,9 @@ # URL accessible by other services in the same Service Connect namespace # This renders to 'http://backup-service:50052' backup_local_url = "http://${local.backup_sc_dns_name}:${local.backup_service_container_http_port}" + + # Fargate-specific URL for Fargate services to communicate with Fargate backup service + backup_fargate_url = "http://${local.backup_sc_dns_name}-fargate:${local.backup_service_container_http_port}" } resource "aws_ecs_task_definition" "backup_service" { @@ -182,8 +185,31 @@ certificate_arn = data.aws_acm_certificate.backup_service.arn default_action { - type = "forward" - target_group_arn = aws_lb_target_group.backup_service_http.arn + type = "forward" + + # Production: Simple forwarding (unchanged) + target_group_arn = local.is_staging ? null : aws_lb_target_group.backup_service_http.arn + + # Staging: Weighted forwarding + dynamic "forward" { + for_each = local.is_staging ? [1] : [] + content { + target_group { + arn = aws_lb_target_group.backup_service_http.arn + weight = 100 # 100% EC2 + } + + target_group { + arn = aws_lb_target_group.backup_service_http_fargate[0].arn + weight = 0 # 0% Fargate + } + + stickiness { + enabled = false + duration = 10 + } + } + } } lifecycle { diff --git a/services/terraform/remote/service_backup_fargate.tf b/services/terraform/remote/service_backup_fargate.tf new file mode 100644 --- /dev/null +++ b/services/terraform/remote/service_backup_fargate.tf @@ -0,0 +1,144 @@ +# Fargate task definition (staging only) +resource "aws_ecs_task_definition" "backup_service_fargate" { + count = local.is_staging ? 1 : 0 + family = "backup-service-fargate-task-def" + container_definitions = jsonencode([ + { + name = local.backup_service_container_name + image = local.backup_service_server_image + essential = true + portMappings = [ + { + name = local.backup_sc_port_name + containerPort = local.backup_service_container_http_port + protocol = "tcp" + appProtocol = "http" + }, + ] + environment = [ + { + name = "RUST_LOG" + value = local.is_staging ? "info,backup=debug,comm_lib=debug" : "info" + }, + { + name = "BLOB_SERVICE_URL", + value = local.blob_fargate_url + }, + { + name = "IDENTITY_SERVICE_ENDPOINT", + value = local.identity_fargate_url + }, + { + name = "COMM_SERVICES_USE_JSON_LOGS", + value = local.comm_services_use_json_logs + } + ] + logConfiguration = { + "logDriver" = "awslogs" + "options" = { + "awslogs-create-group" = "true" + "awslogs-group" = "/ecs/backup-service-fargate-task-def" + "awslogs-region" = "us-east-2" + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + task_role_arn = aws_iam_role.backup_service.arn + execution_role_arn = aws_iam_role.ecs_task_execution.arn + network_mode = "awsvpc" + cpu = "256" + memory = "512" + requires_compatibilities = ["FARGATE"] + + skip_destroy = true +} + +# Fargate ECS Service (staging only) +resource "aws_ecs_service" "backup_service_fargate" { + count = local.is_staging ? 1 : 0 + name = "backup-service-fargate" + cluster = aws_ecs_cluster.comm_services.id + launch_type = "FARGATE" + + task_definition = aws_ecs_task_definition.backup_service_fargate[0].arn + force_new_deployment = true + + network_configuration { + subnets = [ + aws_subnet.public_a.id, + aws_subnet.public_b.id, + aws_subnet.public_c.id, + ] + security_groups = [aws_security_group.backup_service.id] + assign_public_ip = true + } + + service_connect_configuration { + enabled = true + service { + discovery_name = "${local.backup_sc_dns_name}-fargate" + port_name = local.backup_sc_port_name + client_alias { + port = local.backup_service_container_http_port + dns_name = "${local.backup_sc_dns_name}-fargate" + } + } + } + + # HTTP + load_balancer { + target_group_arn = aws_lb_target_group.backup_service_http_fargate[0].arn + container_name = local.backup_service_container_name + container_port = local.backup_service_container_http_port + } + + deployment_circuit_breaker { + enable = true + rollback = true + } + + lifecycle { + ignore_changes = [desired_count] + } + + enable_execute_command = true + enable_ecs_managed_tags = true +} + +# Fargate HTTP target group (staging only) +resource "aws_lb_target_group" "backup_service_http_fargate" { + count = local.is_staging ? 1 : 0 + name = "backup-service-http-fargate-tg" + port = local.backup_service_container_http_port + protocol = "HTTP" + vpc_id = aws_vpc.default.id + target_type = "ip" + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + + protocol = "HTTP" + path = "/health" + matcher = "200-204" + } +} + +# Auto-scaling for Fargate service (staging only) +module "backup_service_fargate_autoscaling" { + source = "../modules/fargate-autoscaling" + + create_resources = local.is_staging + service_name = local.is_staging ? aws_ecs_service.backup_service_fargate[0].name : "" + cluster_name = aws_ecs_cluster.comm_services.name + + min_capacity = 1 + max_capacity = 4 + cpu_target = 40.0 + memory_target = 50.0 + + scale_in_cooldown = 300 # 5 minutes + scale_out_cooldown = 60 # 1 minute +} \ No newline at end of file diff --git a/services/terraform/remote/service_blob.tf b/services/terraform/remote/service_blob.tf --- a/services/terraform/remote/service_blob.tf +++ b/services/terraform/remote/service_blob.tf @@ -12,6 +12,9 @@ # This renders to 'http://blob-service:50053' blob_local_url = "http://${local.blob_sc_dns_name}:${local.blob_service_container_http_port}" + # Fargate-specific URL for Fargate services to communicate with Fargate blob service + blob_fargate_url = "http://${local.blob_sc_dns_name}-fargate:${local.blob_service_container_http_port}" + blob_service_container_grpc_port = 50051 blob_service_grpc_public_port = 50053 blob_service_domain_name = "blob.${local.root_domain}" @@ -185,8 +188,31 @@ certificate_arn = data.aws_acm_certificate.blob_service.arn default_action { - type = "forward" - target_group_arn = aws_lb_target_group.blob_service_http.arn + type = "forward" + + # Production: Simple forwarding (unchanged) + target_group_arn = local.is_staging ? null : aws_lb_target_group.blob_service_http.arn + + # Staging: Weighted forwarding + dynamic "forward" { + for_each = local.is_staging ? [1] : [] + content { + target_group { + arn = aws_lb_target_group.blob_service_http.arn + weight = 100 # 100% EC2 + } + + target_group { + arn = aws_lb_target_group.blob_service_http_fargate[0].arn + weight = 0 # 0% Fargate + } + + stickiness { + enabled = false + duration = 10 + } + } + } } lifecycle { diff --git a/services/terraform/remote/service_blob_fargate.tf b/services/terraform/remote/service_blob_fargate.tf new file mode 100644 --- /dev/null +++ b/services/terraform/remote/service_blob_fargate.tf @@ -0,0 +1,141 @@ +# Fargate task definition (staging only) +resource "aws_ecs_task_definition" "blob_service_fargate" { + count = local.is_staging ? 1 : 0 + family = "blob-service-fargate-task-def" + container_definitions = jsonencode([ + { + name = local.blob_service_container_name + image = local.blob_service_server_image + essential = true + portMappings = [ + { + name = local.blob_sc_port_name + containerPort = local.blob_service_container_http_port + protocol = "tcp" + appProtocol = "http" + } + ] + environment = [ + { + name = "RUST_LOG" + value = local.is_staging ? "info,blob=debug,comm_lib=debug" : "info" + }, + { + name = "BLOB_S3_BUCKET_NAME", + value = local.blob_service_s3_bucket + }, + { + name = "IDENTITY_SERVICE_ENDPOINT", + value = local.identity_fargate_url + }, + { + name = "COMM_SERVICES_USE_JSON_LOGS", + value = local.comm_services_use_json_logs + } + ] + logConfiguration = { + "logDriver" = "awslogs" + "options" = { + "awslogs-create-group" = "true" + "awslogs-group" = "/ecs/blob-service-fargate-task-def" + "awslogs-region" = "us-east-2" + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + task_role_arn = aws_iam_role.services_ddb_full_access.arn + execution_role_arn = aws_iam_role.ecs_task_execution.arn + network_mode = "awsvpc" + cpu = "256" + memory = "512" + requires_compatibilities = ["FARGATE"] + + skip_destroy = true +} + +# Fargate ECS Service (staging only) +resource "aws_ecs_service" "blob_service_fargate" { + count = local.is_staging ? 1 : 0 + name = "blob-service-fargate" + cluster = aws_ecs_cluster.comm_services.id + launch_type = "FARGATE" + + task_definition = aws_ecs_task_definition.blob_service_fargate[0].arn + force_new_deployment = true + + network_configuration { + subnets = [ + aws_subnet.public_a.id, + aws_subnet.public_b.id, + aws_subnet.public_c.id, + ] + security_groups = [aws_security_group.blob_service.id] + assign_public_ip = true + } + + service_connect_configuration { + enabled = true + service { + discovery_name = "${local.blob_sc_dns_name}-fargate" + port_name = local.blob_sc_port_name + client_alias { + port = local.blob_service_container_http_port + dns_name = "${local.blob_sc_dns_name}-fargate" + } + } + } + + # HTTP + load_balancer { + target_group_arn = aws_lb_target_group.blob_service_http_fargate[0].arn + container_name = local.blob_service_container_name + container_port = local.blob_service_container_http_port + } + + deployment_circuit_breaker { + enable = true + rollback = true + } + + lifecycle { + ignore_changes = [desired_count] + } +} + +# Fargate HTTP target group (staging only) +resource "aws_lb_target_group" "blob_service_http_fargate" { + count = local.is_staging ? 1 : 0 + name = "blob-service-http-fargate-tg" + port = local.blob_service_container_http_port + protocol = "HTTP" + vpc_id = aws_vpc.default.id + target_type = "ip" + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + + protocol = "HTTP" + path = "/health" + matcher = "200-499" + } +} + +# Auto-scaling for Fargate service (staging only) +module "blob_service_fargate_autoscaling" { + source = "../modules/fargate-autoscaling" + + create_resources = local.is_staging + service_name = local.is_staging ? aws_ecs_service.blob_service_fargate[0].name : "" + cluster_name = aws_ecs_cluster.comm_services.name + + min_capacity = 1 + max_capacity = 4 + cpu_target = 35.0 + memory_target = 45.0 + + scale_in_cooldown = 300 # 5 minutes + scale_out_cooldown = 60 # 1 minute +} \ No newline at end of file diff --git a/services/terraform/remote/service_identity.tf b/services/terraform/remote/service_identity.tf --- a/services/terraform/remote/service_identity.tf +++ b/services/terraform/remote/service_identity.tf @@ -16,6 +16,9 @@ # This renders to e.g. 'http://identity-service:50054' identity_local_url = "http://${local.identity_sc_dns_name}:${local.identity_service_container_grpc_port}" + # Fargate-specific URL for Fargate services to communicate with Fargate identity service + identity_fargate_url = "http://${local.identity_sc_dns_name}-fargate:${local.identity_service_container_grpc_port}" + # Port that is exposed to the public SSL endpoint (appended to domain name) identity_service_grpc_public_port = 50054 identity_service_domain_name = "identity.${local.root_domain}" @@ -277,8 +280,31 @@ certificate_arn = data.aws_acm_certificate.identity_service.arn default_action { - type = "forward" - target_group_arn = aws_lb_target_group.identity_service_ws.arn + type = "forward" + + # Production: Simple forwarding (unchanged) + target_group_arn = local.is_staging ? null : aws_lb_target_group.identity_service_ws.arn + + # Staging: Weighted forwarding + dynamic "forward" { + for_each = local.is_staging ? [1] : [] + content { + target_group { + arn = aws_lb_target_group.identity_service_ws.arn + weight = 100 # 100% EC2 + } + + target_group { + arn = aws_lb_target_group.identity_service_ws_fargate[0].arn + weight = 0 # 0% Fargate + } + + stickiness { + enabled = false + duration = 10 + } + } + } } lifecycle { @@ -295,8 +321,31 @@ certificate_arn = data.aws_acm_certificate.identity_service.arn default_action { - type = "forward" - target_group_arn = aws_lb_target_group.identity_service_grpc.arn + type = "forward" + + # Production: Simple forwarding (unchanged) + target_group_arn = local.is_staging ? null : aws_lb_target_group.identity_service_grpc.arn + + # Staging: Weighted forwarding + dynamic "forward" { + for_each = local.is_staging ? [1] : [] + content { + target_group { + arn = aws_lb_target_group.identity_service_grpc.arn + weight = 100 # Start with 100% EC2 + } + + target_group { + arn = aws_lb_target_group.identity_service_grpc_fargate[0].arn + weight = 0 # Start with 0% Fargate + } + + stickiness { + enabled = true + duration = 10 + } + } + } } lifecycle { diff --git a/services/terraform/remote/service_identity_fargate.tf b/services/terraform/remote/service_identity_fargate.tf new file mode 100644 --- /dev/null +++ b/services/terraform/remote/service_identity_fargate.tf @@ -0,0 +1,212 @@ +# Fargate task definition (staging only) +resource "aws_ecs_task_definition" "identity_service_fargate" { + count = local.is_staging ? 1 : 0 + family = "identity-service-fargate-task-def" + container_definitions = jsonencode([ + { + name = local.identity_service_container_name + image = local.identity_service_server_image + essential = true + portMappings = [ + { + name = local.identity_sc_port_name + containerPort = local.identity_service_container_grpc_port + protocol = "tcp" + appProtocol = "grpc" + }, + { + name = local.identity_sc_ws_port_name + containerPort = local.identity_service_container_ws_port + protocol = "tcp" + appProtocol = "http" + } + ] + environment = [ + { + name = "RUST_LOG" + value = local.is_staging ? "info,identity=debug,comm_lib=debug" : "info" + }, + { + name = "KEYSERVER_PUBLIC_KEY" + value = nonsensitive(local.secrets["keyserverPublicKey"]) + }, + { + name = "TUNNELBROKER_GRPC_ENDPOINT" + value = local.tunnelbroker_fargate_grpc_url + }, + { + name = "BACKUP_SERVICE_URL", + value = local.backup_fargate_url + }, + { + name = "BLOB_SERVICE_URL", + value = local.blob_fargate_url + }, + { + name = "OPENSEARCH_ENDPOINT" + value = module.shared.opensearch_domain_identity.endpoint + }, + { + name = "ALLOW_ORIGIN_LIST" + value = local.is_staging ? local.staging_allow_origin_list : local.production_allow_origin_list + }, + { + name = "COMM_SERVICES_USE_JSON_LOGS", + value = local.comm_services_use_json_logs + }, + { + name = "REDACT_SENSITIVE_DATA", + value = local.is_staging ? "false" : "true" + } + ] + secrets = [ + { + name = "OPAQUE_SERVER_SETUP" + valueFrom = data.aws_secretsmanager_secret.identity_server_setup.arn + } + ] + logConfiguration = { + "logDriver" = "awslogs" + "options" = { + "awslogs-create-group" = "true" + "awslogs-group" = "/ecs/identity-service-fargate-task-def" + "awslogs-region" = "us-east-2" + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + task_role_arn = aws_iam_role.services_ddb_full_access.arn + execution_role_arn = aws_iam_role.ecs_task_execution.arn + network_mode = "awsvpc" + cpu = "256" + memory = "512" + requires_compatibilities = ["FARGATE"] + + skip_destroy = true +} + +# Fargate ECS Service (staging only) +resource "aws_ecs_service" "identity_service_fargate" { + count = local.is_staging ? 1 : 0 + name = "identity-service-fargate" + cluster = aws_ecs_cluster.comm_services.id + launch_type = "FARGATE" + + task_definition = aws_ecs_task_definition.identity_service_fargate[0].arn + force_new_deployment = true + + network_configuration { + subnets = [ + aws_subnet.public_a.id, + aws_subnet.public_b.id, + aws_subnet.public_c.id, + ] + security_groups = [aws_security_group.identity_service.id] + assign_public_ip = true + } + + service_connect_configuration { + enabled = true + service { + discovery_name = "${local.identity_sc_dns_name}-fargate" + port_name = local.identity_sc_port_name + client_alias { + port = local.identity_service_container_grpc_port + dns_name = "${local.identity_sc_dns_name}-fargate" + } + } + } + + # WebSocket + load_balancer { + target_group_arn = aws_lb_target_group.identity_service_ws_fargate[0].arn + container_name = local.identity_service_container_name + container_port = local.identity_service_container_ws_port + } + + # gRPC + load_balancer { + target_group_arn = aws_lb_target_group.identity_service_grpc_fargate[0].arn + container_name = local.identity_service_container_name + container_port = local.identity_service_container_grpc_port + } + + deployment_circuit_breaker { + enable = true + rollback = true + } + + lifecycle { + ignore_changes = [desired_count] + } + + enable_execute_command = true + enable_ecs_managed_tags = true +} + +# Fargate gRPC target group (staging only) +resource "aws_lb_target_group" "identity_service_grpc_fargate" { + count = local.is_staging ? 1 : 0 + name = "identity-service-grpc-fargate-tg" + port = local.identity_service_container_grpc_port + protocol = "HTTP" + protocol_version = "HTTP2" + vpc_id = aws_vpc.default.id + target_type = "ip" + + stickiness { + type = "lb_cookie" + cookie_duration = 10 + enabled = true + } + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + + protocol = "HTTP" + port = "traffic-port" + path = "/" + matcher = "200-499" + } +} + +# Fargate WebSocket target group (staging only) +resource "aws_lb_target_group" "identity_service_ws_fargate" { + count = local.is_staging ? 1 : 0 + name = "identity-service-ws-fargate-tg" + port = local.identity_service_container_ws_port + protocol = "HTTP" + protocol_version = "HTTP1" + vpc_id = aws_vpc.default.id + target_type = "ip" + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + + protocol = "HTTP" + path = "/health" + matcher = "200" + } +} + +# Auto-scaling for Fargate service (staging only) +module "identity_service_fargate_autoscaling" { + source = "../modules/fargate-autoscaling" + + create_resources = local.is_staging + service_name = local.is_staging ? aws_ecs_service.identity_service_fargate[0].name : "" + cluster_name = aws_ecs_cluster.comm_services.name + + min_capacity = 1 + max_capacity = 6 + cpu_target = 35.0 + memory_target = 45.0 + + scale_in_cooldown = 300 # 5 minutes + scale_out_cooldown = 60 # 1 minute +} \ No newline at end of file diff --git a/services/terraform/remote/service_tunnelbroker.tf b/services/terraform/remote/service_tunnelbroker.tf --- a/services/terraform/remote/service_tunnelbroker.tf +++ b/services/terraform/remote/service_tunnelbroker.tf @@ -15,6 +15,9 @@ # Used for other services to connect to Tunnelbroker gRPC endpoint tunnelbroker_local_grpc_url = "http://${local.tunnelbroker_config.local_dns_name}:${local.tunnelbroker_config.grpc_port}" + # Fargate-specific URL for Fargate services to communicate with Fargate tunnelbroker + tunnelbroker_fargate_grpc_url = "http://${local.tunnelbroker_config.local_dns_name}-fargate:${local.tunnelbroker_config.grpc_port}" + # utility locals tunnelbroker_docker_image = "${local.tunnelbroker_config.docker_image}:${local.tunnelbroker_config.docker_tag}" rabbitmq_password = local.secrets.amqpPassword[local.environment] @@ -316,8 +319,31 @@ certificate_arn = data.aws_acm_certificate.tunnelbroker.arn default_action { - type = "forward" - target_group_arn = aws_lb_target_group.tunnelbroker_ws.arn + type = "forward" + + # Production: Simple forwarding (unchanged) + target_group_arn = local.is_staging ? null : aws_lb_target_group.tunnelbroker_ws.arn + + # Staging: Weighted forwarding + dynamic "forward" { + for_each = local.is_staging ? [1] : [] + content { + target_group { + arn = aws_lb_target_group.tunnelbroker_ws.arn + weight = 100 # Switch back to 100% EC2 + } + + target_group { + arn = aws_lb_target_group.tunnelbroker_ws_fargate[0].arn + weight = 0 # Switch back to 0% Fargate + } + + stickiness { + enabled = false + duration = 10 + } + } + } } lifecycle { @@ -335,8 +361,23 @@ certificate_arn = data.aws_acm_certificate.tunnelbroker.arn default_action { - type = "forward" - target_group_arn = aws_lb_target_group.tunnelbroker_grpc.arn + type = "forward" + forward { + target_group { + arn = aws_lb_target_group.tunnelbroker_grpc.arn + weight = 100 # 100% EC2 + } + + target_group { + arn = aws_lb_target_group.tunnelbroker_grpc_fargate[0].arn + weight = 0 # 0% Fargate + } + + stickiness { + enabled = false + duration = 10 + } + } } lifecycle { diff --git a/services/terraform/remote/service_tunnelbroker_fargate.tf b/services/terraform/remote/service_tunnelbroker_fargate.tf new file mode 100644 --- /dev/null +++ b/services/terraform/remote/service_tunnelbroker_fargate.tf @@ -0,0 +1,210 @@ +# Fargate task definition (staging only) +resource "aws_ecs_task_definition" "tunnelbroker_fargate" { + count = local.is_staging ? 1 : 0 + family = "tunnelbroker-fargate-task-def" + container_definitions = jsonencode([ + { + name = local.tunnelbroker_config.container_name + image = local.tunnelbroker_docker_image + essential = true + portMappings = [ + { + name = "tunnelbroker_ws" + containerPort = local.tunnelbroker_config.websocket_port + protocol = "tcp" + appProtocol = "http" + }, + { + name = local.tunnelbroker_config.grpc_port_name + containerPort = local.tunnelbroker_config.grpc_port + protocol = "tcp" + appProtocol = "grpc" + } + ] + environment = [ + { + name = "RUST_LOG" + value = local.is_staging ? "info,tunnelbroker=debug,comm_lib=debug" : "info" + }, + { + name = "AMQP_URI", + value = local.amqp_endpoint + }, + { + name = "AMQP_USERNAME" + value = "comm" + }, + { + name = "AMQP_PASSWORD" + value = nonsensitive(local.rabbitmq_password) + }, + { + name = "COMM_TUNNELBROKER_IDENTITY_ENDPOINT", + value = local.identity_fargate_url + }, + { + name = "BLOB_SERVICE_URL", + value = local.blob_fargate_url + }, + { + name = "BLOB_SERVICE_PUBLIC_URL", + value = "https://${local.blob_service_domain_name}" + }, + { + name = "COMM_SERVICES_USE_JSON_LOGS", + value = local.comm_services_use_json_logs + }, + { + name = "REDACT_SENSITIVE_DATA", + value = local.is_staging ? "false" : "true" + } + ] + secrets = [ + { + name = "APNS_CONFIG" + valueFrom = data.aws_secretsmanager_secret.tunnelbroker_apns.arn + }, + { + name = "FCM_CONFIG" + valueFrom = data.aws_secretsmanager_secret.tunnelbroker_fcm.arn + }, + { + name = "WEB_PUSH_CONFIG" + valueFrom = data.aws_secretsmanager_secret.tunnelbroker_web_push.arn + }, + { + name = "WNS_CONFIG" + valueFrom = data.aws_secretsmanager_secret.tunnelbroker_wns.arn + } + ] + logConfiguration = { + "logDriver" = "awslogs" + "options" = { + "awslogs-create-group" = "true" + "awslogs-group" = "/ecs/tunnelbroker-fargate-task-def" + "awslogs-region" = "us-east-2" + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + task_role_arn = aws_iam_role.services_ddb_full_access.arn + execution_role_arn = aws_iam_role.ecs_task_execution.arn + network_mode = "awsvpc" + cpu = "256" + memory = "512" + requires_compatibilities = ["FARGATE"] + + skip_destroy = true +} + +# Fargate ECS Service (staging only) +resource "aws_ecs_service" "tunnelbroker_fargate" { + count = local.is_staging ? 1 : 0 + name = "tunnelbroker-fargate" + cluster = aws_ecs_cluster.comm_services.id + launch_type = "FARGATE" + + task_definition = aws_ecs_task_definition.tunnelbroker_fargate[0].arn + force_new_deployment = true + + network_configuration { + subnets = [ + aws_subnet.public_a.id, + aws_subnet.public_b.id, + aws_subnet.public_c.id, + ] + security_groups = [aws_security_group.tunnelbroker.id] + assign_public_ip = true + } + + service_connect_configuration { + enabled = true + service { + discovery_name = "${local.tunnelbroker_config.local_dns_name}-fargate" + port_name = local.tunnelbroker_config.grpc_port_name + client_alias { + port = local.tunnelbroker_config.grpc_port + dns_name = "${local.tunnelbroker_config.local_dns_name}-fargate" + } + } + } + + # Websocket + load_balancer { + target_group_arn = aws_lb_target_group.tunnelbroker_ws_fargate[0].arn + container_name = local.tunnelbroker_config.container_name + container_port = local.tunnelbroker_config.websocket_port + } + + # gRPC (only exists in staging) + load_balancer { + target_group_arn = aws_lb_target_group.tunnelbroker_grpc_fargate[0].arn + container_name = local.tunnelbroker_config.container_name + container_port = local.tunnelbroker_config.grpc_port + } + + deployment_circuit_breaker { + enable = true + rollback = true + } + + lifecycle { + ignore_changes = [desired_count] + } +} + +# Fargate WebSocket target group (staging only) +resource "aws_lb_target_group" "tunnelbroker_ws_fargate" { + count = local.is_staging ? 1 : 0 + name = "tunnelbroker-ws-fargate-tg" + port = local.tunnelbroker_config.websocket_port + protocol = "HTTP" + protocol_version = "HTTP1" + vpc_id = aws_vpc.default.id + target_type = "ip" + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + + protocol = "HTTP" + path = "/health" + matcher = "200" + } +} + +# Fargate gRPC target group (staging only) +resource "aws_lb_target_group" "tunnelbroker_grpc_fargate" { + count = local.is_staging ? 1 : 0 + name = "tunnelbroker-grpc-fargate-tg" + port = local.tunnelbroker_config.grpc_port + protocol = "HTTP" + protocol_version = "GRPC" + vpc_id = aws_vpc.default.id + target_type = "ip" + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + } +} + +# Auto-scaling for Fargate service (staging only) +module "tunnelbroker_fargate_autoscaling" { + source = "../modules/fargate-autoscaling" + + create_resources = local.is_staging + service_name = local.is_staging ? aws_ecs_service.tunnelbroker_fargate[0].name : "" + cluster_name = aws_ecs_cluster.comm_services.name + + min_capacity = 1 + max_capacity = 8 + cpu_target = 30.0 + memory_target = 40.0 + + scale_in_cooldown = 300 # 5 minutes + scale_out_cooldown = 60 # 1 minute +} \ No newline at end of file