Provision an Amazon ECS Cluster Using Terraform
Description
Terraform is an infrastructure-as-code tool that makes handling infrastructure more straightforward and manageable. A Terraform module is a collection of configuration files that encapsulate groups of resources dedicated to one task. Modules can be used to package and reuse resource configurations, making it easier to manage larger application components like an Amazon ECS Cluster.
In this demo, you will provision an Amazon ECS Cluster into an existing Amazon VPC.
Learning objectives
Define a Terraform module that deploys Amazon ECS resources
Apply an Auto Scaling Group Policy to respond to ECS metrics
Deploy an Amazon ECS Cluster into an existing Amazon VPC using Terraform
Prerequisites
IDE with configured IAM credentials
Installed Terraform with IDE
cloning repo: https://github.com/noweder/ECS-Terraform.git
Below resources pre-created in your AWS account to be referenced in later steps:
1 Virtual Private Cloud
2 Public Subnets
2 Private Subnets
Public-facing Application Load Balancer
Internal-facing Application Load Balancer
Target Architecture
Demo Steps
Initiate Terraform by running the following command from the cloned directory path:
terrafrom initReviewing the Terraform Variables file
The variables.tf file defines the name, description, and expected data type for each variable referenced in the main.tf file. Feel free to review the data types of each variable.
You will configure the terraform.tfvars file in the next step. This file will include the actual values for each variable. These values will be retrieved from the existing infrastructure.
The outputs.tf file defines the expected output values for the deployment. In this demo, the CloudWatch Log Group names and the ECS Cluster ARN will be output after a successful deployment.
Defining an Amazon ECS Cluster Using Terraform
In this step, you will configure an Amazon ECS cluster, service, and task definition in the main Terraform configuration file. This lab step will also explore the
terraform.tfvarsfile that provides configuration values to the ECS Cluster.The ECS Cluster will contain two ECS Services.
A frontend service will deploy tasks (containers) behind the public-facing Application Load Balancer (ALB) and in each of the public subnets. These tasks are based on an existing ECR image that serves a simple webpage and data retrieved from the backend.
The backend service tasks will be behind the internal-facing application load balancer, inside each private subnet. The ECR image used for the backend service generates sample data that is passed to the frontend service.
Once the ECS Cluster is deployed, you will be able to access the entire application by navigating to the public ALB's URL.
Double-click the terraform.tfvars file to open it in the editor, then paste in the following configuration values:
app_name = "lab" ecs_role_arn = "arn:aws:iam::574227279091:role/lab-ecs-task-execution-role" ecs_services = { frontend = { image = "574227279091.dkr.ecr.us-west-2.amazonaws.com/frontend:1.0.0" cpu = 256 memory = 512 container_port = 8080 host_port = 8080 desired_count = 2 is_public = true protocol = "HTTP" auto_scaling = { max_capacity = 3 min_capacity = 2 cpu_threshold = 50 memory_threshold = 50 } } backend = { image = "574227279091.dkr.ecr.us-west-2.amazonaws.com/backend:1.0.0" cpu = 256 memory = 512 container_port = 8080 host_port = 8080 desired_count = 2 is_public = false protocol = "HTTP" auto_scaling = { max_capacity = 3 min_capacity = 2 cpu_threshold = 75 memory_threshold = 75 } } } internal_alb_dns = "internal-lab-internal-400236659.us-west-2.elb.amazonaws.com" private_subnet_ids = [ "subnet-0198a9e41d0bca224", "subnet-017d8d0f81ff2975e" ] public_subnet_ids = [ "subnet-05f01cddf0795094f", "subnet-097cabee00a37f8b5" ] security_group_ids = [ "sg-020dd20b13ae12807", "sg-0269de162c343c363" ] target_group_arns = { backend = { arn = "arn:aws:elasticloadbalancing:us-west-2:574227279091:targetgroup/backend-tg/8798f2ad7abee6f8" } frontend = { arn = "arn:aws:elasticloadbalancing:us-west-2:574227279091:targetgroup/frontend-tg/5d5b9e91c3b1ba49" } }The most important value defined in this file is the
ecs_servicesmap. This map of objects includes thefrontendandbackendservice configurations. The use of maps, objects, and lists allows you to template an application effectively. Terraform provides meta-arguments such asfor_each, that can be used to traverse a map or list to reduce the number of resources defined in yourmain.tffile.Open the main.tf file and paste below code at the end:
resource "aws_ecs_cluster" "ecs_cluster" { name = lower("${var.app_name}-cluster") } # ECS Services resource "aws_ecs_service" "service" { for_each = var.ecs_services name = "${each.key}-service" cluster = aws_ecs_cluster.ecs_cluster.id task_definition = aws_ecs_task_definition.ecs_task_definition[each.key].arn launch_type = "FARGATE" desired_count = each.value.desired_count network_configuration { subnets = each.value.is_public == true ? var.public_subnet_ids : var.private_subnet_ids assign_public_ip = each.value.is_public security_groups = var.security_group_ids } load_balancer { target_group_arn = var.target_group_arns[each.key].arn container_name = each.key container_port = each.value.container_port } } # ECS Task Definitions resource "aws_ecs_task_definition" "ecs_task_definition" { for_each = var.ecs_services family = "\({lower(var.app_name)}-\){each.key}" execution_role_arn = var.ecs_role_arn requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" memory = each.value.memory cpu = each.value.cpu container_definitions = jsonencode([ { name = each.key image = each.value.image cpu = each.value.cpu memory = each.value.memory essential = true environment = [ { name = "INTERNAL_ALB", value = var.internal_alb_dns }, { name = "SERVICE_HOST", value = var.internal_alb_dns }, { name = "SERVER_SERVLET_CONTEXT_PATH", value = each.value.is_public == true ? "/" : "/${each.key}" }, { name = "SERVICES", value = "backend" }, { name = "SERVICE", value = each.key }, { name = "SERVICE_NAME", value = each.key } ] portMappings = [ { containerPort = each.value.container_port } ] logConfiguration = { logDriver = "awslogs" options = { awslogs-group = "${lower(each.key)}-logs" awslogs-region = data.aws_region.current.name awslogs-stream-prefix = var.app_name } } } ]) }The first resource is an ECS Cluster. The cluster is named
lab-clusterafter retrieving theapp_namevariable from the terraform.tfvars file.The ECS Service definition uses the
for_eachmeta-argument to create one service for each object defined in theecs_servicesvariable. Theeach.keywill resolve tofrontendandbackend. Instances ofeach.valueare followed by the corresponding attribute name of the object. Thedesired_countof tasks for each service useseach.value.desired_countand resolves to2. Surrounding these dynamic calls with${ }allows you to resolve and concatenate the value into a string, i.e.name = frontend-service.The
network_configurationreferences theis_publicattribute of each ECS service to determine which subnet to deploy the services into. For each service, if this attribute is set totrue, the service is deployed into the public subnets, and each task is assigned a public IP address. If set tofalse, no public IP is assigned, and the service is deployed into the private subnets.The
target_group_arnsmap determines which ALB each service is associated with. Thebackendtarget group is associated with the internal ALB, which is not publicly accessible. Thefrontendtarget group is associated with the public ALB since it will serve the application's webpage.The
ecs_task_definitionresource defines the tasks, or containers, within each ECS service. All ECS tasks are set to the Fargate launch type and assume the same task execution IAM Role. This role provides the task agent with permission to perform AWS API calls on your behalf.container_definitionsinclude which ECR image to use, CPU and memory configurations, as well as environment variables to be passed into the container. The environment variables in this task definition will be referenced by both frontend and backend services and are specific to the ECR image used in this lab.The task
portMappingsallow the containers to access the container port to send or receive traffic. Data will travel between frontend and backend tasks using this port.Finally, the
logConfigurationdefines theawslogslog driver which is used to send logs from the containers to Amazon CloudWatch Logs. Theoptionsdefine which CloudWatch Logs Group and AWS Region to send logs to.Applying CloudWatch Monitoring and Auto Scaling to an ECS Cluster With Terraform
In this lab step, you will define CloudWatch Logs that store container logs for each of the ECS Services. You will also configure your ECS Services with Auto Scaling Group policies that scale the service task count in or out depending on certain ECS metrics.
Paste the following code to the end of the main.tf file:
# CloudWatch Log Groups resource "aws_cloudwatch_log_group" "ecs_cw_log_group" { for_each = toset(keys(var.ecs_services)) name = lower("${each.key}-logs") } # ECS Auto Scaling Configuration resource "aws_appautoscaling_target" "service_autoscaling" { for_each = var.ecs_services max_capacity = each.value.auto_scaling.max_capacity min_capacity = each.value.auto_scaling.min_capacity resource_id = "service/\({aws_ecs_cluster.ecs_cluster.name}/\){aws_ecs_service.service[each.key].name}" scalable_dimension = "ecs:service:DesiredCount" service_namespace = "ecs" } # Auto Scaling Policies resource "aws_appautoscaling_policy" "ecs_policy_memory" { for_each = var.ecs_services name = "${var.app_name}-memory-autoscaling" policy_type = "TargetTrackingScaling" resource_id = aws_appautoscaling_target.service_autoscaling[each.key].resource_id scalable_dimension = aws_appautoscaling_target.service_autoscaling[each.key].scalable_dimension service_namespace = aws_appautoscaling_target.service_autoscaling[each.key].service_namespace target_tracking_scaling_policy_configuration { predefined_metric_specification { predefined_metric_type = "ECSServiceAverageMemoryUtilization" } target_value = each.value.auto_scaling.memory_threshold } } resource "aws_appautoscaling_policy" "ecs_policy_cpu" { for_each = var.ecs_services name = "${var.app_name}-cpu-autoscaling" policy_type = "TargetTrackingScaling" resource_id = aws_appautoscaling_target.service_autoscaling[each.key].resource_id scalable_dimension = aws_appautoscaling_target.service_autoscaling[each.key].scalable_dimension service_namespace = aws_appautoscaling_target.service_autoscaling[each.key].service_namespace target_tracking_scaling_policy_configuration { predefined_metric_specification { predefined_metric_type = "ECSServiceAverageCPUUtilization" } target_value = each.value.auto_scaling.cpu_threshold } }
In this lab step, you will deploy and test your ECS application using the Terraform CLI.
1. In the browser IDE terminal, enter the following command to validate and summarize your deployment: [**Copy code**](https://platform.qa.com/lab/provisioning-an-amazon-ecs-cluster-using-terraform/session-page/#) ```plaintext terraform plan -no-color > plan.txt ``` The command will send the output to the **plan.txt** file rather than displaying it in the terminal. There will be a total of 13 resources outlined in this plan and sorted in alphabetical order. This command also runs `terraform validate` before outputting the plan. Any misaligned variable types or misconfigured resources will be output if found. Feel free to explore this file and the resolved resource values. 2. Enter the following command to deploy your ECS Cluster: [**Copy code**](https://platform.qa.com/lab/provisioning-an-amazon-ecs-cluster-using-terraform/session-page/#) ```plaintext terraform apply --auto-approve ``` *Note*: The `terraform apply` process will take a few seconds to apply the changes to AWS, but the ECS Cluster may take up to 3 minutes to become accessible. The following message will display in the terminal, along with the predefined output variables: 1. Open the ALB URL in a new browser tab to confirm the application is running <img src="https://assets.platform.qa.com/labs/uploads/provisioning-ecs-cluster-terraform/steps/4_deploy/assets/final-webpage.png" alt="" style="" /> The application is a simple **API Results** webpage. Each time you refresh the page, the **frontend** ECS tasks retrieve a new set of data from the **backend** tasks, then display them in a table organized by **Record ID** ### **Summary** By completing this lab, you have accomplished the following tasks: * Defined a Terraform module that deploys Amazon ECS resources * Applied an Auto Scaling Group Policy to respond to ECS metrics * Deployed an Amazon ECS Cluster into an existing Amazon VPC using Terraform


