################################################################################ # Cluster ################################################################################ module "ecs_cluster" { source = "./modules/cluster" cluster_name = local.name # Capacity provider fargate_capacity_providers = { FARGATE = { default_capacity_provider_strategy = { weight = 50 base = 20 } } FARGATE_SPOT = { default_capacity_provider_strategy = { weight = 50 } } } tags = local.tags } ################################################################################ # Service ################################################################################ module "ecs_service" { source = "./modules/service" name = local.name cluster_arn = module.ecs_cluster.arn desired_count = 1 cpu = 1024 memory = 4096 # Enables ECS Exec enable_execute_command = true # Container definition(s) container_definitions = { (local.container_name) = { cpu = 512 memory = 1024 image = "richarvey/chat-app:latest" port_mappings = [ { name = local.container_name containerPort = local.container_port hostPort = local.container_port protocol = "tcp" } ] environment = [ { name = "REDIS_ENDPOINT" value = "valkey" }, ] memory_reservation = 100 } } service_connect_configuration = { namespace = aws_service_discovery_http_namespace.this.arn service = { client_alias = { port = local.container_port dns_name = local.container_name } port_name = local.container_name discovery_name = local.container_name } } load_balancer = { service = { target_group_arn = module.alb.target_groups["ex_ecs"].arn container_name = local.container_name container_port = local.container_port } } subnet_ids = module.vpc.private_subnets security_group_rules = { alb_ingress_3000 = { type = "ingress" from_port = local.container_port to_port = local.container_port protocol = "tcp" description = "Service port" source_security_group_id = module.alb.security_group_id } egress_all = { type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } service_tags = { "ServiceTag" = "Tag on service level" } tags = local.tags } resource "aws_ecs_task_definition" "task" { family = "service" network_mode = "awsvpc" requires_compatibilities = ["FARGATE", "EC2"] cpu = 512 memory = 1024 container_definitions = jsonencode([ { name = "valkey" image = "valkey/valkey:7.2.4-rc1-alpine" cpu = 512 memory = 1024 essential = true # if true and if fails, all other containers fail. Must have at least one essential portMappings = [ { name = "valkey" containerPort = 6379 hostPort = 6379 } ] } ]) } resource "aws_ecs_service" "service" { name = "valkey" cluster = module.ecs_cluster.id task_definition = aws_ecs_task_definition.task.id desired_count = 1 launch_type = "FARGATE" platform_version = "LATEST" network_configuration { assign_public_ip = false security_groups = [aws_security_group.sg.id] subnets = module.vpc.private_subnets } lifecycle { ignore_changes = [task_definition] } service_connect_configuration { enabled = true namespace = "chat-app-demo" service { discovery_name = "valkey" port_name = "valkey" client_alias { dns_name = "valkey" port = 6379 } } } } resource "aws_security_group" "sg" { name = "ecs" vpc_id = module.vpc.vpc_id egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } ingress { protocol = -1 self = true from_port = 0 to_port = 0 description = "" } ingress { from_port = 6379 to_port = 6379 protocol = "tcp" self = "false" cidr_blocks = ["0.0.0.0/0"] description = "Port 80" } } resource "null_resource" "update_desired_count" { triggers = { # Changes to this value will trigger the API call execution below desired_count = 3 } provisioner "local-exec" { interpreter = ["/bin/bash", "-c"] # Note: this requires the awscli to be installed locally where Terraform is executed command = <<-EOT aws ecs update-service \ --cluster ${module.ecs_cluster.name} \ --service ${module.ecs_service.name} \ --desired-count ${null_resource.update_desired_count.triggers.desired_count} EOT } } ################################################################################ # Supporting Resources ################################################################################ resource "aws_service_discovery_http_namespace" "this" { name = local.name description = "CloudMap namespace for ${local.name}" tags = local.tags } module "alb" { source = "terraform-aws-modules/alb/aws" version = "~> 9.0" name = local.name load_balancer_type = "application" vpc_id = module.vpc.vpc_id subnets = module.vpc.public_subnets # For example only enable_deletion_protection = false # Security Group security_group_ingress_rules = { all_http = { from_port = 80 to_port = 80 ip_protocol = "tcp" cidr_ipv4 = "0.0.0.0/0" } } security_group_egress_rules = { all = { ip_protocol = "-1" cidr_ipv4 = module.vpc.vpc_cidr_block } } listeners = { ex_http = { port = 80 protocol = "HTTP" forward = { target_group_key = "ex_ecs" } } } target_groups = { ex_ecs = { backend_protocol = "HTTP" backend_port = local.container_port target_type = "ip" deregistration_delay = 5 load_balancing_cross_zone_enabled = true health_check = { enabled = true healthy_threshold = 5 interval = 30 matcher = "200" path = "/" port = "traffic-port" protocol = "HTTP" timeout = 5 unhealthy_threshold = 2 } # There's nothing to attach here in this definition. Instead, # ECS will attach the IPs of the tasks to this target group create_attachment = false } } tags = local.tags }