From 727674c4308d07b77e8c19ea877eba5b71d56902 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Wed, 9 Aug 2023 13:40:40 +0000 Subject: [PATCH 01/22] Adding Module and Example for ECS cluster monitoring with ecs_observer --- .../ec2-autoscaling/README.md | 94 ++ .../ec2-autoscaling/main.tf | 341 +++++ .../ec2-autoscaling/outputs.tf | 132 ++ .../ec2-autoscaling/variables.tf | 0 .../ec2-autoscaling/versions.tf | 10 + .../modules/cluster/README.md | 214 +++ .../modules/cluster/main.tf | 327 ++++ .../modules/cluster/outputs.tf | 70 + .../modules/cluster/variables.tf | 169 +++ .../modules/cluster/versions.tf | 10 + .../modules/service/README.md | 351 +++++ .../modules/service/main.tf | 1329 +++++++++++++++++ .../modules/service/outputs.tf | 152 ++ .../modules/service/variables.tf | 643 ++++++++ .../modules/service/versions.tf | 10 + modules/ecs_monitoring/README.md | 72 + modules/ecs_monitoring/configs/config.yaml | 130 ++ modules/ecs_monitoring/locals.tf | 9 + modules/ecs_monitoring/main.tf | 46 + modules/ecs_monitoring/outputs.tf | 19 + .../task_definitions/otel_collector.json | 21 + modules/ecs_monitoring/variables.tf | 14 + modules/ecs_monitoring/versions.tf | 10 + 23 files changed, 4173 insertions(+) create mode 100644 examples/ecs-cluster-with-vpc/ec2-autoscaling/README.md create mode 100644 examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf create mode 100644 examples/ecs-cluster-with-vpc/ec2-autoscaling/outputs.tf create mode 100644 examples/ecs-cluster-with-vpc/ec2-autoscaling/variables.tf create mode 100644 examples/ecs-cluster-with-vpc/ec2-autoscaling/versions.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/cluster/README.md create mode 100644 examples/ecs-cluster-with-vpc/modules/cluster/main.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/cluster/outputs.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/cluster/variables.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/cluster/versions.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/service/README.md create mode 100644 examples/ecs-cluster-with-vpc/modules/service/main.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/service/outputs.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/service/variables.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/service/versions.tf create mode 100644 modules/ecs_monitoring/README.md create mode 100644 modules/ecs_monitoring/configs/config.yaml create mode 100644 modules/ecs_monitoring/locals.tf create mode 100644 modules/ecs_monitoring/main.tf create mode 100644 modules/ecs_monitoring/outputs.tf create mode 100644 modules/ecs_monitoring/task_definitions/otel_collector.json create mode 100644 modules/ecs_monitoring/variables.tf create mode 100644 modules/ecs_monitoring/versions.tf diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/README.md b/examples/ecs-cluster-with-vpc/ec2-autoscaling/README.md new file mode 100644 index 00000000..f468c359 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/ec2-autoscaling/README.md @@ -0,0 +1,94 @@ +# ECS Cluster w/ EC2 Autoscaling + +Configuration in this directory creates: + +- ECS cluster using EC2 autoscaling groups +- Autoscaling groups with IAM instance profile to be used by ECS cluster +- Example ECS service that utilizes + - Mounts a host volume into the container definition + - Load balancer target group attachment + - Security group for access to the example service + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 8.0 | +| [alb\_sg](#module\_alb\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | +| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | +| [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | ./modules/cluster | n/a | +| [ecs\_service](#module\_ecs\_service) | ./modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_ssm_parameter.ecs_optimized_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [service\_autoscaling\_policies](#output\_service\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [service\_autoscaling\_scheduled\_actions](#output\_service\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [service\_container\_definitions](#output\_service\_container\_definitions) | Container definitions | +| [service\_iam\_role\_arn](#output\_service\_iam\_role\_arn) | Service IAM role ARN | +| [service\_iam\_role\_name](#output\_service\_iam\_role\_name) | Service IAM role name | +| [service\_iam\_role\_unique\_id](#output\_service\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [service\_id](#output\_service\_id) | ARN that identifies the service | +| [service\_name](#output\_service\_name) | Name of the service | +| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | +| [service\_task\_exec\_iam\_role\_arn](#output\_service\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [service\_task\_exec\_iam\_role\_name](#output\_service\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [service\_task\_exec\_iam\_role\_unique\_id](#output\_service\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [service\_task\_set\_arn](#output\_service\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [service\_task\_set\_id](#output\_service\_task\_set\_id) | The ID of the task set | +| [service\_task\_set\_stability\_status](#output\_service\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [service\_task\_set\_status](#output\_service\_task\_set\_status) | The status of the task set | +| [service\_tasks\_iam\_role\_arn](#output\_service\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [service\_tasks\_iam\_role\_name](#output\_service\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [service\_tasks\_iam\_role\_unique\_id](#output\_service\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf b/examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf new file mode 100644 index 00000000..6fc246a7 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf @@ -0,0 +1,341 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + container_name = "ecs-sample" + container_port = 80 + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Cluster +################################################################################ + +module "ecs_cluster" { + source = "./modules/cluster" + + cluster_name = local.name + + # Capacity provider - autoscaling groups + default_capacity_provider_use_fargate = false + autoscaling_capacity_providers = { + # On-demand instances + ex-1 = { + auto_scaling_group_arn = module.autoscaling["ex-1"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + # Spot instances + ex-2 = { + auto_scaling_group_arn = module.autoscaling["ex-2"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + + cluster_settings = { + name = "containerInsights" + value = "disabled" + } + } + } + + tags = local.tags +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "./modules/service" + + # Service + name = local.name + cluster_arn = module.ecs_cluster.arn + + # Task Definition + requires_compatibilities = ["EC2"] + capacity_provider_strategy = { + # On-demand instances + ex-1 = { + capacity_provider = module.ecs_cluster.autoscaling_capacity_providers["ex-1"].name + weight = 1 + base = 1 + } + } + + volume = { + my-vol = {} + } + + # Container definition(s) + container_definitions = { + (local.container_name) = { + image = "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest" + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + protocol = "tcp" + } + ] + + mount_points = [ + { + sourceVolume = "my-vol", + containerPath = "/var/www/my-vol" + } + ] + + entry_point = ["/usr/sbin/apache2", "-D", "FOREGROUND"] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + } + } + + load_balancer = { + service = { + target_group_arn = element(module.alb.target_group_arns, 0) + container_name = local.container_name + container_port = local.container_port + } + } + + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_http_ingress = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb_sg.security_group_id + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +module "alb_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = "${local.name}-service" + description = "Service security group" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] + + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks + + tags = local.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 8.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + security_groups = [module.alb_sg.security_group_id] + + http_tcp_listeners = [ + { + port = local.container_port + protocol = "HTTP" + target_group_index = 0 + }, + ] + + target_groups = [ + { + name = "${local.name}-${local.container_name}" + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + }, + ] + + tags = local.tags +} + +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + for_each = { + # On-demand instances + ex-1 = { + instance_type = "t3.large" + use_mixed_instances_policy = false + mixed_instances_policy = {} + user_data = <<-EOT + #!/bin/bash + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + EOF + EOT + } + # Spot instances + ex-2 = { + instance_type = "t3.medium" + use_mixed_instances_policy = true + mixed_instances_policy = { + instances_distribution = { + on_demand_base_capacity = 0 + on_demand_percentage_above_base_capacity = 0 + spot_allocation_strategy = "price-capacity-optimized" + } + + override = [ + { + instance_type = "m4.large" + weighted_capacity = "2" + }, + { + instance_type = "t3.large" + weighted_capacity = "1" + }, + ] + } + user_data = <<-EOT + #!/bin/bash + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + ECS_ENABLE_SPOT_INSTANCE_DRAINING=true + EOF + EOT + } + } + + name = "${local.name}-${each.key}" + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = each.value.instance_type + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(each.value.user_data) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 5 + desired_capacity = 2 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + # Required for managed_termination_protection = "ENABLED" + protect_from_scale_in = true + + # Spot instances + use_mixed_instances_policy = each.value.use_mixed_instances_policy + mixed_instances_policy = each.value.mixed_instances_policy + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = module.vpc.vpc_id + + computed_ingress_with_source_security_group_id = [ + { + rule = "http-80-tcp" + source_security_group_id = module.alb_sg.security_group_id + } + ] + number_of_computed_ingress_with_source_security_group_id = 1 + + egress_rules = ["all-all"] + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/outputs.tf b/examples/ecs-cluster-with-vpc/ec2-autoscaling/outputs.tf new file mode 100644 index 00000000..2f6f85a7 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/ec2-autoscaling/outputs.tf @@ -0,0 +1,132 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs_cluster.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs_cluster.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs_cluster.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs_cluster.cluster_capacity_providers +} + +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs_cluster.autoscaling_capacity_providers +} + +################################################################################ +# Service +################################################################################ + +output "service_id" { + description = "ARN that identifies the service" + value = module.ecs_service.id +} + +output "service_name" { + description = "Name of the service" + value = module.ecs_service.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.ecs_service.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.ecs_service.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.ecs_service.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.ecs_service.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.ecs_service.task_definition_arn +} + +output "service_task_definition_revision" { + description = "Revision of the task in a particular family" + value = module.ecs_service.task_definition_revision +} + +output "service_task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_service.task_exec_iam_role_name +} + +output "service_task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_service.task_exec_iam_role_arn +} + +output "service_task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.ecs_service.task_exec_iam_role_unique_id +} + +output "service_tasks_iam_role_name" { + description = "Tasks IAM role name" + value = module.ecs_service.tasks_iam_role_name +} + +output "service_tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = module.ecs_service.tasks_iam_role_arn +} + +output "service_tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = module.ecs_service.tasks_iam_role_unique_id +} + +output "service_task_set_id" { + description = "The ID of the task set" + value = module.ecs_service.task_set_id +} + +output "service_task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = module.ecs_service.task_set_arn +} + +output "service_task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = module.ecs_service.task_set_stability_status +} + +output "service_task_set_status" { + description = "The status of the task set" + value = module.ecs_service.task_set_status +} + +output "service_autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = module.ecs_service.autoscaling_policies +} + +output "service_autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = module.ecs_service.autoscaling_scheduled_actions +} diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/variables.tf b/examples/ecs-cluster-with-vpc/ec2-autoscaling/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/versions.tf b/examples/ecs-cluster-with-vpc/ec2-autoscaling/versions.tf new file mode 100644 index 00000000..290d2218 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/ec2-autoscaling/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.55" + } + } +} diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/README.md b/examples/ecs-cluster-with-vpc/modules/cluster/README.md new file mode 100644 index 00000000..0f4adb65 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/cluster/README.md @@ -0,0 +1,214 @@ +# Amazon ECS Cluster Terraform Module + +Terraform module which creates Amazon ECS (Elastic Container Service) cluster resources on AWS. + +## Available Features + +- ECS cluster +- Fargate capacity providers +- EC2 AutoScaling Group capacity providers +- ECS Service w/ task definition, task set, and container definition support + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) + +## Usage + +### Fargate Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + cluster_name = "ecs-fargate" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +### EC2 Autoscaling Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + cluster_name = "ecs-ec2" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + # Disable creation of cluster and all resources + create = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_capacity_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_capacity_provider) | resource | +| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_ecs_cluster_capacity_providers.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | +| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events | `number` | `90` | no | +| [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | A map of additional tags to add to the log group created | `map(string)` | `{}` | no | +| [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | +| [cluster\_settings](#input\_cluster\_settings) | Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `map(string)` |
{
"name": "containerInsights",
"value": "enabled"
}
| no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | +| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | ARN that identifies the cluster | +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [id](#output\_id) | ID that identifies the cluster | +| [name](#output\_name) | Name that identifies the cluster | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/main.tf b/examples/ecs-cluster-with-vpc/modules/cluster/main.tf new file mode 100644 index 00000000..6830af2e --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/cluster/main.tf @@ -0,0 +1,327 @@ +data "aws_partition" "current" {} + +################################################################################ +# Cluster +################################################################################ + +locals { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, null) + } + } +} + +resource "aws_ecs_cluster" "this" { + count = var.create ? 1 : 0 + + name = var.cluster_name + + dynamic "configuration" { + for_each = var.create_cloudwatch_log_group ? [var.cluster_configuration] : [] + + content { + dynamic "execute_command_configuration" { + for_each = try([merge(local.execute_command_configuration, configuration.value.execute_command_configuration)], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "configuration" { + for_each = !var.create_cloudwatch_log_group && length(var.cluster_configuration) > 0 ? [var.cluster_configuration] : [] + + content { + dynamic "execute_command_configuration" { + for_each = try([configuration.value.execute_command_configuration], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "service_connect_defaults" { + for_each = length(var.cluster_service_connect_defaults) > 0 ? [var.cluster_service_connect_defaults] : [] + + content { + namespace = service_connect_defaults.value.namespace + } + } + + dynamic "setting" { + for_each = [var.cluster_settings] + + content { + name = setting.value.name + value = setting.value.value + } + } + + tags = var.tags +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +resource "aws_cloudwatch_log_group" "this" { + count = var.create && var.create_cloudwatch_log_group ? 1 : 0 + + name = "/aws/ecs/${var.cluster_name}" + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = merge(var.tags, var.cloudwatch_log_group_tags) +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +locals { + default_capacity_providers = merge( + { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, + { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } + ) +} + +resource "aws_ecs_cluster_capacity_providers" "this" { + count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 + + cluster_name = aws_ecs_cluster.this[0].name + capacity_providers = distinct(concat( + [for k, v in var.fargate_capacity_providers : try(v.name, k)], + [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] + )) + + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations + dynamic "default_capacity_provider_strategy" { + for_each = local.default_capacity_providers + iterator = strategy + + content { + capacity_provider = try(strategy.value.name, strategy.key) + base = try(strategy.value.default_capacity_provider_strategy.base, null) + weight = try(strategy.value.default_capacity_provider_strategy.weight, null) + } + } + + depends_on = [ + aws_ecs_capacity_provider.this + ] +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +resource "aws_ecs_capacity_provider" "this" { + for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } + + name = try(each.value.name, each.key) + + auto_scaling_group_provider { + auto_scaling_group_arn = each.value.auto_scaling_group_arn + # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work + managed_termination_protection = length(try([each.value.managed_scaling], [])) == 0 ? "DISABLED" : try(each.value.managed_termination_protection, null) + + dynamic "managed_scaling" { + for_each = try([each.value.managed_scaling], []) + + content { + instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) + maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) + minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) + status = try(managed_scaling.value.status, null) + target_capacity = try(managed_scaling.value.target_capacity, null) + } + } + } + + tags = merge(var.tags, try(each.value.tags, {})) +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +locals { + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.cluster_name), "") + + create_task_exec_iam_role = var.create && var.create_task_exec_iam_role + create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy +} + +data "aws_iam_policy_document" "task_exec_assume" { + count = local.create_task_exec_iam_role ? 1 : 0 + + statement { + sid = "ECSTaskExecutionAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "task_exec" { + count = local.create_task_exec_iam_role ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + path = var.task_exec_iam_role_path + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.cluster_name}") + + assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json + permissions_boundary = var.task_exec_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec_additional" { + for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } + + role = aws_iam_role.task_exec[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "Logs" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["*"] + } + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "ECR" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] + + content { + sid = "GetSSMParams" + actions = ["ssm:GetParameters"] + resources = var.task_exec_ssm_param_arns + } + } + + dynamic "statement" { + for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] + + content { + sid = "GetSecrets" + actions = ["secretsmanager:GetSecretValue"] + resources = var.task_exec_secret_arns + } + } + + dynamic "statement" { + for_each = var.task_exec_iam_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") + policy = data.aws_iam_policy_document.task_exec[0].json + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + role = aws_iam_role.task_exec[0].name + policy_arn = aws_iam_policy.task_exec[0].arn +} diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/outputs.tf b/examples/ecs-cluster-with-vpc/modules/cluster/outputs.tf new file mode 100644 index 00000000..7ddfbb5d --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/cluster/outputs.tf @@ -0,0 +1,70 @@ +################################################################################ +# Cluster +################################################################################ + +output "arn" { + description = "ARN that identifies the cluster" + value = try(aws_ecs_cluster.this[0].arn, null) +} + +output "id" { + description = "ID that identifies the cluster" + value = try(aws_ecs_cluster.this[0].id, null) +} + +output "name" { + description = "Name that identifies the cluster" + value = try(aws_ecs_cluster.this[0].name, null) +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, null) +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, null) +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = { for k, v in aws_ecs_cluster_capacity_providers.this : v.id => v } +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +output "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = aws_ecs_capacity_provider.this +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = try(aws_iam_role.task_exec[0].arn, null) +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = try(aws_iam_role.task_exec[0].unique_id, null) +} diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/variables.tf b/examples/ecs-cluster-with-vpc/modules/cluster/variables.tf new file mode 100644 index 00000000..2b9a52b2 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/cluster/variables.tf @@ -0,0 +1,169 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Cluster +################################################################################ + +variable "cluster_name" { + description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = "" +} + +variable "cluster_configuration" { + description = "The execute command configuration for the cluster" + type = any + default = {} +} + +variable "cluster_settings" { + description = "Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" + type = map(string) + default = { + name = "containerInsights" + value = "enabled" + } +} + +variable "cluster_service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = map(string) + default = {} +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events" + type = number + default = 90 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "cloudwatch_log_group_tags" { + description = "A map of additional tags to add to the log group created" + type = map(string) + default = {} +} + +################################################################################ +# Capacity Providers +################################################################################ + +variable "default_capacity_provider_use_fargate" { + description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" + type = bool + default = true +} + +variable "fargate_capacity_providers" { + description = "Map of Fargate capacity provider definitions to use for the cluster" + type = any + default = {} +} + +variable "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity provider definitions to create for the cluster" + type = any + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = false +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/versions.tf b/examples/ecs-cluster-with-vpc/modules/cluster/versions.tf new file mode 100644 index 00000000..290d2218 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/cluster/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.55" + } + } +} diff --git a/examples/ecs-cluster-with-vpc/modules/service/README.md b/examples/ecs-cluster-with-vpc/modules/service/README.md new file mode 100644 index 00000000..3faa6e50 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/service/README.md @@ -0,0 +1,351 @@ +# Amazon ECS Service Module + +Configuration in this directory creates an Amazon ECS Service and associated resources. + +Some notable configurations to be aware of when using this module: +1. `desired_count`/`scale` is always ignored; the module is designed to utilize autoscaling by default (though it can be disabled) +2. The default configuration is intended for `FARGATE` use + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) + +### Logging + +Please refer to [FireLens examples repository](https://github.com/aws-samples/amazon-ecs-firelens-examples) for logging configuration examples for FireLens on Amazon ECS and AWS Fargate. + +## Usage + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + name = "example" + cluster_arn = "arn:aws:ecs:us-west-2:123456789012:cluster/default" + + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + ecs-sample = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = "example" + service = { + client_alias = { + port = 80 + dns_name = "ecs-sample" + } + port_name = "ecs-sample" + discovery_name = "ecs-sample" + } + } + + load_balancer = { + service = { + target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + container_name = "ecs-sample" + container_port = 80 + } + } + + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "Service port" + source_security_group_id = "sg-12345678" + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # Disable creation of service and all resources + create = false + + # Disable creation of the service IAM role; `iam_role_arn` should be provided + create_iam_role = false + + # Disable creation of the task definition; `task_definition_arn` should be provided + create_task_definition = false + + # Disable creation of the task execution IAM role; `task_exec_iam_role_arn` should be provided + create_task_exec_iam_role = false + + # Disable creation of the task execution IAM role policy + create_task_exec_policy = false + + # Disable creation of the tasks IAM role; `tasks_iam_role_arn` should be provided + create_tasks_iam_role = false + + # Disable creation of the service security group + create_security_group = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [container\_definition](#module\_container\_definition) | ../container-definition | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_scheduled_action.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_scheduled_action) | resource | +| [aws_appautoscaling_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_ecs_service.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ecs_task_set.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | +| [aws_ecs_task_set.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | +| [aws_iam_policy.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy_attachment.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_task_definition) | data source | +| [aws_iam_policy_document.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.service_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.tasks_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alarms](#input\_alarms) | Information about the CloudWatch alarms | `any` | `{}` | no | +| [assign\_public\_ip](#input\_assign\_public\_ip) | Assign a public IP address to the ENI (Fargate launch type only) | `bool` | `false` | no | +| [autoscaling\_max\_capacity](#input\_autoscaling\_max\_capacity) | Maximum number of tasks to run in your service | `number` | `10` | no | +| [autoscaling\_min\_capacity](#input\_autoscaling\_min\_capacity) | Minimum number of tasks to run in your service | `number` | `1` | no | +| [autoscaling\_policies](#input\_autoscaling\_policies) | Map of autoscaling policies to create for the service | `any` |
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
| no | +| [autoscaling\_scheduled\_actions](#input\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions to create for the service | `any` | `{}` | no | +| [capacity\_provider\_strategy](#input\_capacity\_provider\_strategy) | Capacity provider strategies to use for the service. Can be one or more | `any` | `{}` | no | +| [cluster\_arn](#input\_cluster\_arn) | ARN of the ECS cluster where the resources will be provisioned | `string` | `""` | no | +| [container\_definition\_defaults](#input\_container\_definition\_defaults) | A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions` | `any` | `{}` | no | +| [container\_definitions](#input\_container\_definitions) | A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document | `any` | `{}` | no | +| [cpu](#input\_cpu) | Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `1024` | no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether the ECS service IAM role should be created | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | +| [create\_task\_definition](#input\_create\_task\_definition) | Determines whether to create a task definition or use existing/provided | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `true` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [create\_tasks\_iam\_role](#input\_create\_tasks\_iam\_role) | Determines whether the ECS tasks IAM role should be created | `bool` | `true` | no | +| [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Configuration block for deployment circuit breaker | `any` | `{}` | no | +| [deployment\_controller](#input\_deployment\_controller) | Configuration block for deployment controller configuration | `any` | `{}` | no | +| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment | `number` | `200` | no | +| [deployment\_minimum\_healthy\_percent](#input\_deployment\_minimum\_healthy\_percent) | Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `66` | no | +| [desired\_count](#input\_desired\_count) | Number of instances of the task definition to place and keep running. Defaults to `0` | `number` | `1` | no | +| [enable\_autoscaling](#input\_enable\_autoscaling) | Determines whether to enable autoscaling for the service | `bool` | `true` | no | +| [enable\_ecs\_managed\_tags](#input\_enable\_ecs\_managed\_tags) | Specifies whether to enable Amazon ECS managed tags for the tasks within the service | `bool` | `true` | no | +| [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | +| [ephemeral\_storage](#input\_ephemeral\_storage) | The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate | `any` | `{}` | no | +| [external\_id](#input\_external\_id) | The external ID associated with the task set | `string` | `null` | no | +| [family](#input\_family) | A unique name for your task definition | `string` | `null` | no | +| [force\_delete](#input\_force\_delete) | Whether to allow deleting the task set without waiting for scaling down to 0 | `bool` | `null` | no | +| [force\_new\_deployment](#input\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates | `bool` | `true` | no | +| [health\_check\_grace\_period\_seconds](#input\_health\_check\_grace\_period\_seconds) | Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers | `number` | `null` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_statements](#input\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [ignore\_task\_definition\_changes](#input\_ignore\_task\_definition\_changes) | Whether changes to service `task_definition` changes should be ignored | `bool` | `false` | no | +| [inference\_accelerator](#input\_inference\_accelerator) | Configuration block(s) with Inference Accelerators settings | `any` | `{}` | no | +| [ipc\_mode](#input\_ipc\_mode) | IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none` | `string` | `null` | no | +| [launch\_type](#input\_launch\_type) | Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE` | `string` | `"FARGATE"` | no | +| [load\_balancer](#input\_load\_balancer) | Configuration block for load balancers | `any` | `{}` | no | +| [memory](#input\_memory) | Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `2048` | no | +| [name](#input\_name) | Name of the service (up to 255 letters, numbers, hyphens, and underscores) | `string` | `null` | no | +| [network\_mode](#input\_network\_mode) | Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host` | `string` | `"awsvpc"` | no | +| [ordered\_placement\_strategy](#input\_ordered\_placement\_strategy) | Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence | `any` | `{}` | no | +| [pid\_mode](#input\_pid\_mode) | Process namespace to use for the containers in the task. The valid values are `host` and `task` | `string` | `null` | no | +| [placement\_constraints](#input\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition | `any` | `{}` | no | +| [platform\_version](#input\_platform\_version) | Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST` | `string` | `null` | no | +| [propagate\_tags](#input\_propagate\_tags) | Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION` | `string` | `null` | no | +| [proxy\_configuration](#input\_proxy\_configuration) | Configuration block for the App Mesh proxy | `any` | `{}` | no | +| [requires\_compatibilities](#input\_requires\_compatibilities) | Set of launch types required by the task. The valid values are `EC2` and `FARGATE` | `list(string)` |
[
"FARGATE"
]
| no | +| [runtime\_platform](#input\_runtime\_platform) | Configuration block for `runtime_platform` that containers in your task may use | `any` |
{
"cpu_architecture": "X86_64",
"operating_system_family": "LINUX"
}
| no | +| [scale](#input\_scale) | A floating-point percentage of the desired number of tasks to place and keep running in the task set | `any` | `{}` | no | +| [scheduling\_strategy](#input\_scheduling\_strategy) | Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA` | `string` | `null` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | List of security groups to associate with the task or service | `list(string)` | `[]` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | +| [service\_connect\_configuration](#input\_service\_connect\_configuration) | The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace | `any` | `{}` | no | +| [service\_registries](#input\_service\_registries) | Service discovery registries for the service | `any` | `{}` | no | +| [skip\_destroy](#input\_skip\_destroy) | If true, the task is not deleted when the service is deleted | `bool` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of subnets to associate with the task or service | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_definition\_arn](#input\_task\_definition\_arn) | Existing task definition ARN. Required when `create_task_definition` is `false` | `string` | `null` | no | +| [task\_definition\_placement\_constraints](#input\_task\_definition\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service | `any` | `{}` | no | +| [task\_exec\_iam\_role\_arn](#input\_task\_exec\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [task\_tags](#input\_task\_tags) | A map of additional tags to add to the task definition/set created | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_arn](#input\_tasks\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [tasks\_iam\_role\_description](#input\_tasks\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [tasks\_iam\_role\_name](#input\_tasks\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [tasks\_iam\_role\_path](#input\_tasks\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [tasks\_iam\_role\_permissions\_boundary](#input\_tasks\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [tasks\_iam\_role\_policies](#input\_tasks\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_statements](#input\_tasks\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [tasks\_iam\_role\_tags](#input\_tasks\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_use\_name\_prefix](#input\_tasks\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the service | `map(string)` | `{}` | no | +| [triggers](#input\_triggers) | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()` | `any` | `{}` | no | +| [volume](#input\_volume) | Configuration block for volumes that containers in your task may use | `any` | `{}` | no | +| [wait\_for\_steady\_state](#input\_wait\_for\_steady\_state) | If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false` | `bool` | `null` | no | +| [wait\_until\_stable](#input\_wait\_until\_stable) | Whether terraform should wait until the task set has reached `STEADY_STATE` | `bool` | `null` | no | +| [wait\_until\_stable\_timeout](#input\_wait\_until\_stable\_timeout) | Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or Āµs), `ms`, `s`, `m`, and `h`. Default `10m` | `number` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_policies](#output\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [autoscaling\_scheduled\_actions](#output\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [container\_definitions](#output\_container\_definitions) | Container definitions | +| [iam\_role\_arn](#output\_iam\_role\_arn) | Service IAM role ARN | +| [iam\_role\_name](#output\_iam\_role\_name) | Service IAM role name | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [id](#output\_id) | ARN that identifies the service | +| [name](#output\_name) | Name of the service | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [task\_definition\_arn](#output\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [task\_definition\_family](#output\_task\_definition\_family) | The unique name of the task definition | +| [task\_definition\_revision](#output\_task\_definition\_revision) | Revision of the task in a particular family | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [task\_set\_arn](#output\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [task\_set\_id](#output\_task\_set\_id) | The ID of the task set | +| [task\_set\_stability\_status](#output\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [task\_set\_status](#output\_task\_set\_status) | The status of the task set | +| [tasks\_iam\_role\_arn](#output\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [tasks\_iam\_role\_name](#output\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [tasks\_iam\_role\_unique\_id](#output\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/ecs-cluster-with-vpc/modules/service/main.tf b/examples/ecs-cluster-with-vpc/modules/service/main.tf new file mode 100644 index 00000000..0b9141ac --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/service/main.tf @@ -0,0 +1,1329 @@ +data "aws_region" "current" {} +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + account_id = data.aws_caller_identity.current.account_id + dns_suffix = data.aws_partition.current.dns_suffix + partition = data.aws_partition.current.partition + region = data.aws_region.current.name +} + +################################################################################ +# Service +################################################################################ + +locals { + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-external.html + is_external_deployment = try(var.deployment_controller.type, null) == "EXTERNAL" + is_daemon = var.scheduling_strategy == "DAEMON" + is_fargate = var.launch_type == "FARGATE" + + # Flattened `network_configuration` + network_configuration = { + assign_public_ip = var.assign_public_ip + security_groups = flatten(concat([try(aws_security_group.this[0].id, [])], var.security_group_ids)) + subnets = var.subnet_ids + } +} + +resource "aws_ecs_service" "this" { + count = var.create && !var.ignore_task_definition_changes ? 1 : 0 + + dynamic "alarms" { + for_each = length(var.alarms) > 0 ? [var.alarms] : [] + + content { + alarm_names = alarms.value.alarm_names + enable = try(alarms.value.enable, true) + rollback = try(alarms.value.rollback, true) + } + } + + dynamic "capacity_provider_strategy" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + cluster = var.cluster_arn + + dynamic "deployment_circuit_breaker" { + for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + + content { + enable = deployment_circuit_breaker.value.enable + rollback = deployment_circuit_breaker.value.rollback + } + } + + dynamic "deployment_controller" { + for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + + content { + type = try(deployment_controller.value.type, null) + } + } + + deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent + deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent + desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment + health_check_grace_period_seconds = var.health_check_grace_period_seconds + iam_role = local.iam_role_arn + launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "load_balancer" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + + content { + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + elb_name = try(load_balancer.value.elb_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + } + } + + name = var.name + + dynamic "network_configuration" { + # Set by task set if deployment controller is external + for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + + content { + field = try(ordered_placement_strategy.value.field, null) + type = ordered_placement_strategy.value.type + } + } + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + # Set by task set if deployment controller is external + platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy + + dynamic "service_connect_configuration" { + for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + + content { + enabled = try(service_connect_configuration.value.enabled, true) + + dynamic "log_configuration" { + for_each = try([service_connect_configuration.value.log_configuration], []) + + content { + log_driver = try(log_configuration.value.log_driver, null) + options = try(log_configuration.value.options, null) + + dynamic "secret_option" { + for_each = try(log_configuration.value.secret_option, []) + + content { + name = secret_option.value.name + value_from = secret_option.value.value_from + } + } + } + } + + namespace = lookup(service_connect_configuration.value, "namespace", null) + + dynamic "service" { + for_each = try([service_connect_configuration.value.service], []) + + content { + + dynamic "client_alias" { + for_each = try([service.value.client_alias], []) + + content { + dns_name = try(client_alias.value.dns_name, null) + port = client_alias.value.port + } + } + + discovery_name = try(service.value.discovery_name, null) + ingress_port_override = try(service.value.ingress_port_override, null) + port_name = service.value.port_name + } + } + } + } + + dynamic "service_registries" { + # Set by task set if deployment controller is external + for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + task_definition = local.task_definition + triggers = var.triggers + wait_for_steady_state = var.wait_for_steady_state + + propagate_tags = var.propagate_tags + tags = var.tags + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + depends_on = [aws_iam_role_policy_attachment.service] + + lifecycle { + ignore_changes = [ + desired_count, # Always ignored + ] + } +} + +################################################################################ +# Service - Ignore `task_definition` +################################################################################ + +resource "aws_ecs_service" "ignore_task_definition" { + count = var.create && var.ignore_task_definition_changes ? 1 : 0 + + dynamic "alarms" { + for_each = length(var.alarms) > 0 ? [var.alarms] : [] + + content { + alarm_names = alarms.value.alarm_names + enable = try(alarms.value.enable, true) + rollback = try(alarms.value.rollback, true) + } + } + + dynamic "capacity_provider_strategy" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + cluster = var.cluster_arn + + dynamic "deployment_circuit_breaker" { + for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + + content { + enable = deployment_circuit_breaker.value.enable + rollback = deployment_circuit_breaker.value.rollback + } + } + + dynamic "deployment_controller" { + for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + + content { + type = try(deployment_controller.value.type, null) + } + } + + deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent + deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent + desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment + health_check_grace_period_seconds = var.health_check_grace_period_seconds + iam_role = local.iam_role_arn + launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "load_balancer" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + + content { + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + elb_name = try(load_balancer.value.elb_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + } + } + + name = var.name + + dynamic "network_configuration" { + # Set by task set if deployment controller is external + for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + + content { + field = try(ordered_placement_strategy.value.field, null) + type = ordered_placement_strategy.value.type + } + } + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + # Set by task set if deployment controller is external + platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy + + dynamic "service_connect_configuration" { + for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + + content { + enabled = try(service_connect_configuration.value.enabled, true) + + dynamic "log_configuration" { + for_each = try([service_connect_configuration.value.log_configuration], []) + + content { + log_driver = try(log_configuration.value.log_driver, null) + options = try(log_configuration.value.options, null) + + dynamic "secret_option" { + for_each = try(log_configuration.value.secret_option, []) + + content { + name = secret_option.value.name + value_from = secret_option.value.value_from + } + } + } + } + + namespace = lookup(service_connect_configuration.value, "namespace", null) + + dynamic "service" { + for_each = try([service_connect_configuration.value.service], []) + + content { + + dynamic "client_alias" { + for_each = try([service.value.client_alias], []) + + content { + dns_name = try(client_alias.value.dns_name, null) + port = client_alias.value.port + } + } + + discovery_name = try(service.value.discovery_name, null) + ingress_port_override = try(service.value.ingress_port_override, null) + port_name = service.value.port_name + } + } + } + } + + dynamic "service_registries" { + # Set by task set if deployment controller is external + for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + task_definition = local.task_definition + triggers = var.triggers + wait_for_steady_state = var.wait_for_steady_state + + propagate_tags = var.propagate_tags + tags = var.tags + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + depends_on = [aws_iam_role_policy_attachment.service] + + lifecycle { + ignore_changes = [ + desired_count, # Always ignored + task_definition, + load_balancer, + ] + } +} + +################################################################################ +# Service - IAM Role +################################################################################ + +locals { + # Role is not required if task definition uses `awsvpc` network mode or if a load balancer is not used + needs_iam_role = var.network_mode != "awsvpc" && length(var.load_balancer) > 0 + create_iam_role = var.create && var.create_iam_role && local.needs_iam_role + iam_role_arn = local.needs_iam_role ? try(aws_iam_role.service[0].arn, var.iam_role_arn) : null + + iam_role_name = try(coalesce(var.iam_role_name, var.name), "") +} + +data "aws_iam_policy_document" "service_assume" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "ECSServiceAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs.${local.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "service" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.service_assume[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +data "aws_iam_policy_document" "service" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "ECSService" + resources = ["*"] + + actions = [ + "ec2:Describe*", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:Describe*", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:RegisterTargets" + ] + } + + dynamic "statement" { + for_each = var.iam_role_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "service" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + description = coalesce(var.iam_role_description, "ECS service policy that allows Amazon ECS to make calls to your load balancer on your behalf") + policy = data.aws_iam_policy_document.service[0].json + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "service" { + count = local.create_iam_role ? 1 : 0 + + role = aws_iam_role.service[0].name + policy_arn = aws_iam_policy.service[0].arn +} + +################################################################################ +# Container Definition +################################################################################ + +module "container_definition" { + source = "../container-definition" + + for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition } + + operating_system_family = try(var.runtime_platform.operating_system_family, "LINUX") + + # Container Definition + command = try(each.value.command, var.container_definition_defaults.command, []) + cpu = try(each.value.cpu, var.container_definition_defaults.cpu, null) + dependencies = try(each.value.dependencies, var.container_definition_defaults.dependencies, []) # depends_on is a reserved word + disable_networking = try(each.value.disable_networking, var.container_definition_defaults.disable_networking, null) + dns_search_domains = try(each.value.dns_search_domains, var.container_definition_defaults.dns_search_domains, []) + dns_servers = try(each.value.dns_servers, var.container_definition_defaults.dns_servers, []) + docker_labels = try(each.value.docker_labels, var.container_definition_defaults.docker_labels, {}) + docker_security_options = try(each.value.docker_security_options, var.container_definition_defaults.docker_security_options, []) + entrypoint = try(each.value.entrypoint, var.container_definition_defaults.entrypoint, []) + environment = try(each.value.environment, var.container_definition_defaults.environment, []) + environment_files = try(each.value.environment_files, var.container_definition_defaults.environment_files, []) + essential = try(each.value.essential, var.container_definition_defaults.essential, null) + extra_hosts = try(each.value.extra_hosts, var.container_definition_defaults.extra_hosts, []) + firelens_configuration = try(each.value.firelens_configuration, var.container_definition_defaults.firelens_configuration, {}) + health_check = try(each.value.health_check, var.container_definition_defaults.health_check, {}) + hostname = try(each.value.hostname, var.container_definition_defaults.hostname, null) + image = try(each.value.image, var.container_definition_defaults.image, null) + interactive = try(each.value.interactive, var.container_definition_defaults.interactive, false) + links = try(each.value.links, var.container_definition_defaults.links, []) + linux_parameters = try(each.value.linux_parameters, var.container_definition_defaults.linux_parameters, {}) + log_configuration = try(each.value.log_configuration, var.container_definition_defaults.log_configuration, {}) + memory = try(each.value.memory, var.container_definition_defaults.memory, null) + memory_reservation = try(each.value.memory_reservation, var.container_definition_defaults.memory_reservation, null) + mount_points = try(each.value.mount_points, var.container_definition_defaults.mount_points, []) + name = try(each.value.name, each.key) + port_mappings = try(each.value.port_mappings, var.container_definition_defaults.port_mappings, []) + privileged = try(each.value.privileged, var.container_definition_defaults.privileged, false) + pseudo_terminal = try(each.value.pseudo_terminal, var.container_definition_defaults.pseudo_terminal, false) + readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.container_definition_defaults.readonly_root_filesystem, true) + repository_credentials = try(each.value.repository_credentials, var.container_definition_defaults.repository_credentials, {}) + resource_requirements = try(each.value.resource_requirements, var.container_definition_defaults.resource_requirements, []) + secrets = try(each.value.secrets, var.container_definition_defaults.secrets, []) + start_timeout = try(each.value.start_timeout, var.container_definition_defaults.start_timeout, 30) + stop_timeout = try(each.value.stop_timeout, var.container_definition_defaults.stop_timeout, 120) + system_controls = try(each.value.system_controls, var.container_definition_defaults.system_controls, []) + ulimits = try(each.value.ulimits, var.container_definition_defaults.ulimits, []) + user = try(each.value.user, var.container_definition_defaults.user, 0) + volumes_from = try(each.value.volumes_from, var.container_definition_defaults.volumes_from, []) + working_directory = try(each.value.working_directory, var.container_definition_defaults.working_directory, null) + + # CloudWatch Log Group + service = var.name + enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.container_definition_defaults.enable_cloudwatch_logging, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.container_definition_defaults.create_cloudwatch_log_group, true) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.container_definition_defaults.cloudwatch_log_group_retention_in_days, 14) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.container_definition_defaults.cloudwatch_log_group_kms_key_id, null) + + tags = var.tags +} + +################################################################################ +# Task Definition +################################################################################ + +locals { + create_task_definition = var.create && var.create_task_definition + + # This allows us to query both the existing as well as Terraform's state and get + # and get the max version of either source, useful for when external resources + # update the container definition + max_task_def_revision = local.create_task_definition ? max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision) : 0 + task_definition = local.create_task_definition ? "${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}" : var.task_definition_arn +} + +# This allows us to query both the existing as well as Terraform's state and get +# and get the max version of either source, useful for when external resources +# update the container definition +data "aws_ecs_task_definition" "this" { + count = local.create_task_definition ? 1 : 0 + + task_definition = aws_ecs_task_definition.this[0].family + + depends_on = [ + # Needs to exist first on first deployment + aws_ecs_task_definition.this + ] +} + +resource "aws_ecs_task_definition" "this" { + count = local.create_task_definition ? 1 : 0 + + # Convert map of maps to array of maps before JSON encoding + container_definitions = jsonencode([for k, v in module.container_definition : v.container_definition]) + cpu = var.cpu + + dynamic "ephemeral_storage" { + for_each = length(var.ephemeral_storage) > 0 ? [var.ephemeral_storage] : [] + + content { + size_in_gib = ephemeral_storage.value.size_in_gib + } + } + + execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn) + family = coalesce(var.family, var.name) + + dynamic "inference_accelerator" { + for_each = var.inference_accelerator + + content { + device_name = inference_accelerator.value.device_name + device_type = inference_accelerator.value.device_type + } + } + + ipc_mode = var.ipc_mode + memory = var.memory + network_mode = var.network_mode + pid_mode = var.pid_mode + + dynamic "placement_constraints" { + for_each = var.task_definition_placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + dynamic "proxy_configuration" { + for_each = length(var.proxy_configuration) > 0 ? [var.proxy_configuration] : [] + + content { + container_name = proxy_configuration.value.container_name + properties = try(proxy_configuration.value.properties, null) + type = try(proxy_configuration.value.type, null) + } + } + + requires_compatibilities = var.requires_compatibilities + + dynamic "runtime_platform" { + for_each = length(var.runtime_platform) > 0 ? [var.runtime_platform] : [] + + content { + cpu_architecture = try(runtime_platform.value.cpu_architecture, null) + operating_system_family = try(runtime_platform.value.operating_system_family, null) + } + } + + skip_destroy = var.skip_destroy + task_role_arn = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn) + + dynamic "volume" { + for_each = var.volume + + content { + dynamic "docker_volume_configuration" { + for_each = try([volume.value.docker_volume_configuration], []) + + content { + autoprovision = try(docker_volume_configuration.value.autoprovision, null) + driver = try(docker_volume_configuration.value.driver, null) + driver_opts = try(docker_volume_configuration.value.driver_opts, null) + labels = try(docker_volume_configuration.value.labels, null) + scope = try(docker_volume_configuration.value.scope, null) + } + } + + dynamic "efs_volume_configuration" { + for_each = try([volume.value.efs_volume_configuration], []) + + content { + dynamic "authorization_config" { + for_each = try([efs_volume_configuration.value.authorization_config], []) + + content { + access_point_id = try(authorization_config.value.access_point_id, null) + iam = try(authorization_config.value.iam, null) + } + } + + file_system_id = efs_volume_configuration.value.file_system_id + root_directory = try(efs_volume_configuration.value.root_directory, null) + transit_encryption = try(efs_volume_configuration.value.transit_encryption, null) + transit_encryption_port = try(efs_volume_configuration.value.transit_encryption_port, null) + } + } + + dynamic "fsx_windows_file_server_volume_configuration" { + for_each = try([volume.value.fsx_windows_file_server_volume_configuration], []) + + content { + dynamic "authorization_config" { + for_each = try([fsx_windows_file_server_volume_configuration.value.authorization_config], []) + + content { + credentials_parameter = authorization_config.value.credentials_parameter + domain = authorization_config.value.domain + } + } + + file_system_id = fsx_windows_file_server_volume_configuration.value.file_system_id + root_directory = fsx_windows_file_server_volume_configuration.value.root_directory + } + } + + host_path = try(volume.value.host_path, null) + name = try(volume.value.name, volume.key) + } + } + + tags = merge(var.tags, var.task_tags) + + lifecycle { + create_before_destroy = true + } +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +locals { + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") + + create_task_exec_iam_role = local.create_task_definition && var.create_task_exec_iam_role + create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy +} + +data "aws_iam_policy_document" "task_exec_assume" { + count = local.create_task_exec_iam_role ? 1 : 0 + + statement { + sid = "ECSTaskExecutionAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.${local.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "task_exec" { + count = local.create_task_exec_iam_role ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + path = var.task_exec_iam_role_path + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${local.task_exec_iam_role_name}") + + assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json + permissions_boundary = var.task_exec_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec_additional" { + for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } + + role = aws_iam_role.task_exec[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "Logs" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["*"] + } + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "ECR" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] + + content { + sid = "GetSSMParams" + actions = ["ssm:GetParameters"] + resources = var.task_exec_ssm_param_arns + } + } + + dynamic "statement" { + for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] + + content { + sid = "GetSecrets" + actions = ["secretsmanager:GetSecretValue"] + resources = var.task_exec_secret_arns + } + } + + dynamic "statement" { + for_each = var.task_exec_iam_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") + policy = data.aws_iam_policy_document.task_exec[0].json + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + role = aws_iam_role.task_exec[0].name + policy_arn = aws_iam_policy.task_exec[0].arn +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +locals { + tasks_iam_role_name = try(coalesce(var.tasks_iam_role_name, var.name), "") + create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role +} + +data "aws_iam_policy_document" "tasks_assume" { + count = local.create_tasks_iam_role ? 1 : 0 + + statement { + sid = "ECSTasksAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.${local.dns_suffix}"] + } + + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:*"] + } + + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [local.account_id] + } + } +} + +resource "aws_iam_role" "tasks" { + count = local.create_tasks_iam_role ? 1 : 0 + + name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name + name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + path = var.tasks_iam_role_path + description = var.tasks_iam_role_description + + assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json + permissions_boundary = var.tasks_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.tasks_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "tasks" { + for_each = { for k, v in var.tasks_iam_role_policies : k => v if local.create_tasks_iam_role } + + role = aws_iam_role.tasks[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "tasks" { + count = local.create_tasks_iam_role && length(var.tasks_iam_role_statements) > 0 ? 1 : 0 + + dynamic "statement" { + for_each = var.tasks_iam_role_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_role_policy" "tasks" { + count = local.create_tasks_iam_role && length(var.tasks_iam_role_statements) > 0 ? 1 : 0 + + name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name + name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + policy = data.aws_iam_policy_document.tasks[0].json + role = aws_iam_role.tasks[0].id +} + +################################################################################ +# Task Set +################################################################################ + +resource "aws_ecs_task_set" "this" { + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html + count = local.create_task_definition && local.is_external_deployment && !var.ignore_task_definition_changes ? 1 : 0 + + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) + cluster = var.cluster_arn + external_id = var.external_id + task_definition = local.task_definition + + dynamic "network_configuration" { + for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "load_balancer" { + for_each = var.load_balancer + + content { + load_balancer_name = try(load_balancer.value.load_balancer_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + container_name = load_balancer.value.container_name + container_port = try(load_balancer.value.container_port, null) + } + } + + dynamic "service_registries" { + for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "capacity_provider_strategy" { + for_each = var.capacity_provider_strategy + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + platform_version = local.is_fargate ? var.platform_version : null + + dynamic "scale" { + for_each = length(var.scale) > 0 ? [var.scale] : [] + + content { + unit = try(scale.value.unit, null) + value = try(scale.value.value, null) + } + } + + force_delete = var.force_delete + wait_until_stable = var.wait_until_stable + wait_until_stable_timeout = var.wait_until_stable_timeout + + tags = merge(var.tags, var.task_tags) + + lifecycle { + ignore_changes = [ + scale, # Always ignored + ] + } +} + +################################################################################ +# Task Set - Ignore `task_definition` +################################################################################ + +resource "aws_ecs_task_set" "ignore_task_definition" { + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html + count = local.create_task_definition && local.is_external_deployment && var.ignore_task_definition_changes ? 1 : 0 + + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) + cluster = var.cluster_arn + external_id = var.external_id + task_definition = local.task_definition + + dynamic "network_configuration" { + for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "load_balancer" { + for_each = var.load_balancer + + content { + load_balancer_name = try(load_balancer.value.load_balancer_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + container_name = load_balancer.value.container_name + container_port = try(load_balancer.value.container_port, null) + } + } + + dynamic "service_registries" { + for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "capacity_provider_strategy" { + for_each = var.capacity_provider_strategy + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + platform_version = local.is_fargate ? var.platform_version : null + + dynamic "scale" { + for_each = length(var.scale) > 0 ? [var.scale] : [] + + content { + unit = try(scale.value.unit, null) + value = try(scale.value.value, null) + } + } + + force_delete = var.force_delete + wait_until_stable = var.wait_until_stable + wait_until_stable_timeout = var.wait_until_stable_timeout + + tags = merge(var.tags, var.task_tags) + + lifecycle { + ignore_changes = [ + scale, # Always ignored + task_definition, + ] + } +} + +################################################################################ +# Autoscaling +################################################################################ + +locals { + enable_autoscaling = var.create && var.enable_autoscaling && !local.is_daemon + + cluster_name = element(split("/", var.cluster_arn), 1) +} + +resource "aws_appautoscaling_target" "this" { + count = local.enable_autoscaling ? 1 : 0 + + # Desired needs to be between or equal to min/max + min_capacity = min(var.autoscaling_min_capacity, var.desired_count) + max_capacity = max(var.autoscaling_max_capacity, var.desired_count) + + resource_id = "service/${local.cluster_name}/${try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name)}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +resource "aws_appautoscaling_policy" "this" { + for_each = { for k, v in var.autoscaling_policies : k => v if local.enable_autoscaling } + + name = try(each.value.name, each.key) + policy_type = try(each.value.policy_type, "TargetTrackingScaling") + resource_id = aws_appautoscaling_target.this[0].resource_id + scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension + service_namespace = aws_appautoscaling_target.this[0].service_namespace + + dynamic "step_scaling_policy_configuration" { + for_each = try([each.value.step_scaling_policy_configuration], []) + + content { + adjustment_type = try(step_scaling_policy_configuration.value.adjustment_type, null) + cooldown = try(step_scaling_policy_configuration.value.cooldown, null) + metric_aggregation_type = try(step_scaling_policy_configuration.value.metric_aggregation_type, null) + min_adjustment_magnitude = try(step_scaling_policy_configuration.value.min_adjustment_magnitude, null) + + dynamic "step_adjustment" { + for_each = try(step_scaling_policy_configuration.value.step_adjustment, []) + + content { + metric_interval_lower_bound = try(step_adjustment.value.metric_interval_lower_bound, null) + metric_interval_upper_bound = try(step_adjustment.value.metric_interval_upper_bound, null) + scaling_adjustment = try(step_adjustment.value.scaling_adjustment, null) + } + } + } + } + + dynamic "target_tracking_scaling_policy_configuration" { + for_each = try(each.value.policy_type, null) == "TargetTrackingScaling" ? try([each.value.target_tracking_scaling_policy_configuration], []) : [] + + content { + dynamic "customized_metric_specification" { + for_each = try([target_tracking_scaling_policy_configuration.value.customized_metric_specification], []) + + content { + dynamic "dimensions" { + for_each = try(customized_metric_specification.value.dimensions, []) + + content { + name = dimensions.value.name + value = dimensions.value.value + } + } + + metric_name = customized_metric_specification.value.metric_name + namespace = customized_metric_specification.value.namespace + statistic = customized_metric_specification.value.statistic + unit = try(customized_metric_specification.value.unit, null) + } + } + + disable_scale_in = try(target_tracking_scaling_policy_configuration.value.disable_scale_in, null) + + dynamic "predefined_metric_specification" { + for_each = try([target_tracking_scaling_policy_configuration.value.predefined_metric_specification], []) + + content { + predefined_metric_type = predefined_metric_specification.value.predefined_metric_type + resource_label = try(predefined_metric_specification.value.resource_label, null) + } + } + + scale_in_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_in_cooldown, 300) + scale_out_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_out_cooldown, 60) + target_value = try(target_tracking_scaling_policy_configuration.value.target_value, 75) + } + } +} + +resource "aws_appautoscaling_scheduled_action" "this" { + for_each = { for k, v in var.autoscaling_scheduled_actions : k => v if local.enable_autoscaling } + + name = try(each.value.name, each.key) + service_namespace = aws_appautoscaling_target.this[0].service_namespace + resource_id = aws_appautoscaling_target.this[0].resource_id + scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension + + scalable_target_action { + min_capacity = each.value.min_capacity + max_capacity = each.value.max_capacity + } + + schedule = each.value.schedule + start_time = try(each.value.start_time, null) + end_time = try(each.value.end_time, null) + timezone = try(each.value.timezone, null) +} + +################################################################################ +# Security Group +################################################################################ + +locals { + create_security_group = var.create && var.create_security_group && var.network_mode == "awsvpc" + security_group_name = try(coalesce(var.security_group_name, var.name), "") +} + +data "aws_subnet" "this" { + count = local.create_security_group ? 1 : 0 + + id = element(var.subnet_ids, 0) +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = data.aws_subnet.this[0].vpc_id + + tags = merge(var.tags, var.security_group_tags) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "this" { + for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } + + # Required + security_group_id = aws_security_group.this[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = lookup(each.value, "description", null) + cidr_blocks = lookup(each.value, "cidr_blocks", null) + ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null) + prefix_list_ids = lookup(each.value, "prefix_list_ids", null) + self = lookup(each.value, "self", null) + source_security_group_id = lookup(each.value, "source_security_group_id", null) +} diff --git a/examples/ecs-cluster-with-vpc/modules/service/outputs.tf b/examples/ecs-cluster-with-vpc/modules/service/outputs.tf new file mode 100644 index 00000000..059a64d7 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/service/outputs.tf @@ -0,0 +1,152 @@ +################################################################################ +# Service +################################################################################ + +output "id" { + description = "ARN that identifies the service" + value = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id, null) +} + +output "name" { + description = "Name of the service" + value = try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name, null) +} + +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "Service IAM role name" + value = try(aws_iam_role.service[0].name, null) +} + +output "iam_role_arn" { + description = "Service IAM role ARN" + value = try(aws_iam_role.service[0].arn, null) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = try(aws_iam_role.service[0].unique_id, null) +} + +################################################################################ +# Container Definition +################################################################################ + +output "container_definitions" { + description = "Container definitions" + value = module.container_definition +} + +################################################################################ +# Task Definition +################################################################################ + +output "task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = try(aws_ecs_task_definition.this[0].arn, null) +} + +output "task_definition_revision" { + description = "Revision of the task in a particular family" + value = try(aws_ecs_task_definition.this[0].revision, null) +} + +output "task_definition_family" { + description = "The unique name of the task definition" + value = try(aws_ecs_task_definition.this[0].family, null) +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = try(aws_iam_role.task_exec[0].arn, null) +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = try(aws_iam_role.task_exec[0].unique_id, null) +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +output "tasks_iam_role_name" { + description = "Tasks IAM role name" + value = try(aws_iam_role.tasks[0].name, null) +} + +output "tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = try(aws_iam_role.tasks[0].arn, null) +} + +output "tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = try(aws_iam_role.tasks[0].unique_id, null) +} + +################################################################################ +# Task Set +################################################################################ + +output "task_set_id" { + description = "The ID of the task set" + value = try(aws_ecs_task_set.this[0].task_set_id, aws_ecs_task_set.ignore_task_definition[0].task_set_id, null) +} + +output "task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = try(aws_ecs_task_set.this[0].arn, aws_ecs_task_set.ignore_task_definition[0].arn, null) +} + +output "task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = try(aws_ecs_task_set.this[0].stability_status, aws_ecs_task_set.ignore_task_definition[0].stability_status, null) +} + +output "task_set_status" { + description = "The status of the task set" + value = try(aws_ecs_task_set.this[0].status, aws_ecs_task_set.ignore_task_definition[0].status, null) +} + +################################################################################ +# Autoscaling +################################################################################ + +output "autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = aws_appautoscaling_policy.this +} + +output "autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = aws_appautoscaling_scheduled_action.this +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = try(aws_security_group.this[0].arn, null) +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, null) +} diff --git a/examples/ecs-cluster-with-vpc/modules/service/variables.tf b/examples/ecs-cluster-with-vpc/modules/service/variables.tf new file mode 100644 index 00000000..bd8c7303 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/service/variables.tf @@ -0,0 +1,643 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Service +################################################################################ + +variable "ignore_task_definition_changes" { + description = "Whether changes to service `task_definition` changes should be ignored" + type = bool + default = false +} + +variable "alarms" { + description = "Information about the CloudWatch alarms" + type = any + default = {} +} + +variable "capacity_provider_strategy" { + description = "Capacity provider strategies to use for the service. Can be one or more" + type = any + default = {} +} + +variable "cluster_arn" { + description = "ARN of the ECS cluster where the resources will be provisioned" + type = string + default = "" +} + +variable "deployment_circuit_breaker" { + description = "Configuration block for deployment circuit breaker" + type = any + default = {} +} + +variable "deployment_controller" { + description = "Configuration block for deployment controller configuration" + type = any + default = {} +} + +variable "deployment_maximum_percent" { + description = "Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment" + type = number + default = 200 +} + +variable "deployment_minimum_healthy_percent" { + description = "Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment" + type = number + default = 66 +} + +variable "desired_count" { + description = "Number of instances of the task definition to place and keep running. Defaults to `0`" + type = number + default = 1 +} + +variable "enable_ecs_managed_tags" { + description = "Specifies whether to enable Amazon ECS managed tags for the tasks within the service" + type = bool + default = true +} + +variable "enable_execute_command" { + description = "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + type = bool + default = false +} + +variable "force_new_deployment" { + description = "Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates" + type = bool + default = true +} + +variable "health_check_grace_period_seconds" { + description = "Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers" + type = number + default = null +} + +variable "launch_type" { + description = "Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE`" + type = string + default = "FARGATE" +} + +variable "load_balancer" { + description = "Configuration block for load balancers" + type = any + default = {} +} + +variable "name" { + description = "Name of the service (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = null +} + +variable "assign_public_ip" { + description = "Assign a public IP address to the ENI (Fargate launch type only)" + type = bool + default = false +} + +variable "security_group_ids" { + description = "List of security groups to associate with the task or service" + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "List of subnets to associate with the task or service" + type = list(string) + default = [] +} + +variable "ordered_placement_strategy" { + description = "Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence" + type = any + default = {} +} + +variable "placement_constraints" { + description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition" + type = any + default = {} +} + +variable "platform_version" { + description = "Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST`" + type = string + default = null +} + +variable "propagate_tags" { + description = "Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION`" + type = string + default = null +} + +variable "scheduling_strategy" { + description = "Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA`" + type = string + default = null +} + +variable "service_connect_configuration" { + description = "The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace" + type = any + default = {} +} + +variable "service_registries" { + description = "Service discovery registries for the service" + type = any + default = {} +} + +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the service" + type = map(string) + default = {} +} + +variable "triggers" { + description = "Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()`" + type = any + default = {} +} + +variable "wait_for_steady_state" { + description = "If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false`" + type = bool + default = null +} + +################################################################################ +# Service - IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether the ECS service IAM role should be created" + type = bool + default = true +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "iam_role_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Task Definition +################################################################################ + +variable "create_task_definition" { + description = "Determines whether to create a task definition or use existing/provided" + type = bool + default = true +} + +variable "task_definition_arn" { + description = "Existing task definition ARN. Required when `create_task_definition` is `false`" + type = string + default = null +} + +variable "container_definitions" { + description = "A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document" + type = any + default = {} +} + +variable "container_definition_defaults" { + description = "A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions`" + type = any + default = {} +} + +variable "cpu" { + description = "Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" + type = number + default = 1024 +} + +variable "ephemeral_storage" { + description = "The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate" + type = any + default = {} +} + +variable "family" { + description = "A unique name for your task definition" + type = string + default = null +} + +variable "inference_accelerator" { + description = "Configuration block(s) with Inference Accelerators settings" + type = any + default = {} +} + +variable "ipc_mode" { + description = "IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`" + type = string + default = null +} + +variable "memory" { + description = "Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" + type = number + default = 2048 +} + +variable "network_mode" { + description = "Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host`" + type = string + default = "awsvpc" +} + +variable "pid_mode" { + description = "Process namespace to use for the containers in the task. The valid values are `host` and `task`" + type = string + default = null +} + +variable "task_definition_placement_constraints" { + description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service" + type = any + default = {} +} + +variable "proxy_configuration" { + description = "Configuration block for the App Mesh proxy" + type = any + default = {} +} + +variable "requires_compatibilities" { + description = "Set of launch types required by the task. The valid values are `EC2` and `FARGATE`" + type = list(string) + default = ["FARGATE"] +} + +variable "runtime_platform" { + description = "Configuration block for `runtime_platform` that containers in your task may use" + type = any + default = { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + } +} + +variable "skip_destroy" { + description = "If true, the task is not deleted when the service is deleted" + type = bool + default = null +} + +variable "volume" { + description = "Configuration block for volumes that containers in your task may use" + type = any + default = {} +} + +variable "task_tags" { + description = "A map of additional tags to add to the task definition/set created" + type = map(string) + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = true +} + +variable "task_exec_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +variable "create_tasks_iam_role" { + description = "Determines whether the ECS tasks IAM role should be created" + type = bool + default = true +} + +variable "tasks_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "tasks_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "tasks_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "tasks_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "tasks_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "tasks_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "tasks_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "tasks_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "tasks_iam_role_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Task Set +################################################################################ + +variable "external_id" { + description = "The external ID associated with the task set" + type = string + default = null +} + +variable "scale" { + description = "A floating-point percentage of the desired number of tasks to place and keep running in the task set" + type = any + default = {} +} + +variable "force_delete" { + description = "Whether to allow deleting the task set without waiting for scaling down to 0" + type = bool + default = null +} + +variable "wait_until_stable" { + description = "Whether terraform should wait until the task set has reached `STEADY_STATE`" + type = bool + default = null +} + +variable "wait_until_stable_timeout" { + description = "Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or Āµs), `ms`, `s`, `m`, and `h`. Default `10m`" + type = number + default = null +} + +################################################################################ +# Autoscaling +################################################################################ + +variable "enable_autoscaling" { + description = "Determines whether to enable autoscaling for the service" + type = bool + default = true +} + +variable "autoscaling_min_capacity" { + description = "Minimum number of tasks to run in your service" + type = number + default = 1 +} + +variable "autoscaling_max_capacity" { + description = "Maximum number of tasks to run in your service" + type = number + default = 10 +} + +variable "autoscaling_policies" { + description = "Map of autoscaling policies to create for the service" + type = any + default = { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + } +} + +variable "autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions to create for the service" + type = any + default = {} +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines if a security group is created" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description of the security group created" + type = string + default = null +} + +variable "security_group_rules" { + description = "Security group rules to add to the security group created" + type = any + default = {} +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} diff --git a/examples/ecs-cluster-with-vpc/modules/service/versions.tf b/examples/ecs-cluster-with-vpc/modules/service/versions.tf new file mode 100644 index 00000000..290d2218 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/service/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.55" + } + } +} diff --git a/modules/ecs_monitoring/README.md b/modules/ecs_monitoring/README.md new file mode 100644 index 00000000..e3ba2658 --- /dev/null +++ b/modules/ecs_monitoring/README.md @@ -0,0 +1,72 @@ +# Observability Module for ECS Monitoring using ecs_observer + +This module provides ECS cluster monitoring with the following resources: + +- AWS Distro For OpenTelemetry Operator and Collector for Metrics and Traces +- Creates Grafana Dashboards on Amazon Managed Grafana. +- Create SSM Parameter to store the ADOT config yaml file +- Creates Prometheus Dashboards on Amazon Managed Prometheus. + +## Pre-requisites +1. ECS Cluster with EC2 under examples --> ecs-cluster-with-vpc +2. Update your exisitng App(workload) ECS Task Definition to add below label: + Set ECS_PROMETHEUS_EXPORTER_PORT to point to the containerPort where the Prometheus metrics are exposed + Set Java_EMF_Metrics to true. The CloudWatch agent uses this flag to generated the embedded metric format in the log event. +3. Make sure to update the placeholder values in the below files + configs/config.yaml + task_definitions/otel_collector.json + + +This module makes use of the below open source +[aws-managed-grafana](https://github.com/terraform-aws-modules/terraform-aws-managed-service-grafana) +[aws-managed-prometheus](https://github.com/terraform-aws-modules/terraform-aws-managed-service-prometheus) + +See examples using this Terraform modules in the **Amazon ECS** section of [this documentation](https://aws-observability.github.io/terraform-aws-observability-accelerator/) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.1.0 | +| [aws](#requirement\_aws) | >= 5.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.0.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [managed\_grafana\_default](#module\_managed\_grafana\_default) | terraform-aws-modules/managed-service-grafana/aws | n/a | +| [managed\_prometheus\_default](#module\_managed\_prometheus\_default) | terraform-aws-modules/managed-service-prometheus/aws | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_ecs_service.adot_ecs_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.adot_ecs_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ssm_parameter.adot-config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_ecs\_cluster\_name](#input\_aws\_ecs\_cluster\_name) | Name of your ECS cluster | `string` | n/a | yes | +| [executionRoleArn](#input\_executionRoleArn) | ARN of the IAM Execution Role | `string` | n/a | yes | +| [taskRoleArn](#input\_taskRoleArn) | ARN of the IAM Task Role | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [grafana\_workspace\_endpoint](#output\_grafana\_workspace\_endpoint) | The endpoint of the Grafana workspace | +| [grafana\_workspace\_id](#output\_grafana\_workspace\_id) | The ID of the Grafana workspace | +| [prometheus\_workspace\_endpoint](#output\_prometheus\_workspace\_endpoint) | Prometheus endpoint available for this workspace | +| [prometheus\_workspace\_id](#output\_prometheus\_workspace\_id) | Identifier of the workspace | + diff --git a/modules/ecs_monitoring/configs/config.yaml b/modules/ecs_monitoring/configs/config.yaml new file mode 100644 index 00000000..2b0146c9 --- /dev/null +++ b/modules/ecs_monitoring/configs/config.yaml @@ -0,0 +1,130 @@ +extensions: + sigv4auth: + region: "us-east-1" + service: "aps" + ecs_observer: # extension type is ecs_observer + cluster_name: 'aot-test-cluster' # cluster name need to configured manually + cluster_region: 'us-east-1' # region can be configured directly or use AWS_REGION env var + result_file: '/etc/ecs_sd_targets.yaml' # the directory for file must already exists + refresh_interval: 60s + job_label_name: prometheus_job + # JMX + docker_labels: + - port_label: 'ECS_PROMETHEUS_EXPORTER_PORT' + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + prometheus: + config: + scrape_configs: + - job_name: "ecssd" + file_sd_configs: + - files: + - '/etc/ecs_sd_targets.yaml' + relabel_configs: + - source_labels: [ __meta_ecs_cluster_name ] + action: replace + target_label: ClusterName + - source_labels: [ __meta_ecs_service_name ] + action: replace + target_label: ServiceName + - source_labels: [ __meta_ecs_task_definition_family ] + action: replace + target_label: TaskDefinitionFamily + - source_labels: [ __meta_ecs_task_launch_type ] + action: replace + target_label: LaunchType + - source_labels: [ __meta_ecs_container_name ] + action: replace + target_label: container_name + - action: labelmap + regex: ^__meta_ecs_container_labels_(.+)$ + replacement: '$$1' + awsecscontainermetrics: + collection_interval: 15s + +processors: + resource: + attributes: + - key: receiver + value: "prometheus" + action: insert + filter: + metrics: + include: + match_type: strict + metric_names: + - ecs.task.memory.utilized + - ecs.task.memory.reserved + - ecs.task.memory.usage + - ecs.task.cpu.utilized + - ecs.task.cpu.reserved + - ecs.task.cpu.usage.vcpu + - ecs.task.network.rate.rx + - ecs.task.network.rate.tx + - ecs.task.storage.read_bytes + - ecs.task.storage.write_bytes + metricstransform: + transforms: + - include: ".*" + match_type: regexp + action: update + operations: + - label: prometheus_job + new_label: job + action: update_label + - include: ecs.task.memory.utilized + action: update + new_name: MemoryUtilized + - include: ecs.task.memory.reserved + action: update + new_name: MemoryReserved + - include: ecs.task.memory.usage + action: update + new_name: MemoryUsage + - include: ecs.task.cpu.utilized + action: update + new_name: CpuUtilized + - include: ecs.task.cpu.reserved + action: update + new_name: CpuReserved + - include: ecs.task.cpu.usage.vcpu + action: update + new_name: CpuUsage + - include: ecs.task.network.rate.rx + action: update + new_name: NetworkRxBytes + - include: ecs.task.network.rate.tx + action: update + new_name: NetworkTxBytes + - include: ecs.task.storage.read_bytes + action: update + new_name: StorageReadBytes + - include: ecs.task.storage.write_bytes + action: update + new_name: StorageWriteBytes + +exporters: + prometheusremotewrite: + endpoint: "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-f9a9b3d8-511e-4640-9b2d-15fbd53f7209/api/v1/remote_write" + auth: + authenticator: sigv4auth + logging: + loglevel: debug + +service: + extensions: [ecs_observer, sigv4auth] + pipelines: + metrics: + receivers: [prometheus] + processors: [resource, metricstransform] + exporters: [prometheusremotewrite] + metrics/ecs: + receivers: [awsecscontainermetrics] + processors: [filter] + exporters: [logging, prometheusremotewrite] \ No newline at end of file diff --git a/modules/ecs_monitoring/locals.tf b/modules/ecs_monitoring/locals.tf new file mode 100644 index 00000000..fe2f2e5c --- /dev/null +++ b/modules/ecs_monitoring/locals.tf @@ -0,0 +1,9 @@ +data "aws_region" "current" {} + +locals { + taskRoleArn = var.taskRoleArn + executionRoleArn = var.executionRoleArn + region = data.aws_region.current.name + name = "amg-ex-${replace(basename(path.cwd), "_", "-")}" + description = "AWS Managed Grafana service for ${local.name}" +} diff --git a/modules/ecs_monitoring/main.tf b/modules/ecs_monitoring/main.tf new file mode 100644 index 00000000..eb2bead7 --- /dev/null +++ b/modules/ecs_monitoring/main.tf @@ -0,0 +1,46 @@ +# SSM Parameter +resource "aws_ssm_parameter" "adot-config" { + name = "/observability_aws/otel_collector_conf" + description = "SSM parameter for aws-observability-accelerator/otel-collector-config" + type = "String" + value = yamlencode(file("configs/config.yaml")) +} + +############################################ +# Managed Grafana and Prometheus Module +############################################ + +module "managed_grafana_default" { + source = "terraform-aws-modules/managed-service-grafana/aws" + name = "${local.name}-default" + associate_license = false +} + +module "managed_prometheus_default" { + source = "terraform-aws-modules/managed-service-prometheus/aws" + workspace_alias = "${local.name}-default" +} + +########################################### +# Task Definition for ADOT ECS Prometheus +########################################### +resource "aws_ecs_task_definition" "adot_ecs_prometheus" { + family = "adot_prometheus_td" + task_role_arn = var.taskRoleArn + execution_role_arn = var.executionRoleArn + network_mode = "bridge" + requires_compatibilities = ["EC2"] + cpu = "256" + memory = "512" + container_definitions = file("task_definitions/otel_collector.json") +} + +############################################ +# ECS Service +############################################ +resource "aws_ecs_service" "adot_ecs_prometheus" { + name = "adot_prometheus_svc" + cluster = var.aws_ecs_cluster_name + task_definition = aws_ecs_task_definition.adot_ecs_prometheus.arn + desired_count = 1 +} diff --git a/modules/ecs_monitoring/outputs.tf b/modules/ecs_monitoring/outputs.tf new file mode 100644 index 00000000..c9df0363 --- /dev/null +++ b/modules/ecs_monitoring/outputs.tf @@ -0,0 +1,19 @@ +output "grafana_workspace_id" { + description = "The ID of the Grafana workspace" + value = module.managed_grafana_default.workspace_id +} + +output "grafana_workspace_endpoint" { + description = "The endpoint of the Grafana workspace" + value = module.managed_grafana_default.workspace_endpoint +} + +output "prometheus_workspace_id" { + description = "Identifier of the workspace" + value = module.managed_prometheus_default.workspace_id +} + +output "prometheus_workspace_endpoint" { + description = "Prometheus endpoint available for this workspace" + value = module.managed_prometheus_default.workspace_prometheus_endpoint +} diff --git a/modules/ecs_monitoring/task_definitions/otel_collector.json b/modules/ecs_monitoring/task_definitions/otel_collector.json new file mode 100644 index 00000000..86f66dfe --- /dev/null +++ b/modules/ecs_monitoring/task_definitions/otel_collector.json @@ -0,0 +1,21 @@ +[ + { + "name": "adot", + "image": "amazon/aws-otel-collector:v0.31.0", + "secrets": [ + { + "name": "AOT_CONFIG_CONTENT", + "valueFrom": "/aws-observability-accelerator/otel-collector-config" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "True", + "awslogs-group": "/adot/collector", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs-prometheus" + } + } + } +] diff --git a/modules/ecs_monitoring/variables.tf b/modules/ecs_monitoring/variables.tf new file mode 100644 index 00000000..ce2275e0 --- /dev/null +++ b/modules/ecs_monitoring/variables.tf @@ -0,0 +1,14 @@ +variable "aws_ecs_cluster_name" { + description = "Name of your ECS cluster" + type = string +} + +variable "taskRoleArn" { + description = "ARN of the IAM Task Role" + type = string +} + +variable "executionRoleArn" { + description = "ARN of the IAM Execution Role" + type = string +} diff --git a/modules/ecs_monitoring/versions.tf b/modules/ecs_monitoring/versions.tf new file mode 100644 index 00000000..e426124f --- /dev/null +++ b/modules/ecs_monitoring/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0.0" + } + } +} From 1900fc8fd2fc23a9ecfa4d3d8041a653d1b6e92d Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Wed, 9 Aug 2023 13:45:03 +0000 Subject: [PATCH 02/22] Adding Module and Example for ECS cluster monitoring with ecs_observer --- .../modules/container-definition/README.md | 200 ++++++++++++ .../modules/container-definition/main.tf | 72 +++++ .../modules/container-definition/outputs.tf | 22 ++ .../modules/container-definition/variables.tf | 305 ++++++++++++++++++ .../modules/container-definition/versions.tf | 10 + 5 files changed, 609 insertions(+) create mode 100644 examples/ecs-cluster-with-vpc/modules/container-definition/README.md create mode 100644 examples/ecs-cluster-with-vpc/modules/container-definition/main.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/container-definition/outputs.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/container-definition/variables.tf create mode 100644 examples/ecs-cluster-with-vpc/modules/container-definition/versions.tf diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/README.md b/examples/ecs-cluster-with-vpc/modules/container-definition/README.md new file mode 100644 index 00000000..71eea02b --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/container-definition/README.md @@ -0,0 +1,200 @@ +# Amazon ECS Container Definition Module + +Configuration in this directory creates an Amazon ECS container definition. + +The module defaults to creating and utilizing a CloudWatch log group. You can disable this behavior by setting `enable_cloudwatch_logging` = `false` - useful for scenarios where Firelens is used for log forwarding. + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/design.md) + +## Usage + +### Standard + +```hcl +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + + name = "example" + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + memory_reservation = 100 + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### W/ Firelens + +```hcl +module "fluentbit_ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "fluent-bit" + + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +module "example_ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + + name = "example" + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default is 30 days | `number` | `30` | no | +| [command](#input\_command) | The command that's passed to the container | `list(string)` | `[]` | no | +| [cpu](#input\_cpu) | The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value | `number` | `null` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [dependencies](#input\_dependencies) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
condition = string
containerName = string
}))
| `[]` | no | +| [disable\_networking](#input\_disable\_networking) | When this parameter is true, networking is disabled within the container | `bool` | `null` | no | +| [dns\_search\_domains](#input\_dns\_search\_domains) | Container DNS search domains. A list of DNS search domains that are presented to the container | `list(string)` | `[]` | no | +| [dns\_servers](#input\_dns\_servers) | Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers | `list(string)` | `[]` | no | +| [docker\_labels](#input\_docker\_labels) | A key/value map of labels to add to the container | `map(string)` | `{}` | no | +| [docker\_security\_options](#input\_docker\_security\_options) | A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type | `list(string)` | `[]` | no | +| [enable\_cloudwatch\_logging](#input\_enable\_cloudwatch\_logging) | Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers | `bool` | `true` | no | +| [entrypoint](#input\_entrypoint) | The entry point that is passed to the container | `list(string)` | `[]` | no | +| [environment](#input\_environment) | The environment variables to pass to the container |
list(object({
name = string
value = string
}))
| `[]` | no | +| [environment\_files](#input\_environment\_files) | A list of files containing the environment variables to pass to a container |
list(object({
value = string
type = string
}))
| `[]` | no | +| [essential](#input\_essential) | If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped | `bool` | `null` | no | +| [extra\_hosts](#input\_extra\_hosts) | A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container |
list(object({
hostname = string
ipAddress = string
}))
| `[]` | no | +| [firelens\_configuration](#input\_firelens\_configuration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide | `any` | `{}` | no | +| [health\_check](#input\_health\_check) | The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html) | `any` | `{}` | no | +| [hostname](#input\_hostname) | The hostname to use for your container | `string` | `null` | no | +| [image](#input\_image) | The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest` | `string` | `null` | no | +| [interactive](#input\_interactive) | When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated | `bool` | `false` | no | +| [links](#input\_links) | The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge` | `list(string)` | `[]` | no | +| [linux\_parameters](#input\_linux\_parameters) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | +| [log\_configuration](#input\_log\_configuration) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | +| [memory](#input\_memory) | The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified | `number` | `null` | no | +| [memory\_reservation](#input\_memory\_reservation) | The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance | `number` | `null` | no | +| [mount\_points](#input\_mount\_points) | The mount points for data volumes in your container | `list(any)` | `[]` | no | +| [name](#input\_name) | The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed | `string` | `null` | no | +| [operating\_system\_family](#input\_operating\_system\_family) | The OS family for task | `string` | `"LINUX"` | no | +| [port\_mappings](#input\_port\_mappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort | `list(any)` | `[]` | no | +| [privileged](#input\_privileged) | When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user) | `bool` | `false` | no | +| [pseudo\_terminal](#input\_pseudo\_terminal) | When this parameter is true, a `TTY` is allocated | `bool` | `false` | no | +| [readonly\_root\_filesystem](#input\_readonly\_root\_filesystem) | When this parameter is true, the container is given read-only access to its root file system | `bool` | `true` | no | +| [repository\_credentials](#input\_repository\_credentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials | `map(string)` | `{}` | no | +| [resource\_requirements](#input\_resource\_requirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU |
list(object({
type = string
value = string
}))
| `[]` | no | +| [secrets](#input\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide |
list(object({
name = string
valueFrom = string
}))
| `[]` | no | +| [service](#input\_service) | The name of the service that the container definition is associated with | `string` | `""` | no | +| [start\_timeout](#input\_start\_timeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | +| [stop\_timeout](#input\_stop\_timeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `120` | no | +| [system\_controls](#input\_system\_controls) | A list of namespaced kernel parameters to set in the container | `list(map(string))` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [ulimits](#input\_ulimits) | A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker |
list(object({
hardLimit = number
name = string
softLimit = number
}))
| `[]` | no | +| [user](#input\_user) | The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set | `string` | `null` | no | +| [volumes\_from](#input\_volumes\_from) | Data volumes to mount from another container | `list(any)` | `[]` | no | +| [working\_directory](#input\_working\_directory) | The working directory to run commands inside the container | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [container\_definition](#output\_container\_definition) | Container definition | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/main.tf b/examples/ecs-cluster-with-vpc/modules/container-definition/main.tf new file mode 100644 index 00000000..0091a818 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/container-definition/main.tf @@ -0,0 +1,72 @@ +data "aws_region" "current" {} + +locals { + is_not_windows = contains(["LINUX"], var.operating_system_family) + + log_configuration = merge( + { for k, v in { + logDriver = "awslogs", + options = { + awslogs-region = data.aws_region.current.name, + awslogs-group = try(aws_cloudwatch_log_group.this[0].name, ""), + awslogs-stream-prefix = "ecs" + }, + } : k => v if var.enable_cloudwatch_logging }, + var.log_configuration + ) + + definition = { + command = length(var.command) > 0 ? var.command : null + cpu = var.cpu + dependsOn = length(var.dependencies) > 0 ? var.dependencies : null # depends_on is a reserved word + disableNetworking = local.is_not_windows ? var.disable_networking : null + dnsSearchDomains = local.is_not_windows && length(var.dns_search_domains) > 0 ? var.dns_search_domains : null + dnsServers = local.is_not_windows && length(var.dns_servers) > 0 ? var.dns_servers : null + dockerLabels = length(var.docker_labels) > 0 ? var.docker_labels : null + dockerSecurityOptions = length(var.docker_security_options) > 0 ? var.docker_security_options : null + entrypoint = length(var.entrypoint) > 0 ? var.entrypoint : null + environment = var.environment + environmentFiles = length(var.environment_files) > 0 ? var.environment_files : null + essential = var.essential + extraHosts = local.is_not_windows && length(var.extra_hosts) > 0 ? var.extra_hosts : null + firelensConfiguration = length(var.firelens_configuration) > 0 ? var.firelens_configuration : null + healthCheck = length(var.health_check) > 0 ? var.health_check : null + hostname = var.hostname + image = var.image + interactive = var.interactive + links = local.is_not_windows && length(var.links) > 0 ? var.links : null + linuxParameters = local.is_not_windows && length(var.linux_parameters) > 0 ? var.linux_parameters : null + logConfiguration = length(local.log_configuration) > 0 ? local.log_configuration : null + memory = var.memory + memoryReservation = var.memory_reservation + mountPoints = var.mount_points + name = var.name + portMappings = var.port_mappings + privileged = local.is_not_windows ? var.privileged : null + pseudoTerminal = var.pseudo_terminal + readonlyRootFilesystem = local.is_not_windows ? var.readonly_root_filesystem : null + repositoryCredentials = length(var.repository_credentials) > 0 ? var.repository_credentials : null + resourceRequirements = length(var.resource_requirements) > 0 ? var.resource_requirements : null + secrets = length(var.secrets) > 0 ? var.secrets : null + startTimeout = var.start_timeout + stopTimeout = var.stop_timeout + systemControls = length(var.system_controls) > 0 ? var.system_controls : null + ulimits = local.is_not_windows && length(var.ulimits) > 0 ? var.ulimits : null + user = local.is_not_windows ? var.user : null + volumesFrom = var.volumes_from + workingDirectory = var.working_directory + } + + # Strip out all null values, ECS API will provide defaults in place of null/empty values + container_definition = { for k, v in local.definition : k => v if v != null } +} + +resource "aws_cloudwatch_log_group" "this" { + count = var.create_cloudwatch_log_group && var.enable_cloudwatch_logging ? 1 : 0 + + name = "/aws/ecs/${var.service}/${var.name}" + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = var.tags +} diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/outputs.tf b/examples/ecs-cluster-with-vpc/modules/container-definition/outputs.tf new file mode 100644 index 00000000..66a1d36d --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/container-definition/outputs.tf @@ -0,0 +1,22 @@ +################################################################################ +# Container Definition +################################################################################ + +output "container_definition" { + description = "Container definition" + value = local.container_definition +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, null) +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, null) +} diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/variables.tf b/examples/ecs-cluster-with-vpc/modules/container-definition/variables.tf new file mode 100644 index 00000000..b7861c0e --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/container-definition/variables.tf @@ -0,0 +1,305 @@ +variable "operating_system_family" { + description = "The OS family for task" + type = string + default = "LINUX" +} + +################################################################################ +# Container Definition +################################################################################ + +variable "command" { + description = "The command that's passed to the container" + type = list(string) + default = [] +} + +variable "cpu" { + description = "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + type = number + default = null +} + +variable "dependencies" { + description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + type = list(object({ + condition = string + containerName = string + })) + default = [] +} + +variable "disable_networking" { + description = "When this parameter is true, networking is disabled within the container" + type = bool + default = null +} + +variable "dns_search_domains" { + description = "Container DNS search domains. A list of DNS search domains that are presented to the container" + type = list(string) + default = [] +} + +variable "dns_servers" { + description = "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + type = list(string) + default = [] +} + +variable "docker_labels" { + description = "A key/value map of labels to add to the container" + type = map(string) + default = {} +} + +variable "docker_security_options" { + description = "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + type = list(string) + default = [] +} + +variable "entrypoint" { + description = "The entry point that is passed to the container" + type = list(string) + default = [] +} + +variable "environment" { + description = "The environment variables to pass to the container" + type = list(object({ + name = string + value = string + })) + default = [] +} + +variable "environment_files" { + description = "A list of files containing the environment variables to pass to a container" + type = list(object({ + value = string + type = string + })) + default = [] +} + +variable "essential" { + description = "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + type = bool + default = null +} + +variable "extra_hosts" { + description = "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + type = list(object({ + hostname = string + ipAddress = string + })) + default = [] +} + +variable "firelens_configuration" { + description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + type = any + default = {} +} + +variable "health_check" { + description = "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + type = any + default = {} +} + +variable "hostname" { + description = "The hostname to use for your container" + type = string + default = null +} + +variable "image" { + description = "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + type = string + default = null +} + +variable "interactive" { + description = "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + type = bool + default = false +} + +variable "links" { + description = "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + type = list(string) + default = [] +} + +variable "linux_parameters" { + description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + type = any + default = {} +} + +variable "log_configuration" { + description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + type = any + default = {} +} + +variable "memory" { + description = "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + type = number + default = null +} + +variable "memory_reservation" { + description = "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + type = number + default = null +} + +variable "mount_points" { + description = "The mount points for data volumes in your container" + type = list(any) + default = [] +} + +variable "name" { + description = "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + type = string + default = null +} + +variable "port_mappings" { + description = "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + type = list(any) + default = [] +} + +variable "privileged" { + description = "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + type = bool + default = false +} + +variable "pseudo_terminal" { + description = "When this parameter is true, a `TTY` is allocated" + type = bool + default = false +} + +variable "readonly_root_filesystem" { + description = "When this parameter is true, the container is given read-only access to its root file system" + type = bool + default = true +} + +variable "repository_credentials" { + description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + type = map(string) + default = {} +} + +variable "resource_requirements" { + description = "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + type = list(object({ + type = string + value = string + })) + default = [] +} + +variable "secrets" { + description = "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + type = list(object({ + name = string + valueFrom = string + })) + default = [] +} + +variable "start_timeout" { + description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + type = number + default = 30 +} + +variable "stop_timeout" { + description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + type = number + default = 120 +} + +variable "system_controls" { + description = "A list of namespaced kernel parameters to set in the container" + type = list(map(string)) + default = [] +} + +variable "ulimits" { + description = "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + type = list(object({ + hardLimit = number + name = string + softLimit = number + })) + default = [] +} + +variable "user" { + description = "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + type = string + default = null +} + +variable "volumes_from" { + description = "Data volumes to mount from another container" + type = list(any) + default = [] +} + +variable "working_directory" { + description = "The working directory to run commands inside the container" + type = string + default = null +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "service" { + description = "The name of the service that the container definition is associated with" + type = string + default = "" +} + +variable "enable_cloudwatch_logging" { + description = "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + type = bool + default = true +} + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events. Default is 30 days" + type = number + default = 30 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/versions.tf b/examples/ecs-cluster-with-vpc/modules/container-definition/versions.tf new file mode 100644 index 00000000..290d2218 --- /dev/null +++ b/examples/ecs-cluster-with-vpc/modules/container-definition/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.55" + } + } +} From c1ac8b8abbf1e109519c412cb161e73281cddbb9 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Thu, 10 Aug 2023 06:38:20 +0000 Subject: [PATCH 03/22] Incorporating PR comments --- .../{ec2-autoscaling => }/README.md | 0 .../{ec2-autoscaling => }/main.tf | 11 +++++++ .../{ec2-autoscaling => }/outputs.tf | 0 .../{ec2-autoscaling => }/variables.tf | 0 .../{ec2-autoscaling => }/versions.tf | 0 .../README.md | 30 +++++++++++-------- .../ecs-monitoring}/cluster/README.md | 0 .../ecs-monitoring}/cluster/main.tf | 0 .../ecs-monitoring}/cluster/outputs.tf | 0 .../ecs-monitoring}/cluster/variables.tf | 0 .../ecs-monitoring}/cluster/versions.tf | 0 .../configs/config.yaml | 0 .../container-definition/README.md | 0 .../container-definition/main.tf | 0 .../container-definition/outputs.tf | 0 .../container-definition/variables.tf | 0 .../container-definition/versions.tf | 0 .../locals.tf | 0 .../main.tf | 11 ++++--- .../outputs.tf | 0 .../ecs-monitoring}/service/README.md | 0 .../ecs-monitoring}/service/main.tf | 0 .../ecs-monitoring}/service/outputs.tf | 0 .../ecs-monitoring}/service/variables.tf | 0 .../ecs-monitoring}/service/versions.tf | 0 .../task-definitions}/otel_collector.json | 0 .../variables.tf | 0 .../versions.tf | 0 28 files changed, 35 insertions(+), 17 deletions(-) rename examples/ecs-cluster-with-vpc/{ec2-autoscaling => }/README.md (100%) rename examples/ecs-cluster-with-vpc/{ec2-autoscaling => }/main.tf (97%) rename examples/ecs-cluster-with-vpc/{ec2-autoscaling => }/outputs.tf (100%) rename examples/ecs-cluster-with-vpc/{ec2-autoscaling => }/variables.tf (100%) rename examples/ecs-cluster-with-vpc/{ec2-autoscaling => }/versions.tf (100%) rename modules/{ecs_monitoring => ecs-monitoring}/README.md (74%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/cluster/README.md (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/cluster/main.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/cluster/outputs.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/cluster/variables.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/cluster/versions.tf (100%) rename modules/{ecs_monitoring => ecs-monitoring}/configs/config.yaml (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/container-definition/README.md (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/container-definition/main.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/container-definition/outputs.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/container-definition/variables.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/container-definition/versions.tf (100%) rename modules/{ecs_monitoring => ecs-monitoring}/locals.tf (100%) rename modules/{ecs_monitoring => ecs-monitoring}/main.tf (83%) rename modules/{ecs_monitoring => ecs-monitoring}/outputs.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/service/README.md (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/service/main.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/service/outputs.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/service/variables.tf (100%) rename {examples/ecs-cluster-with-vpc/modules => modules/ecs-monitoring}/service/versions.tf (100%) rename modules/{ecs_monitoring/task_definitions => ecs-monitoring/task-definitions}/otel_collector.json (100%) rename modules/{ecs_monitoring => ecs-monitoring}/variables.tf (100%) rename modules/{ecs_monitoring => ecs-monitoring}/versions.tf (100%) diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/README.md b/examples/ecs-cluster-with-vpc/README.md similarity index 100% rename from examples/ecs-cluster-with-vpc/ec2-autoscaling/README.md rename to examples/ecs-cluster-with-vpc/README.md diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf b/examples/ecs-cluster-with-vpc/main.tf similarity index 97% rename from examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf rename to examples/ecs-cluster-with-vpc/main.tf index 6fc246a7..6dd0122e 100644 --- a/examples/ecs-cluster-with-vpc/ec2-autoscaling/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -339,3 +339,14 @@ module "vpc" { tags = local.tags } + +module "managed_ecs_monitoring" { + source = "../../modules/ecs-monitoring" + aws_ecs_cluster_name = var.aws_ecs_cluster_name + taskRoleArn = var.taskRoleArn + executionRoleArn = var.executionRoleArn + + depends_on = [ + module.ecs_service + ] +} \ No newline at end of file diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/outputs.tf b/examples/ecs-cluster-with-vpc/outputs.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/ec2-autoscaling/outputs.tf rename to examples/ecs-cluster-with-vpc/outputs.tf diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/variables.tf b/examples/ecs-cluster-with-vpc/variables.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/ec2-autoscaling/variables.tf rename to examples/ecs-cluster-with-vpc/variables.tf diff --git a/examples/ecs-cluster-with-vpc/ec2-autoscaling/versions.tf b/examples/ecs-cluster-with-vpc/versions.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/ec2-autoscaling/versions.tf rename to examples/ecs-cluster-with-vpc/versions.tf diff --git a/modules/ecs_monitoring/README.md b/modules/ecs-monitoring/README.md similarity index 74% rename from modules/ecs_monitoring/README.md rename to modules/ecs-monitoring/README.md index e3ba2658..b87ca895 100644 --- a/modules/ecs_monitoring/README.md +++ b/modules/ecs-monitoring/README.md @@ -5,21 +5,25 @@ This module provides ECS cluster monitoring with the following resources: - AWS Distro For OpenTelemetry Operator and Collector for Metrics and Traces - Creates Grafana Dashboards on Amazon Managed Grafana. - Create SSM Parameter to store the ADOT config yaml file -- Creates Prometheus Dashboards on Amazon Managed Prometheus. ## Pre-requisites -1. ECS Cluster with EC2 under examples --> ecs-cluster-with-vpc -2. Update your exisitng App(workload) ECS Task Definition to add below label: - Set ECS_PROMETHEUS_EXPORTER_PORT to point to the containerPort where the Prometheus metrics are exposed - Set Java_EMF_Metrics to true. The CloudWatch agent uses this flag to generated the embedded metric format in the log event. -3. Make sure to update the placeholder values in the below files - configs/config.yaml - task_definitions/otel_collector.json - - -This module makes use of the below open source -[aws-managed-grafana](https://github.com/terraform-aws-modules/terraform-aws-managed-service-grafana) -[aws-managed-prometheus](https://github.com/terraform-aws-modules/terraform-aws-managed-service-prometheus) +* ECS Cluster with EC2 using examples --> ecs-cluster-with-vpc +* Create a `Prometheus Workspace` either using the Console or using the commented code under modules/ecs-monitoring/main.tf. +* Update your exisitng App(workload) *ECS Task Definition* to add below label/environment variable + - Set ***ECS_PROMETHEUS_EXPORTER_PORT*** to point to the containerPort where the Prometheus metrics are exposed + - Set ***Java_EMF_Metrics*** to true. The CloudWatch agent uses this flag to generated the embedded metric format in the log event. +* Make sure to update the placeholder values in the below files + - configs/config.yaml + - region + - cluster_name + - cluster_region + - prometheusremotewrite --> endpoint + - task-definitions/otel_collector.json + - awslogs-region + +This module makes use of the below open source projects: +* [aws-managed-grafana](https://github.com/terraform-aws-modules/terraform-aws-managed-service-grafana) +* [aws-managed-prometheus](https://github.com/terraform-aws-modules/terraform-aws-managed-service-prometheus) See examples using this Terraform modules in the **Amazon ECS** section of [this documentation](https://aws-observability.github.io/terraform-aws-observability-accelerator/) diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/README.md b/modules/ecs-monitoring/cluster/README.md similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/cluster/README.md rename to modules/ecs-monitoring/cluster/README.md diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/main.tf b/modules/ecs-monitoring/cluster/main.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/cluster/main.tf rename to modules/ecs-monitoring/cluster/main.tf diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/outputs.tf b/modules/ecs-monitoring/cluster/outputs.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/cluster/outputs.tf rename to modules/ecs-monitoring/cluster/outputs.tf diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/variables.tf b/modules/ecs-monitoring/cluster/variables.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/cluster/variables.tf rename to modules/ecs-monitoring/cluster/variables.tf diff --git a/examples/ecs-cluster-with-vpc/modules/cluster/versions.tf b/modules/ecs-monitoring/cluster/versions.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/cluster/versions.tf rename to modules/ecs-monitoring/cluster/versions.tf diff --git a/modules/ecs_monitoring/configs/config.yaml b/modules/ecs-monitoring/configs/config.yaml similarity index 100% rename from modules/ecs_monitoring/configs/config.yaml rename to modules/ecs-monitoring/configs/config.yaml diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/README.md b/modules/ecs-monitoring/container-definition/README.md similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/container-definition/README.md rename to modules/ecs-monitoring/container-definition/README.md diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/main.tf b/modules/ecs-monitoring/container-definition/main.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/container-definition/main.tf rename to modules/ecs-monitoring/container-definition/main.tf diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/outputs.tf b/modules/ecs-monitoring/container-definition/outputs.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/container-definition/outputs.tf rename to modules/ecs-monitoring/container-definition/outputs.tf diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/variables.tf b/modules/ecs-monitoring/container-definition/variables.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/container-definition/variables.tf rename to modules/ecs-monitoring/container-definition/variables.tf diff --git a/examples/ecs-cluster-with-vpc/modules/container-definition/versions.tf b/modules/ecs-monitoring/container-definition/versions.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/container-definition/versions.tf rename to modules/ecs-monitoring/container-definition/versions.tf diff --git a/modules/ecs_monitoring/locals.tf b/modules/ecs-monitoring/locals.tf similarity index 100% rename from modules/ecs_monitoring/locals.tf rename to modules/ecs-monitoring/locals.tf diff --git a/modules/ecs_monitoring/main.tf b/modules/ecs-monitoring/main.tf similarity index 83% rename from modules/ecs_monitoring/main.tf rename to modules/ecs-monitoring/main.tf index eb2bead7..6054959a 100644 --- a/modules/ecs_monitoring/main.tf +++ b/modules/ecs-monitoring/main.tf @@ -16,10 +16,13 @@ module "managed_grafana_default" { associate_license = false } -module "managed_prometheus_default" { - source = "terraform-aws-modules/managed-service-prometheus/aws" - workspace_alias = "${local.name}-default" -} +## Commented this module, as AMP workspace is a pre-requiste for this solution. +## You can use this code to create a AMP workspace + +# module "managed_prometheus_default" { +# source = "terraform-aws-modules/managed-service-prometheus/aws" +# workspace_alias = "${local.name}-default" +# } ########################################### # Task Definition for ADOT ECS Prometheus diff --git a/modules/ecs_monitoring/outputs.tf b/modules/ecs-monitoring/outputs.tf similarity index 100% rename from modules/ecs_monitoring/outputs.tf rename to modules/ecs-monitoring/outputs.tf diff --git a/examples/ecs-cluster-with-vpc/modules/service/README.md b/modules/ecs-monitoring/service/README.md similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/service/README.md rename to modules/ecs-monitoring/service/README.md diff --git a/examples/ecs-cluster-with-vpc/modules/service/main.tf b/modules/ecs-monitoring/service/main.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/service/main.tf rename to modules/ecs-monitoring/service/main.tf diff --git a/examples/ecs-cluster-with-vpc/modules/service/outputs.tf b/modules/ecs-monitoring/service/outputs.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/service/outputs.tf rename to modules/ecs-monitoring/service/outputs.tf diff --git a/examples/ecs-cluster-with-vpc/modules/service/variables.tf b/modules/ecs-monitoring/service/variables.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/service/variables.tf rename to modules/ecs-monitoring/service/variables.tf diff --git a/examples/ecs-cluster-with-vpc/modules/service/versions.tf b/modules/ecs-monitoring/service/versions.tf similarity index 100% rename from examples/ecs-cluster-with-vpc/modules/service/versions.tf rename to modules/ecs-monitoring/service/versions.tf diff --git a/modules/ecs_monitoring/task_definitions/otel_collector.json b/modules/ecs-monitoring/task-definitions/otel_collector.json similarity index 100% rename from modules/ecs_monitoring/task_definitions/otel_collector.json rename to modules/ecs-monitoring/task-definitions/otel_collector.json diff --git a/modules/ecs_monitoring/variables.tf b/modules/ecs-monitoring/variables.tf similarity index 100% rename from modules/ecs_monitoring/variables.tf rename to modules/ecs-monitoring/variables.tf diff --git a/modules/ecs_monitoring/versions.tf b/modules/ecs-monitoring/versions.tf similarity index 100% rename from modules/ecs_monitoring/versions.tf rename to modules/ecs-monitoring/versions.tf From e2e7af7719093ad6fd7bf3bdf220318594e5b4eb Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Thu, 10 Aug 2023 11:41:55 +0000 Subject: [PATCH 04/22] Restructuring Examples and modules folder for ECS, Added content in main Readme --- README.md | 12 + examples/ecs-cluster-with-vpc/main.tf | 222 +-- modules/ecs-monitoring/cluster/README.md | 214 --- modules/ecs-monitoring/cluster/main.tf | 327 ---- modules/ecs-monitoring/cluster/outputs.tf | 70 - modules/ecs-monitoring/cluster/variables.tf | 169 --- modules/ecs-monitoring/cluster/versions.tf | 10 - .../container-definition/README.md | 200 --- .../container-definition/main.tf | 72 - .../container-definition/outputs.tf | 22 - .../container-definition/variables.tf | 305 ---- .../container-definition/versions.tf | 10 - modules/ecs-monitoring/service/README.md | 351 ----- modules/ecs-monitoring/service/main.tf | 1329 ----------------- modules/ecs-monitoring/service/outputs.tf | 152 -- modules/ecs-monitoring/service/variables.tf | 643 -------- modules/ecs-monitoring/service/versions.tf | 10 - 17 files changed, 42 insertions(+), 4076 deletions(-) delete mode 100644 modules/ecs-monitoring/cluster/README.md delete mode 100644 modules/ecs-monitoring/cluster/main.tf delete mode 100644 modules/ecs-monitoring/cluster/outputs.tf delete mode 100644 modules/ecs-monitoring/cluster/variables.tf delete mode 100644 modules/ecs-monitoring/cluster/versions.tf delete mode 100644 modules/ecs-monitoring/container-definition/README.md delete mode 100644 modules/ecs-monitoring/container-definition/main.tf delete mode 100644 modules/ecs-monitoring/container-definition/outputs.tf delete mode 100644 modules/ecs-monitoring/container-definition/variables.tf delete mode 100644 modules/ecs-monitoring/container-definition/versions.tf delete mode 100644 modules/ecs-monitoring/service/README.md delete mode 100644 modules/ecs-monitoring/service/main.tf delete mode 100644 modules/ecs-monitoring/service/outputs.tf delete mode 100644 modules/ecs-monitoring/service/variables.tf delete mode 100644 modules/ecs-monitoring/service/versions.tf diff --git a/README.md b/README.md index 0e450fe3..cd7d1473 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,18 @@ module "eks_monitoring" { } ``` +#### Amazon ECS monitoring +ECS cluster with VPC and EC2 can be created using the example [here](./examples/ecs_cluster_with_vpc) + +```hcl +module "ecs_monitoring" { + source = "github.com/aws-observability/terraform-aws-observability-accelerator/modules/ecs-monitoring" + + aws_ecs_cluster_name = module.ecs_cluster.cluster_name + taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn + executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn +} +``` Grafana Dashboards image diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index 6dd0122e..36862ee3 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -5,7 +5,7 @@ provider "aws" { data "aws_availability_zones" "available" {} locals { - region = "eu-west-1" + region = "us-east-1" name = "ex-${basename(path.cwd)}" vpc_cidr = "10.0.0.0/16" @@ -26,12 +26,15 @@ locals { ################################################################################ module "ecs_cluster" { - source = "./modules/cluster" + source = "terraform-aws-modules/ecs/aws" cluster_name = local.name # Capacity provider - autoscaling groups default_capacity_provider_use_fargate = false + create_task_exec_iam_role = true + task_exec_iam_role_name = "ecs_monitor_task_exec_role" + task_exec_iam_role_policies = {"module.ecs_cluster.module.cluster.aws_iam_policy.task_exec[0]" : "arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess" } autoscaling_capacity_providers = { # On-demand instances ex-1 = { @@ -50,164 +53,11 @@ module "ecs_cluster" { base = 20 } } - # Spot instances - ex-2 = { - auto_scaling_group_arn = module.autoscaling["ex-2"].autoscaling_group_arn - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 - } - - default_capacity_provider_strategy = { - weight = 40 - } - - cluster_settings = { - name = "containerInsights" - value = "disabled" - } - } } tags = local.tags } -################################################################################ -# Service -################################################################################ - -module "ecs_service" { - source = "./modules/service" - - # Service - name = local.name - cluster_arn = module.ecs_cluster.arn - - # Task Definition - requires_compatibilities = ["EC2"] - capacity_provider_strategy = { - # On-demand instances - ex-1 = { - capacity_provider = module.ecs_cluster.autoscaling_capacity_providers["ex-1"].name - weight = 1 - base = 1 - } - } - - volume = { - my-vol = {} - } - - # Container definition(s) - container_definitions = { - (local.container_name) = { - image = "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest" - port_mappings = [ - { - name = local.container_name - containerPort = local.container_port - protocol = "tcp" - } - ] - - mount_points = [ - { - sourceVolume = "my-vol", - containerPath = "/var/www/my-vol" - } - ] - - entry_point = ["/usr/sbin/apache2", "-D", "FOREGROUND"] - - # Example image used requires access to write to root filesystem - readonly_root_filesystem = false - } - } - - load_balancer = { - service = { - target_group_arn = element(module.alb.target_group_arns, 0) - container_name = local.container_name - container_port = local.container_port - } - } - - subnet_ids = module.vpc.private_subnets - security_group_rules = { - alb_http_ingress = { - type = "ingress" - from_port = local.container_port - to_port = local.container_port - protocol = "tcp" - description = "Service port" - source_security_group_id = module.alb_sg.security_group_id - } - } - - tags = local.tags -} - -################################################################################ -# Supporting Resources -################################################################################ - -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux -data "aws_ssm_parameter" "ecs_optimized_ami" { - name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" -} - -module "alb_sg" { - source = "terraform-aws-modules/security-group/aws" - version = "~> 5.0" - - name = "${local.name}-service" - description = "Service security group" - vpc_id = module.vpc.vpc_id - - ingress_rules = ["http-80-tcp"] - ingress_cidr_blocks = ["0.0.0.0/0"] - - egress_rules = ["all-all"] - egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks - - tags = local.tags -} - -module "alb" { - source = "terraform-aws-modules/alb/aws" - version = "~> 8.0" - - name = local.name - - load_balancer_type = "application" - - vpc_id = module.vpc.vpc_id - subnets = module.vpc.public_subnets - security_groups = [module.alb_sg.security_group_id] - - http_tcp_listeners = [ - { - port = local.container_port - protocol = "HTTP" - target_group_index = 0 - }, - ] - - target_groups = [ - { - name = "${local.name}-${local.container_name}" - backend_protocol = "HTTP" - backend_port = local.container_port - target_type = "ip" - }, - ] - - tags = local.tags -} module "autoscaling" { source = "terraform-aws-modules/autoscaling/aws" @@ -229,39 +79,6 @@ module "autoscaling" { EOF EOT } - # Spot instances - ex-2 = { - instance_type = "t3.medium" - use_mixed_instances_policy = true - mixed_instances_policy = { - instances_distribution = { - on_demand_base_capacity = 0 - on_demand_percentage_above_base_capacity = 0 - spot_allocation_strategy = "price-capacity-optimized" - } - - override = [ - { - instance_type = "m4.large" - weighted_capacity = "2" - }, - { - instance_type = "t3.large" - weighted_capacity = "1" - }, - ] - } - user_data = <<-EOT - #!/bin/bash - cat <<'EOF' >> /etc/ecs/ecs.config - ECS_CLUSTER=${local.name} - ECS_LOGLEVEL=debug - ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} - ECS_ENABLE_TASK_IAM_ROLE=true - ECS_ENABLE_SPOT_INSTANCE_DRAINING=true - EOF - EOT - } } name = "${local.name}-${each.key}" @@ -340,11 +157,32 @@ module "vpc" { tags = local.tags } -module "managed_ecs_monitoring" { +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +module "alb_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = "${local.name}-service" + description = "Service security group" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] + + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks + + tags = local.tags +} + +module "ecs_monitoring" { source = "../../modules/ecs-monitoring" - aws_ecs_cluster_name = var.aws_ecs_cluster_name - taskRoleArn = var.taskRoleArn - executionRoleArn = var.executionRoleArn + aws_ecs_cluster_name = module.ecs_cluster.cluster_name + taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn + executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn depends_on = [ module.ecs_service diff --git a/modules/ecs-monitoring/cluster/README.md b/modules/ecs-monitoring/cluster/README.md deleted file mode 100644 index 0f4adb65..00000000 --- a/modules/ecs-monitoring/cluster/README.md +++ /dev/null @@ -1,214 +0,0 @@ -# Amazon ECS Cluster Terraform Module - -Terraform module which creates Amazon ECS (Elastic Container Service) cluster resources on AWS. - -## Available Features - -- ECS cluster -- Fargate capacity providers -- EC2 AutoScaling Group capacity providers -- ECS Service w/ task definition, task set, and container definition support - -For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) - -## Usage - -### Fargate Capacity Providers - -```hcl -module "ecs_cluster" { - source = "terraform-aws-modules/ecs/aws//modules/cluster" - - cluster_name = "ecs-fargate" - - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = "/aws/ecs/aws-ec2" - } - } - } - - fargate_capacity_providers = { - FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - } - } - FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 - } - } - } - - tags = { - Environment = "Development" - Project = "EcsEc2" - } -} -``` - -### EC2 Autoscaling Capacity Providers - -```hcl -module "ecs_cluster" { - source = "terraform-aws-modules/ecs/aws//modules/cluster" - - cluster_name = "ecs-ec2" - - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = "/aws/ecs/aws-ec2" - } - } - } - - autoscaling_capacity_providers = { - one = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 - } - - default_capacity_provider_strategy = { - weight = 60 - base = 20 - } - } - two = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 - } - - default_capacity_provider_strategy = { - weight = 40 - } - } - } - - tags = { - Environment = "Development" - Project = "EcsEc2" - } -} -``` - -## Conditional Creation - -The following values are provided to toggle on/off creation of the associated resources as desired: - -```hcl -module "ecs_cluster" { - source = "terraform-aws-modules/ecs/aws//modules/cluster" - - # Disable creation of cluster and all resources - create = false - - # ... omitted -} -``` - -## Examples - -- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) -- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) -- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.55 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 4.55 | - -## Modules - -No modules. - -## Resources - -| Name | Type | -|------|------| -| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | -| [aws_ecs_capacity_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_capacity_provider) | resource | -| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | -| [aws_ecs_cluster_capacity_providers.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | -| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | -| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | -| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events | `number` | `90` | no | -| [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | A map of additional tags to add to the log group created | `map(string)` | `{}` | no | -| [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | -| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | -| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | -| [cluster\_settings](#input\_cluster\_settings) | Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `map(string)` |
{
"name": "containerInsights",
"value": "enabled"
}
| no | -| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | -| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | -| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | -| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | -| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | -| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | -| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | -| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | -| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | -| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | -| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | -| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | -| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | -| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | -| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | - -## Outputs - -| Name | Description | -|------|-------------| -| [arn](#output\_arn) | ARN that identifies the cluster | -| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | -| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | -| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | -| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | -| [id](#output\_id) | ID that identifies the cluster | -| [name](#output\_name) | Name that identifies the cluster | -| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | -| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | -| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | - - -## License - -Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs-monitoring/cluster/main.tf b/modules/ecs-monitoring/cluster/main.tf deleted file mode 100644 index 6830af2e..00000000 --- a/modules/ecs-monitoring/cluster/main.tf +++ /dev/null @@ -1,327 +0,0 @@ -data "aws_partition" "current" {} - -################################################################################ -# Cluster -################################################################################ - -locals { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, null) - } - } -} - -resource "aws_ecs_cluster" "this" { - count = var.create ? 1 : 0 - - name = var.cluster_name - - dynamic "configuration" { - for_each = var.create_cloudwatch_log_group ? [var.cluster_configuration] : [] - - content { - dynamic "execute_command_configuration" { - for_each = try([merge(local.execute_command_configuration, configuration.value.execute_command_configuration)], [{}]) - - content { - kms_key_id = try(execute_command_configuration.value.kms_key_id, null) - logging = try(execute_command_configuration.value.logging, "DEFAULT") - - dynamic "log_configuration" { - for_each = try([execute_command_configuration.value.log_configuration], []) - - content { - cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) - cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) - s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) - s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) - s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) - } - } - } - } - } - } - - dynamic "configuration" { - for_each = !var.create_cloudwatch_log_group && length(var.cluster_configuration) > 0 ? [var.cluster_configuration] : [] - - content { - dynamic "execute_command_configuration" { - for_each = try([configuration.value.execute_command_configuration], [{}]) - - content { - kms_key_id = try(execute_command_configuration.value.kms_key_id, null) - logging = try(execute_command_configuration.value.logging, "DEFAULT") - - dynamic "log_configuration" { - for_each = try([execute_command_configuration.value.log_configuration], []) - - content { - cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) - cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) - s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) - s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) - s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) - } - } - } - } - } - } - - dynamic "service_connect_defaults" { - for_each = length(var.cluster_service_connect_defaults) > 0 ? [var.cluster_service_connect_defaults] : [] - - content { - namespace = service_connect_defaults.value.namespace - } - } - - dynamic "setting" { - for_each = [var.cluster_settings] - - content { - name = setting.value.name - value = setting.value.value - } - } - - tags = var.tags -} - -################################################################################ -# CloudWatch Log Group -################################################################################ - -resource "aws_cloudwatch_log_group" "this" { - count = var.create && var.create_cloudwatch_log_group ? 1 : 0 - - name = "/aws/ecs/${var.cluster_name}" - retention_in_days = var.cloudwatch_log_group_retention_in_days - kms_key_id = var.cloudwatch_log_group_kms_key_id - - tags = merge(var.tags, var.cloudwatch_log_group_tags) -} - -################################################################################ -# Cluster Capacity Providers -################################################################################ - -locals { - default_capacity_providers = merge( - { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, - { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } - ) -} - -resource "aws_ecs_cluster_capacity_providers" "this" { - count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 - - cluster_name = aws_ecs_cluster.this[0].name - capacity_providers = distinct(concat( - [for k, v in var.fargate_capacity_providers : try(v.name, k)], - [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] - )) - - # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations - dynamic "default_capacity_provider_strategy" { - for_each = local.default_capacity_providers - iterator = strategy - - content { - capacity_provider = try(strategy.value.name, strategy.key) - base = try(strategy.value.default_capacity_provider_strategy.base, null) - weight = try(strategy.value.default_capacity_provider_strategy.weight, null) - } - } - - depends_on = [ - aws_ecs_capacity_provider.this - ] -} - -################################################################################ -# Capacity Provider - Autoscaling Group(s) -################################################################################ - -resource "aws_ecs_capacity_provider" "this" { - for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } - - name = try(each.value.name, each.key) - - auto_scaling_group_provider { - auto_scaling_group_arn = each.value.auto_scaling_group_arn - # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work - managed_termination_protection = length(try([each.value.managed_scaling], [])) == 0 ? "DISABLED" : try(each.value.managed_termination_protection, null) - - dynamic "managed_scaling" { - for_each = try([each.value.managed_scaling], []) - - content { - instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) - maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) - minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) - status = try(managed_scaling.value.status, null) - target_capacity = try(managed_scaling.value.target_capacity, null) - } - } - } - - tags = merge(var.tags, try(each.value.tags, {})) -} - -################################################################################ -# Task Execution - IAM Role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html -################################################################################ - -locals { - task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.cluster_name), "") - - create_task_exec_iam_role = var.create && var.create_task_exec_iam_role - create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy -} - -data "aws_iam_policy_document" "task_exec_assume" { - count = local.create_task_exec_iam_role ? 1 : 0 - - statement { - sid = "ECSTaskExecutionAssumeRole" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ecs-tasks.${data.aws_partition.current.dns_suffix}"] - } - } -} - -resource "aws_iam_role" "task_exec" { - count = local.create_task_exec_iam_role ? 1 : 0 - - name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name - name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null - path = var.task_exec_iam_role_path - description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.cluster_name}") - - assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json - permissions_boundary = var.task_exec_iam_role_permissions_boundary - force_detach_policies = true - - tags = merge(var.tags, var.task_exec_iam_role_tags) -} - -resource "aws_iam_role_policy_attachment" "task_exec_additional" { - for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } - - role = aws_iam_role.task_exec[0].name - policy_arn = each.value -} - -data "aws_iam_policy_document" "task_exec" { - count = local.create_task_exec_policy ? 1 : 0 - - # Pulled from AmazonECSTaskExecutionRolePolicy - statement { - sid = "Logs" - actions = [ - "logs:CreateLogStream", - "logs:PutLogEvents", - ] - resources = ["*"] - } - - # Pulled from AmazonECSTaskExecutionRolePolicy - statement { - sid = "ECR" - actions = [ - "ecr:GetAuthorizationToken", - "ecr:BatchCheckLayerAvailability", - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage", - ] - resources = ["*"] - } - - dynamic "statement" { - for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] - - content { - sid = "GetSSMParams" - actions = ["ssm:GetParameters"] - resources = var.task_exec_ssm_param_arns - } - } - - dynamic "statement" { - for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] - - content { - sid = "GetSecrets" - actions = ["secretsmanager:GetSecretValue"] - resources = var.task_exec_secret_arns - } - } - - dynamic "statement" { - for_each = var.task_exec_iam_statements - - content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) - - dynamic "principals" { - for_each = try(statement.value.principals, []) - - content { - type = principals.value.type - identifiers = principals.value.identifiers - } - } - - dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) - - content { - type = not_principals.value.type - identifiers = not_principals.value.identifiers - } - } - - dynamic "condition" { - for_each = try(statement.value.conditions, []) - - content { - test = condition.value.test - values = condition.value.values - variable = condition.value.variable - } - } - } - } -} - -resource "aws_iam_policy" "task_exec" { - count = local.create_task_exec_policy ? 1 : 0 - - name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name - name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null - description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") - policy = data.aws_iam_policy_document.task_exec[0].json - - tags = merge(var.tags, var.task_exec_iam_role_tags) -} - -resource "aws_iam_role_policy_attachment" "task_exec" { - count = local.create_task_exec_policy ? 1 : 0 - - role = aws_iam_role.task_exec[0].name - policy_arn = aws_iam_policy.task_exec[0].arn -} diff --git a/modules/ecs-monitoring/cluster/outputs.tf b/modules/ecs-monitoring/cluster/outputs.tf deleted file mode 100644 index 7ddfbb5d..00000000 --- a/modules/ecs-monitoring/cluster/outputs.tf +++ /dev/null @@ -1,70 +0,0 @@ -################################################################################ -# Cluster -################################################################################ - -output "arn" { - description = "ARN that identifies the cluster" - value = try(aws_ecs_cluster.this[0].arn, null) -} - -output "id" { - description = "ID that identifies the cluster" - value = try(aws_ecs_cluster.this[0].id, null) -} - -output "name" { - description = "Name that identifies the cluster" - value = try(aws_ecs_cluster.this[0].name, null) -} - -################################################################################ -# CloudWatch Log Group -################################################################################ - -output "cloudwatch_log_group_name" { - description = "Name of cloudwatch log group created" - value = try(aws_cloudwatch_log_group.this[0].name, null) -} - -output "cloudwatch_log_group_arn" { - description = "Arn of cloudwatch log group created" - value = try(aws_cloudwatch_log_group.this[0].arn, null) -} - -################################################################################ -# Cluster Capacity Providers -################################################################################ - -output "cluster_capacity_providers" { - description = "Map of cluster capacity providers attributes" - value = { for k, v in aws_ecs_cluster_capacity_providers.this : v.id => v } -} - -################################################################################ -# Capacity Provider - Autoscaling Group(s) -################################################################################ - -output "autoscaling_capacity_providers" { - description = "Map of autoscaling capacity providers created and their attributes" - value = aws_ecs_capacity_provider.this -} - -################################################################################ -# Task Execution - IAM Role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html -################################################################################ - -output "task_exec_iam_role_name" { - description = "Task execution IAM role name" - value = try(aws_iam_role.task_exec[0].name, null) -} - -output "task_exec_iam_role_arn" { - description = "Task execution IAM role ARN" - value = try(aws_iam_role.task_exec[0].arn, null) -} - -output "task_exec_iam_role_unique_id" { - description = "Stable and unique string identifying the task execution IAM role" - value = try(aws_iam_role.task_exec[0].unique_id, null) -} diff --git a/modules/ecs-monitoring/cluster/variables.tf b/modules/ecs-monitoring/cluster/variables.tf deleted file mode 100644 index 2b9a52b2..00000000 --- a/modules/ecs-monitoring/cluster/variables.tf +++ /dev/null @@ -1,169 +0,0 @@ -variable "create" { - description = "Determines whether resources will be created (affects all resources)" - type = bool - default = true -} - -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} -} - -################################################################################ -# Cluster -################################################################################ - -variable "cluster_name" { - description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" - type = string - default = "" -} - -variable "cluster_configuration" { - description = "The execute command configuration for the cluster" - type = any - default = {} -} - -variable "cluster_settings" { - description = "Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" - type = map(string) - default = { - name = "containerInsights" - value = "enabled" - } -} - -variable "cluster_service_connect_defaults" { - description = "Configures a default Service Connect namespace" - type = map(string) - default = {} -} - -################################################################################ -# CloudWatch Log Group -################################################################################ - -variable "create_cloudwatch_log_group" { - description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" - type = bool - default = true -} - -variable "cloudwatch_log_group_retention_in_days" { - description = "Number of days to retain log events" - type = number - default = 90 -} - -variable "cloudwatch_log_group_kms_key_id" { - description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" - type = string - default = null -} - -variable "cloudwatch_log_group_tags" { - description = "A map of additional tags to add to the log group created" - type = map(string) - default = {} -} - -################################################################################ -# Capacity Providers -################################################################################ - -variable "default_capacity_provider_use_fargate" { - description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" - type = bool - default = true -} - -variable "fargate_capacity_providers" { - description = "Map of Fargate capacity provider definitions to use for the cluster" - type = any - default = {} -} - -variable "autoscaling_capacity_providers" { - description = "Map of autoscaling capacity provider definitions to create for the cluster" - type = any - default = {} -} - -################################################################################ -# Task Execution - IAM Role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html -################################################################################ - -variable "create_task_exec_iam_role" { - description = "Determines whether the ECS task definition IAM role should be created" - type = bool - default = false -} - -variable "task_exec_iam_role_name" { - description = "Name to use on IAM role created" - type = string - default = null -} - -variable "task_exec_iam_role_use_name_prefix" { - description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" - type = bool - default = true -} - -variable "task_exec_iam_role_path" { - description = "IAM role path" - type = string - default = null -} - -variable "task_exec_iam_role_description" { - description = "Description of the role" - type = string - default = null -} - -variable "task_exec_iam_role_permissions_boundary" { - description = "ARN of the policy that is used to set the permissions boundary for the IAM role" - type = string - default = null -} - -variable "task_exec_iam_role_tags" { - description = "A map of additional tags to add to the IAM role created" - type = map(string) - default = {} -} - -variable "task_exec_iam_role_policies" { - description = "Map of IAM role policy ARNs to attach to the IAM role" - type = map(string) - default = {} -} - -variable "create_task_exec_policy" { - description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" - type = bool - default = true -} - -variable "task_exec_ssm_param_arns" { - description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" - type = list(string) - default = ["arn:aws:ssm:*:*:parameter/*"] -} - -variable "task_exec_secret_arns" { - description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" - type = list(string) - default = ["arn:aws:secretsmanager:*:*:secret:*"] -} - -variable "task_exec_iam_statements" { - description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} -} diff --git a/modules/ecs-monitoring/cluster/versions.tf b/modules/ecs-monitoring/cluster/versions.tf deleted file mode 100644 index 290d2218..00000000 --- a/modules/ecs-monitoring/cluster/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.55" - } - } -} diff --git a/modules/ecs-monitoring/container-definition/README.md b/modules/ecs-monitoring/container-definition/README.md deleted file mode 100644 index 71eea02b..00000000 --- a/modules/ecs-monitoring/container-definition/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# Amazon ECS Container Definition Module - -Configuration in this directory creates an Amazon ECS container definition. - -The module defaults to creating and utilizing a CloudWatch log group. You can disable this behavior by setting `enable_cloudwatch_logging` = `false` - useful for scenarios where Firelens is used for log forwarding. - -For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/design.md) - -## Usage - -### Standard - -```hcl -module "ecs_container_definition" { - source = "terraform-aws-modules/ecs/aws//modules/container-definition" - - name = "example" - cpu = 512 - memory = 1024 - essential = true - image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ - { - name = "ecs-sample" - containerPort = 80 - protocol = "tcp" - } - ] - - # Example image used requires access to write to root filesystem - readonly_root_filesystem = false - - memory_reservation = 100 - - tags = { - Environment = "dev" - Terraform = "true" - } -} -``` - -### W/ Firelens - -```hcl -module "fluentbit_ecs_container_definition" { - source = "terraform-aws-modules/ecs/aws//modules/container-definition" - name = "fluent-bit" - - cpu = 512 - memory = 1024 - essential = true - image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" - firelens_configuration = { - type = "fluentbit" - } - memory_reservation = 50 - - tags = { - Environment = "dev" - Terraform = "true" - } -} - -module "example_ecs_container_definition" { - source = "terraform-aws-modules/ecs/aws//modules/container-definition" - - name = "example" - cpu = 512 - memory = 1024 - essential = true - image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ - { - name = "ecs-sample" - containerPort = 80 - protocol = "tcp" - } - ] - - # Example image used requires access to write to root filesystem - readonly_root_filesystem = false - - dependencies = [{ - containerName = "fluent-bit" - condition = "START" - }] - - enable_cloudwatch_logging = false - log_configuration = { - logDriver = "awsfirelens" - options = { - Name = "firehose" - region = "eu-west-1" - delivery_stream = "my-stream" - log-driver-buffer-limit = "2097152" - } - } - memory_reservation = 100 - - tags = { - Environment = "dev" - Terraform = "true" - } -} -``` - -## Examples - -- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) -- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) -- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.55 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 4.55 | - -## Modules - -No modules. - -## Resources - -| Name | Type | -|------|------| -| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | -| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | -| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default is 30 days | `number` | `30` | no | -| [command](#input\_command) | The command that's passed to the container | `list(string)` | `[]` | no | -| [cpu](#input\_cpu) | The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value | `number` | `null` | no | -| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | -| [dependencies](#input\_dependencies) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
condition = string
containerName = string
}))
| `[]` | no | -| [disable\_networking](#input\_disable\_networking) | When this parameter is true, networking is disabled within the container | `bool` | `null` | no | -| [dns\_search\_domains](#input\_dns\_search\_domains) | Container DNS search domains. A list of DNS search domains that are presented to the container | `list(string)` | `[]` | no | -| [dns\_servers](#input\_dns\_servers) | Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers | `list(string)` | `[]` | no | -| [docker\_labels](#input\_docker\_labels) | A key/value map of labels to add to the container | `map(string)` | `{}` | no | -| [docker\_security\_options](#input\_docker\_security\_options) | A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type | `list(string)` | `[]` | no | -| [enable\_cloudwatch\_logging](#input\_enable\_cloudwatch\_logging) | Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers | `bool` | `true` | no | -| [entrypoint](#input\_entrypoint) | The entry point that is passed to the container | `list(string)` | `[]` | no | -| [environment](#input\_environment) | The environment variables to pass to the container |
list(object({
name = string
value = string
}))
| `[]` | no | -| [environment\_files](#input\_environment\_files) | A list of files containing the environment variables to pass to a container |
list(object({
value = string
type = string
}))
| `[]` | no | -| [essential](#input\_essential) | If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped | `bool` | `null` | no | -| [extra\_hosts](#input\_extra\_hosts) | A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container |
list(object({
hostname = string
ipAddress = string
}))
| `[]` | no | -| [firelens\_configuration](#input\_firelens\_configuration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide | `any` | `{}` | no | -| [health\_check](#input\_health\_check) | The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html) | `any` | `{}` | no | -| [hostname](#input\_hostname) | The hostname to use for your container | `string` | `null` | no | -| [image](#input\_image) | The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest` | `string` | `null` | no | -| [interactive](#input\_interactive) | When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated | `bool` | `false` | no | -| [links](#input\_links) | The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge` | `list(string)` | `[]` | no | -| [linux\_parameters](#input\_linux\_parameters) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | -| [log\_configuration](#input\_log\_configuration) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | -| [memory](#input\_memory) | The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified | `number` | `null` | no | -| [memory\_reservation](#input\_memory\_reservation) | The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance | `number` | `null` | no | -| [mount\_points](#input\_mount\_points) | The mount points for data volumes in your container | `list(any)` | `[]` | no | -| [name](#input\_name) | The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed | `string` | `null` | no | -| [operating\_system\_family](#input\_operating\_system\_family) | The OS family for task | `string` | `"LINUX"` | no | -| [port\_mappings](#input\_port\_mappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort | `list(any)` | `[]` | no | -| [privileged](#input\_privileged) | When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user) | `bool` | `false` | no | -| [pseudo\_terminal](#input\_pseudo\_terminal) | When this parameter is true, a `TTY` is allocated | `bool` | `false` | no | -| [readonly\_root\_filesystem](#input\_readonly\_root\_filesystem) | When this parameter is true, the container is given read-only access to its root file system | `bool` | `true` | no | -| [repository\_credentials](#input\_repository\_credentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials | `map(string)` | `{}` | no | -| [resource\_requirements](#input\_resource\_requirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU |
list(object({
type = string
value = string
}))
| `[]` | no | -| [secrets](#input\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide |
list(object({
name = string
valueFrom = string
}))
| `[]` | no | -| [service](#input\_service) | The name of the service that the container definition is associated with | `string` | `""` | no | -| [start\_timeout](#input\_start\_timeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | -| [stop\_timeout](#input\_stop\_timeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `120` | no | -| [system\_controls](#input\_system\_controls) | A list of namespaced kernel parameters to set in the container | `list(map(string))` | `[]` | no | -| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | -| [ulimits](#input\_ulimits) | A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker |
list(object({
hardLimit = number
name = string
softLimit = number
}))
| `[]` | no | -| [user](#input\_user) | The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set | `string` | `null` | no | -| [volumes\_from](#input\_volumes\_from) | Data volumes to mount from another container | `list(any)` | `[]` | no | -| [working\_directory](#input\_working\_directory) | The working directory to run commands inside the container | `string` | `null` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | -| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | -| [container\_definition](#output\_container\_definition) | Container definition | - - -## License - -Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs-monitoring/container-definition/main.tf b/modules/ecs-monitoring/container-definition/main.tf deleted file mode 100644 index 0091a818..00000000 --- a/modules/ecs-monitoring/container-definition/main.tf +++ /dev/null @@ -1,72 +0,0 @@ -data "aws_region" "current" {} - -locals { - is_not_windows = contains(["LINUX"], var.operating_system_family) - - log_configuration = merge( - { for k, v in { - logDriver = "awslogs", - options = { - awslogs-region = data.aws_region.current.name, - awslogs-group = try(aws_cloudwatch_log_group.this[0].name, ""), - awslogs-stream-prefix = "ecs" - }, - } : k => v if var.enable_cloudwatch_logging }, - var.log_configuration - ) - - definition = { - command = length(var.command) > 0 ? var.command : null - cpu = var.cpu - dependsOn = length(var.dependencies) > 0 ? var.dependencies : null # depends_on is a reserved word - disableNetworking = local.is_not_windows ? var.disable_networking : null - dnsSearchDomains = local.is_not_windows && length(var.dns_search_domains) > 0 ? var.dns_search_domains : null - dnsServers = local.is_not_windows && length(var.dns_servers) > 0 ? var.dns_servers : null - dockerLabels = length(var.docker_labels) > 0 ? var.docker_labels : null - dockerSecurityOptions = length(var.docker_security_options) > 0 ? var.docker_security_options : null - entrypoint = length(var.entrypoint) > 0 ? var.entrypoint : null - environment = var.environment - environmentFiles = length(var.environment_files) > 0 ? var.environment_files : null - essential = var.essential - extraHosts = local.is_not_windows && length(var.extra_hosts) > 0 ? var.extra_hosts : null - firelensConfiguration = length(var.firelens_configuration) > 0 ? var.firelens_configuration : null - healthCheck = length(var.health_check) > 0 ? var.health_check : null - hostname = var.hostname - image = var.image - interactive = var.interactive - links = local.is_not_windows && length(var.links) > 0 ? var.links : null - linuxParameters = local.is_not_windows && length(var.linux_parameters) > 0 ? var.linux_parameters : null - logConfiguration = length(local.log_configuration) > 0 ? local.log_configuration : null - memory = var.memory - memoryReservation = var.memory_reservation - mountPoints = var.mount_points - name = var.name - portMappings = var.port_mappings - privileged = local.is_not_windows ? var.privileged : null - pseudoTerminal = var.pseudo_terminal - readonlyRootFilesystem = local.is_not_windows ? var.readonly_root_filesystem : null - repositoryCredentials = length(var.repository_credentials) > 0 ? var.repository_credentials : null - resourceRequirements = length(var.resource_requirements) > 0 ? var.resource_requirements : null - secrets = length(var.secrets) > 0 ? var.secrets : null - startTimeout = var.start_timeout - stopTimeout = var.stop_timeout - systemControls = length(var.system_controls) > 0 ? var.system_controls : null - ulimits = local.is_not_windows && length(var.ulimits) > 0 ? var.ulimits : null - user = local.is_not_windows ? var.user : null - volumesFrom = var.volumes_from - workingDirectory = var.working_directory - } - - # Strip out all null values, ECS API will provide defaults in place of null/empty values - container_definition = { for k, v in local.definition : k => v if v != null } -} - -resource "aws_cloudwatch_log_group" "this" { - count = var.create_cloudwatch_log_group && var.enable_cloudwatch_logging ? 1 : 0 - - name = "/aws/ecs/${var.service}/${var.name}" - retention_in_days = var.cloudwatch_log_group_retention_in_days - kms_key_id = var.cloudwatch_log_group_kms_key_id - - tags = var.tags -} diff --git a/modules/ecs-monitoring/container-definition/outputs.tf b/modules/ecs-monitoring/container-definition/outputs.tf deleted file mode 100644 index 66a1d36d..00000000 --- a/modules/ecs-monitoring/container-definition/outputs.tf +++ /dev/null @@ -1,22 +0,0 @@ -################################################################################ -# Container Definition -################################################################################ - -output "container_definition" { - description = "Container definition" - value = local.container_definition -} - -################################################################################ -# CloudWatch Log Group -################################################################################ - -output "cloudwatch_log_group_name" { - description = "Name of cloudwatch log group created" - value = try(aws_cloudwatch_log_group.this[0].name, null) -} - -output "cloudwatch_log_group_arn" { - description = "Arn of cloudwatch log group created" - value = try(aws_cloudwatch_log_group.this[0].arn, null) -} diff --git a/modules/ecs-monitoring/container-definition/variables.tf b/modules/ecs-monitoring/container-definition/variables.tf deleted file mode 100644 index b7861c0e..00000000 --- a/modules/ecs-monitoring/container-definition/variables.tf +++ /dev/null @@ -1,305 +0,0 @@ -variable "operating_system_family" { - description = "The OS family for task" - type = string - default = "LINUX" -} - -################################################################################ -# Container Definition -################################################################################ - -variable "command" { - description = "The command that's passed to the container" - type = list(string) - default = [] -} - -variable "cpu" { - description = "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" - type = number - default = null -} - -variable "dependencies" { - description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" - type = list(object({ - condition = string - containerName = string - })) - default = [] -} - -variable "disable_networking" { - description = "When this parameter is true, networking is disabled within the container" - type = bool - default = null -} - -variable "dns_search_domains" { - description = "Container DNS search domains. A list of DNS search domains that are presented to the container" - type = list(string) - default = [] -} - -variable "dns_servers" { - description = "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" - type = list(string) - default = [] -} - -variable "docker_labels" { - description = "A key/value map of labels to add to the container" - type = map(string) - default = {} -} - -variable "docker_security_options" { - description = "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" - type = list(string) - default = [] -} - -variable "entrypoint" { - description = "The entry point that is passed to the container" - type = list(string) - default = [] -} - -variable "environment" { - description = "The environment variables to pass to the container" - type = list(object({ - name = string - value = string - })) - default = [] -} - -variable "environment_files" { - description = "A list of files containing the environment variables to pass to a container" - type = list(object({ - value = string - type = string - })) - default = [] -} - -variable "essential" { - description = "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" - type = bool - default = null -} - -variable "extra_hosts" { - description = "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" - type = list(object({ - hostname = string - ipAddress = string - })) - default = [] -} - -variable "firelens_configuration" { - description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" - type = any - default = {} -} - -variable "health_check" { - description = "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" - type = any - default = {} -} - -variable "hostname" { - description = "The hostname to use for your container" - type = string - default = null -} - -variable "image" { - description = "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" - type = string - default = null -} - -variable "interactive" { - description = "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" - type = bool - default = false -} - -variable "links" { - description = "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" - type = list(string) - default = [] -} - -variable "linux_parameters" { - description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" - type = any - default = {} -} - -variable "log_configuration" { - description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" - type = any - default = {} -} - -variable "memory" { - description = "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" - type = number - default = null -} - -variable "memory_reservation" { - description = "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" - type = number - default = null -} - -variable "mount_points" { - description = "The mount points for data volumes in your container" - type = list(any) - default = [] -} - -variable "name" { - description = "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" - type = string - default = null -} - -variable "port_mappings" { - description = "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" - type = list(any) - default = [] -} - -variable "privileged" { - description = "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" - type = bool - default = false -} - -variable "pseudo_terminal" { - description = "When this parameter is true, a `TTY` is allocated" - type = bool - default = false -} - -variable "readonly_root_filesystem" { - description = "When this parameter is true, the container is given read-only access to its root file system" - type = bool - default = true -} - -variable "repository_credentials" { - description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" - type = map(string) - default = {} -} - -variable "resource_requirements" { - description = "The type and amount of a resource to assign to a container. The only supported resource is a GPU" - type = list(object({ - type = string - value = string - })) - default = [] -} - -variable "secrets" { - description = "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" - type = list(object({ - name = string - valueFrom = string - })) - default = [] -} - -variable "start_timeout" { - description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" - type = number - default = 30 -} - -variable "stop_timeout" { - description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" - type = number - default = 120 -} - -variable "system_controls" { - description = "A list of namespaced kernel parameters to set in the container" - type = list(map(string)) - default = [] -} - -variable "ulimits" { - description = "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" - type = list(object({ - hardLimit = number - name = string - softLimit = number - })) - default = [] -} - -variable "user" { - description = "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" - type = string - default = null -} - -variable "volumes_from" { - description = "Data volumes to mount from another container" - type = list(any) - default = [] -} - -variable "working_directory" { - description = "The working directory to run commands inside the container" - type = string - default = null -} - -################################################################################ -# CloudWatch Log Group -################################################################################ - -variable "service" { - description = "The name of the service that the container definition is associated with" - type = string - default = "" -} - -variable "enable_cloudwatch_logging" { - description = "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" - type = bool - default = true -} - -variable "create_cloudwatch_log_group" { - description = "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" - type = bool - default = true -} - -variable "cloudwatch_log_group_retention_in_days" { - description = "Number of days to retain log events. Default is 30 days" - type = number - default = 30 -} - -variable "cloudwatch_log_group_kms_key_id" { - description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" - type = string - default = null -} - -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} -} diff --git a/modules/ecs-monitoring/container-definition/versions.tf b/modules/ecs-monitoring/container-definition/versions.tf deleted file mode 100644 index 290d2218..00000000 --- a/modules/ecs-monitoring/container-definition/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.55" - } - } -} diff --git a/modules/ecs-monitoring/service/README.md b/modules/ecs-monitoring/service/README.md deleted file mode 100644 index 3faa6e50..00000000 --- a/modules/ecs-monitoring/service/README.md +++ /dev/null @@ -1,351 +0,0 @@ -# Amazon ECS Service Module - -Configuration in this directory creates an Amazon ECS Service and associated resources. - -Some notable configurations to be aware of when using this module: -1. `desired_count`/`scale` is always ignored; the module is designed to utilize autoscaling by default (though it can be disabled) -2. The default configuration is intended for `FARGATE` use - -For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) - -### Logging - -Please refer to [FireLens examples repository](https://github.com/aws-samples/amazon-ecs-firelens-examples) for logging configuration examples for FireLens on Amazon ECS and AWS Fargate. - -## Usage - -```hcl -module "ecs_service" { - source = "terraform-aws-modules/ecs/aws//modules/service" - - name = "example" - cluster_arn = "arn:aws:ecs:us-west-2:123456789012:cluster/default" - - cpu = 1024 - memory = 4096 - - # Container definition(s) - container_definitions = { - - fluent-bit = { - cpu = 512 - memory = 1024 - essential = true - image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" - firelens_configuration = { - type = "fluentbit" - } - memory_reservation = 50 - } - - ecs-sample = { - cpu = 512 - memory = 1024 - essential = true - image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ - { - name = "ecs-sample" - containerPort = 80 - protocol = "tcp" - } - ] - - # Example image used requires access to write to root filesystem - readonly_root_filesystem = false - - dependencies = [{ - containerName = "fluent-bit" - condition = "START" - }] - - enable_cloudwatch_logging = false - log_configuration = { - logDriver = "awsfirelens" - options = { - Name = "firehose" - region = "eu-west-1" - delivery_stream = "my-stream" - log-driver-buffer-limit = "2097152" - } - } - memory_reservation = 100 - } - } - - service_connect_configuration = { - namespace = "example" - service = { - client_alias = { - port = 80 - dns_name = "ecs-sample" - } - port_name = "ecs-sample" - discovery_name = "ecs-sample" - } - } - - load_balancer = { - service = { - target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" - container_name = "ecs-sample" - container_port = 80 - } - } - - subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] - security_group_rules = { - alb_ingress_3000 = { - type = "ingress" - from_port = 80 - to_port = 80 - protocol = "tcp" - description = "Service port" - source_security_group_id = "sg-12345678" - } - egress_all = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } - } - - tags = { - Environment = "dev" - Terraform = "true" - } -} -``` - -## Conditional Creation - -The following values are provided to toggle on/off creation of the associated resources as desired: - -```hcl -module "ecs_service" { - source = "terraform-aws-modules/ecs/aws//modules/service" - - # Disable creation of service and all resources - create = false - - # Disable creation of the service IAM role; `iam_role_arn` should be provided - create_iam_role = false - - # Disable creation of the task definition; `task_definition_arn` should be provided - create_task_definition = false - - # Disable creation of the task execution IAM role; `task_exec_iam_role_arn` should be provided - create_task_exec_iam_role = false - - # Disable creation of the task execution IAM role policy - create_task_exec_policy = false - - # Disable creation of the tasks IAM role; `tasks_iam_role_arn` should be provided - create_tasks_iam_role = false - - # Disable creation of the service security group - create_security_group = false - - # ... omitted -} -``` - -## Examples - -- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) -- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) -- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.55 | - -## Providers - -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 4.55 | - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [container\_definition](#module\_container\_definition) | ../container-definition | n/a | - -## Resources - -| Name | Type | -|------|------| -| [aws_appautoscaling_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | -| [aws_appautoscaling_scheduled_action.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_scheduled_action) | resource | -| [aws_appautoscaling_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | -| [aws_ecs_service.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | -| [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | -| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | -| [aws_ecs_task_set.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | -| [aws_ecs_task_set.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | -| [aws_iam_policy.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_role.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | -| [aws_iam_role_policy_attachment.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | -| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | -| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | -| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_task_definition) | data source | -| [aws_iam_policy_document.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.service_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.tasks_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | -| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | -| [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [alarms](#input\_alarms) | Information about the CloudWatch alarms | `any` | `{}` | no | -| [assign\_public\_ip](#input\_assign\_public\_ip) | Assign a public IP address to the ENI (Fargate launch type only) | `bool` | `false` | no | -| [autoscaling\_max\_capacity](#input\_autoscaling\_max\_capacity) | Maximum number of tasks to run in your service | `number` | `10` | no | -| [autoscaling\_min\_capacity](#input\_autoscaling\_min\_capacity) | Minimum number of tasks to run in your service | `number` | `1` | no | -| [autoscaling\_policies](#input\_autoscaling\_policies) | Map of autoscaling policies to create for the service | `any` |
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
| no | -| [autoscaling\_scheduled\_actions](#input\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions to create for the service | `any` | `{}` | no | -| [capacity\_provider\_strategy](#input\_capacity\_provider\_strategy) | Capacity provider strategies to use for the service. Can be one or more | `any` | `{}` | no | -| [cluster\_arn](#input\_cluster\_arn) | ARN of the ECS cluster where the resources will be provisioned | `string` | `""` | no | -| [container\_definition\_defaults](#input\_container\_definition\_defaults) | A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions` | `any` | `{}` | no | -| [container\_definitions](#input\_container\_definitions) | A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document | `any` | `{}` | no | -| [cpu](#input\_cpu) | Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `1024` | no | -| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | -| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether the ECS service IAM role should be created | `bool` | `true` | no | -| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | -| [create\_task\_definition](#input\_create\_task\_definition) | Determines whether to create a task definition or use existing/provided | `bool` | `true` | no | -| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `true` | no | -| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | -| [create\_tasks\_iam\_role](#input\_create\_tasks\_iam\_role) | Determines whether the ECS tasks IAM role should be created | `bool` | `true` | no | -| [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Configuration block for deployment circuit breaker | `any` | `{}` | no | -| [deployment\_controller](#input\_deployment\_controller) | Configuration block for deployment controller configuration | `any` | `{}` | no | -| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment | `number` | `200` | no | -| [deployment\_minimum\_healthy\_percent](#input\_deployment\_minimum\_healthy\_percent) | Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `66` | no | -| [desired\_count](#input\_desired\_count) | Number of instances of the task definition to place and keep running. Defaults to `0` | `number` | `1` | no | -| [enable\_autoscaling](#input\_enable\_autoscaling) | Determines whether to enable autoscaling for the service | `bool` | `true` | no | -| [enable\_ecs\_managed\_tags](#input\_enable\_ecs\_managed\_tags) | Specifies whether to enable Amazon ECS managed tags for the tasks within the service | `bool` | `true` | no | -| [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | -| [ephemeral\_storage](#input\_ephemeral\_storage) | The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate | `any` | `{}` | no | -| [external\_id](#input\_external\_id) | The external ID associated with the task set | `string` | `null` | no | -| [family](#input\_family) | A unique name for your task definition | `string` | `null` | no | -| [force\_delete](#input\_force\_delete) | Whether to allow deleting the task set without waiting for scaling down to 0 | `bool` | `null` | no | -| [force\_new\_deployment](#input\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates | `bool` | `true` | no | -| [health\_check\_grace\_period\_seconds](#input\_health\_check\_grace\_period\_seconds) | Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers | `number` | `null` | no | -| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | -| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | -| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | -| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | -| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | -| [iam\_role\_statements](#input\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | -| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [ignore\_task\_definition\_changes](#input\_ignore\_task\_definition\_changes) | Whether changes to service `task_definition` changes should be ignored | `bool` | `false` | no | -| [inference\_accelerator](#input\_inference\_accelerator) | Configuration block(s) with Inference Accelerators settings | `any` | `{}` | no | -| [ipc\_mode](#input\_ipc\_mode) | IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none` | `string` | `null` | no | -| [launch\_type](#input\_launch\_type) | Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE` | `string` | `"FARGATE"` | no | -| [load\_balancer](#input\_load\_balancer) | Configuration block for load balancers | `any` | `{}` | no | -| [memory](#input\_memory) | Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `2048` | no | -| [name](#input\_name) | Name of the service (up to 255 letters, numbers, hyphens, and underscores) | `string` | `null` | no | -| [network\_mode](#input\_network\_mode) | Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host` | `string` | `"awsvpc"` | no | -| [ordered\_placement\_strategy](#input\_ordered\_placement\_strategy) | Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence | `any` | `{}` | no | -| [pid\_mode](#input\_pid\_mode) | Process namespace to use for the containers in the task. The valid values are `host` and `task` | `string` | `null` | no | -| [placement\_constraints](#input\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition | `any` | `{}` | no | -| [platform\_version](#input\_platform\_version) | Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST` | `string` | `null` | no | -| [propagate\_tags](#input\_propagate\_tags) | Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION` | `string` | `null` | no | -| [proxy\_configuration](#input\_proxy\_configuration) | Configuration block for the App Mesh proxy | `any` | `{}` | no | -| [requires\_compatibilities](#input\_requires\_compatibilities) | Set of launch types required by the task. The valid values are `EC2` and `FARGATE` | `list(string)` |
[
"FARGATE"
]
| no | -| [runtime\_platform](#input\_runtime\_platform) | Configuration block for `runtime_platform` that containers in your task may use | `any` |
{
"cpu_architecture": "X86_64",
"operating_system_family": "LINUX"
}
| no | -| [scale](#input\_scale) | A floating-point percentage of the desired number of tasks to place and keep running in the task set | `any` | `{}` | no | -| [scheduling\_strategy](#input\_scheduling\_strategy) | Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA` | `string` | `null` | no | -| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | -| [security\_group\_ids](#input\_security\_group\_ids) | List of security groups to associate with the task or service | `list(string)` | `[]` | no | -| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | -| [security\_group\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no | -| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | -| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | -| [service\_connect\_configuration](#input\_service\_connect\_configuration) | The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace | `any` | `{}` | no | -| [service\_registries](#input\_service\_registries) | Service discovery registries for the service | `any` | `{}` | no | -| [skip\_destroy](#input\_skip\_destroy) | If true, the task is not deleted when the service is deleted | `bool` | `null` | no | -| [subnet\_ids](#input\_subnet\_ids) | List of subnets to associate with the task or service | `list(string)` | `[]` | no | -| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | -| [task\_definition\_arn](#input\_task\_definition\_arn) | Existing task definition ARN. Required when `create_task_definition` is `false` | `string` | `null` | no | -| [task\_definition\_placement\_constraints](#input\_task\_definition\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service | `any` | `{}` | no | -| [task\_exec\_iam\_role\_arn](#input\_task\_exec\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | -| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | -| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | -| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | -| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | -| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | -| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | -| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | -| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | -| [task\_tags](#input\_task\_tags) | A map of additional tags to add to the task definition/set created | `map(string)` | `{}` | no | -| [tasks\_iam\_role\_arn](#input\_tasks\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | -| [tasks\_iam\_role\_description](#input\_tasks\_iam\_role\_description) | Description of the role | `string` | `null` | no | -| [tasks\_iam\_role\_name](#input\_tasks\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | -| [tasks\_iam\_role\_path](#input\_tasks\_iam\_role\_path) | IAM role path | `string` | `null` | no | -| [tasks\_iam\_role\_permissions\_boundary](#input\_tasks\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | -| [tasks\_iam\_role\_policies](#input\_tasks\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | -| [tasks\_iam\_role\_statements](#input\_tasks\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [tasks\_iam\_role\_tags](#input\_tasks\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | -| [tasks\_iam\_role\_use\_name\_prefix](#input\_tasks\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the service | `map(string)` | `{}` | no | -| [triggers](#input\_triggers) | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()` | `any` | `{}` | no | -| [volume](#input\_volume) | Configuration block for volumes that containers in your task may use | `any` | `{}` | no | -| [wait\_for\_steady\_state](#input\_wait\_for\_steady\_state) | If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false` | `bool` | `null` | no | -| [wait\_until\_stable](#input\_wait\_until\_stable) | Whether terraform should wait until the task set has reached `STEADY_STATE` | `bool` | `null` | no | -| [wait\_until\_stable\_timeout](#input\_wait\_until\_stable\_timeout) | Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or Āµs), `ms`, `s`, `m`, and `h`. Default `10m` | `number` | `null` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [autoscaling\_policies](#output\_autoscaling\_policies) | Map of autoscaling policies and their attributes | -| [autoscaling\_scheduled\_actions](#output\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | -| [container\_definitions](#output\_container\_definitions) | Container definitions | -| [iam\_role\_arn](#output\_iam\_role\_arn) | Service IAM role ARN | -| [iam\_role\_name](#output\_iam\_role\_name) | Service IAM role name | -| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | -| [id](#output\_id) | ARN that identifies the service | -| [name](#output\_name) | Name of the service | -| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | -| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | -| [task\_definition\_arn](#output\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | -| [task\_definition\_family](#output\_task\_definition\_family) | The unique name of the task definition | -| [task\_definition\_revision](#output\_task\_definition\_revision) | Revision of the task in a particular family | -| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | -| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | -| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | -| [task\_set\_arn](#output\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | -| [task\_set\_id](#output\_task\_set\_id) | The ID of the task set | -| [task\_set\_stability\_status](#output\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | -| [task\_set\_status](#output\_task\_set\_status) | The status of the task set | -| [tasks\_iam\_role\_arn](#output\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | -| [tasks\_iam\_role\_name](#output\_tasks\_iam\_role\_name) | Tasks IAM role name | -| [tasks\_iam\_role\_unique\_id](#output\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | - - -## License - -Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs-monitoring/service/main.tf b/modules/ecs-monitoring/service/main.tf deleted file mode 100644 index 0b9141ac..00000000 --- a/modules/ecs-monitoring/service/main.tf +++ /dev/null @@ -1,1329 +0,0 @@ -data "aws_region" "current" {} -data "aws_partition" "current" {} -data "aws_caller_identity" "current" {} - -locals { - account_id = data.aws_caller_identity.current.account_id - dns_suffix = data.aws_partition.current.dns_suffix - partition = data.aws_partition.current.partition - region = data.aws_region.current.name -} - -################################################################################ -# Service -################################################################################ - -locals { - # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-external.html - is_external_deployment = try(var.deployment_controller.type, null) == "EXTERNAL" - is_daemon = var.scheduling_strategy == "DAEMON" - is_fargate = var.launch_type == "FARGATE" - - # Flattened `network_configuration` - network_configuration = { - assign_public_ip = var.assign_public_ip - security_groups = flatten(concat([try(aws_security_group.this[0].id, [])], var.security_group_ids)) - subnets = var.subnet_ids - } -} - -resource "aws_ecs_service" "this" { - count = var.create && !var.ignore_task_definition_changes ? 1 : 0 - - dynamic "alarms" { - for_each = length(var.alarms) > 0 ? [var.alarms] : [] - - content { - alarm_names = alarms.value.alarm_names - enable = try(alarms.value.enable, true) - rollback = try(alarms.value.rollback, true) - } - } - - dynamic "capacity_provider_strategy" { - # Set by task set if deployment controller is external - for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } - - content { - base = try(capacity_provider_strategy.value.base, null) - capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) - } - } - - cluster = var.cluster_arn - - dynamic "deployment_circuit_breaker" { - for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] - - content { - enable = deployment_circuit_breaker.value.enable - rollback = deployment_circuit_breaker.value.rollback - } - } - - dynamic "deployment_controller" { - for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] - - content { - type = try(deployment_controller.value.type, null) - } - } - - deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent - deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent - desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count - enable_ecs_managed_tags = var.enable_ecs_managed_tags - enable_execute_command = var.enable_execute_command - force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment - health_check_grace_period_seconds = var.health_check_grace_period_seconds - iam_role = local.iam_role_arn - launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type - - dynamic "load_balancer" { - # Set by task set if deployment controller is external - for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } - - content { - container_name = load_balancer.value.container_name - container_port = load_balancer.value.container_port - elb_name = try(load_balancer.value.elb_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) - } - } - - name = var.name - - dynamic "network_configuration" { - # Set by task set if deployment controller is external - for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] - - content { - assign_public_ip = network_configuration.value.assign_public_ip - security_groups = network_configuration.value.security_groups - subnets = network_configuration.value.subnets - } - } - - dynamic "ordered_placement_strategy" { - for_each = var.ordered_placement_strategy - - content { - field = try(ordered_placement_strategy.value.field, null) - type = ordered_placement_strategy.value.type - } - } - - dynamic "placement_constraints" { - for_each = var.placement_constraints - - content { - expression = try(placement_constraints.value.expression, null) - type = placement_constraints.value.type - } - } - - # Set by task set if deployment controller is external - platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null - scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy - - dynamic "service_connect_configuration" { - for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] - - content { - enabled = try(service_connect_configuration.value.enabled, true) - - dynamic "log_configuration" { - for_each = try([service_connect_configuration.value.log_configuration], []) - - content { - log_driver = try(log_configuration.value.log_driver, null) - options = try(log_configuration.value.options, null) - - dynamic "secret_option" { - for_each = try(log_configuration.value.secret_option, []) - - content { - name = secret_option.value.name - value_from = secret_option.value.value_from - } - } - } - } - - namespace = lookup(service_connect_configuration.value, "namespace", null) - - dynamic "service" { - for_each = try([service_connect_configuration.value.service], []) - - content { - - dynamic "client_alias" { - for_each = try([service.value.client_alias], []) - - content { - dns_name = try(client_alias.value.dns_name, null) - port = client_alias.value.port - } - } - - discovery_name = try(service.value.discovery_name, null) - ingress_port_override = try(service.value.ingress_port_override, null) - port_name = service.value.port_name - } - } - } - } - - dynamic "service_registries" { - # Set by task set if deployment controller is external - for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] - - content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) - registry_arn = service_registries.value.registry_arn - } - } - - task_definition = local.task_definition - triggers = var.triggers - wait_for_steady_state = var.wait_for_steady_state - - propagate_tags = var.propagate_tags - tags = var.tags - - timeouts { - create = try(var.timeouts.create, null) - update = try(var.timeouts.update, null) - delete = try(var.timeouts.delete, null) - } - - depends_on = [aws_iam_role_policy_attachment.service] - - lifecycle { - ignore_changes = [ - desired_count, # Always ignored - ] - } -} - -################################################################################ -# Service - Ignore `task_definition` -################################################################################ - -resource "aws_ecs_service" "ignore_task_definition" { - count = var.create && var.ignore_task_definition_changes ? 1 : 0 - - dynamic "alarms" { - for_each = length(var.alarms) > 0 ? [var.alarms] : [] - - content { - alarm_names = alarms.value.alarm_names - enable = try(alarms.value.enable, true) - rollback = try(alarms.value.rollback, true) - } - } - - dynamic "capacity_provider_strategy" { - # Set by task set if deployment controller is external - for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } - - content { - base = try(capacity_provider_strategy.value.base, null) - capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) - } - } - - cluster = var.cluster_arn - - dynamic "deployment_circuit_breaker" { - for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] - - content { - enable = deployment_circuit_breaker.value.enable - rollback = deployment_circuit_breaker.value.rollback - } - } - - dynamic "deployment_controller" { - for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] - - content { - type = try(deployment_controller.value.type, null) - } - } - - deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent - deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent - desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count - enable_ecs_managed_tags = var.enable_ecs_managed_tags - enable_execute_command = var.enable_execute_command - force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment - health_check_grace_period_seconds = var.health_check_grace_period_seconds - iam_role = local.iam_role_arn - launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type - - dynamic "load_balancer" { - # Set by task set if deployment controller is external - for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } - - content { - container_name = load_balancer.value.container_name - container_port = load_balancer.value.container_port - elb_name = try(load_balancer.value.elb_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) - } - } - - name = var.name - - dynamic "network_configuration" { - # Set by task set if deployment controller is external - for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] - - content { - assign_public_ip = network_configuration.value.assign_public_ip - security_groups = network_configuration.value.security_groups - subnets = network_configuration.value.subnets - } - } - - dynamic "ordered_placement_strategy" { - for_each = var.ordered_placement_strategy - - content { - field = try(ordered_placement_strategy.value.field, null) - type = ordered_placement_strategy.value.type - } - } - - dynamic "placement_constraints" { - for_each = var.placement_constraints - - content { - expression = try(placement_constraints.value.expression, null) - type = placement_constraints.value.type - } - } - - # Set by task set if deployment controller is external - platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null - scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy - - dynamic "service_connect_configuration" { - for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] - - content { - enabled = try(service_connect_configuration.value.enabled, true) - - dynamic "log_configuration" { - for_each = try([service_connect_configuration.value.log_configuration], []) - - content { - log_driver = try(log_configuration.value.log_driver, null) - options = try(log_configuration.value.options, null) - - dynamic "secret_option" { - for_each = try(log_configuration.value.secret_option, []) - - content { - name = secret_option.value.name - value_from = secret_option.value.value_from - } - } - } - } - - namespace = lookup(service_connect_configuration.value, "namespace", null) - - dynamic "service" { - for_each = try([service_connect_configuration.value.service], []) - - content { - - dynamic "client_alias" { - for_each = try([service.value.client_alias], []) - - content { - dns_name = try(client_alias.value.dns_name, null) - port = client_alias.value.port - } - } - - discovery_name = try(service.value.discovery_name, null) - ingress_port_override = try(service.value.ingress_port_override, null) - port_name = service.value.port_name - } - } - } - } - - dynamic "service_registries" { - # Set by task set if deployment controller is external - for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] - - content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) - registry_arn = service_registries.value.registry_arn - } - } - - task_definition = local.task_definition - triggers = var.triggers - wait_for_steady_state = var.wait_for_steady_state - - propagate_tags = var.propagate_tags - tags = var.tags - - timeouts { - create = try(var.timeouts.create, null) - update = try(var.timeouts.update, null) - delete = try(var.timeouts.delete, null) - } - - depends_on = [aws_iam_role_policy_attachment.service] - - lifecycle { - ignore_changes = [ - desired_count, # Always ignored - task_definition, - load_balancer, - ] - } -} - -################################################################################ -# Service - IAM Role -################################################################################ - -locals { - # Role is not required if task definition uses `awsvpc` network mode or if a load balancer is not used - needs_iam_role = var.network_mode != "awsvpc" && length(var.load_balancer) > 0 - create_iam_role = var.create && var.create_iam_role && local.needs_iam_role - iam_role_arn = local.needs_iam_role ? try(aws_iam_role.service[0].arn, var.iam_role_arn) : null - - iam_role_name = try(coalesce(var.iam_role_name, var.name), "") -} - -data "aws_iam_policy_document" "service_assume" { - count = local.create_iam_role ? 1 : 0 - - statement { - sid = "ECSServiceAssumeRole" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ecs.${local.dns_suffix}"] - } - } -} - -resource "aws_iam_role" "service" { - count = local.create_iam_role ? 1 : 0 - - name = var.iam_role_use_name_prefix ? null : local.iam_role_name - name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null - path = var.iam_role_path - description = var.iam_role_description - - assume_role_policy = data.aws_iam_policy_document.service_assume[0].json - permissions_boundary = var.iam_role_permissions_boundary - force_detach_policies = true - - tags = merge(var.tags, var.iam_role_tags) -} - -data "aws_iam_policy_document" "service" { - count = local.create_iam_role ? 1 : 0 - - statement { - sid = "ECSService" - resources = ["*"] - - actions = [ - "ec2:Describe*", - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", - "elasticloadbalancing:DeregisterTargets", - "elasticloadbalancing:Describe*", - "elasticloadbalancing:RegisterInstancesWithLoadBalancer", - "elasticloadbalancing:RegisterTargets" - ] - } - - dynamic "statement" { - for_each = var.iam_role_statements - - content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) - - dynamic "principals" { - for_each = try(statement.value.principals, []) - - content { - type = principals.value.type - identifiers = principals.value.identifiers - } - } - - dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) - - content { - type = not_principals.value.type - identifiers = not_principals.value.identifiers - } - } - - dynamic "condition" { - for_each = try(statement.value.conditions, []) - - content { - test = condition.value.test - values = condition.value.values - variable = condition.value.variable - } - } - } - } -} - -resource "aws_iam_policy" "service" { - count = local.create_iam_role ? 1 : 0 - - name = var.iam_role_use_name_prefix ? null : local.iam_role_name - name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null - description = coalesce(var.iam_role_description, "ECS service policy that allows Amazon ECS to make calls to your load balancer on your behalf") - policy = data.aws_iam_policy_document.service[0].json - - tags = merge(var.tags, var.iam_role_tags) -} - -resource "aws_iam_role_policy_attachment" "service" { - count = local.create_iam_role ? 1 : 0 - - role = aws_iam_role.service[0].name - policy_arn = aws_iam_policy.service[0].arn -} - -################################################################################ -# Container Definition -################################################################################ - -module "container_definition" { - source = "../container-definition" - - for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition } - - operating_system_family = try(var.runtime_platform.operating_system_family, "LINUX") - - # Container Definition - command = try(each.value.command, var.container_definition_defaults.command, []) - cpu = try(each.value.cpu, var.container_definition_defaults.cpu, null) - dependencies = try(each.value.dependencies, var.container_definition_defaults.dependencies, []) # depends_on is a reserved word - disable_networking = try(each.value.disable_networking, var.container_definition_defaults.disable_networking, null) - dns_search_domains = try(each.value.dns_search_domains, var.container_definition_defaults.dns_search_domains, []) - dns_servers = try(each.value.dns_servers, var.container_definition_defaults.dns_servers, []) - docker_labels = try(each.value.docker_labels, var.container_definition_defaults.docker_labels, {}) - docker_security_options = try(each.value.docker_security_options, var.container_definition_defaults.docker_security_options, []) - entrypoint = try(each.value.entrypoint, var.container_definition_defaults.entrypoint, []) - environment = try(each.value.environment, var.container_definition_defaults.environment, []) - environment_files = try(each.value.environment_files, var.container_definition_defaults.environment_files, []) - essential = try(each.value.essential, var.container_definition_defaults.essential, null) - extra_hosts = try(each.value.extra_hosts, var.container_definition_defaults.extra_hosts, []) - firelens_configuration = try(each.value.firelens_configuration, var.container_definition_defaults.firelens_configuration, {}) - health_check = try(each.value.health_check, var.container_definition_defaults.health_check, {}) - hostname = try(each.value.hostname, var.container_definition_defaults.hostname, null) - image = try(each.value.image, var.container_definition_defaults.image, null) - interactive = try(each.value.interactive, var.container_definition_defaults.interactive, false) - links = try(each.value.links, var.container_definition_defaults.links, []) - linux_parameters = try(each.value.linux_parameters, var.container_definition_defaults.linux_parameters, {}) - log_configuration = try(each.value.log_configuration, var.container_definition_defaults.log_configuration, {}) - memory = try(each.value.memory, var.container_definition_defaults.memory, null) - memory_reservation = try(each.value.memory_reservation, var.container_definition_defaults.memory_reservation, null) - mount_points = try(each.value.mount_points, var.container_definition_defaults.mount_points, []) - name = try(each.value.name, each.key) - port_mappings = try(each.value.port_mappings, var.container_definition_defaults.port_mappings, []) - privileged = try(each.value.privileged, var.container_definition_defaults.privileged, false) - pseudo_terminal = try(each.value.pseudo_terminal, var.container_definition_defaults.pseudo_terminal, false) - readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.container_definition_defaults.readonly_root_filesystem, true) - repository_credentials = try(each.value.repository_credentials, var.container_definition_defaults.repository_credentials, {}) - resource_requirements = try(each.value.resource_requirements, var.container_definition_defaults.resource_requirements, []) - secrets = try(each.value.secrets, var.container_definition_defaults.secrets, []) - start_timeout = try(each.value.start_timeout, var.container_definition_defaults.start_timeout, 30) - stop_timeout = try(each.value.stop_timeout, var.container_definition_defaults.stop_timeout, 120) - system_controls = try(each.value.system_controls, var.container_definition_defaults.system_controls, []) - ulimits = try(each.value.ulimits, var.container_definition_defaults.ulimits, []) - user = try(each.value.user, var.container_definition_defaults.user, 0) - volumes_from = try(each.value.volumes_from, var.container_definition_defaults.volumes_from, []) - working_directory = try(each.value.working_directory, var.container_definition_defaults.working_directory, null) - - # CloudWatch Log Group - service = var.name - enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.container_definition_defaults.enable_cloudwatch_logging, true) - create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.container_definition_defaults.create_cloudwatch_log_group, true) - cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.container_definition_defaults.cloudwatch_log_group_retention_in_days, 14) - cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.container_definition_defaults.cloudwatch_log_group_kms_key_id, null) - - tags = var.tags -} - -################################################################################ -# Task Definition -################################################################################ - -locals { - create_task_definition = var.create && var.create_task_definition - - # This allows us to query both the existing as well as Terraform's state and get - # and get the max version of either source, useful for when external resources - # update the container definition - max_task_def_revision = local.create_task_definition ? max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision) : 0 - task_definition = local.create_task_definition ? "${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}" : var.task_definition_arn -} - -# This allows us to query both the existing as well as Terraform's state and get -# and get the max version of either source, useful for when external resources -# update the container definition -data "aws_ecs_task_definition" "this" { - count = local.create_task_definition ? 1 : 0 - - task_definition = aws_ecs_task_definition.this[0].family - - depends_on = [ - # Needs to exist first on first deployment - aws_ecs_task_definition.this - ] -} - -resource "aws_ecs_task_definition" "this" { - count = local.create_task_definition ? 1 : 0 - - # Convert map of maps to array of maps before JSON encoding - container_definitions = jsonencode([for k, v in module.container_definition : v.container_definition]) - cpu = var.cpu - - dynamic "ephemeral_storage" { - for_each = length(var.ephemeral_storage) > 0 ? [var.ephemeral_storage] : [] - - content { - size_in_gib = ephemeral_storage.value.size_in_gib - } - } - - execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn) - family = coalesce(var.family, var.name) - - dynamic "inference_accelerator" { - for_each = var.inference_accelerator - - content { - device_name = inference_accelerator.value.device_name - device_type = inference_accelerator.value.device_type - } - } - - ipc_mode = var.ipc_mode - memory = var.memory - network_mode = var.network_mode - pid_mode = var.pid_mode - - dynamic "placement_constraints" { - for_each = var.task_definition_placement_constraints - - content { - expression = try(placement_constraints.value.expression, null) - type = placement_constraints.value.type - } - } - - dynamic "proxy_configuration" { - for_each = length(var.proxy_configuration) > 0 ? [var.proxy_configuration] : [] - - content { - container_name = proxy_configuration.value.container_name - properties = try(proxy_configuration.value.properties, null) - type = try(proxy_configuration.value.type, null) - } - } - - requires_compatibilities = var.requires_compatibilities - - dynamic "runtime_platform" { - for_each = length(var.runtime_platform) > 0 ? [var.runtime_platform] : [] - - content { - cpu_architecture = try(runtime_platform.value.cpu_architecture, null) - operating_system_family = try(runtime_platform.value.operating_system_family, null) - } - } - - skip_destroy = var.skip_destroy - task_role_arn = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn) - - dynamic "volume" { - for_each = var.volume - - content { - dynamic "docker_volume_configuration" { - for_each = try([volume.value.docker_volume_configuration], []) - - content { - autoprovision = try(docker_volume_configuration.value.autoprovision, null) - driver = try(docker_volume_configuration.value.driver, null) - driver_opts = try(docker_volume_configuration.value.driver_opts, null) - labels = try(docker_volume_configuration.value.labels, null) - scope = try(docker_volume_configuration.value.scope, null) - } - } - - dynamic "efs_volume_configuration" { - for_each = try([volume.value.efs_volume_configuration], []) - - content { - dynamic "authorization_config" { - for_each = try([efs_volume_configuration.value.authorization_config], []) - - content { - access_point_id = try(authorization_config.value.access_point_id, null) - iam = try(authorization_config.value.iam, null) - } - } - - file_system_id = efs_volume_configuration.value.file_system_id - root_directory = try(efs_volume_configuration.value.root_directory, null) - transit_encryption = try(efs_volume_configuration.value.transit_encryption, null) - transit_encryption_port = try(efs_volume_configuration.value.transit_encryption_port, null) - } - } - - dynamic "fsx_windows_file_server_volume_configuration" { - for_each = try([volume.value.fsx_windows_file_server_volume_configuration], []) - - content { - dynamic "authorization_config" { - for_each = try([fsx_windows_file_server_volume_configuration.value.authorization_config], []) - - content { - credentials_parameter = authorization_config.value.credentials_parameter - domain = authorization_config.value.domain - } - } - - file_system_id = fsx_windows_file_server_volume_configuration.value.file_system_id - root_directory = fsx_windows_file_server_volume_configuration.value.root_directory - } - } - - host_path = try(volume.value.host_path, null) - name = try(volume.value.name, volume.key) - } - } - - tags = merge(var.tags, var.task_tags) - - lifecycle { - create_before_destroy = true - } -} - -################################################################################ -# Task Execution - IAM Role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html -################################################################################ - -locals { - task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") - - create_task_exec_iam_role = local.create_task_definition && var.create_task_exec_iam_role - create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy -} - -data "aws_iam_policy_document" "task_exec_assume" { - count = local.create_task_exec_iam_role ? 1 : 0 - - statement { - sid = "ECSTaskExecutionAssumeRole" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ecs-tasks.${local.dns_suffix}"] - } - } -} - -resource "aws_iam_role" "task_exec" { - count = local.create_task_exec_iam_role ? 1 : 0 - - name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name - name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null - path = var.task_exec_iam_role_path - description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${local.task_exec_iam_role_name}") - - assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json - permissions_boundary = var.task_exec_iam_role_permissions_boundary - force_detach_policies = true - - tags = merge(var.tags, var.task_exec_iam_role_tags) -} - -resource "aws_iam_role_policy_attachment" "task_exec_additional" { - for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } - - role = aws_iam_role.task_exec[0].name - policy_arn = each.value -} - -data "aws_iam_policy_document" "task_exec" { - count = local.create_task_exec_policy ? 1 : 0 - - # Pulled from AmazonECSTaskExecutionRolePolicy - statement { - sid = "Logs" - actions = [ - "logs:CreateLogStream", - "logs:PutLogEvents", - ] - resources = ["*"] - } - - # Pulled from AmazonECSTaskExecutionRolePolicy - statement { - sid = "ECR" - actions = [ - "ecr:GetAuthorizationToken", - "ecr:BatchCheckLayerAvailability", - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage", - ] - resources = ["*"] - } - - dynamic "statement" { - for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] - - content { - sid = "GetSSMParams" - actions = ["ssm:GetParameters"] - resources = var.task_exec_ssm_param_arns - } - } - - dynamic "statement" { - for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] - - content { - sid = "GetSecrets" - actions = ["secretsmanager:GetSecretValue"] - resources = var.task_exec_secret_arns - } - } - - dynamic "statement" { - for_each = var.task_exec_iam_statements - - content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) - - dynamic "principals" { - for_each = try(statement.value.principals, []) - - content { - type = principals.value.type - identifiers = principals.value.identifiers - } - } - - dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) - - content { - type = not_principals.value.type - identifiers = not_principals.value.identifiers - } - } - - dynamic "condition" { - for_each = try(statement.value.conditions, []) - - content { - test = condition.value.test - values = condition.value.values - variable = condition.value.variable - } - } - } - } -} - -resource "aws_iam_policy" "task_exec" { - count = local.create_task_exec_policy ? 1 : 0 - - name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name - name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null - description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") - policy = data.aws_iam_policy_document.task_exec[0].json - - tags = merge(var.tags, var.task_exec_iam_role_tags) -} - -resource "aws_iam_role_policy_attachment" "task_exec" { - count = local.create_task_exec_policy ? 1 : 0 - - role = aws_iam_role.task_exec[0].name - policy_arn = aws_iam_policy.task_exec[0].arn -} - -################################################################################ -# Tasks - IAM role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html -################################################################################ - -locals { - tasks_iam_role_name = try(coalesce(var.tasks_iam_role_name, var.name), "") - create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role -} - -data "aws_iam_policy_document" "tasks_assume" { - count = local.create_tasks_iam_role ? 1 : 0 - - statement { - sid = "ECSTasksAssumeRole" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ecs-tasks.${local.dns_suffix}"] - } - - # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role - condition { - test = "ArnLike" - variable = "aws:SourceArn" - values = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:*"] - } - - condition { - test = "StringEquals" - variable = "aws:SourceAccount" - values = [local.account_id] - } - } -} - -resource "aws_iam_role" "tasks" { - count = local.create_tasks_iam_role ? 1 : 0 - - name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name - name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null - path = var.tasks_iam_role_path - description = var.tasks_iam_role_description - - assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json - permissions_boundary = var.tasks_iam_role_permissions_boundary - force_detach_policies = true - - tags = merge(var.tags, var.tasks_iam_role_tags) -} - -resource "aws_iam_role_policy_attachment" "tasks" { - for_each = { for k, v in var.tasks_iam_role_policies : k => v if local.create_tasks_iam_role } - - role = aws_iam_role.tasks[0].name - policy_arn = each.value -} - -data "aws_iam_policy_document" "tasks" { - count = local.create_tasks_iam_role && length(var.tasks_iam_role_statements) > 0 ? 1 : 0 - - dynamic "statement" { - for_each = var.tasks_iam_role_statements - - content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) - - dynamic "principals" { - for_each = try(statement.value.principals, []) - - content { - type = principals.value.type - identifiers = principals.value.identifiers - } - } - - dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) - - content { - type = not_principals.value.type - identifiers = not_principals.value.identifiers - } - } - - dynamic "condition" { - for_each = try(statement.value.conditions, []) - - content { - test = condition.value.test - values = condition.value.values - variable = condition.value.variable - } - } - } - } -} - -resource "aws_iam_role_policy" "tasks" { - count = local.create_tasks_iam_role && length(var.tasks_iam_role_statements) > 0 ? 1 : 0 - - name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name - name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null - policy = data.aws_iam_policy_document.tasks[0].json - role = aws_iam_role.tasks[0].id -} - -################################################################################ -# Task Set -################################################################################ - -resource "aws_ecs_task_set" "this" { - # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html - count = local.create_task_definition && local.is_external_deployment && !var.ignore_task_definition_changes ? 1 : 0 - - service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) - cluster = var.cluster_arn - external_id = var.external_id - task_definition = local.task_definition - - dynamic "network_configuration" { - for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] - - content { - assign_public_ip = network_configuration.value.assign_public_ip - security_groups = network_configuration.value.security_groups - subnets = network_configuration.value.subnets - } - } - - dynamic "load_balancer" { - for_each = var.load_balancer - - content { - load_balancer_name = try(load_balancer.value.load_balancer_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) - container_name = load_balancer.value.container_name - container_port = try(load_balancer.value.container_port, null) - } - } - - dynamic "service_registries" { - for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] - - content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) - registry_arn = service_registries.value.registry_arn - } - } - - launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type - - dynamic "capacity_provider_strategy" { - for_each = var.capacity_provider_strategy - - content { - base = try(capacity_provider_strategy.value.base, null) - capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) - } - } - - platform_version = local.is_fargate ? var.platform_version : null - - dynamic "scale" { - for_each = length(var.scale) > 0 ? [var.scale] : [] - - content { - unit = try(scale.value.unit, null) - value = try(scale.value.value, null) - } - } - - force_delete = var.force_delete - wait_until_stable = var.wait_until_stable - wait_until_stable_timeout = var.wait_until_stable_timeout - - tags = merge(var.tags, var.task_tags) - - lifecycle { - ignore_changes = [ - scale, # Always ignored - ] - } -} - -################################################################################ -# Task Set - Ignore `task_definition` -################################################################################ - -resource "aws_ecs_task_set" "ignore_task_definition" { - # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html - count = local.create_task_definition && local.is_external_deployment && var.ignore_task_definition_changes ? 1 : 0 - - service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) - cluster = var.cluster_arn - external_id = var.external_id - task_definition = local.task_definition - - dynamic "network_configuration" { - for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] - - content { - assign_public_ip = network_configuration.value.assign_public_ip - security_groups = network_configuration.value.security_groups - subnets = network_configuration.value.subnets - } - } - - dynamic "load_balancer" { - for_each = var.load_balancer - - content { - load_balancer_name = try(load_balancer.value.load_balancer_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) - container_name = load_balancer.value.container_name - container_port = try(load_balancer.value.container_port, null) - } - } - - dynamic "service_registries" { - for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] - - content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) - registry_arn = service_registries.value.registry_arn - } - } - - launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type - - dynamic "capacity_provider_strategy" { - for_each = var.capacity_provider_strategy - - content { - base = try(capacity_provider_strategy.value.base, null) - capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) - } - } - - platform_version = local.is_fargate ? var.platform_version : null - - dynamic "scale" { - for_each = length(var.scale) > 0 ? [var.scale] : [] - - content { - unit = try(scale.value.unit, null) - value = try(scale.value.value, null) - } - } - - force_delete = var.force_delete - wait_until_stable = var.wait_until_stable - wait_until_stable_timeout = var.wait_until_stable_timeout - - tags = merge(var.tags, var.task_tags) - - lifecycle { - ignore_changes = [ - scale, # Always ignored - task_definition, - ] - } -} - -################################################################################ -# Autoscaling -################################################################################ - -locals { - enable_autoscaling = var.create && var.enable_autoscaling && !local.is_daemon - - cluster_name = element(split("/", var.cluster_arn), 1) -} - -resource "aws_appautoscaling_target" "this" { - count = local.enable_autoscaling ? 1 : 0 - - # Desired needs to be between or equal to min/max - min_capacity = min(var.autoscaling_min_capacity, var.desired_count) - max_capacity = max(var.autoscaling_max_capacity, var.desired_count) - - resource_id = "service/${local.cluster_name}/${try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name)}" - scalable_dimension = "ecs:service:DesiredCount" - service_namespace = "ecs" -} - -resource "aws_appautoscaling_policy" "this" { - for_each = { for k, v in var.autoscaling_policies : k => v if local.enable_autoscaling } - - name = try(each.value.name, each.key) - policy_type = try(each.value.policy_type, "TargetTrackingScaling") - resource_id = aws_appautoscaling_target.this[0].resource_id - scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension - service_namespace = aws_appautoscaling_target.this[0].service_namespace - - dynamic "step_scaling_policy_configuration" { - for_each = try([each.value.step_scaling_policy_configuration], []) - - content { - adjustment_type = try(step_scaling_policy_configuration.value.adjustment_type, null) - cooldown = try(step_scaling_policy_configuration.value.cooldown, null) - metric_aggregation_type = try(step_scaling_policy_configuration.value.metric_aggregation_type, null) - min_adjustment_magnitude = try(step_scaling_policy_configuration.value.min_adjustment_magnitude, null) - - dynamic "step_adjustment" { - for_each = try(step_scaling_policy_configuration.value.step_adjustment, []) - - content { - metric_interval_lower_bound = try(step_adjustment.value.metric_interval_lower_bound, null) - metric_interval_upper_bound = try(step_adjustment.value.metric_interval_upper_bound, null) - scaling_adjustment = try(step_adjustment.value.scaling_adjustment, null) - } - } - } - } - - dynamic "target_tracking_scaling_policy_configuration" { - for_each = try(each.value.policy_type, null) == "TargetTrackingScaling" ? try([each.value.target_tracking_scaling_policy_configuration], []) : [] - - content { - dynamic "customized_metric_specification" { - for_each = try([target_tracking_scaling_policy_configuration.value.customized_metric_specification], []) - - content { - dynamic "dimensions" { - for_each = try(customized_metric_specification.value.dimensions, []) - - content { - name = dimensions.value.name - value = dimensions.value.value - } - } - - metric_name = customized_metric_specification.value.metric_name - namespace = customized_metric_specification.value.namespace - statistic = customized_metric_specification.value.statistic - unit = try(customized_metric_specification.value.unit, null) - } - } - - disable_scale_in = try(target_tracking_scaling_policy_configuration.value.disable_scale_in, null) - - dynamic "predefined_metric_specification" { - for_each = try([target_tracking_scaling_policy_configuration.value.predefined_metric_specification], []) - - content { - predefined_metric_type = predefined_metric_specification.value.predefined_metric_type - resource_label = try(predefined_metric_specification.value.resource_label, null) - } - } - - scale_in_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_in_cooldown, 300) - scale_out_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_out_cooldown, 60) - target_value = try(target_tracking_scaling_policy_configuration.value.target_value, 75) - } - } -} - -resource "aws_appautoscaling_scheduled_action" "this" { - for_each = { for k, v in var.autoscaling_scheduled_actions : k => v if local.enable_autoscaling } - - name = try(each.value.name, each.key) - service_namespace = aws_appautoscaling_target.this[0].service_namespace - resource_id = aws_appautoscaling_target.this[0].resource_id - scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension - - scalable_target_action { - min_capacity = each.value.min_capacity - max_capacity = each.value.max_capacity - } - - schedule = each.value.schedule - start_time = try(each.value.start_time, null) - end_time = try(each.value.end_time, null) - timezone = try(each.value.timezone, null) -} - -################################################################################ -# Security Group -################################################################################ - -locals { - create_security_group = var.create && var.create_security_group && var.network_mode == "awsvpc" - security_group_name = try(coalesce(var.security_group_name, var.name), "") -} - -data "aws_subnet" "this" { - count = local.create_security_group ? 1 : 0 - - id = element(var.subnet_ids, 0) -} - -resource "aws_security_group" "this" { - count = local.create_security_group ? 1 : 0 - - name = var.security_group_use_name_prefix ? null : local.security_group_name - name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null - description = var.security_group_description - vpc_id = data.aws_subnet.this[0].vpc_id - - tags = merge(var.tags, var.security_group_tags) - - lifecycle { - create_before_destroy = true - } -} - -resource "aws_security_group_rule" "this" { - for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } - - # Required - security_group_id = aws_security_group.this[0].id - protocol = each.value.protocol - from_port = each.value.from_port - to_port = each.value.to_port - type = each.value.type - - # Optional - description = lookup(each.value, "description", null) - cidr_blocks = lookup(each.value, "cidr_blocks", null) - ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null) - prefix_list_ids = lookup(each.value, "prefix_list_ids", null) - self = lookup(each.value, "self", null) - source_security_group_id = lookup(each.value, "source_security_group_id", null) -} diff --git a/modules/ecs-monitoring/service/outputs.tf b/modules/ecs-monitoring/service/outputs.tf deleted file mode 100644 index 059a64d7..00000000 --- a/modules/ecs-monitoring/service/outputs.tf +++ /dev/null @@ -1,152 +0,0 @@ -################################################################################ -# Service -################################################################################ - -output "id" { - description = "ARN that identifies the service" - value = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id, null) -} - -output "name" { - description = "Name of the service" - value = try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name, null) -} - -################################################################################ -# IAM Role -################################################################################ - -output "iam_role_name" { - description = "Service IAM role name" - value = try(aws_iam_role.service[0].name, null) -} - -output "iam_role_arn" { - description = "Service IAM role ARN" - value = try(aws_iam_role.service[0].arn, null) -} - -output "iam_role_unique_id" { - description = "Stable and unique string identifying the service IAM role" - value = try(aws_iam_role.service[0].unique_id, null) -} - -################################################################################ -# Container Definition -################################################################################ - -output "container_definitions" { - description = "Container definitions" - value = module.container_definition -} - -################################################################################ -# Task Definition -################################################################################ - -output "task_definition_arn" { - description = "Full ARN of the Task Definition (including both `family` and `revision`)" - value = try(aws_ecs_task_definition.this[0].arn, null) -} - -output "task_definition_revision" { - description = "Revision of the task in a particular family" - value = try(aws_ecs_task_definition.this[0].revision, null) -} - -output "task_definition_family" { - description = "The unique name of the task definition" - value = try(aws_ecs_task_definition.this[0].family, null) -} - -################################################################################ -# Task Execution - IAM Role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html -################################################################################ - -output "task_exec_iam_role_name" { - description = "Task execution IAM role name" - value = try(aws_iam_role.task_exec[0].name, null) -} - -output "task_exec_iam_role_arn" { - description = "Task execution IAM role ARN" - value = try(aws_iam_role.task_exec[0].arn, null) -} - -output "task_exec_iam_role_unique_id" { - description = "Stable and unique string identifying the task execution IAM role" - value = try(aws_iam_role.task_exec[0].unique_id, null) -} - -################################################################################ -# Tasks - IAM role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html -################################################################################ - -output "tasks_iam_role_name" { - description = "Tasks IAM role name" - value = try(aws_iam_role.tasks[0].name, null) -} - -output "tasks_iam_role_arn" { - description = "Tasks IAM role ARN" - value = try(aws_iam_role.tasks[0].arn, null) -} - -output "tasks_iam_role_unique_id" { - description = "Stable and unique string identifying the tasks IAM role" - value = try(aws_iam_role.tasks[0].unique_id, null) -} - -################################################################################ -# Task Set -################################################################################ - -output "task_set_id" { - description = "The ID of the task set" - value = try(aws_ecs_task_set.this[0].task_set_id, aws_ecs_task_set.ignore_task_definition[0].task_set_id, null) -} - -output "task_set_arn" { - description = "The Amazon Resource Name (ARN) that identifies the task set" - value = try(aws_ecs_task_set.this[0].arn, aws_ecs_task_set.ignore_task_definition[0].arn, null) -} - -output "task_set_stability_status" { - description = "The stability status. This indicates whether the task set has reached a steady state" - value = try(aws_ecs_task_set.this[0].stability_status, aws_ecs_task_set.ignore_task_definition[0].stability_status, null) -} - -output "task_set_status" { - description = "The status of the task set" - value = try(aws_ecs_task_set.this[0].status, aws_ecs_task_set.ignore_task_definition[0].status, null) -} - -################################################################################ -# Autoscaling -################################################################################ - -output "autoscaling_policies" { - description = "Map of autoscaling policies and their attributes" - value = aws_appautoscaling_policy.this -} - -output "autoscaling_scheduled_actions" { - description = "Map of autoscaling scheduled actions and their attributes" - value = aws_appautoscaling_scheduled_action.this -} - -################################################################################ -# Security Group -################################################################################ - -output "security_group_arn" { - description = "Amazon Resource Name (ARN) of the security group" - value = try(aws_security_group.this[0].arn, null) -} - -output "security_group_id" { - description = "ID of the security group" - value = try(aws_security_group.this[0].id, null) -} diff --git a/modules/ecs-monitoring/service/variables.tf b/modules/ecs-monitoring/service/variables.tf deleted file mode 100644 index bd8c7303..00000000 --- a/modules/ecs-monitoring/service/variables.tf +++ /dev/null @@ -1,643 +0,0 @@ -variable "create" { - description = "Determines whether resources will be created (affects all resources)" - type = bool - default = true -} - -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} -} - -################################################################################ -# Service -################################################################################ - -variable "ignore_task_definition_changes" { - description = "Whether changes to service `task_definition` changes should be ignored" - type = bool - default = false -} - -variable "alarms" { - description = "Information about the CloudWatch alarms" - type = any - default = {} -} - -variable "capacity_provider_strategy" { - description = "Capacity provider strategies to use for the service. Can be one or more" - type = any - default = {} -} - -variable "cluster_arn" { - description = "ARN of the ECS cluster where the resources will be provisioned" - type = string - default = "" -} - -variable "deployment_circuit_breaker" { - description = "Configuration block for deployment circuit breaker" - type = any - default = {} -} - -variable "deployment_controller" { - description = "Configuration block for deployment controller configuration" - type = any - default = {} -} - -variable "deployment_maximum_percent" { - description = "Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment" - type = number - default = 200 -} - -variable "deployment_minimum_healthy_percent" { - description = "Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment" - type = number - default = 66 -} - -variable "desired_count" { - description = "Number of instances of the task definition to place and keep running. Defaults to `0`" - type = number - default = 1 -} - -variable "enable_ecs_managed_tags" { - description = "Specifies whether to enable Amazon ECS managed tags for the tasks within the service" - type = bool - default = true -} - -variable "enable_execute_command" { - description = "Specifies whether to enable Amazon ECS Exec for the tasks within the service" - type = bool - default = false -} - -variable "force_new_deployment" { - description = "Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates" - type = bool - default = true -} - -variable "health_check_grace_period_seconds" { - description = "Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers" - type = number - default = null -} - -variable "launch_type" { - description = "Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE`" - type = string - default = "FARGATE" -} - -variable "load_balancer" { - description = "Configuration block for load balancers" - type = any - default = {} -} - -variable "name" { - description = "Name of the service (up to 255 letters, numbers, hyphens, and underscores)" - type = string - default = null -} - -variable "assign_public_ip" { - description = "Assign a public IP address to the ENI (Fargate launch type only)" - type = bool - default = false -} - -variable "security_group_ids" { - description = "List of security groups to associate with the task or service" - type = list(string) - default = [] -} - -variable "subnet_ids" { - description = "List of subnets to associate with the task or service" - type = list(string) - default = [] -} - -variable "ordered_placement_strategy" { - description = "Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence" - type = any - default = {} -} - -variable "placement_constraints" { - description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition" - type = any - default = {} -} - -variable "platform_version" { - description = "Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST`" - type = string - default = null -} - -variable "propagate_tags" { - description = "Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION`" - type = string - default = null -} - -variable "scheduling_strategy" { - description = "Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA`" - type = string - default = null -} - -variable "service_connect_configuration" { - description = "The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace" - type = any - default = {} -} - -variable "service_registries" { - description = "Service discovery registries for the service" - type = any - default = {} -} - -variable "timeouts" { - description = "Create, update, and delete timeout configurations for the service" - type = map(string) - default = {} -} - -variable "triggers" { - description = "Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()`" - type = any - default = {} -} - -variable "wait_for_steady_state" { - description = "If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false`" - type = bool - default = null -} - -################################################################################ -# Service - IAM Role -################################################################################ - -variable "create_iam_role" { - description = "Determines whether the ECS service IAM role should be created" - type = bool - default = true -} - -variable "iam_role_arn" { - description = "Existing IAM role ARN" - type = string - default = null -} - -variable "iam_role_name" { - description = "Name to use on IAM role created" - type = string - default = null -} - -variable "iam_role_use_name_prefix" { - description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" - type = bool - default = true -} - -variable "iam_role_path" { - description = "IAM role path" - type = string - default = null -} - -variable "iam_role_description" { - description = "Description of the role" - type = string - default = null -} - -variable "iam_role_permissions_boundary" { - description = "ARN of the policy that is used to set the permissions boundary for the IAM role" - type = string - default = null -} - -variable "iam_role_tags" { - description = "A map of additional tags to add to the IAM role created" - type = map(string) - default = {} -} - -variable "iam_role_statements" { - description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} -} - -################################################################################ -# Task Definition -################################################################################ - -variable "create_task_definition" { - description = "Determines whether to create a task definition or use existing/provided" - type = bool - default = true -} - -variable "task_definition_arn" { - description = "Existing task definition ARN. Required when `create_task_definition` is `false`" - type = string - default = null -} - -variable "container_definitions" { - description = "A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document" - type = any - default = {} -} - -variable "container_definition_defaults" { - description = "A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions`" - type = any - default = {} -} - -variable "cpu" { - description = "Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" - type = number - default = 1024 -} - -variable "ephemeral_storage" { - description = "The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate" - type = any - default = {} -} - -variable "family" { - description = "A unique name for your task definition" - type = string - default = null -} - -variable "inference_accelerator" { - description = "Configuration block(s) with Inference Accelerators settings" - type = any - default = {} -} - -variable "ipc_mode" { - description = "IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`" - type = string - default = null -} - -variable "memory" { - description = "Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" - type = number - default = 2048 -} - -variable "network_mode" { - description = "Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host`" - type = string - default = "awsvpc" -} - -variable "pid_mode" { - description = "Process namespace to use for the containers in the task. The valid values are `host` and `task`" - type = string - default = null -} - -variable "task_definition_placement_constraints" { - description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service" - type = any - default = {} -} - -variable "proxy_configuration" { - description = "Configuration block for the App Mesh proxy" - type = any - default = {} -} - -variable "requires_compatibilities" { - description = "Set of launch types required by the task. The valid values are `EC2` and `FARGATE`" - type = list(string) - default = ["FARGATE"] -} - -variable "runtime_platform" { - description = "Configuration block for `runtime_platform` that containers in your task may use" - type = any - default = { - operating_system_family = "LINUX" - cpu_architecture = "X86_64" - } -} - -variable "skip_destroy" { - description = "If true, the task is not deleted when the service is deleted" - type = bool - default = null -} - -variable "volume" { - description = "Configuration block for volumes that containers in your task may use" - type = any - default = {} -} - -variable "task_tags" { - description = "A map of additional tags to add to the task definition/set created" - type = map(string) - default = {} -} - -################################################################################ -# Task Execution - IAM Role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html -################################################################################ - -variable "create_task_exec_iam_role" { - description = "Determines whether the ECS task definition IAM role should be created" - type = bool - default = true -} - -variable "task_exec_iam_role_arn" { - description = "Existing IAM role ARN" - type = string - default = null -} - -variable "task_exec_iam_role_name" { - description = "Name to use on IAM role created" - type = string - default = null -} - -variable "task_exec_iam_role_use_name_prefix" { - description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" - type = bool - default = true -} - -variable "task_exec_iam_role_path" { - description = "IAM role path" - type = string - default = null -} - -variable "task_exec_iam_role_description" { - description = "Description of the role" - type = string - default = null -} - -variable "task_exec_iam_role_permissions_boundary" { - description = "ARN of the policy that is used to set the permissions boundary for the IAM role" - type = string - default = null -} - -variable "task_exec_iam_role_tags" { - description = "A map of additional tags to add to the IAM role created" - type = map(string) - default = {} -} - -variable "task_exec_iam_role_policies" { - description = "Map of IAM role policy ARNs to attach to the IAM role" - type = map(string) - default = {} -} - -variable "create_task_exec_policy" { - description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" - type = bool - default = true -} - -variable "task_exec_ssm_param_arns" { - description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" - type = list(string) - default = ["arn:aws:ssm:*:*:parameter/*"] -} - -variable "task_exec_secret_arns" { - description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" - type = list(string) - default = ["arn:aws:secretsmanager:*:*:secret:*"] -} - -variable "task_exec_iam_statements" { - description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} -} - -################################################################################ -# Tasks - IAM role -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html -################################################################################ - -variable "create_tasks_iam_role" { - description = "Determines whether the ECS tasks IAM role should be created" - type = bool - default = true -} - -variable "tasks_iam_role_arn" { - description = "Existing IAM role ARN" - type = string - default = null -} - -variable "tasks_iam_role_name" { - description = "Name to use on IAM role created" - type = string - default = null -} - -variable "tasks_iam_role_use_name_prefix" { - description = "Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix" - type = bool - default = true -} - -variable "tasks_iam_role_path" { - description = "IAM role path" - type = string - default = null -} - -variable "tasks_iam_role_description" { - description = "Description of the role" - type = string - default = null -} - -variable "tasks_iam_role_permissions_boundary" { - description = "ARN of the policy that is used to set the permissions boundary for the IAM role" - type = string - default = null -} - -variable "tasks_iam_role_tags" { - description = "A map of additional tags to add to the IAM role created" - type = map(string) - default = {} -} - -variable "tasks_iam_role_policies" { - description = "Map of IAM role policy ARNs to attach to the IAM role" - type = map(string) - default = {} -} - -variable "tasks_iam_role_statements" { - description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} -} - -################################################################################ -# Task Set -################################################################################ - -variable "external_id" { - description = "The external ID associated with the task set" - type = string - default = null -} - -variable "scale" { - description = "A floating-point percentage of the desired number of tasks to place and keep running in the task set" - type = any - default = {} -} - -variable "force_delete" { - description = "Whether to allow deleting the task set without waiting for scaling down to 0" - type = bool - default = null -} - -variable "wait_until_stable" { - description = "Whether terraform should wait until the task set has reached `STEADY_STATE`" - type = bool - default = null -} - -variable "wait_until_stable_timeout" { - description = "Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or Āµs), `ms`, `s`, `m`, and `h`. Default `10m`" - type = number - default = null -} - -################################################################################ -# Autoscaling -################################################################################ - -variable "enable_autoscaling" { - description = "Determines whether to enable autoscaling for the service" - type = bool - default = true -} - -variable "autoscaling_min_capacity" { - description = "Minimum number of tasks to run in your service" - type = number - default = 1 -} - -variable "autoscaling_max_capacity" { - description = "Maximum number of tasks to run in your service" - type = number - default = 10 -} - -variable "autoscaling_policies" { - description = "Map of autoscaling policies to create for the service" - type = any - default = { - cpu = { - policy_type = "TargetTrackingScaling" - - target_tracking_scaling_policy_configuration = { - predefined_metric_specification = { - predefined_metric_type = "ECSServiceAverageCPUUtilization" - } - } - } - memory = { - policy_type = "TargetTrackingScaling" - - target_tracking_scaling_policy_configuration = { - predefined_metric_specification = { - predefined_metric_type = "ECSServiceAverageMemoryUtilization" - } - } - } - } -} - -variable "autoscaling_scheduled_actions" { - description = "Map of autoscaling scheduled actions to create for the service" - type = any - default = {} -} - -################################################################################ -# Security Group -################################################################################ - -variable "create_security_group" { - description = "Determines if a security group is created" - type = bool - default = true -} - -variable "security_group_name" { - description = "Name to use on security group created" - type = string - default = null -} - -variable "security_group_use_name_prefix" { - description = "Determines whether the security group name (`security_group_name`) is used as a prefix" - type = bool - default = true -} - -variable "security_group_description" { - description = "Description of the security group created" - type = string - default = null -} - -variable "security_group_rules" { - description = "Security group rules to add to the security group created" - type = any - default = {} -} - -variable "security_group_tags" { - description = "A map of additional tags to add to the security group created" - type = map(string) - default = {} -} diff --git a/modules/ecs-monitoring/service/versions.tf b/modules/ecs-monitoring/service/versions.tf deleted file mode 100644 index 290d2218..00000000 --- a/modules/ecs-monitoring/service/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.55" - } - } -} From 0ef42946e768dd932cd899c53a481cdcd8a36e94 Mon Sep 17 00:00:00 2001 From: Ruchika Modi <106240341+ruchimo@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:29:51 +0530 Subject: [PATCH 05/22] Fixing path as per PR comments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd7d1473..83b172da 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ ECS cluster with VPC and EC2 can be created using the example [here](./examples/ ```hcl module "ecs_monitoring" { - source = "github.com/aws-observability/terraform-aws-observability-accelerator/modules/ecs-monitoring" + source = "github.com/aws-observability/terraform-aws-observability-accelerator//modules/ecs-monitoring" aws_ecs_cluster_name = module.ecs_cluster.cluster_name taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn From 6071dbd11caa381023670903704e6fcc789b5930 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Mon, 14 Aug 2023 13:03:48 +0000 Subject: [PATCH 06/22] Parameterzing the config files, incorporated PR review comments --- modules/ecs-monitoring/README.md | 17 +++------ modules/ecs-monitoring/configs/config.yaml | 36 +++++++++---------- modules/ecs-monitoring/locals.tf | 24 +++++++++++++ modules/ecs-monitoring/main.tf | 15 ++++---- modules/ecs-monitoring/outputs.tf | 16 ++++----- .../task-definitions/otel_collector.json | 6 ++-- modules/ecs-monitoring/variables.tf | 12 +++++++ modules/ecs-monitoring/versions.tf | 2 +- 8 files changed, 79 insertions(+), 49 deletions(-) diff --git a/modules/ecs-monitoring/README.md b/modules/ecs-monitoring/README.md index b87ca895..68137783 100644 --- a/modules/ecs-monitoring/README.md +++ b/modules/ecs-monitoring/README.md @@ -4,7 +4,7 @@ This module provides ECS cluster monitoring with the following resources: - AWS Distro For OpenTelemetry Operator and Collector for Metrics and Traces - Creates Grafana Dashboards on Amazon Managed Grafana. -- Create SSM Parameter to store the ADOT config yaml file +- Creates SSM Parameter to store and distribute the ADOT config file ## Pre-requisites * ECS Cluster with EC2 using examples --> ecs-cluster-with-vpc @@ -12,14 +12,6 @@ This module provides ECS cluster monitoring with the following resources: * Update your exisitng App(workload) *ECS Task Definition* to add below label/environment variable - Set ***ECS_PROMETHEUS_EXPORTER_PORT*** to point to the containerPort where the Prometheus metrics are exposed - Set ***Java_EMF_Metrics*** to true. The CloudWatch agent uses this flag to generated the embedded metric format in the log event. -* Make sure to update the placeholder values in the below files - - configs/config.yaml - - region - - cluster_name - - cluster_region - - prometheusremotewrite --> endpoint - - task-definitions/otel_collector.json - - awslogs-region This module makes use of the below open source projects: * [aws-managed-grafana](https://github.com/terraform-aws-modules/terraform-aws-managed-service-grafana) @@ -32,7 +24,7 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.1.0 | +| [terraform](#requirement\_terraform) | >= 1.0.0 | | [aws](#requirement\_aws) | >= 5.0.0 | ## Providers @@ -46,7 +38,6 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | Name | Source | Version | |------|--------|---------| | [managed\_grafana\_default](#module\_managed\_grafana\_default) | terraform-aws-modules/managed-service-grafana/aws | n/a | -| [managed\_prometheus\_default](#module\_managed\_prometheus\_default) | terraform-aws-modules/managed-service-prometheus/aws | n/a | ## Resources @@ -62,6 +53,8 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [aws\_ecs\_cluster\_name](#input\_aws\_ecs\_cluster\_name) | Name of your ECS cluster | `string` | n/a | yes | +| [ecs\_adot\_cpu](#input\_ecs\_adot\_cpu) | CPU to be allocated for the ADOT ECS TASK | `string` | `"256"` | no | +| [ecs\_adot\_mem](#input\_ecs\_adot\_mem) | Memory to be allocated for the ADOT ECS TASK | `string` | `"512"` | no | | [executionRoleArn](#input\_executionRoleArn) | ARN of the IAM Execution Role | `string` | n/a | yes | | [taskRoleArn](#input\_taskRoleArn) | ARN of the IAM Task Role | `string` | n/a | yes | @@ -71,6 +64,4 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this |------|-------------| | [grafana\_workspace\_endpoint](#output\_grafana\_workspace\_endpoint) | The endpoint of the Grafana workspace | | [grafana\_workspace\_id](#output\_grafana\_workspace\_id) | The ID of the Grafana workspace | -| [prometheus\_workspace\_endpoint](#output\_prometheus\_workspace\_endpoint) | Prometheus endpoint available for this workspace | -| [prometheus\_workspace\_id](#output\_prometheus\_workspace\_id) | Identifier of the workspace | diff --git a/modules/ecs-monitoring/configs/config.yaml b/modules/ecs-monitoring/configs/config.yaml index 2b0146c9..8c62a6ee 100644 --- a/modules/ecs-monitoring/configs/config.yaml +++ b/modules/ecs-monitoring/configs/config.yaml @@ -1,52 +1,52 @@ extensions: sigv4auth: - region: "us-east-1" + region: "${aws_region}" service: "aps" ecs_observer: # extension type is ecs_observer - cluster_name: 'aot-test-cluster' # cluster name need to configured manually - cluster_region: 'us-east-1' # region can be configured directly or use AWS_REGION env var - result_file: '/etc/ecs_sd_targets.yaml' # the directory for file must already exists - refresh_interval: 60s + cluster_name: "${cluster_name}" # cluster name need to configured manually + cluster_region: "${cluster_region}" # region can be configured directly or use AWS_REGION env var + result_file: "/etc/ecs_sd_targets.yaml" # the directory for file must already exists + refresh_interval: ${refresh_interval} job_label_name: prometheus_job # JMX docker_labels: - - port_label: 'ECS_PROMETHEUS_EXPORTER_PORT' + - port_label: "ECS_PROMETHEUS_EXPORTER_PORT" receivers: otlp: protocols: grpc: - endpoint: 0.0.0.0:4317 + endpoint: ${otlpGrpcEndpoint} http: - endpoint: 0.0.0.0:4318 + endpoint: ${otlpHttpEndpoint} prometheus: config: scrape_configs: - job_name: "ecssd" file_sd_configs: - files: - - '/etc/ecs_sd_targets.yaml' + - "/etc/ecs_sd_targets.yaml" relabel_configs: - - source_labels: [ __meta_ecs_cluster_name ] + - source_labels: [__meta_ecs_cluster_name] action: replace target_label: ClusterName - - source_labels: [ __meta_ecs_service_name ] + - source_labels: [__meta_ecs_service_name] action: replace target_label: ServiceName - - source_labels: [ __meta_ecs_task_definition_family ] + - source_labels: [__meta_ecs_task_definition_family] action: replace target_label: TaskDefinitionFamily - - source_labels: [ __meta_ecs_task_launch_type ] + - source_labels: [__meta_ecs_task_launch_type] action: replace target_label: LaunchType - - source_labels: [ __meta_ecs_container_name ] + - source_labels: [__meta_ecs_container_name] action: replace target_label: container_name - action: labelmap regex: ^__meta_ecs_container_labels_(.+)$ - replacement: '$$1' + replacement: "$$1" awsecscontainermetrics: - collection_interval: 15s + collection_interval: ${ecs_metrics_collection_interval} processors: resource: @@ -111,7 +111,7 @@ processors: exporters: prometheusremotewrite: - endpoint: "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-f9a9b3d8-511e-4640-9b2d-15fbd53f7209/api/v1/remote_write" + endpoint: "${amp_remote_write_ep}" auth: authenticator: sigv4auth logging: @@ -127,4 +127,4 @@ service: metrics/ecs: receivers: [awsecscontainermetrics] processors: [filter] - exporters: [logging, prometheusremotewrite] \ No newline at end of file + exporters: [logging, prometheusremotewrite] diff --git a/modules/ecs-monitoring/locals.tf b/modules/ecs-monitoring/locals.tf index fe2f2e5c..bb527aa1 100644 --- a/modules/ecs-monitoring/locals.tf +++ b/modules/ecs-monitoring/locals.tf @@ -6,4 +6,28 @@ locals { region = data.aws_region.current.name name = "amg-ex-${replace(basename(path.cwd), "_", "-")}" description = "AWS Managed Grafana service for ${local.name}" + + default_otel_values = { + aws_region = data.aws_region.current.name + cluster_name = var.aws_ecs_cluster_name + cluster_region = data.aws_region.current.name + refresh_interval = "60s" + ecs_metrics_collection_interval = "15s" + amp_remote_write_ep = "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-f9a9b3d8-511e-4640-9b2d-15fbd53f7209/api/v1/remote_write" + otlpGrpcEndpoint = "0.0.0.0:4317" + otlpHttpEndpoint = "0.0.0.0:4318" + } + + ssm_param_value = yamlencode( + templatefile("${path.module}/configs/config.yaml", local.default_otel_values) + ) + + container_def_default_values = { + container_name = "adot_new" + otel_image_ver = "v0.31.0" + aws_region = data.aws_region.current.name + } + + container_definitions = templatefile("${path.module}/task_definitions/otel_collector.json", local.container_def_default_values) + } diff --git a/modules/ecs-monitoring/main.tf b/modules/ecs-monitoring/main.tf index 6054959a..63bd8f75 100644 --- a/modules/ecs-monitoring/main.tf +++ b/modules/ecs-monitoring/main.tf @@ -1,9 +1,10 @@ -# SSM Parameter +# SSM Parameter for storing and distrivuting the ADOT config resource "aws_ssm_parameter" "adot-config" { - name = "/observability_aws/otel_collector_conf" + name = "/terraform-aws-observability/otel_collector_config" description = "SSM parameter for aws-observability-accelerator/otel-collector-config" type = "String" - value = yamlencode(file("configs/config.yaml")) + value = local.ssm_param_value + tier = "Intelligent-Tiering" } ############################################ @@ -16,8 +17,10 @@ module "managed_grafana_default" { associate_license = false } +##################### ## Commented this module, as AMP workspace is a pre-requiste for this solution. ## You can use this code to create a AMP workspace +##################### # module "managed_prometheus_default" { # source = "terraform-aws-modules/managed-service-prometheus/aws" @@ -33,9 +36,9 @@ resource "aws_ecs_task_definition" "adot_ecs_prometheus" { execution_role_arn = var.executionRoleArn network_mode = "bridge" requires_compatibilities = ["EC2"] - cpu = "256" - memory = "512" - container_definitions = file("task_definitions/otel_collector.json") + cpu = var.ecs_adot_cpu + memory = var.ecs_adot_mem + container_definitions = local.container_definitions } ############################################ diff --git a/modules/ecs-monitoring/outputs.tf b/modules/ecs-monitoring/outputs.tf index c9df0363..f3075eef 100644 --- a/modules/ecs-monitoring/outputs.tf +++ b/modules/ecs-monitoring/outputs.tf @@ -8,12 +8,12 @@ output "grafana_workspace_endpoint" { value = module.managed_grafana_default.workspace_endpoint } -output "prometheus_workspace_id" { - description = "Identifier of the workspace" - value = module.managed_prometheus_default.workspace_id -} +# output "prometheus_workspace_id" { +# description = "Identifier of the workspace" +# value = module.managed_prometheus_default.workspace_id +# } -output "prometheus_workspace_endpoint" { - description = "Prometheus endpoint available for this workspace" - value = module.managed_prometheus_default.workspace_prometheus_endpoint -} +# output "prometheus_workspace_endpoint" { +# description = "Prometheus endpoint available for this workspace" +# value = module.managed_prometheus_default.workspace_prometheus_endpoint +# } diff --git a/modules/ecs-monitoring/task-definitions/otel_collector.json b/modules/ecs-monitoring/task-definitions/otel_collector.json index 86f66dfe..8328fe53 100644 --- a/modules/ecs-monitoring/task-definitions/otel_collector.json +++ b/modules/ecs-monitoring/task-definitions/otel_collector.json @@ -1,11 +1,11 @@ [ { - "name": "adot", - "image": "amazon/aws-otel-collector:v0.31.0", + "name": "${container_name}", + "image": "amazon/aws-otel-collector:${otel_image_ver}", "secrets": [ { "name": "AOT_CONFIG_CONTENT", - "valueFrom": "/aws-observability-accelerator/otel-collector-config" + "valueFrom": "/terraform-aws-observability/otel_collector_config" } ], "logConfiguration": { diff --git a/modules/ecs-monitoring/variables.tf b/modules/ecs-monitoring/variables.tf index ce2275e0..8c2a258b 100644 --- a/modules/ecs-monitoring/variables.tf +++ b/modules/ecs-monitoring/variables.tf @@ -12,3 +12,15 @@ variable "executionRoleArn" { description = "ARN of the IAM Execution Role" type = string } + +variable "ecs_adot_cpu" { + description = "CPU to be allocated for the ADOT ECS TASK" + type = string + default = "256" +} + +variable "ecs_adot_mem" { + description = "Memory to be allocated for the ADOT ECS TASK" + type = string + default = "512" +} diff --git a/modules/ecs-monitoring/versions.tf b/modules/ecs-monitoring/versions.tf index e426124f..45dce904 100644 --- a/modules/ecs-monitoring/versions.tf +++ b/modules/ecs-monitoring/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.1.0" + required_version = ">= 1.0.0" required_providers { aws = { From 3ca49bd47c7bd51bcdcc50c6ebc1b7ea5cad43e7 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Thu, 24 Aug 2023 12:09:02 +0000 Subject: [PATCH 07/22] Adding condition for AMP WS and fixing AMP endpoint --- modules/ecs-monitoring/locals.tf | 15 ++++++++------- modules/ecs-monitoring/main.tf | 10 ++++++---- modules/ecs-monitoring/variables.tf | 6 ++++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/modules/ecs-monitoring/locals.tf b/modules/ecs-monitoring/locals.tf index bb527aa1..907d830d 100644 --- a/modules/ecs-monitoring/locals.tf +++ b/modules/ecs-monitoring/locals.tf @@ -1,11 +1,12 @@ data "aws_region" "current" {} locals { - taskRoleArn = var.taskRoleArn - executionRoleArn = var.executionRoleArn - region = data.aws_region.current.name - name = "amg-ex-${replace(basename(path.cwd), "_", "-")}" - description = "AWS Managed Grafana service for ${local.name}" + taskRoleArn = var.taskRoleArn + executionRoleArn = var.executionRoleArn + region = data.aws_region.current.name + name = "amg-ex-${replace(basename(path.cwd), "_", "-")}" + description = "AWS Managed Grafana service for ${local.name}" + prometheus_ws_endpoint = module.managed_prometheus_default[0].workspace_prometheus_endpoint default_otel_values = { aws_region = data.aws_region.current.name @@ -13,7 +14,7 @@ locals { cluster_region = data.aws_region.current.name refresh_interval = "60s" ecs_metrics_collection_interval = "15s" - amp_remote_write_ep = "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-f9a9b3d8-511e-4640-9b2d-15fbd53f7209/api/v1/remote_write" + amp_remote_write_ep = "${local.prometheus_ws_endpoint}api/v1/remote_write" otlpGrpcEndpoint = "0.0.0.0:4317" otlpHttpEndpoint = "0.0.0.0:4318" } @@ -28,6 +29,6 @@ locals { aws_region = data.aws_region.current.name } - container_definitions = templatefile("${path.module}/task_definitions/otel_collector.json", local.container_def_default_values) + container_definitions = templatefile("${path.module}/task-definitions/otel_collector.json", local.container_def_default_values) } diff --git a/modules/ecs-monitoring/main.tf b/modules/ecs-monitoring/main.tf index 63bd8f75..4c098ba0 100644 --- a/modules/ecs-monitoring/main.tf +++ b/modules/ecs-monitoring/main.tf @@ -22,10 +22,12 @@ module "managed_grafana_default" { ## You can use this code to create a AMP workspace ##################### -# module "managed_prometheus_default" { -# source = "terraform-aws-modules/managed-service-prometheus/aws" -# workspace_alias = "${local.name}-default" -# } +module "managed_prometheus_default" { + count = var.create_managed_prometheus_ws ? 1 : 0 + + source = "terraform-aws-modules/managed-service-prometheus/aws" + workspace_alias = "${local.name}-default" +} ########################################### # Task Definition for ADOT ECS Prometheus diff --git a/modules/ecs-monitoring/variables.tf b/modules/ecs-monitoring/variables.tf index 8c2a258b..a083f119 100644 --- a/modules/ecs-monitoring/variables.tf +++ b/modules/ecs-monitoring/variables.tf @@ -24,3 +24,9 @@ variable "ecs_adot_mem" { type = string default = "512" } + +variable "create_managed_prometheus_ws" { + description = "Creates a Workspace for Amazon Managed Prometheus" + type = bool + default = true +} \ No newline at end of file From ac1058d6d3b09b862dcbdeea55db470e07704153 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Fri, 1 Sep 2023 07:09:13 +0000 Subject: [PATCH 08/22] Adding Document for ECS Monitoring and parameterized some variables --- docs/ecs/ecs-monitoring-on-ec2.md | 62 +++++++++++++++++++++++++++++ mkdocs.yml | 3 ++ modules/ecs-monitoring/locals.tf | 12 +++--- modules/ecs-monitoring/variables.tf | 37 +++++++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 docs/ecs/ecs-monitoring-on-ec2.md diff --git a/docs/ecs/ecs-monitoring-on-ec2.md b/docs/ecs/ecs-monitoring-on-ec2.md new file mode 100644 index 00000000..b90a0c64 --- /dev/null +++ b/docs/ecs/ecs-monitoring-on-ec2.md @@ -0,0 +1,62 @@ +# Amazon ECS on EC2 cluster monitoring + +This example demonstrates how to monitor your Amazon Elastic Container Service on EC2 +(Amazon ECS) cluster with the Observability Accelerator's +[ECS monitoring module](https://github.com/aws-observability/terraform-aws-observability-accelerator/tree/main/modules/ecs-monitoring). + +The module collects Prometheus metrics from tasks running on ECS and sends it to Prometheus using AWS Distro for OpenTelemetry Collector (ADOT). +You can either run the collector as a sidecar or deploy the collector as its own ECS service for entire cluster. +ECS tasks with Prometheus endpoints are discovered using extension +[ecsobserver](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/observer/ecsobserver/README.md). +(Unlike EKS, there is no builtin discovery for ECS inside prometheus) + +Additionally, you can optionally collect custom Prometheus metrics from your applications running +on your ECS cluster. + +## Prerequisites + +!!! note + Make sure to complete the [prerequisites section](https://aws-observability.github.io/terraform-aws-observability-accelerator/concepts/#prerequisites) before proceeding. + +## Available Samples for various Worklods +Make sure to update your exisitng Application Task Definitions based on the workload type :- + +#### 1. [Java/JMX workload for ECS Clusters](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights-Prometheus-Sample-Workloads-ECS-javajmx.html) +#### 2. [NGINX workload for Amazon ECS clusters](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights-Prometheus-Setup-nginx-ecs.html) +#### 3. [App Mesh workload](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights-Prometheus-Sample-Workloads-ECS-appmesh.html) + +## Setup + +#### 1. Add the ECS Monitoring Module to your exisitng ECS CLuster + +``` +module "ecs_monitoring" { + source = "../../modules/ecs-monitoring" + aws_ecs_cluster_name = module.ecs_cluster.cluster_name + taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn + executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn + + depends_on = [ + module.ecs_service + ] +} +``` + +## Deploy + +Simply run this command to deploy the example + +```bash +terraform apply +``` + +## Visualization + + +## Cleanup + +To clean up your environment, destroy the Terraform example by running + +```sh +terraform destroy +``` diff --git a/mkdocs.yml b/mkdocs.yml index 077dcb0f..2fe638f8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,9 +38,12 @@ nav: - Amazon CloudWatch Container Insights: - Amazon EKS: container-insights/eks.md - Monitoring Managed Service for Prometheus Workspaces: workloads/managed-prometheus.md + - Amazon ECS: + - Cluster Monitoring: ecs/ecs-monitoring-on-ec2.md - Supporting Examples: - EKS Cluster with VPC: helpers/new-eks-cluster.md - Amazon Managed Grafana setup: helpers/managed-grafana.md + - ECS Cluster with VPC: helpers/ecs-cluster-with-vpc.md - Support & Feedback: support.md - Contributors: contributors.md diff --git a/modules/ecs-monitoring/locals.tf b/modules/ecs-monitoring/locals.tf index 907d830d..d792c743 100644 --- a/modules/ecs-monitoring/locals.tf +++ b/modules/ecs-monitoring/locals.tf @@ -12,11 +12,11 @@ locals { aws_region = data.aws_region.current.name cluster_name = var.aws_ecs_cluster_name cluster_region = data.aws_region.current.name - refresh_interval = "60s" - ecs_metrics_collection_interval = "15s" + refresh_interval = var.refresh_interval + ecs_metrics_collection_interval = var.ecs_metrics_collection_interval amp_remote_write_ep = "${local.prometheus_ws_endpoint}api/v1/remote_write" - otlpGrpcEndpoint = "0.0.0.0:4317" - otlpHttpEndpoint = "0.0.0.0:4318" + otlpGrpcEndpoint = var.otlpGrpcEndpoint + otlpHttpEndpoint = var.otlpHttpEndpoint } ssm_param_value = yamlencode( @@ -24,8 +24,8 @@ locals { ) container_def_default_values = { - container_name = "adot_new" - otel_image_ver = "v0.31.0" + container_name = var.container_name + otel_image_ver = var.otel_image_ver aws_region = data.aws_region.current.name } diff --git a/modules/ecs-monitoring/variables.tf b/modules/ecs-monitoring/variables.tf index a083f119..d1569d5f 100644 --- a/modules/ecs-monitoring/variables.tf +++ b/modules/ecs-monitoring/variables.tf @@ -29,4 +29,41 @@ variable "create_managed_prometheus_ws" { description = "Creates a Workspace for Amazon Managed Prometheus" type = bool default = true +} + +variable "refresh_interval" { + description = "Refresh interval for ecs_observer" + type = string + default = "60s" +} + +variable "ecs_metrics_collection_interval" { + description = "Collection interval for ecs metrics" + type = string + default = "15s" +} + +variable "otlpGrpcEndpoint" { + description = "otlpGrpcEndpoint" + type = string + default = "0.0.0.0:4317" +} + + +variable "otlpHttpEndpoint" { + description = "otlpHttpEndpoint" + type = string + default = "0.0.0.0:4318" +} + +variable "container_name" { + description = "Container Name for Adot" + type = string + default = "adot_new" +} + +variable "otel_image_ver" { + description = "Otel Docker Image version" + type = string + default = "v0.31.0" } \ No newline at end of file From 23c073aa43c2c5ee01dafc40ec66a8dd51a23f9e Mon Sep 17 00:00:00 2001 From: Ruchika Modi <106240341+ruchimo@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:43:32 +0530 Subject: [PATCH 09/22] Added sample dashboard --- docs/ecs/ecs-monitoring-on-ec2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ecs/ecs-monitoring-on-ec2.md b/docs/ecs/ecs-monitoring-on-ec2.md index b90a0c64..37d2dee0 100644 --- a/docs/ecs/ecs-monitoring-on-ec2.md +++ b/docs/ecs/ecs-monitoring-on-ec2.md @@ -51,6 +51,7 @@ terraform apply ``` ## Visualization +![image](https://github.com/ruchimo/terraform-aws-observability-accelerator/assets/106240341/006c387e-92e8-45c8-ae2e-825900990741) ## Cleanup From c1d8303a4aff9b50cdb5b55e5ba35e37f57fe126 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Fri, 1 Sep 2023 07:22:41 +0000 Subject: [PATCH 10/22] Adding Document for ECS Monitoring and parameterized some variables --- docs/helpers/ecs-cluster-with-vpc.md | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/helpers/ecs-cluster-with-vpc.md diff --git a/docs/helpers/ecs-cluster-with-vpc.md b/docs/helpers/ecs-cluster-with-vpc.md new file mode 100644 index 00000000..8820a9b5 --- /dev/null +++ b/docs/helpers/ecs-cluster-with-vpc.md @@ -0,0 +1,46 @@ +# Example Amazon ECS Cluster with VPC +This example deploys an AWS ECS Cluster with VPC and also add the ECS Monitoring module +[ECS Cluster example](https://github.com/aws-observability/terraform-aws-observability-accelerator/tree/main/examples/ecs-cluster-with-vpc).vpc + +## Prerequisites + +!!! note + Make sure to complete the [prerequisites section](https://aws-observability.github.io/terraform-aws-observability-accelerator/concepts/#prerequisites) before proceeding. + +## Setup +#### 1. Download sources and initialize TerraformĀ¶ + +``` +git clone https://github.com/aws-observability/terraform-aws-observability-accelerator.git +cd terraform-aws-observability-accelerator/examples/ecs-cluster-with-vpc +terraform init +``` + +#### 2. AWS RegionĀ¶ +Specify the AWS Region where the resources will be deployed: + +``` +export TF_VAR_aws_region=xxx +``` + +#### 3. Terraform Plan to validate the changes/updates + +``` +terraform plan +``` + +## Deploy + +Simply run this command to deploy the example + +```bash +terraform apply +``` + +## Cleanup + +To clean up your environment, destroy the Terraform example by running + +```sh +terraform destroy +``` From b2ae876af2ac1e8e9a3bdc84faf0c31054be288f Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Mon, 4 Sep 2023 06:50:08 +0000 Subject: [PATCH 11/22] Fixing failures detected by pre-commit --- docs/ecs/ecs-monitoring-on-ec2.md | 12 +- docs/helpers/ecs-cluster-with-vpc.md | 2 +- examples/ecs-cluster-with-vpc/README.md | 5 +- examples/ecs-cluster-with-vpc/main.tf | 21 ++-- examples/ecs-cluster-with-vpc/outputs.tf | 152 ++++------------------- modules/ecs-monitoring/README.md | 10 +- modules/ecs-monitoring/locals.tf | 2 - modules/ecs-monitoring/main.tf | 6 +- modules/ecs-monitoring/variables.tf | 26 ++-- 9 files changed, 69 insertions(+), 167 deletions(-) diff --git a/docs/ecs/ecs-monitoring-on-ec2.md b/docs/ecs/ecs-monitoring-on-ec2.md index 37d2dee0..16f81197 100644 --- a/docs/ecs/ecs-monitoring-on-ec2.md +++ b/docs/ecs/ecs-monitoring-on-ec2.md @@ -4,10 +4,10 @@ This example demonstrates how to monitor your Amazon Elastic Container Service o (Amazon ECS) cluster with the Observability Accelerator's [ECS monitoring module](https://github.com/aws-observability/terraform-aws-observability-accelerator/tree/main/modules/ecs-monitoring). -The module collects Prometheus metrics from tasks running on ECS and sends it to Prometheus using AWS Distro for OpenTelemetry Collector (ADOT). -You can either run the collector as a sidecar or deploy the collector as its own ECS service for entire cluster. -ECS tasks with Prometheus endpoints are discovered using extension -[ecsobserver](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/observer/ecsobserver/README.md). +The module collects Prometheus metrics from tasks running on ECS and sends it to Prometheus using AWS Distro for OpenTelemetry Collector (ADOT). +You can either run the collector as a sidecar or deploy the collector as its own ECS service for entire cluster. +ECS tasks with Prometheus endpoints are discovered using extension +[ecsobserver](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/observer/ecsobserver/README.md). (Unlike EKS, there is no builtin discovery for ECS inside prometheus) Additionally, you can optionally collect custom Prometheus metrics from your applications running @@ -35,9 +35,9 @@ module "ecs_monitoring" { aws_ecs_cluster_name = module.ecs_cluster.cluster_name taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn - + depends_on = [ - module.ecs_service + module.ecs_cluster ] } ``` diff --git a/docs/helpers/ecs-cluster-with-vpc.md b/docs/helpers/ecs-cluster-with-vpc.md index 8820a9b5..96c8022e 100644 --- a/docs/helpers/ecs-cluster-with-vpc.md +++ b/docs/helpers/ecs-cluster-with-vpc.md @@ -1,4 +1,4 @@ -# Example Amazon ECS Cluster with VPC +# Example Amazon ECS Cluster with VPC This example deploys an AWS ECS Cluster with VPC and also add the ECS Monitoring module [ECS Cluster example](https://github.com/aws-observability/terraform-aws-observability-accelerator/tree/main/examples/ecs-cluster-with-vpc).vpc diff --git a/examples/ecs-cluster-with-vpc/README.md b/examples/ecs-cluster-with-vpc/README.md index f468c359..bd37f52a 100644 --- a/examples/ecs-cluster-with-vpc/README.md +++ b/examples/ecs-cluster-with-vpc/README.md @@ -39,12 +39,11 @@ Note that this example may create resources which will incur monetary charges on | Name | Source | Version | |------|--------|---------| -| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 8.0 | | [alb\_sg](#module\_alb\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | | [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | | [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | -| [ecs\_cluster](#module\_ecs\_cluster) | ./modules/cluster | n/a | -| [ecs\_service](#module\_ecs\_service) | ./modules/service | n/a | +| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws | n/a | +| [ecs\_monitoring](#module\_ecs\_monitoring) | ../../modules/ecs-monitoring | n/a | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | ## Resources diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index 36862ee3..f8a15bb1 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -27,14 +27,15 @@ locals { module "ecs_cluster" { source = "terraform-aws-modules/ecs/aws" + version = "5.2.2" cluster_name = local.name # Capacity provider - autoscaling groups default_capacity_provider_use_fargate = false - create_task_exec_iam_role = true - task_exec_iam_role_name = "ecs_monitor_task_exec_role" - task_exec_iam_role_policies = {"module.ecs_cluster.module.cluster.aws_iam_policy.task_exec[0]" : "arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess" } + create_task_exec_iam_role = true + task_exec_iam_role_name = "ecs_monitor_task_exec_role" + task_exec_iam_role_policies = { "module.ecs_cluster.module.cluster.aws_iam_policy.task_exec[0]" : "arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess" } autoscaling_capacity_providers = { # On-demand instances ex-1 = { @@ -179,12 +180,12 @@ module "alb_sg" { } module "ecs_monitoring" { - source = "../../modules/ecs-monitoring" - aws_ecs_cluster_name = module.ecs_cluster.cluster_name - taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn - executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn - + source = "../../modules/ecs-monitoring" + aws_ecs_cluster_name = module.ecs_cluster.cluster_name + taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn + executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn + depends_on = [ - module.ecs_service + module.ecs_cluster ] -} \ No newline at end of file +} diff --git a/examples/ecs-cluster-with-vpc/outputs.tf b/examples/ecs-cluster-with-vpc/outputs.tf index 2f6f85a7..fc2330f0 100644 --- a/examples/ecs-cluster-with-vpc/outputs.tf +++ b/examples/ecs-cluster-with-vpc/outputs.tf @@ -2,131 +2,27 @@ # Cluster ################################################################################ -output "cluster_arn" { - description = "ARN that identifies the cluster" - value = module.ecs_cluster.arn -} - -output "cluster_id" { - description = "ID that identifies the cluster" - value = module.ecs_cluster.id -} - -output "cluster_name" { - description = "Name that identifies the cluster" - value = module.ecs_cluster.name -} - -output "cluster_capacity_providers" { - description = "Map of cluster capacity providers attributes" - value = module.ecs_cluster.cluster_capacity_providers -} - -output "cluster_autoscaling_capacity_providers" { - description = "Map of capacity providers created and their attributes" - value = module.ecs_cluster.autoscaling_capacity_providers -} - -################################################################################ -# Service -################################################################################ - -output "service_id" { - description = "ARN that identifies the service" - value = module.ecs_service.id -} - -output "service_name" { - description = "Name of the service" - value = module.ecs_service.name -} - -output "service_iam_role_name" { - description = "Service IAM role name" - value = module.ecs_service.iam_role_name -} - -output "service_iam_role_arn" { - description = "Service IAM role ARN" - value = module.ecs_service.iam_role_arn -} - -output "service_iam_role_unique_id" { - description = "Stable and unique string identifying the service IAM role" - value = module.ecs_service.iam_role_unique_id -} - -output "service_container_definitions" { - description = "Container definitions" - value = module.ecs_service.container_definitions -} - -output "service_task_definition_arn" { - description = "Full ARN of the Task Definition (including both `family` and `revision`)" - value = module.ecs_service.task_definition_arn -} - -output "service_task_definition_revision" { - description = "Revision of the task in a particular family" - value = module.ecs_service.task_definition_revision -} - -output "service_task_exec_iam_role_name" { - description = "Task execution IAM role name" - value = module.ecs_service.task_exec_iam_role_name -} - -output "service_task_exec_iam_role_arn" { - description = "Task execution IAM role ARN" - value = module.ecs_service.task_exec_iam_role_arn -} - -output "service_task_exec_iam_role_unique_id" { - description = "Stable and unique string identifying the task execution IAM role" - value = module.ecs_service.task_exec_iam_role_unique_id -} - -output "service_tasks_iam_role_name" { - description = "Tasks IAM role name" - value = module.ecs_service.tasks_iam_role_name -} - -output "service_tasks_iam_role_arn" { - description = "Tasks IAM role ARN" - value = module.ecs_service.tasks_iam_role_arn -} - -output "service_tasks_iam_role_unique_id" { - description = "Stable and unique string identifying the tasks IAM role" - value = module.ecs_service.tasks_iam_role_unique_id -} - -output "service_task_set_id" { - description = "The ID of the task set" - value = module.ecs_service.task_set_id -} - -output "service_task_set_arn" { - description = "The Amazon Resource Name (ARN) that identifies the task set" - value = module.ecs_service.task_set_arn -} - -output "service_task_set_stability_status" { - description = "The stability status. This indicates whether the task set has reached a steady state" - value = module.ecs_service.task_set_stability_status -} - -output "service_task_set_status" { - description = "The status of the task set" - value = module.ecs_service.task_set_status -} - -output "service_autoscaling_policies" { - description = "Map of autoscaling policies and their attributes" - value = module.ecs_service.autoscaling_policies -} - -output "service_autoscaling_scheduled_actions" { - description = "Map of autoscaling scheduled actions and their attributes" - value = module.ecs_service.autoscaling_scheduled_actions -} +# output "cluster_arn" { +# description = "ARN that identifies the cluster" +# value = module.ecs_cluster.arn +# } + +# output "cluster_id" { +# description = "ID that identifies the cluster" +# value = module.ecs_cluster.id +# } + +# output "cluster_name" { +# description = "Name that identifies the cluster" +# value = module.ecs_cluster.name +# } + +# output "cluster_capacity_providers" { +# description = "Map of cluster capacity providers attributes" +# value = module.ecs_cluster.cluster_capacity_providers +# } + +# output "cluster_autoscaling_capacity_providers" { +# description = "Map of capacity providers created and their attributes" +# value = module.ecs_cluster.autoscaling_capacity_providers +# } \ No newline at end of file diff --git a/modules/ecs-monitoring/README.md b/modules/ecs-monitoring/README.md index 68137783..0891d5fb 100644 --- a/modules/ecs-monitoring/README.md +++ b/modules/ecs-monitoring/README.md @@ -38,6 +38,7 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | Name | Source | Version | |------|--------|---------| | [managed\_grafana\_default](#module\_managed\_grafana\_default) | terraform-aws-modules/managed-service-grafana/aws | n/a | +| [managed\_prometheus\_default](#module\_managed\_prometheus\_default) | terraform-aws-modules/managed-service-prometheus/aws | n/a | ## Resources @@ -45,7 +46,7 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this |------|------| | [aws_ecs_service.adot_ecs_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | | [aws_ecs_task_definition.adot_ecs_prometheus](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | -| [aws_ssm_parameter.adot-config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.adot_config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs @@ -53,9 +54,16 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [aws\_ecs\_cluster\_name](#input\_aws\_ecs\_cluster\_name) | Name of your ECS cluster | `string` | n/a | yes | +| [container\_name](#input\_container\_name) | Container Name for Adot | `string` | `"adot_new"` | no | +| [create\_managed\_prometheus\_ws](#input\_create\_managed\_prometheus\_ws) | Creates a Workspace for Amazon Managed Prometheus | `bool` | `true` | no | | [ecs\_adot\_cpu](#input\_ecs\_adot\_cpu) | CPU to be allocated for the ADOT ECS TASK | `string` | `"256"` | no | | [ecs\_adot\_mem](#input\_ecs\_adot\_mem) | Memory to be allocated for the ADOT ECS TASK | `string` | `"512"` | no | +| [ecs\_metrics\_collection\_interval](#input\_ecs\_metrics\_collection\_interval) | Collection interval for ecs metrics | `string` | `"15s"` | no | | [executionRoleArn](#input\_executionRoleArn) | ARN of the IAM Execution Role | `string` | n/a | yes | +| [otel\_image\_ver](#input\_otel\_image\_ver) | Otel Docker Image version | `string` | `"v0.31.0"` | no | +| [otlpGrpcEndpoint](#input\_otlpGrpcEndpoint) | otlpGrpcEndpoint | `string` | `"0.0.0.0:4317"` | no | +| [otlpHttpEndpoint](#input\_otlpHttpEndpoint) | otlpHttpEndpoint | `string` | `"0.0.0.0:4318"` | no | +| [refresh\_interval](#input\_refresh\_interval) | Refresh interval for ecs\_observer | `string` | `"60s"` | no | | [taskRoleArn](#input\_taskRoleArn) | ARN of the IAM Task Role | `string` | n/a | yes | ## Outputs diff --git a/modules/ecs-monitoring/locals.tf b/modules/ecs-monitoring/locals.tf index d792c743..6f319b7a 100644 --- a/modules/ecs-monitoring/locals.tf +++ b/modules/ecs-monitoring/locals.tf @@ -1,8 +1,6 @@ data "aws_region" "current" {} locals { - taskRoleArn = var.taskRoleArn - executionRoleArn = var.executionRoleArn region = data.aws_region.current.name name = "amg-ex-${replace(basename(path.cwd), "_", "-")}" description = "AWS Managed Grafana service for ${local.name}" diff --git a/modules/ecs-monitoring/main.tf b/modules/ecs-monitoring/main.tf index 4c098ba0..28ef1e63 100644 --- a/modules/ecs-monitoring/main.tf +++ b/modules/ecs-monitoring/main.tf @@ -1,5 +1,5 @@ # SSM Parameter for storing and distrivuting the ADOT config -resource "aws_ssm_parameter" "adot-config" { +resource "aws_ssm_parameter" "adot_config" { name = "/terraform-aws-observability/otel_collector_config" description = "SSM parameter for aws-observability-accelerator/otel-collector-config" type = "String" @@ -23,8 +23,8 @@ module "managed_grafana_default" { ##################### module "managed_prometheus_default" { - count = var.create_managed_prometheus_ws ? 1 : 0 - + count = var.create_managed_prometheus_ws ? 1 : 0 + source = "terraform-aws-modules/managed-service-prometheus/aws" workspace_alias = "${local.name}-default" } diff --git a/modules/ecs-monitoring/variables.tf b/modules/ecs-monitoring/variables.tf index d1569d5f..51b80335 100644 --- a/modules/ecs-monitoring/variables.tf +++ b/modules/ecs-monitoring/variables.tf @@ -33,37 +33,37 @@ variable "create_managed_prometheus_ws" { variable "refresh_interval" { description = "Refresh interval for ecs_observer" - type = string - default = "60s" + type = string + default = "60s" } variable "ecs_metrics_collection_interval" { description = "Collection interval for ecs metrics" - type = string - default = "15s" + type = string + default = "15s" } variable "otlpGrpcEndpoint" { description = "otlpGrpcEndpoint" - type = string - default = "0.0.0.0:4317" + type = string + default = "0.0.0.0:4317" } variable "otlpHttpEndpoint" { description = "otlpHttpEndpoint" - type = string - default = "0.0.0.0:4318" + type = string + default = "0.0.0.0:4318" } variable "container_name" { description = "Container Name for Adot" - type = string - default = "adot_new" + type = string + default = "adot_new" } variable "otel_image_ver" { description = "Otel Docker Image version" - type = string - default = "v0.31.0" -} \ No newline at end of file + type = string + default = "v0.31.0" +} From 617082016854c804f7c6e005519ebe9125931bec Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Mon, 4 Sep 2023 06:57:08 +0000 Subject: [PATCH 12/22] Fixing failures detected by pre-commit --- examples/ecs-cluster-with-vpc/README.md | 30 ++---------------------- examples/ecs-cluster-with-vpc/main.tf | 2 +- examples/ecs-cluster-with-vpc/outputs.tf | 2 +- 3 files changed, 4 insertions(+), 30 deletions(-) diff --git a/examples/ecs-cluster-with-vpc/README.md b/examples/ecs-cluster-with-vpc/README.md index bd37f52a..224ec350 100644 --- a/examples/ecs-cluster-with-vpc/README.md +++ b/examples/ecs-cluster-with-vpc/README.md @@ -42,7 +42,7 @@ Note that this example may create resources which will incur monetary charges on | [alb\_sg](#module\_alb\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | | [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | | [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | -| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws | n/a | +| [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws | 5.2.2 | | [ecs\_monitoring](#module\_ecs\_monitoring) | ../../modules/ecs-monitoring | n/a | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | @@ -59,33 +59,7 @@ No inputs. ## Outputs -| Name | Description | -|------|-------------| -| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | -| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | -| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | -| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | -| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | -| [service\_autoscaling\_policies](#output\_service\_autoscaling\_policies) | Map of autoscaling policies and their attributes | -| [service\_autoscaling\_scheduled\_actions](#output\_service\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | -| [service\_container\_definitions](#output\_service\_container\_definitions) | Container definitions | -| [service\_iam\_role\_arn](#output\_service\_iam\_role\_arn) | Service IAM role ARN | -| [service\_iam\_role\_name](#output\_service\_iam\_role\_name) | Service IAM role name | -| [service\_iam\_role\_unique\_id](#output\_service\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | -| [service\_id](#output\_service\_id) | ARN that identifies the service | -| [service\_name](#output\_service\_name) | Name of the service | -| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | -| [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | -| [service\_task\_exec\_iam\_role\_arn](#output\_service\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | -| [service\_task\_exec\_iam\_role\_name](#output\_service\_task\_exec\_iam\_role\_name) | Task execution IAM role name | -| [service\_task\_exec\_iam\_role\_unique\_id](#output\_service\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | -| [service\_task\_set\_arn](#output\_service\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | -| [service\_task\_set\_id](#output\_service\_task\_set\_id) | The ID of the task set | -| [service\_task\_set\_stability\_status](#output\_service\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | -| [service\_task\_set\_status](#output\_service\_task\_set\_status) | The status of the task set | -| [service\_tasks\_iam\_role\_arn](#output\_service\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | -| [service\_tasks\_iam\_role\_name](#output\_service\_tasks\_iam\_role\_name) | Tasks IAM role name | -| [service\_tasks\_iam\_role\_unique\_id](#output\_service\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | +No outputs. ## License diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index f8a15bb1..0bd798e0 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -26,7 +26,7 @@ locals { ################################################################################ module "ecs_cluster" { - source = "terraform-aws-modules/ecs/aws" + source = "terraform-aws-modules/ecs/aws" version = "5.2.2" cluster_name = local.name diff --git a/examples/ecs-cluster-with-vpc/outputs.tf b/examples/ecs-cluster-with-vpc/outputs.tf index fc2330f0..f6a548e2 100644 --- a/examples/ecs-cluster-with-vpc/outputs.tf +++ b/examples/ecs-cluster-with-vpc/outputs.tf @@ -25,4 +25,4 @@ # output "cluster_autoscaling_capacity_providers" { # description = "Map of capacity providers created and their attributes" # value = module.ecs_cluster.autoscaling_capacity_providers -# } \ No newline at end of file +# } From 86ebf4ef5221ce914c5c8f5105c643527a0bc98d Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Fri, 27 Oct 2023 10:48:34 +0000 Subject: [PATCH 13/22] Fixing failures detected by pre-commit --- examples/ecs-cluster-with-vpc/outputs.tf | 28 ---------------------- modules/ecs-monitoring/configs/config.yaml | 4 ++-- modules/ecs-monitoring/locals.tf | 4 ++-- modules/ecs-monitoring/main.tf | 9 ++----- modules/ecs-monitoring/outputs.tf | 16 ++++++------- modules/ecs-monitoring/variables.tf | 8 +++---- 6 files changed, 18 insertions(+), 51 deletions(-) diff --git a/examples/ecs-cluster-with-vpc/outputs.tf b/examples/ecs-cluster-with-vpc/outputs.tf index f6a548e2..e69de29b 100644 --- a/examples/ecs-cluster-with-vpc/outputs.tf +++ b/examples/ecs-cluster-with-vpc/outputs.tf @@ -1,28 +0,0 @@ -################################################################################ -# Cluster -################################################################################ - -# output "cluster_arn" { -# description = "ARN that identifies the cluster" -# value = module.ecs_cluster.arn -# } - -# output "cluster_id" { -# description = "ID that identifies the cluster" -# value = module.ecs_cluster.id -# } - -# output "cluster_name" { -# description = "Name that identifies the cluster" -# value = module.ecs_cluster.name -# } - -# output "cluster_capacity_providers" { -# description = "Map of cluster capacity providers attributes" -# value = module.ecs_cluster.cluster_capacity_providers -# } - -# output "cluster_autoscaling_capacity_providers" { -# description = "Map of capacity providers created and their attributes" -# value = module.ecs_cluster.autoscaling_capacity_providers -# } diff --git a/modules/ecs-monitoring/configs/config.yaml b/modules/ecs-monitoring/configs/config.yaml index 8c62a6ee..9a46484d 100644 --- a/modules/ecs-monitoring/configs/config.yaml +++ b/modules/ecs-monitoring/configs/config.yaml @@ -16,9 +16,9 @@ receivers: otlp: protocols: grpc: - endpoint: ${otlpGrpcEndpoint} + endpoint: ${otlp_grpc_endpoint} http: - endpoint: ${otlpHttpEndpoint} + endpoint: ${otlp_http_endpoint} prometheus: config: scrape_configs: diff --git a/modules/ecs-monitoring/locals.tf b/modules/ecs-monitoring/locals.tf index 6f319b7a..42fc2a90 100644 --- a/modules/ecs-monitoring/locals.tf +++ b/modules/ecs-monitoring/locals.tf @@ -13,8 +13,8 @@ locals { refresh_interval = var.refresh_interval ecs_metrics_collection_interval = var.ecs_metrics_collection_interval amp_remote_write_ep = "${local.prometheus_ws_endpoint}api/v1/remote_write" - otlpGrpcEndpoint = var.otlpGrpcEndpoint - otlpHttpEndpoint = var.otlpHttpEndpoint + otlp_grpc_endpoint = var.otlp_grpc_endpoint + otlp_http_endpoint = var.otlp_http_endpoint } ssm_param_value = yamlencode( diff --git a/modules/ecs-monitoring/main.tf b/modules/ecs-monitoring/main.tf index 28ef1e63..af4d509c 100644 --- a/modules/ecs-monitoring/main.tf +++ b/modules/ecs-monitoring/main.tf @@ -17,11 +17,6 @@ module "managed_grafana_default" { associate_license = false } -##################### -## Commented this module, as AMP workspace is a pre-requiste for this solution. -## You can use this code to create a AMP workspace -##################### - module "managed_prometheus_default" { count = var.create_managed_prometheus_ws ? 1 : 0 @@ -34,8 +29,8 @@ module "managed_prometheus_default" { ########################################### resource "aws_ecs_task_definition" "adot_ecs_prometheus" { family = "adot_prometheus_td" - task_role_arn = var.taskRoleArn - execution_role_arn = var.executionRoleArn + task_role_arn = var.task_role_arn + execution_role_arn = var.execution_role_arn network_mode = "bridge" requires_compatibilities = ["EC2"] cpu = var.ecs_adot_cpu diff --git a/modules/ecs-monitoring/outputs.tf b/modules/ecs-monitoring/outputs.tf index f3075eef..c9df0363 100644 --- a/modules/ecs-monitoring/outputs.tf +++ b/modules/ecs-monitoring/outputs.tf @@ -8,12 +8,12 @@ output "grafana_workspace_endpoint" { value = module.managed_grafana_default.workspace_endpoint } -# output "prometheus_workspace_id" { -# description = "Identifier of the workspace" -# value = module.managed_prometheus_default.workspace_id -# } +output "prometheus_workspace_id" { + description = "Identifier of the workspace" + value = module.managed_prometheus_default.workspace_id +} -# output "prometheus_workspace_endpoint" { -# description = "Prometheus endpoint available for this workspace" -# value = module.managed_prometheus_default.workspace_prometheus_endpoint -# } +output "prometheus_workspace_endpoint" { + description = "Prometheus endpoint available for this workspace" + value = module.managed_prometheus_default.workspace_prometheus_endpoint +} diff --git a/modules/ecs-monitoring/variables.tf b/modules/ecs-monitoring/variables.tf index 51b80335..7ce94c2b 100644 --- a/modules/ecs-monitoring/variables.tf +++ b/modules/ecs-monitoring/variables.tf @@ -3,12 +3,12 @@ variable "aws_ecs_cluster_name" { type = string } -variable "taskRoleArn" { +variable "task_role_arn" { description = "ARN of the IAM Task Role" type = string } -variable "executionRoleArn" { +variable "execution_role_arn" { description = "ARN of the IAM Execution Role" type = string } @@ -43,14 +43,14 @@ variable "ecs_metrics_collection_interval" { default = "15s" } -variable "otlpGrpcEndpoint" { +variable "otlp_grpc_endpoint" { description = "otlpGrpcEndpoint" type = string default = "0.0.0.0:4317" } -variable "otlpHttpEndpoint" { +variable "otlp_http_endpoint" { description = "otlpHttpEndpoint" type = string default = "0.0.0.0:4318" From 70f7e440d1bb0b0594cc1e54223cf42b26fad009 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Sun, 29 Oct 2023 19:26:53 +0100 Subject: [PATCH 14/22] Pre-commit fixes --- modules/ecs-monitoring/README.md | 10 ++++++---- modules/ecs-monitoring/locals.tf | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/ecs-monitoring/README.md b/modules/ecs-monitoring/README.md index 0891d5fb..66b2a1c1 100644 --- a/modules/ecs-monitoring/README.md +++ b/modules/ecs-monitoring/README.md @@ -59,12 +59,12 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | [ecs\_adot\_cpu](#input\_ecs\_adot\_cpu) | CPU to be allocated for the ADOT ECS TASK | `string` | `"256"` | no | | [ecs\_adot\_mem](#input\_ecs\_adot\_mem) | Memory to be allocated for the ADOT ECS TASK | `string` | `"512"` | no | | [ecs\_metrics\_collection\_interval](#input\_ecs\_metrics\_collection\_interval) | Collection interval for ecs metrics | `string` | `"15s"` | no | -| [executionRoleArn](#input\_executionRoleArn) | ARN of the IAM Execution Role | `string` | n/a | yes | +| [execution\_role\_arn](#input\_execution\_role\_arn) | ARN of the IAM Execution Role | `string` | n/a | yes | | [otel\_image\_ver](#input\_otel\_image\_ver) | Otel Docker Image version | `string` | `"v0.31.0"` | no | -| [otlpGrpcEndpoint](#input\_otlpGrpcEndpoint) | otlpGrpcEndpoint | `string` | `"0.0.0.0:4317"` | no | -| [otlpHttpEndpoint](#input\_otlpHttpEndpoint) | otlpHttpEndpoint | `string` | `"0.0.0.0:4318"` | no | +| [otlp\_grpc\_endpoint](#input\_otlp\_grpc\_endpoint) | otlpGrpcEndpoint | `string` | `"0.0.0.0:4317"` | no | +| [otlp\_http\_endpoint](#input\_otlp\_http\_endpoint) | otlpHttpEndpoint | `string` | `"0.0.0.0:4318"` | no | | [refresh\_interval](#input\_refresh\_interval) | Refresh interval for ecs\_observer | `string` | `"60s"` | no | -| [taskRoleArn](#input\_taskRoleArn) | ARN of the IAM Task Role | `string` | n/a | yes | +| [task\_role\_arn](#input\_task\_role\_arn) | ARN of the IAM Task Role | `string` | n/a | yes | ## Outputs @@ -72,4 +72,6 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this |------|-------------| | [grafana\_workspace\_endpoint](#output\_grafana\_workspace\_endpoint) | The endpoint of the Grafana workspace | | [grafana\_workspace\_id](#output\_grafana\_workspace\_id) | The ID of the Grafana workspace | +| [prometheus\_workspace\_endpoint](#output\_prometheus\_workspace\_endpoint) | Prometheus endpoint available for this workspace | +| [prometheus\_workspace\_id](#output\_prometheus\_workspace\_id) | Identifier of the workspace | diff --git a/modules/ecs-monitoring/locals.tf b/modules/ecs-monitoring/locals.tf index 42fc2a90..a06ad308 100644 --- a/modules/ecs-monitoring/locals.tf +++ b/modules/ecs-monitoring/locals.tf @@ -13,8 +13,8 @@ locals { refresh_interval = var.refresh_interval ecs_metrics_collection_interval = var.ecs_metrics_collection_interval amp_remote_write_ep = "${local.prometheus_ws_endpoint}api/v1/remote_write" - otlp_grpc_endpoint = var.otlp_grpc_endpoint - otlp_http_endpoint = var.otlp_http_endpoint + otlp_grpc_endpoint = var.otlp_grpc_endpoint + otlp_http_endpoint = var.otlp_http_endpoint } ssm_param_value = yamlencode( From bdcdc0d334f5aa10f45b81a2aa4dbf261ca559c8 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Mon, 30 Oct 2023 08:20:34 +0000 Subject: [PATCH 15/22] Fixing failures detected by pre-commit --- examples/ecs-cluster-with-vpc/main.tf | 8 ++++---- modules/ecs-monitoring/outputs.tf | 10 ---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index 0bd798e0..d703729b 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -180,10 +180,10 @@ module "alb_sg" { } module "ecs_monitoring" { - source = "../../modules/ecs-monitoring" - aws_ecs_cluster_name = module.ecs_cluster.cluster_name - taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn - executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn + source = "../../modules/ecs-monitoring" + aws_ecs_cluster_name = module.ecs_cluster.cluster_name + task_role_arn = module.ecs_cluster.task_exec_iam_role_arn + execution_role_arn = module.ecs_cluster.task_exec_iam_role_arn depends_on = [ module.ecs_cluster diff --git a/modules/ecs-monitoring/outputs.tf b/modules/ecs-monitoring/outputs.tf index c9df0363..d9416986 100644 --- a/modules/ecs-monitoring/outputs.tf +++ b/modules/ecs-monitoring/outputs.tf @@ -7,13 +7,3 @@ output "grafana_workspace_endpoint" { description = "The endpoint of the Grafana workspace" value = module.managed_grafana_default.workspace_endpoint } - -output "prometheus_workspace_id" { - description = "Identifier of the workspace" - value = module.managed_prometheus_default.workspace_id -} - -output "prometheus_workspace_endpoint" { - description = "Prometheus endpoint available for this workspace" - value = module.managed_prometheus_default.workspace_prometheus_endpoint -} From 90ffa830e8fdb603ad4779e68b4ce0b6159ee1b2 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Mon, 30 Oct 2023 08:28:19 +0000 Subject: [PATCH 16/22] Fixing failures detected by pre-commit --- README.md | 4 ++-- docs/ecs/ecs-monitoring-on-ec2.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 83b172da..78df8d24 100644 --- a/README.md +++ b/README.md @@ -135,8 +135,8 @@ module "ecs_monitoring" { source = "github.com/aws-observability/terraform-aws-observability-accelerator//modules/ecs-monitoring" aws_ecs_cluster_name = module.ecs_cluster.cluster_name - taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn - executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn + task_role_arn = module.ecs_cluster.task_exec_iam_role_arn + execution_role_arn = module.ecs_cluster.task_exec_iam_role_arn } ``` Grafana Dashboards diff --git a/docs/ecs/ecs-monitoring-on-ec2.md b/docs/ecs/ecs-monitoring-on-ec2.md index 16f81197..0d10266f 100644 --- a/docs/ecs/ecs-monitoring-on-ec2.md +++ b/docs/ecs/ecs-monitoring-on-ec2.md @@ -33,8 +33,8 @@ Make sure to update your exisitng Application Task Definitions based on the work module "ecs_monitoring" { source = "../../modules/ecs-monitoring" aws_ecs_cluster_name = module.ecs_cluster.cluster_name - taskRoleArn = module.ecs_cluster.task_exec_iam_role_arn - executionRoleArn = module.ecs_cluster.task_exec_iam_role_arn + task_role_arn = module.ecs_cluster.task_exec_iam_role_arn + execution_role_arn = module.ecs_cluster.task_exec_iam_role_arn depends_on = [ module.ecs_cluster From 4440d0c1e6a1a0fba03c01b8ea2efb664370a1a6 Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Mon, 30 Oct 2023 09:55:34 +0100 Subject: [PATCH 17/22] Pre-commit --- examples/ecs-cluster-with-vpc/main.tf | 8 ++++---- modules/ecs-monitoring/README.md | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index d703729b..94dc2b48 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -180,10 +180,10 @@ module "alb_sg" { } module "ecs_monitoring" { - source = "../../modules/ecs-monitoring" - aws_ecs_cluster_name = module.ecs_cluster.cluster_name - task_role_arn = module.ecs_cluster.task_exec_iam_role_arn - execution_role_arn = module.ecs_cluster.task_exec_iam_role_arn + source = "../../modules/ecs-monitoring" + aws_ecs_cluster_name = module.ecs_cluster.cluster_name + task_role_arn = module.ecs_cluster.task_exec_iam_role_arn + execution_role_arn = module.ecs_cluster.task_exec_iam_role_arn depends_on = [ module.ecs_cluster diff --git a/modules/ecs-monitoring/README.md b/modules/ecs-monitoring/README.md index 66b2a1c1..8f6da073 100644 --- a/modules/ecs-monitoring/README.md +++ b/modules/ecs-monitoring/README.md @@ -72,6 +72,4 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this |------|-------------| | [grafana\_workspace\_endpoint](#output\_grafana\_workspace\_endpoint) | The endpoint of the Grafana workspace | | [grafana\_workspace\_id](#output\_grafana\_workspace\_id) | The ID of the Grafana workspace | -| [prometheus\_workspace\_endpoint](#output\_prometheus\_workspace\_endpoint) | Prometheus endpoint available for this workspace | -| [prometheus\_workspace\_id](#output\_prometheus\_workspace\_id) | Identifier of the workspace | From 0f126e7bf694bdc91a33d1040ecc5521319c0548 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Mon, 30 Oct 2023 10:05:06 +0000 Subject: [PATCH 18/22] Fixing HIGH security alerts detected by pre-commit --- examples/ecs-cluster-with-vpc/main.tf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index d703729b..75d2ffd4 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -55,6 +55,10 @@ module "ecs_cluster" { } } } + + metadata_options = { + http_tokens = "required" + } tags = local.tags } @@ -171,7 +175,7 @@ module "alb_sg" { vpc_id = module.vpc.vpc_id ingress_rules = ["http-80-tcp"] - ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_cidr_blocks = ["10.0.0.0/16"] egress_rules = ["all-all"] egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks From a1c1821baaff7cc01d635c9f35fc648bd11d5c0a Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Tue, 31 Oct 2023 11:36:20 +0000 Subject: [PATCH 19/22] Fixing HIGH security alerts detected by pre-commit --- examples/ecs-cluster-with-vpc/main.tf | 71 ++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index 4613cdf3..00f2f3c7 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -19,6 +19,53 @@ locals { Example = local.name Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" } + + network_acls = { + public_inbound = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_block = "10.0.0.0/16" + }, + { + rule_number = 110 + rule_action = "allow" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_block = "10.0.0.0/16" + }, + { + rule_number = 120 + rule_action = "allow" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_block = "10.0.0.0/16" + } + ] + public_outbound = [ + { + rule_number = 100 + rule_action = "allow" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_block = "10.0.0.0/16" + }, + { + rule_number = 110 + rule_action = "allow" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_block = "10.0.0.0/16" + } + ] + } } ################################################################################ @@ -55,10 +102,6 @@ module "ecs_cluster" { } } } - - metadata_options = { - http_tokens = "required" - } tags = local.tags } @@ -121,6 +164,10 @@ module "autoscaling" { use_mixed_instances_policy = each.value.use_mixed_instances_policy mixed_instances_policy = each.value.mixed_instances_policy + metadata_options = { + http_tokens = "required" + } + tags = local.tags } @@ -140,7 +187,7 @@ module "autoscaling_sg" { ] number_of_computed_ingress_with_source_security_group_id = 1 - egress_rules = ["all-all"] + # egress_rules = ["http-80-tcp"] tags = local.tags } @@ -156,8 +203,16 @@ module "vpc" { private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - enable_nat_gateway = true - single_nat_gateway = true + public_dedicated_network_acl = true + public_inbound_acl_rules = local.network_acls["public_inbound"] + public_outbound_acl_rules = local.network_acls["public_outbound"] + private_dedicated_network_acl = true + private_inbound_acl_rules = local.network_acls["public_inbound"] + private_outbound_acl_rules = local.network_acls["public_outbound"] + + manage_default_network_acl = true + enable_nat_gateway = true + single_nat_gateway = true tags = local.tags } @@ -177,7 +232,7 @@ module "alb_sg" { ingress_rules = ["http-80-tcp"] ingress_cidr_blocks = ["10.0.0.0/16"] - egress_rules = ["all-all"] + # egress_rules = ["http-80-tcp"] egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks tags = local.tags From 59355d632a0a47e23352e51e33aaeb4239902345 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Tue, 31 Oct 2023 11:38:19 +0000 Subject: [PATCH 20/22] Fixing HIGH security alerts detected by pre-commit, 31stOct --- examples/ecs-cluster-with-vpc/main.tf | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/ecs-cluster-with-vpc/main.tf b/examples/ecs-cluster-with-vpc/main.tf index 00f2f3c7..a08e0e38 100644 --- a/examples/ecs-cluster-with-vpc/main.tf +++ b/examples/ecs-cluster-with-vpc/main.tf @@ -187,8 +187,6 @@ module "autoscaling_sg" { ] number_of_computed_ingress_with_source_security_group_id = 1 - # egress_rules = ["http-80-tcp"] - tags = local.tags } @@ -231,9 +229,7 @@ module "alb_sg" { ingress_rules = ["http-80-tcp"] ingress_cidr_blocks = ["10.0.0.0/16"] - - # egress_rules = ["http-80-tcp"] - egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks tags = local.tags } From 8919e6c7e3b05bc6bc06eb823c231ee1fb1fc84e Mon Sep 17 00:00:00 2001 From: Rodrigue Koffi Date: Thu, 2 Nov 2023 09:34:22 +0100 Subject: [PATCH 21/22] Add links after merge --- docs/ecs/ecs-monitoring-on-ec2.md | 3 +-- docs/helpers/ecs-cluster-with-vpc.md | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/ecs/ecs-monitoring-on-ec2.md b/docs/ecs/ecs-monitoring-on-ec2.md index 0d10266f..11e50a85 100644 --- a/docs/ecs/ecs-monitoring-on-ec2.md +++ b/docs/ecs/ecs-monitoring-on-ec2.md @@ -1,8 +1,7 @@ # Amazon ECS on EC2 cluster monitoring This example demonstrates how to monitor your Amazon Elastic Container Service on EC2 -(Amazon ECS) cluster with the Observability Accelerator's -[ECS monitoring module](https://github.com/aws-observability/terraform-aws-observability-accelerator/tree/main/modules/ecs-monitoring). +(Amazon ECS) cluster with the Observability Accelerator's ECS monitoring module The module collects Prometheus metrics from tasks running on ECS and sends it to Prometheus using AWS Distro for OpenTelemetry Collector (ADOT). You can either run the collector as a sidecar or deploy the collector as its own ECS service for entire cluster. diff --git a/docs/helpers/ecs-cluster-with-vpc.md b/docs/helpers/ecs-cluster-with-vpc.md index 96c8022e..a1d44f62 100644 --- a/docs/helpers/ecs-cluster-with-vpc.md +++ b/docs/helpers/ecs-cluster-with-vpc.md @@ -1,6 +1,5 @@ # Example Amazon ECS Cluster with VPC This example deploys an AWS ECS Cluster with VPC and also add the ECS Monitoring module -[ECS Cluster example](https://github.com/aws-observability/terraform-aws-observability-accelerator/tree/main/examples/ecs-cluster-with-vpc).vpc ## Prerequisites From 382987b8ae4ac22387aedd0cf1787a27ba151dc6 Mon Sep 17 00:00:00 2001 From: Ruchika Modi Date: Thu, 2 Nov 2023 11:40:49 +0000 Subject: [PATCH 22/22] 2ndNov - Added condiotnal creation for Grafana WS and module versions for AMG, AMP --- modules/ecs-monitoring/README.md | 7 +++++-- modules/ecs-monitoring/main.tf | 4 ++++ modules/ecs-monitoring/outputs.tf | 14 ++++++++++++-- modules/ecs-monitoring/variables.tf | 6 ++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/modules/ecs-monitoring/README.md b/modules/ecs-monitoring/README.md index 8f6da073..e89f31ab 100644 --- a/modules/ecs-monitoring/README.md +++ b/modules/ecs-monitoring/README.md @@ -37,8 +37,8 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this | Name | Source | Version | |------|--------|---------| -| [managed\_grafana\_default](#module\_managed\_grafana\_default) | terraform-aws-modules/managed-service-grafana/aws | n/a | -| [managed\_prometheus\_default](#module\_managed\_prometheus\_default) | terraform-aws-modules/managed-service-prometheus/aws | n/a | +| [managed\_grafana\_default](#module\_managed\_grafana\_default) | terraform-aws-modules/managed-service-grafana/aws | 2.1.0 | +| [managed\_prometheus\_default](#module\_managed\_prometheus\_default) | terraform-aws-modules/managed-service-prometheus/aws | 2.2.2 | ## Resources @@ -55,6 +55,7 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this |------|-------------|------|---------|:--------:| | [aws\_ecs\_cluster\_name](#input\_aws\_ecs\_cluster\_name) | Name of your ECS cluster | `string` | n/a | yes | | [container\_name](#input\_container\_name) | Container Name for Adot | `string` | `"adot_new"` | no | +| [create\_managed\_grafana\_ws](#input\_create\_managed\_grafana\_ws) | Creates a Workspace for Amazon Managed Grafana | `bool` | `true` | no | | [create\_managed\_prometheus\_ws](#input\_create\_managed\_prometheus\_ws) | Creates a Workspace for Amazon Managed Prometheus | `bool` | `true` | no | | [ecs\_adot\_cpu](#input\_ecs\_adot\_cpu) | CPU to be allocated for the ADOT ECS TASK | `string` | `"256"` | no | | [ecs\_adot\_mem](#input\_ecs\_adot\_mem) | Memory to be allocated for the ADOT ECS TASK | `string` | `"512"` | no | @@ -72,4 +73,6 @@ See examples using this Terraform modules in the **Amazon ECS** section of [this |------|-------------| | [grafana\_workspace\_endpoint](#output\_grafana\_workspace\_endpoint) | The endpoint of the Grafana workspace | | [grafana\_workspace\_id](#output\_grafana\_workspace\_id) | The ID of the Grafana workspace | +| [prometheus\_workspace\_id](#output\_prometheus\_workspace\_id) | Identifier of the workspace | +| [prometheus\_workspace\_prometheus\_endpoint](#output\_prometheus\_workspace\_prometheus\_endpoint) | Prometheus endpoint available for this workspace | diff --git a/modules/ecs-monitoring/main.tf b/modules/ecs-monitoring/main.tf index af4d509c..537cf87a 100644 --- a/modules/ecs-monitoring/main.tf +++ b/modules/ecs-monitoring/main.tf @@ -12,7 +12,10 @@ resource "aws_ssm_parameter" "adot_config" { ############################################ module "managed_grafana_default" { + count = var.create_managed_grafana_ws ? 1 : 0 + source = "terraform-aws-modules/managed-service-grafana/aws" + version = "2.1.0" name = "${local.name}-default" associate_license = false } @@ -21,6 +24,7 @@ module "managed_prometheus_default" { count = var.create_managed_prometheus_ws ? 1 : 0 source = "terraform-aws-modules/managed-service-prometheus/aws" + version = "2.2.2" workspace_alias = "${local.name}-default" } diff --git a/modules/ecs-monitoring/outputs.tf b/modules/ecs-monitoring/outputs.tf index d9416986..9510c032 100644 --- a/modules/ecs-monitoring/outputs.tf +++ b/modules/ecs-monitoring/outputs.tf @@ -1,9 +1,19 @@ output "grafana_workspace_id" { description = "The ID of the Grafana workspace" - value = module.managed_grafana_default.workspace_id + value = try(module.managed_grafana_default[0].workspace_id, "") } output "grafana_workspace_endpoint" { description = "The endpoint of the Grafana workspace" - value = module.managed_grafana_default.workspace_endpoint + value = try(module.managed_grafana_default[0].workspace_endpoint, "") +} + +output "prometheus_workspace_id" { + description = "Identifier of the workspace" + value = try(module.managed_prometheus_default[0].id, "") +} + +output "prometheus_workspace_prometheus_endpoint" { + description = "Prometheus endpoint available for this workspace" + value = try(module.managed_prometheus_default[0].prometheus_endpoint, "") } diff --git a/modules/ecs-monitoring/variables.tf b/modules/ecs-monitoring/variables.tf index 7ce94c2b..c0e782ed 100644 --- a/modules/ecs-monitoring/variables.tf +++ b/modules/ecs-monitoring/variables.tf @@ -25,6 +25,12 @@ variable "ecs_adot_mem" { default = "512" } +variable "create_managed_grafana_ws" { + description = "Creates a Workspace for Amazon Managed Grafana" + type = bool + default = true +} + variable "create_managed_prometheus_ws" { description = "Creates a Workspace for Amazon Managed Prometheus" type = bool