diff --git a/CHANGELOG.md b/CHANGELOG.md index 337a6d7..fb2f7ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## [1.0.106] - 2024-11-01 +- [Fargate Ecs] Add Fargate Ecs Module + ## [1.0.105] - 2024-11-01 - [New Relic] Add ECS Cluster alerts for applications running in Fargate diff --git a/ecs_fargate/cloudwatch.tf b/ecs_fargate/cloudwatch.tf index 312a1a6..ca87f2b 100644 --- a/ecs_fargate/cloudwatch.tf +++ b/ecs_fargate/cloudwatch.tf @@ -1,11 +1,24 @@ +locals { + log_groups = var.ecs_task_def != null ? { + for k, v in var.ecs_task_def.containers : "${v.container_name}" => v + if try(v.log_group_name, null) == null + + } : {} +} + +output "log_groups" { + value = local.log_groups +} resource "aws_cloudwatch_log_group" "main" { - name = "/ecs/${terraform.workspace}/${var.ecs_cluster_name}/${var.ecs_service_name}" + for_each = local.log_groups + + name = join("/", ["ecs", terraform.workspace, var.ecs_cluster_name, var.ecs_service_name, lookup(each.value, "container_name")]) retention_in_days = var.log_retention_days kms_key_id = var.cw_kms_key tags = merge( var.tags, { - "Name" = var.ecr_name + "Name" = join("/", ["ecs", terraform.workspace, var.ecs_cluster_name, var.ecs_service_name, lookup(each.value, "container_name")]) }, ) } \ No newline at end of file diff --git a/ecs_fargate/ecs.tf b/ecs_fargate/ecs.tf index ce22d3b..c612190 100644 --- a/ecs_fargate/ecs.tf +++ b/ecs_fargate/ecs.tf @@ -1,15 +1,14 @@ // compile task containers for resource locals { - task_containers = [ + task_containers = var.ecs_task_def != null ? [ for t in var.ecs_task_def.containers : { name = t.container_name image = t.image_name - essential = true + essential = lookup(t, "essential", true) portMappings = t.port_mappings environment : t.environment_vars - secrets : [ for s in t.secret_vars : { name : s.name, valueFrom : "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/${s.valueFrom}" } @@ -17,16 +16,45 @@ locals { logConfiguration : { logDriver : "awslogs", options : { - awslogs-group : aws_cloudwatch_log_group.main.name + awslogs-group : try(t.log_group_name, null) != null ? t.log_group_name : aws_cloudwatch_log_group.main[t.container_name].name awslogs-region : data.aws_region.current.name, awslogs-stream-prefix : "ecs" } } - volumesFrom : [] - mountPoints : [] + volumesFrom : lookup(t, "volumesFrom", []) + mountPoints : lookup(t, "mountPoints", []) + dependsOn : lookup(t, "dependsOn", []) + cpu : 0 } - ] + ] : [] + fargate_compute = { + // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/fargate-tasks-services.html#fargate-tasks-size + ".25_.5" = { cpu : 256, memory : 512 } + ".25_1" = { cpu : 256, memory : 1024 * 1 } + ".25_2" = { cpu : 256, memory : 1024 * 2 } + + ".5_1" = { cpu : 512, memory : 1024 * 1 } + ".5_2" = { cpu : 512, memory : 1024 * 2 } + ".5_3" = { cpu : 512, memory : 1024 * 3 } + ".5_4" = { cpu : 512, memory : 1024 * 4 } + + "1_2" = { cpu : 1024 * 1, memory : 1024 * 2 } + "1_3" = { cpu : 1024 * 1, memory : 1024 * 3 } + "1_4" = { cpu : 1024 * 1, memory : 1024 * 4 } + "1_5" = { cpu : 1024 * 1, memory : 1024 * 5 } + "1_6" = { cpu : 1024 * 1, memory : 1024 * 6 } + "1_7" = { cpu : 1024 * 1, memory : 1024 * 7 } + "1_8" = { cpu : 1024 * 1, memory : 1024 * 8 } + + "2_4" = { cpu : 1024 * 2, memory : 1024 * 4 } + "2_5" = { cpu : 1024 * 2, memory : 1024 * 5 } + "2_6" = { cpu : 1024 * 2, memory : 1024 * 6 } + "2_7" = { cpu : 1024 * 2, memory : 1024 * 7 } + "2_8" = { cpu : 1024 * 2, memory : 1024 * 8 } + + + } } data "aws_ecs_cluster" "main" { @@ -36,11 +64,11 @@ data "aws_ecs_cluster" "main" { // create a task def if custom task def was not supplied resource "aws_ecs_task_definition" "main" { depends_on = [aws_cloudwatch_log_group.main] - count = length(var.ecs_task_def_custom) == 0 ? 1 : 0 + count = var.ecs_task_def != null ? 1 : 0 execution_role_arn = var.ecs_task_def.execution_role_arn task_role_arn = var.ecs_task_def.task_role_arn - cpu = var.ecs_task_def.cpu - memory = var.ecs_task_def.memory + cpu = local.fargate_compute[var.ecs_compute_config].cpu + memory = local.fargate_compute[var.ecs_compute_config].memory family = var.ecs_task_def.family container_definitions = jsonencode(local.task_containers) @@ -49,8 +77,12 @@ resource "aws_ecs_task_definition" "main" { requires_compatibilities = ["FARGATE"] tags = {} + volume { + name = "" + } + dynamic "volume" { - for_each = var.ecs_task_volumes + for_each = var.ecs_task_efs_volumes content { efs_volume_configuration { file_system_id = volume.value.fs_id @@ -71,12 +103,13 @@ resource "aws_ecs_task_definition" "main" { // create ecs service under cluster resource "aws_ecs_service" "main" { - depends_on = [aws_ecs_task_definition.main, aws_lb_target_group.alb] - name = var.ecs_service_name - cluster = data.aws_ecs_cluster.main.cluster_name - task_definition = length(var.ecs_task_def_custom) == 0 ? aws_ecs_task_definition.main[0].arn : var.ecs_task_def_custom - desired_count = var.ecs_desire_count - health_check_grace_period_seconds = 300 + depends_on = [aws_ecs_task_definition.main, aws_lb_target_group.alb] + name = var.ecs_service_name + cluster = data.aws_ecs_cluster.main.cluster_name + task_definition = length(var.ecs_task_def_custom) == 0 ? aws_ecs_task_definition.main[0].arn : var.ecs_task_def_custom + desired_count = var.ecs_desire_count + + health_check_grace_period_seconds = length(coalesce(var.ecs_load_balancers, {})) != 0 ? 300 : null network_configuration { security_groups = var.ecs_security_group_ids @@ -89,8 +122,19 @@ resource "aws_ecs_service" "main" { rollback = var.ecs_circuit_breaker } + dynamic "volume_configuration" { + for_each = length(coalesce(var.volume_configuration, {})) != 0 ? var.volume_configuration : {} + content { + name = lookup(volume_configuration.value, "name") + managed_ebs_volume { + role_arn = lookup(volume_configuration.value, "managed_ebs_volume").role_arn + } + } + } + + dynamic "load_balancer" { - for_each = var.ecs_load_balancers.alb + for_each = length(coalesce(var.ecs_load_balancers, {})) != 0 ? var.ecs_load_balancers : {} content { target_group_arn = aws_lb_target_group.alb[load_balancer.key].arn container_name = load_balancer.key diff --git a/ecs_fargate/networking.tf b/ecs_fargate/networking.tf index caece38..16fc0a5 100644 --- a/ecs_fargate/networking.tf +++ b/ecs_fargate/networking.tf @@ -1,14 +1,15 @@ data "aws_lb" "alb" { - arn = var.ec2_alb_arn + arn = var.ec2_alb_arn } -data "aws_lb_listener" "selected_443" { +data "aws_lb_listener" "selected" { + count = data.aws_lb.alb != null ? 1 : 0 load_balancer_arn = data.aws_lb.alb.arn - port = 443 + port = coalesce(var.lb_listener_port, 443) } resource "aws_lb_target_group" "alb" { - for_each = var.ecs_load_balancers.alb + for_each = length(coalesce(var.ecs_load_balancers, {})) != 0 ? var.ecs_load_balancers : {} name = substr(join("-", [terraform.workspace, each.key, lookup(each.value, "tls") ? "HTTPS" : "HTTP"]), 0, 32) port = lookup(each.value, "service_port", lookup(each.value, "container_port")) @@ -23,30 +24,51 @@ resource "aws_lb_target_group" "alb" { timeout = 5 unhealthy_threshold = 5 healthy_threshold = 5 - matcher = "200" + matcher = lookup(each.value, "health_check_matcher", "200") } tags = {} } resource "aws_lb_listener_rule" "static" { depends_on = [aws_lb_target_group.alb] - for_each = var.ecs_load_balancers.alb + for_each = length(coalesce(var.ecs_load_balancers, {})) != 0 ? var.ecs_load_balancers : {} - listener_arn = data.aws_lb_listener.selected_443.arn - priority = lookup(each.value, "container_port") - tags = {} + listener_arn = data.aws_lb_listener.selected[0].arn + tags = {} action { type = "forward" target_group_arn = aws_lb_target_group.alb[each.key].arn } - condition { + dynamic "condition" { + for_each = try(lookup(each.value, "conditions").host_header, null) != null ? [1] : [] - host_header { - values = lookup(each.value.conditions, "host_header") + content { + host_header { + values = lookup(each.value, "conditions").host_header + } } + } + dynamic "condition" { + for_each = try(lookup(each.value, "conditions").path_pattern, null) != null ? [1] : [] + + content { + path_pattern { + values = lookup(each.value, "conditions").path_pattern + } + } + } + + dynamic "condition" { + for_each = try(lookup(each.value, "conditions").http_header, null) != null ? [1] : [] + content { + http_header { + values = lookup(each.value, "conditions").http_header.values + http_header_name = lookup(each.value, "conditions").http_header.http_header_name + } + } } lifecycle { ignore_changes = [action] } diff --git a/ecs_fargate/variables.tf b/ecs_fargate/variables.tf index 7fd48e1..7715c55 100644 --- a/ecs_fargate/variables.tf +++ b/ecs_fargate/variables.tf @@ -23,6 +23,8 @@ variable "vpc_id" { variable "ec2_alb_arn" { description = "ARN for Application Load Balancer to attach ecs service" type = string + nullable = true + default = null } @@ -49,68 +51,55 @@ variable "ecs_task_def_custom" { default = "" } variable "ecs_task_def" { - description = "Object to create Task Def" + description = < key, value pair + secret_vars -> key, value pair (should only include ssm name: prepends 'arn:aws:ssm:::parameter/' to value + EOH type = object({ execution_role_arn = string task_role_arn = string - cpu = number - memory = number family = string containers = list(object({ container_name = string + + image_name = string + port_mappings = optional(list(object({ + containerPort = number + protocol = string + }))) log_group_name = string - environment_vars = list(object({ + + # optionals + environment_vars = optional(list(object({ name = string value = string - })) - secret_vars = list(object({ + }))) + secret_vars = optional(list(object({ name = string valueFrom = string - })) - image_name = string - port_mappings = list(object({ - containerPort = number - protocol = string - })) - })) + }))) + })) }) - /* - default = { - execution_role_arn : "arn:aws:iam::748039698304:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS", - task_role_arn : "arn:aws:iam::748039698304:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS", - cpu : 1024, - memory : 1024, - family : "family", - containers : [{ - container_name : "string", - log_group_name : "string" - environment_vars : [{ - name : "string", - value : "string" - }] - secret_vars : [ - { - name : "string", - valueFrom : "string" - } - ] - image_name : "string", - port_mappings : [{ - containerPort : 80, - protocol : "tcp" - }, - { - containerPort : 8080, - protocol : "tcp" - }] - }] - } - */ + nullable = true } +variable "volume_configuration" { + description = "Add Volume to ECS" + type = map(object({ + name = string + volume_configuration = object({ + role_arn = string + }) + }) + ) + default = {} +} -variable "ecs_task_volumes" { +variable "ecs_task_efs_volumes" { description = "Add EFS Mount to ECS" type = list(object({ name = string, @@ -174,54 +163,59 @@ variable "ecs_subnet_ids" { } variable "ecs_load_balancers" { description = "Connect service to load balancer" - type = map(map(object( + type = map(object( { container_port = number tls = bool - conditions = object({ - host_header = list(string) - }) + conditions = optional(object({ + host_header = optional(list(string)) + path_pattern = optional(list(string)) + http_header = optional(object({ + values = optional(list(string)) + http_header_name = optional(string) + })) + })) } - ))) + )) + default = {} - /* - default = { - alb = { - test-service1 = { - container_port = 8081 - tls = false - health_check_path = "/custom" - conditions = { - host_header = ["google.com"] - http_header = { - values = [] - http_header_name = "" - } - http_request_method = [] - path_pattern = [] - query_string = "" - source_ip = [] - } - } - test-service2 = { - container_port = 8081 - tls = false - conditions = { - host_header = ["google.com"] - http_header = { - values = [] - http_header_name = "" - } - http_request_method = [] - path_pattern = [] - query_string = "" - source_ip = [] - } - } - } - } +} - */ +variable "lb_listener_port" { + type = number + default = null + nullable = true + description = "What is the default LB Listener Port for lookup?" +} + +variable "ecs_compute_config" { + description = <