This Terraform module creates an ECS service using FARGATE
compatibilities.
- The module follows aws security best practices and uses checkov to ensure compliance.
- Contains elaborate examples that you can use to setup your ecs-service within a very short time.
- Deploy related multiple resources for your application at once
Examples available here
If you encounter any health check failures while using this module, please consider the following steps to troubleshoot the issue:
-
Verify Docker Container Port Mapping: Ensure that the port mapping between the Docker container and the host port is accurately configured. Incorrect port mapping can lead to health check failures.
-
Adjust ALB Health Check Interval: Some applications require a longer time to become fully operational. Make sure the health check interval of your Application Load Balancer (ALB) is appropriately set to allow enough time for the application to start and respond to health checks.
-
Validate Target Group Health Check Endpoint: Verify that the endpoint used for the target group health checks is correct and returns a 200 status code. This ensures that the health checks are performed against the intended endpoint.
-
Verify ALB Security Group Inbound Traffic: When using an Application Load Balancer, ensure that the port configured in your application is allowed in the ALB security group's inbound traffic rules. By default, the service security group in this module only permits traffic from the ALB security group for the specified ports. Adjust the security group rules accordingly to allow traffic from the desired ports.
For more detailed information on troubleshooting ALB health check failures, you can refer to this Stack Overflow post. It provides additional insights and solutions to common issues related to ALB health checks.
This module supports AWS ECS Service Deployment Control where it provides the capability to trigger a new deployment for the ECS service, even without changes in the Terraform codebase. This feature ensures that the service is updated with the latest changes, even when using the same image tag instead of semantic versioning.
Force Deployment: When deploying an ECS service using the same image tag, triggering a new deployment forces the service to update with the latest changes. This is particularly useful when you want to ensure that the service is always running the latest version of the containerized application, regardless of the image tag. It eliminates the need for manually updating the tag or using semantic versioning for each deployment.
- To take advantage of this feature, ensure the
var.force_new_deployment
variable is set totrue
in your Terraform configuration. For this example, this feature is enabled. Disable it by ensuring thevar.force_new_deployment
tofalse
.
NOTE: These examples use the latest version of this module
data "aws_partition" "current" {}
data "aws_ecs_cluster" "ecs" {
cluster_name = local.supporting_resources_name
}
data "aws_kms_alias" "supporting_kms" {
name = "alias/${local.supporting_resources_name}"
}
data "aws_vpc" "supporting" {
filter {
name = "tag:Name"
values = [local.supporting_resources_name]
}
}
data "aws_subnets" "private" {
filter {
name = "tag:Name"
values = ["${local.supporting_resources_name}*.pri.*"]
}
}
data "aws_subnet" "private" {
for_each = toset(data.aws_subnets.private.ids)
id = each.value
}
data "aws_iam_policy_document" "ecs_assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
locals {
private_subnet_id = [
for i in data.aws_subnet.private : i.id
]
name = "minimum-example"
cluster = data.aws_ecs_cluster.ecs.arn
supporting_resources_name = "terraform-aws-ecs-service"
vpc_id = data.aws_vpc.supporting.id
private_subnets = local.private_subnet_id
partition = data.aws_partition.current.partition
default_container_definitions = jsonencode(
[
{
name = local.name
image = "boldlink/flaskapp:latest"
cpu = 10
memory = 512
essential = true
portMappings = [
{
containerPort = 5000
hostPort = 5000
}
]
}
]
)
task_execution_role_policy_doc = jsonencode(
{
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents",
]
Resource = ["arn:${local.partition}:logs:::log-group:${local.name}"]
},
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = ["*"]
}
] }
)
}
module "ecs_service" {
source = "../../"
name = var.name
family = "${var.name}-task-definition"
network_mode = var.network_mode
cluster = local.cluster
vpc_id = local.vpc_id
task_role_policy = data.aws_iam_policy_document.ecs_assume_role_policy.json
task_execution_role = data.aws_iam_policy_document.ecs_assume_role_policy.json
task_execution_role_policy = local.task_execution_role_policy_doc
container_definitions = local.default_container_definitions
kms_key_id = data.aws_kms_alias.supporting_kms.target_key_arn
tags = local.tags
lb_ingress_rules = var.lb_ingress_rules
network_configuration = {
subnets = local.private_subnets
}
}
Name | Version |
---|---|
terraform | >= 0.14.11 |
aws | >= 5.0.0 |
tls | >= 3.0.0 |
Name | Version |
---|---|
aws | 5.51.1 |
tls | 4.0.5 |
No modules.
Name | Description | Type | Default | Required |
---|---|---|---|---|
access_logs | (Optional) Define an Access Logs block, requires a bucket name and an optional prefix. The S3 bucket must already exist. The default value is false. access_logs = { bucket = string enabled = bollean prefix = string } |
map(string) |
{} |
no |
acm_certificate_arn | ARN of ACM generated/third party certificate | string |
null |
no |
alb_subnets | Subnet IDs for the application load balancer. | list(string) |
[] |
no |
associate_with_waf | Whether to associate created ALB with AWS WAFv2 ACL | bool |
false |
no |
associate_with_wafregional | Whether to associate created ALB with WAF Regional Web ACL | bool |
false |
no |
autoscale_role_arn | (Optional) The ARN of the IAM role that allows Application AutoScaling to modify your scalable target on your behalf. | string |
null |
no |
cluster | Amazon Resource Name (ARN) of cluster which the service runs on | string |
null |
no |
container_definitions | Container definitions provided as valid JSON document. Default uses golang:alpine running a simple hello world. | string |
null |
no |
cpu | Number of cpu units used by the task. If the requires_compatibilities is FARGATE this field is required. | number |
256 |
no |
create_load_balancer | Whether to create a load balancer for ecs. | bool |
false |
no |
create_task_definition | Whether to create the task definition or not | bool |
true |
no |
default_type | Type for default action | string |
"forward" |
no |
deployment_controller_type | (Optional) Type of deployment controller | string |
"ECS" |
no |
desired_count | The number of instances of a task definition | number |
1 |
no |
drop_invalid_header_fields | Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (true) or routed to targets (false). The default is false. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type application. | bool |
false |
no |
enable_autoscaling | Whether to enable autoscaling or not for ecs | bool |
false |
no |
enable_deletion_protection | If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false. | bool |
false |
no |
enable_execute_command | value to enable execute command at the ecs service, default = false | bool |
false |
no |
enable_key_rotation | Choose whether to enable key rotation | bool |
true |
no |
family | (Required) A unique name for your task definition. | string |
null |
no |
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 (e.g., myimage:latest), roll Fargate tasks onto a newer platform version, or immediately deploy ordered_placement_strategy and placement_constraints updates. | bool |
false |
no |
healthy_threshold | (Optional) Number of consecutive health checks successes required before considering an unhealthy target healthy. Defaults to 3. | number |
3 |
no |
idle_timeout | (Optional) The time in seconds that the connection is allowed to be idle. Only valid for Load Balancers of type application. Default: 60 | number |
60 |
no |
internal | (Optional) If true, the LB will be internal. | bool |
false |
no |
interval | (Optional) Approximate amount of time, in seconds, between health checks of an individual target. The range is 5-300. For lambda target groups, it needs to be greater than the timeout of the underlying lambda. Defaults to 30. | number |
30 |
no |
key_deletion_window_in_days | The number of days before the key is deleted | number |
7 |
no |
kms_key_id | The KMS ARN for cloudwatch log group | string |
null |
no |
launch_type | Launch type on which to run your service. The valid values are EC2, FARGATE, and EXTERNAL. | string |
"FARGATE" |
no |
lb_ingress_rules | Ingress rules to add to the load balancer security group. The rules defined here will be used by service security group lb_ingress_rules = [{ from_port = number to_port = number protocol = string description = string cidr_blocks = list(string) }] |
list(any) |
[] |
no |
listener_port | (Required) The port to listen on for the load balancer | number |
80 |
no |
listener_protocol | (Required) The protocol to listen on. Valid values are HTTP, HTTPS, TCP, or SSL | string |
"HTTP" |
no |
load_balancer | (Optional) Configuration block for load balancers, accepts the following arguments: load_balancer = [{ container_name = string # Name of the container to associate with the load balancer container_port = number # Port number the container listens on target_group_arn = string # ARN of the target group to associate with the load balancer when using an external Alb/NLB }] |
any |
[] |
no |
load_balancer_type | (Optional) The type of load balancer to create. Possible values are application, gateway, or network. The default value is application. | string |
"application" |
no |
matcher | (May be required) Response codes to use when checking for a healthy responses from a target. You can specify multiple values (for example, 200,202 for HTTP(s)) | string |
null |
no |
max_capacity | (Required) The max capacity of the scalable target. | number |
2 |
no |
memory | Amount (in MiB) of memory used by the task. If the requires_compatibilities is FARGATE this field is required. | number |
1024 |
no |
min_capacity | (Required) The min capacity of the scalable target. | number |
1 |
no |
name | The service name. | string |
n/a | yes |
network_configuration | (Optional) Network configuration for the service. This parameter is required for task definitions that use the awsvpc network mode to receive their own Elastic Network Interface, and it is not supported for other network modes. | any |
{} |
no |
network_mode | Docker networking mode to use for the containers in the task. Valid values are none, bridge, awsvpc, and host. | string |
"none" |
no |
path | (May be required) Destination for the health check request. Required for HTTP/HTTPS ALB and HTTP NLB. Only applies to HTTP/HTTPS. | string |
"/" |
no |
propagate_tags | (Optional) Whether to propagate the tags from the task definition or the service to the tasks. The valid values are SERVICE and TASK_DEFINITION | string |
"TASK_DEFINITION" |
no |
requires_compatibilities | Set of launch types required by the task. The valid values are EC2 and FARGATE. | list(string) |
[] |
no |
retention_in_days | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653, and 0. If you select 0, the events in the log group are always retained and never expire. | number |
3653 |
no |
scalable_dimension | (Required) The scalable dimension of the scalable target. | string |
"" |
no |
scheduled_actions | Scheduled actions to apply to the ecs scalable target. | list(object({ |
[] |
no |
self_signed_cert_common_name | Distinguished name | string |
"devboldlink.wpengine.com" |
no |
self_signed_cert_organization | The organization owning this self signed certificate | string |
"Boldlink-SIG" |
no |
service_ingress_rules | Ingress rules to add to the service security group. service_ingress_rules = [{ from_port = number to_port = number protocol = string description = string cidr_blocks = list(string) }] |
list(any) |
[] |
no |
service_ingress_rules_sg | Ingress rules to add to the service security group. service_ingress_rules = [{ from_port = number to_port = number protocol = string description = string source_security_group_id = string }] |
list(any) |
[] |
no |
service_namespace | (Required) The AWS service namespace of the scalable target. | string |
"ecs" |
no |
ssl_policy | (Optional) Name of the SSL Policy for the listener. Required if protocol is HTTPS or TLS |
string |
"ELBSecurityPolicy-TLS-1-2-2017-01" |
no |
step_scaling_policies | Scaling policies to apply to the scalable target. Supported policy types are StepScaling and TargetTrackingScaling. list(object({ name = string policy_type = string step_scaling_policy_configuration = object({ adjustment_type = string cooldown = number metric_aggregation_type = string step_adjustments = list(object({ metric_interval_lower_bound = number metric_interval_upper_bound = number scaling_adjustment = number })) }) })) |
any |
[] |
no |
tags | Key Value tags to apply to the resources | map(string) |
{} |
no |
target_scaling_policies | Scaling policies to apply to the scalable target. Supported policy types are StepScaling and SepScaling. list(object({ name = string policy_type = string target_tracking_scaling_policy_configuration = object({ target_value = number scale_in_cooldown = number scale_out_cooldown = number predefined_metric_specification = object({ predefined_metric_type = string }) }) })) |
any |
[] |
no |
target_type | Type of target that you must specify when registering targets with this target group. See doc for supported values. The default is instance. | string |
"ip" |
no |
task_assume_role_policy | The assume role policy for the task role | string |
"" |
no |
task_execution_assume_role_policy | The assume role policy for the task execution role | string |
null |
no |
task_execution_role_policy | Specify the IAM policy for task definition task execution | string |
"" |
no |
task_role_policy | Specify the IAM policy for task role | string |
"" |
no |
tasks_maximum_percent | Upper limit on the number of running tasks. | number |
200 |
no |
tasks_minimum_healthy_percent | Lower limit on the number of running tasks. | number |
100 |
no |
tg_port | Port on which targets receive traffic, unless overridden when registering a specific target. Required when target_type is instance or ip. Does not apply when target_type is lambda. | number |
80 |
no |
tg_protocol | Protocol to use for routing traffic to the targets. Should be one of GENEVE, HTTP, HTTPS, TCP, TCP_UDP, TLS, or UDP. Required when target_type is instance or ip. Does not apply when target_type is lambda. | string |
"HTTP" |
no |
triggers | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with plaintimestamp() triggers = { redeployment = plantimestamp() } |
map(string) |
{} |
no |
volume_name | Name of the volume. This name is referenced in the sourceVolume parameter of container definition in the mountPoints section. | string |
"service-storage" |
no |
vpc_id | VPC ID to be used by ECS. | string |
null |
no |
wafregional_acl_id | The ID of WAF Regional Web ACL to associate load balancer with | string |
null |
no |
web_acl_arn | The ARN of WAF web acl to associate load balancer with | string |
null |
no |
Name | Description |
---|---|
cloudwatch_log_group_arn | ARN of cloudwatch log group |
iam_role_arn_role | The ARN of IAM task role |
iam_role_create_date_role | Creation date of IAM task role |
iam_role_id_role | ID of IAM task role |
iam_role_name_role | Name of IAM task role |
iam_role_unique_id_role | Unique ID of IAM task role |
lb_arn | The ARN of the load balancer (matches id ) |
lb_dns_name | DNS name of load balancer |
lb_dns_zone_id | DNS zone id of load balancer |
lb_sg_arn | ID of the load balancer security group. |
lb_sg_id | ARN of the load balancer security group. |
service_sg_arn | ID of the service security group. |
service_sg_id | ARN of the service security group. |
task_definition_arn | Full ARN of the Task Definition (including both family and revision) |
task_definition_arn_without_revision | ARN of the Task Definition with the trailing revision removed. This may be useful for situations where the latest task definition is always desired. If a revision isn't specified, the latest ACTIVE revision is used. |
task_definition_revision | Revision of the task in a particular family. |
This repository uses third party software:
- pre-commit - Used to help ensure code and documentation consistency
- Install with
brew install pre-commit
- Manually use with
pre-commit run
- Install with
- terraform 0.14.11 For backwards compatibility we are using version 0.14.11 for testing making this the min version tested and without issues with terraform-docs.
- terraform-docs - Used to generate the Inputs and Outputs sections
- Install with
brew install terraform-docs
- Manually use via pre-commit
- Install with
- tflint - Used to lint the Terraform code
- Install with
brew install tflint
- Manually use via pre-commit
- Install with
The example stacks are used by BOLDLink developers to validate the modules by building an actual stack on AWS.
Some of the modules have dependencies on other modules (ex. Ec2 instance depends on the VPC module) so we create them first and use data sources on the examples to use the stacks.
Any supporting resources will be available on the tests/supportingResources
and the lifecycle is managed by the Makefile
targets.
Resources on the tests/supportingResources
folder are not intended for demo or actual implementation purposes, and can be used for reference.
The makefile contained in this repo is optimized for linux paths and the main purpose is to execute testing for now.
- Create all tests stacks including any supporting resources:
make tests
- Clean all tests except existing supporting resources:
make clean
- Clean supporting resources - this is done separately so you can test your module build/modify/destroy independently.
make cleansupporting
- !!!DANGER!!! Clean the state files from examples and test/supportingResources - use with CAUTION!!!
make cleanstatefiles