A somewhat opinionated Terraform module to create Fargate ECS resources on AWS.
This module does the heavy lifting for:
- ECR configuration
- automated service deployment including notifications
- CloudWatch log group and IAM permissions for storing container logs (e.g. for sidecars)
- integration with App Mesh and Application Load Balancers
The following resources are referenced from this module and therefore prerequisites:
- Subnets — within this VPC, there must be at least one subnet tagged with either
Tier = (public|private)
. The fargate Elastic Network Interface will be placed here. - SG (1) — within this VPC there must be a security group
Name = default
- SG (2) — within this VPC there must be a security group to allow traffic from ALB
Name = fargate-allow-alb-traffic
- IAM role — There should be a role named
ssm_ecs_task_execution_role
that will be used as a task execution role
` A service can be attached to a ALB. Neither the ALB nor the Listeners are created by the module (see example app).
Sample for an service running a HTTP
service on port 80
:
module "service" {
source = "..."
target_groups = [
{
name = "${local.service_name}-public"
backend_port = 80
backend_protocol = "HTTP"
target_type = "ip"
health_check = {
enabled = true
path = "/"
}
}
]
https_listener_rules = [{
listener_arn = aws_lb_listener.http.arn
priority = 42
actions = [{
type = "forward"
target_group_index = 0
}]
conditions = [{
path_patterns = ["/"]
host_headers = ["www.example.com"]
}]
}]
}
DNS is also not part of this module and needs to be provided by the caller:
resource "aws_route53_record" "this" {
name = "..."
type = "..."
zone_id = "..."
}
this should point to your ALB. If TLS/HTTPS will be used an ACM certificate is also required.
In order to disable ALB target group attachments (e.g. for services in an App Mesh) set target_groups = []
.
module "service" {
source = "..."
appautoscaling_settings = {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
target_value = 30
max_capacity = 8
min_capacity = 2
disable_scale_in = true
scale_in_cooldown = 120
scale_out_cooldown = 15
}
}
Use this configuration map to enable and alter the autoscaling settings for this app.
key | description |
---|---|
target_value |
(mandatory) the target value, refers to predefined_metric_type |
predefined_metric_type |
see docs for possible values |
max_capacity |
upper threshold for scale out |
min_capacity |
lower threshold for scale in |
disable_scale_in |
prevent scale in if set to true |
scale_in_cooldown |
delay (in seconds) between scale in events |
scale_out_cooldown |
delay (in seconds) between scale out events |
- A shared S3 bucket for storing artifacts from CodePipeline can be used. You can specify it through the
variable
code_pipeline_artifact_bucket
. Otherwise a new bucket is created for every service. - A shared
IAM::Role
for CodePipeline and CodeBuild can be used. You can specify those through the variablescode_pipeline_role_name
andcode_build_role_name
. Otherwise new roles are created for every service. For the permissions required see the module code
Simple Fargate ECS service:
locals {
service_name = "example"
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
module "service" {
source = "stroeer/ecs-fargate/aws"
version = "0.29.1"
assign_public_ip = true
cluster_id = aws_ecs_cluster.main.id
container_port = 80
create_deployment_pipeline = false
desired_count = 1
service_name = local.service_name
vpc_id = module.vpc.vpc_id
target_groups = [
{
name = "${local.service_name}-public"
backend_port = 80
backend_protocol = "HTTP"
target_type = "ip"
health_check = {
enabled = true
path = "/"
}
}
]
https_listener_rules = [{
listener_arn = aws_lb_listener.http.arn
priority = 42
actions = [{
type = "forward"
target_group_index = 0
}]
conditions = [{
path_patterns = ["/"]
}]
}]
container_definitions = jsonencode([
{
command: [
"/bin/sh -c \"echo '<html> <head> <title>Hello from httpd service</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' > /usr/local/apache2/htdocs/index.html && httpd-foreground\""
],
cpu: 256,
entryPoint: ["sh", "-c"],
essential: true,
image: "httpd:2.4",
memory: 512,
name: local.service_name,
portMappings: [{
containerPort: 80
hostPort: 80
protocol: "tcp"
}]
}
])
ecr = {
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration = {
scan_on_push = true
}
}
}
- Service Names
var.service_name = [a-z-]+
Documentation is generated with brew install terraform-docs
(
see Makefile).
Only Terraform 0.12+ is supported.
Release a new module version to the Terraform registry
(BUMP
defaults to patch
):
make BUMP=(major|minor|patch) release
Once create_deployment_pipeline
is set to true
, we will create an automated Deployment Pipeline:
How it works
- You'll need AWS credentials that allow pushing images into the ECR container registry.
- Once you push an image with
[tag=production]
- a Cloudwatch Event will trigger the start of a CodePipeline - ⚠ This tag will only trigger the pipeline. You will need a minimum of 3 tags
production
will trigger the pipelinecontainer.$CONTAINER_NAME
is required to locate the correct container from the service's task-definition.json- One more tag that will be unique and used for the actual deployment and the task-definition.json. A good choice would
be
git.sha
. To be specific, we chose a tag that does notstart with container.
and is none of["local", "production", "staging", "infrastructure"]
That CodePipeline will do the heavy lifting (see deployment flow above)
- Pull the full
imagedefinitions.json
from the ECR registry - Trigger a CodeBuild to transform the
imagedefinitions.json
into aimagedefinitions.json
for deployment - Update the ECS service's task-definition by replacing the specified
imageUri
for the givenname
.
Notifications
We will create a notification rule for the pipeline. You can provide your ARN of a notification rule target (e.g. a SNS
topic ARN) using
codestar_notifications_target_arn
. Otherwise a new SNS topic with required permissions is created for every service.
See
aws_codestarnotifications_notification_rule
for details.
You can then configure an integration between those notifications and AWS Chatbot for example.
- Cognito auth for ALB listeners
- CodeDeploy with ECR trigger
- ECR policies
- Notification for the deployment pipeline [success/failure]
Name | Version |
---|---|
terraform | >= 1.3 |
aws | >= 3.0 |
Name | Version |
---|---|
aws | >= 3.0 |
terraform | n/a |
Name | Source | Version |
---|---|---|
code_deploy | ./modules/deployment | n/a |
container_definition | cloudposse/config/yaml//modules/deepmerge | 0.2.0 |
ecr | ./modules/ecr | n/a |
envoy_container_definition | cloudposse/config/yaml//modules/deepmerge | 0.2.0 |
fluentbit_container_definition | cloudposse/config/yaml//modules/deepmerge | 0.2.0 |
otel_container_definition | cloudposse/config/yaml//modules/deepmerge | 0.2.0 |
sg | registry.terraform.io/terraform-aws-modules/security-group/aws | ~> 3.0 |
Name | Description | Type | Default | Required |
---|---|---|---|---|
additional_container_definitions | Additional container definitions added to the task definition of this service, see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html for allowed parameters. | list(any) |
[] |
no |
app_mesh | Configuration of optional AWS App Mesh integration using an Envoy sidecar. | object({ |
{} |
no |
appautoscaling_settings | Autoscaling configuration for this service. | map(any) |
null |
no |
assign_public_ip | This services will be placed in a public subnet and be assigned a public routable IP. | bool |
false |
no |
capacity_provider_strategy | Capacity provider strategies to use for the service. Can be one or more. | list(object({ |
null |
no |
cloudwatch_logs | CloudWatch logs configuration for the containers of this service. CloudWatch logs will be used as the default log configuration if Firelens is disabled and for the fluentbit and otel containers. | object({ |
{} |
no |
cluster_id | The ECS cluster id that should run this service | string |
n/a | yes |
code_build_role_name | Use an existing role for codebuild permissions that can be reused for multiple services. Otherwise a separate role for this service will be created. | string |
"" |
no |
code_pipeline_artifact_bucket | Use an existing bucket for codepipeline artifacts that can be reused for multiple services. Otherwise a separate bucket for each service will be created. | string |
"" |
no |
code_pipeline_artifact_bucket_sse | AWS KMS master key id for server-side encryption. | any |
{} |
no |
code_pipeline_role_name | Use an existing role for codepipeline permissions that can be reused for multiple services. Otherwise a separate role for this service will be created. | string |
"" |
no |
codestar_notifications_detail_type | The level of detail to include in the notifications for this resource. Possible values are BASIC and FULL. | string |
"BASIC" |
no |
codestar_notifications_event_type_ids | A list of event types associated with this notification rule. For list of allowed events see https://docs.aws.amazon.com/dtconsole/latest/userguide/concepts.html#concepts-api. | list(string) |
[ |
no |
codestar_notifications_kms_master_key_id | AWS KMS master key id for server-side encryption. | string |
null |
no |
codestar_notifications_target_arn | Use an existing ARN for a notification rule target (for example, a SNS Topic ARN). Otherwise a separate sns topic for this service will be created. | string |
"" |
no |
container_definition_overwrites | Additional container definition parameters or overwrites of defaults for your service, see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html for allowed parameters. | any |
{} |
no |
container_name | Defaults to var.service_name, can be overridden if it differs. Used as a target for LB. | string |
"" |
no |
container_port | The port used by the app within the container. | number |
n/a | yes |
cpu | Amount of CPU required by this service. 1024 == 1 vCPU | number |
256 |
no |
create_deployment_pipeline | Creates a deploy pipeline from ECR trigger if create_ecr_repo == true . |
bool |
true |
no |
create_ecr_repository | Create an ECR repository for this service. | bool |
true |
no |
create_ingress_security_group | Create a security group allowing ingress from target groups to the application ports. Disable this for target groups attached to a Network Loadbalancer. | bool |
true |
no |
deployment_circuit_breaker | Deployment circuit breaker configuration. | object({ |
{ |
no |
deployment_maximum_percent | Upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment. Not valid when using the DAEMON scheduling strategy. |
number |
200 |
no |
deployment_minimum_healthy_percent | Lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment. | number |
100 |
no |
desired_count | Desired count of services to be started/running. | number |
0 |
no |
ecr_custom_lifecycle_policy | JSON formatted ECR lifecycle policy used for this repository (disabled the default lifecycle policy), see https://docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html#lifecycle_policy_parameters for details. | string |
null |
no |
ecr_enable_default_lifecycle_policy | Enables an ECR lifecycle policy for this repository which expires all images except for the last 30. | bool |
true |
no |
ecr_force_delete | If true , will delete this repository even if it contains images. |
bool |
false |
no |
ecr_image_scanning_configuration | n/a | map(any) |
{ |
no |
ecr_image_tag_mutability | n/a | string |
"MUTABLE" |
no |
ecr_repository_name | Existing repo to register to use with this service module, e.g. creating deployment pipelines. | string |
"" |
no |
efs_volumes | Configuration block for EFS volumes. | any |
[] |
no |
enable_execute_command | Specifies whether to enable Amazon ECS Exec for the tasks within the service. | bool |
false |
no |
firelens | Configuration for optional custom log routing using FireLens over fluentbit sidecar. | object({ |
{} |
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 |
https_listener_rules | A list of maps describing the Listener Rules for this ALB. Required key/values: actions, conditions. Optional key/values: priority, https_listener_index (default to https_listeners[count.index]) | any |
[] |
no |
memory | Amount of memory [MB] is required by this service. | number |
512 |
no |
otel | Configuration for (optional) AWS Distro für OpenTelemetry sidecar. | object({ |
{} |
no |
platform_version | The platform version on which to run your service. Defaults to LATEST. | string |
"LATEST" |
no |
policy_document | AWS Policy JSON describing the permissions required for this service. | string |
"" |
no |
requires_compatibilities | The launch type the task is using. This enables a check to ensure that all of the parameters used in the task definition meet the requirements of the launch type. | set(string) |
[ |
no |
requires_internet_access | As Fargate does not support IPv6 yet, this is the only way to enable internet access for the service by placing it in a public subnet (but not assigning a public IP). | bool |
false |
no |
security_groups | A list of security group ids that will be attached additionally to the ecs deployment. | list(string) |
[] |
no |
service_name | The service name. Will also be used as Route53 DNS entry. | string |
n/a | yes |
subnet_tags | The subnet tags where the ecs service will be deployed. If not specified all subnets will be used. | map(string) |
null |
no |
tags | Additional tags (_e.g._ { map-migrated : d-example-443255fsf }) | map(string) |
{} |
no |
target_groups | A list of maps containing key/value pairs that define the target groups to be created. Order of these maps is important and the index of these are to be referenced in listener definitions. Required key/values: name, backend_protocol, backend_port | any |
[] |
no |
task_role_arn | ARN of the IAM role that allows your Amazon ECS container task to make calls to other AWS services. If not specified, the default ECS task role created in this module will be used. | string |
"" |
no |
vpc_id | VPC id where the load balancer and other resources will be deployed. | string |
n/a | yes |
Name | Description |
---|---|
autoscaling_target | ECS auto scaling targets if auto scaling enabled. |
aws_alb_target_group_arns | ARNs of the created target groups. |
cloudwatch_log_group | Name of the CloudWatch log group for container logs. |
container_definitions | Container definitions used by this service including all sidecars. |
ecr_repository_arn | Full ARN of the ECR repository. |
ecr_repository_url | URL of the ECR repository. |
ecs_task_exec_role_arn | The ARN of the ECS task role created for this service. |
ecs_task_exec_role_name | The name of the ECS task role created for this service. |
ecs_task_exec_role_unique_id | The unique id of the ECS task role created for this service. |
target_group_arns | ARNs of the created target groups. |