From 41bce9eafabe42cdd8690dd7161c078556365a50 Mon Sep 17 00:00:00 2001 From: oleksa <99fifo@gmail.com> Date: Thu, 9 Jan 2025 15:34:10 +0200 Subject: [PATCH] Add multy TGs example --- examples/multy-tg/README.md | 93 +++++++ examples/multy-tg/main.tf | 471 +++++++++++++++++++++++++++++++++ examples/multy-tg/outputs.tf | 132 +++++++++ examples/multy-tg/variables.tf | 0 examples/multy-tg/versions.tf | 10 + 5 files changed, 706 insertions(+) create mode 100644 examples/multy-tg/README.md create mode 100644 examples/multy-tg/main.tf create mode 100644 examples/multy-tg/outputs.tf create mode 100644 examples/multy-tg/variables.tf create mode 100644 examples/multy-tg/versions.tf diff --git a/examples/multy-tg/README.md b/examples/multy-tg/README.md new file mode 100644 index 00000000..b33e14cc --- /dev/null +++ b/examples/multy-tg/README.md @@ -0,0 +1,93 @@ +# ECS Cluster w/ Multy target groups on ALB + +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.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.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/multy-tg/main.tf b/examples/multy-tg/main.tf new file mode 100644 index 00000000..5a52ca8f --- /dev/null +++ b/examples/multy-tg/main.tf @@ -0,0 +1,471 @@ +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_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 + } + } + } + + tags = local.tags +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "../../modules/service" + + # Service + name = "NGINX" + cluster_arn = module.ecs_cluster.arn + + # Task Definition + requires_compatibilities = ["EC2"] + cpu = 256 + memory = 256 + capacity_provider_strategy = { + # On-demand instances + ex_1 = { + capacity_provider = module.ecs_cluster.autoscaling_capacity_providers["ex_1"].name + weight = 1 + base = 1 + } + } + + # Container definition(s) + container_definitions = { + ("nginx") = { + image = "nginx:latest" + port_mappings = [ + { + name = "nginx" + containerPort = 80 + protocol = "tcp" + } + ] + entrypoint = [ + "/bin/bash", + "-c" + ] + command = [ + "mkdir -p /usr/share/nginx/html/nginx ; echo ' Current Time

Current Time

' > /usr/share/nginx/html/nginx/index.html && exec nginx -g 'daemon off;'" + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + enable_cloudwatch_logging = true + create_cloudwatch_log_group = true + cloudwatch_log_group_name = "/aws/ecs/nginx/nginx" + cloudwatch_log_group_retention_in_days = 7 + + log_configuration = { + logDriver = "awslogs" + } + } + } + + load_balancer = { + service = { + target_group_arn = module.alb.target_groups["nginx"].arn + container_name = "nginx" + container_port = 80 + } + } + + subnet_ids = module.vpc.public_subnets + security_group_rules = { + alb_http_ingress = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb.security_group_id + } + } + + tags = { + Name = "nginx" + Example = "nginx" + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service_2" { + source = "../../modules/service" + + # Service + name = local.name + cluster_arn = module.ecs_cluster.arn + + # Task Definition + requires_compatibilities = ["EC2"] + cpu = 256 + memory = 256 + capacity_provider_strategy = { + # On-demand instances + ex_1 = { + capacity_provider = module.ecs_cluster.autoscaling_capacity_providers["ex_1"].name + weight = 1 + base = 1 + } + } + + # Container definition(s) + container_definitions = { + ("docker-labs") = { + image = "docker/getting-started:latest" + port_mappings = [ + { + name = "docker-labs" + containerPort = local.container_port + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + enable_cloudwatch_logging = true + create_cloudwatch_log_group = true + cloudwatch_log_group_name = "/aws/ecs/${local.name}/docker-labs" + cloudwatch_log_group_retention_in_days = 7 + + log_configuration = { + logDriver = "awslogs" + } + } + } + + load_balancer = { + service = { + target_group_arn = module.alb.target_groups["docker-labs"].arn + container_name = "docker-labs" + 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.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" { + source = "terraform-aws-modules/alb/aws" + version = "~> 9.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + listeners = { + ex_http = { + port = 80 + protocol = "HTTP" + + forward = { + target_group_key = "docker-labs" + } + + rules = { + + docker-labs = { + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_key = "docker-labs" + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + conditions = [{ + path_pattern = { + values = ["/tutorial/*"] + } + tags = { + Name = "dgs" + Example = "dgs" + } + }] + } + + nginx = { + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_key = "nginx" + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + conditions = [{ + path_pattern = { + values = ["/nginx/*"] + } + }] + tags = { + Name = "nginx" + Example = "nginx" + } + } + + } + } + } + + target_groups = { + docker-labs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + nginx = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } + + tags = local.tags +} + +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + for_each = { + # On-demand instances + ex_1 = { + instance_type = "t3.micro" + use_mixed_instances_policy = false + mixed_instances_policy = {} + + user_data = <<-EOF + #!/bin/bash + + # Write ECS configuration + cat <<'EOT' > /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + EOT + EOF + + + } + } + + 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.public_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 2 + desired_capacity = 1 + + # 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.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)] + + map_public_ip_on_launch = true + + enable_nat_gateway = false + + tags = local.tags +} diff --git a/examples/multy-tg/outputs.tf b/examples/multy-tg/outputs.tf new file mode 100644 index 00000000..2f6f85a7 --- /dev/null +++ b/examples/multy-tg/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/multy-tg/variables.tf b/examples/multy-tg/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/multy-tg/versions.tf b/examples/multy-tg/versions.tf new file mode 100644 index 00000000..682191e7 --- /dev/null +++ b/examples/multy-tg/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +}