Skip to content

Terraform module that simplifies the creation of an ECS Service and all the necessary auxiliary components required for the service to function properly.

License

Notifications You must be signed in to change notification settings

zahornyak/terraform-aws-ecs-service

Repository files navigation

Terraform AWS ECS service stack creation

GitHub tag (latest by date)

This module is for whole ECS service stack creation: service, task definition, container definition, alb listener rule, target group, route53 record, security group etc.

Important note:

  • Use connect_to_lb and service_domain to connect service container to load balancer and create route53 A record
  • Use vpc_cidr_block, route_53_zone_name, lb_dns_name only when you dont have previously created resources

Example

Single container

module "ecs_service" {
  source = "zahornyak/ecs-service/aws"

  environment        = "production"
  vpc_id             = "vpc-080fd3099892"
  vpc_cidr_block     = "10.0.0.0/16" # use when you dont have previously created vpc
  service_subnets    = ["subnet-0c264c7154cb", "subnet-09e0d8b22e2"]
  # assign_public_ip = true # if you are using public subnets
  cluster_name       = "production-cluster"
  route_53_zone_id   = "Z01006347593463S0ZFL7A2" # use when you dont have previously created Route53 zone
  route_53_zone_name = "example.com" 
  lb_arn             = "arn:aws:elasticloadbalancing:eu-central-1:1234567890:loadbalancer/app/plugin-development-alb/46555556595fd4b2"
  lb_listener_arn    = "arn:aws:elasticloadbalancing:eu-central-1:1234567890:listener/app/plugin-development-alb/46555556595fd4b2/83d6940f8c9f02db"
  lb_dns_name        = "my-loadbalancer-1234567890.us-west-2.elb.amazonaws.com" # use when you dont have previously created load balancer
  create_ssl         = true # requests ssl for service and attach it to listener rule

  service_name  = "backend"
  desired_count = 1

  container_definitions = {
    proxy = {
      service_domain   = "api-test"
      connect_to_lb    = true
      container_image  = "nginx:latest"
      container_name   = "backend"
      container_cpu    = 256
      container_memory = 256
      containerPort    = 80
      environment      = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
    }
  }

  service_memory = 1024
  service_cpu    = 512
}

Multiple containers

module "ecs_service" {
  source  = "zahornyak/ecs-service/aws"

  environment     = "production"
  vpc_id          = "vpc-080fd3099892"
  service_subnets = ["subnet-0c264c7154cb", "subnet-09e0d8b22e2"]
  # assign_public_ip = true # if you are using public subnets
  cluster_name     = "production-cluster"
  route_53_zone_id = "Z01006347593463S0ZFL7A2"
  lb_arn           = "arn:aws:elasticloadbalancing:eu-central-1:1234567890:loadbalancer/app/plugin-development-alb/46555556595fd4b2"
  lb_listener_arn  = "arn:aws:elasticloadbalancing:eu-central-1:1234567890:listener/app/plugin-development-alb/46555556595fd4b2/83d6940f8c9f02db"
  create_ssl       = true # requests ssl for service and attach it to listener rule

  service_name  = "backend"
  desired_count = 1

  container_definitions = {
    proxy = {
      service_domain   = "api-test"
      connect_to_lb    = true
      container_image  = "nginx:latest"
      container_name   = "proxy"
      container_cpu    = 256
      container_memory = 256
      containerPort    = 80
      environment = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
    }
    
    backend = {
      container_image  = "nginx:latest"
      container_name   = "backend"
      container_cpu    = 256
      container_memory = 256
      container_depends_on = [
        {
          containerName = "proxy"
          condition     = "START"
        }
      ]
      containerPort = 3000
      healthcheck = {
        retries     = 5
        command     = ["CMD-SHELL", "curl -f http://localhost:3000"]
        timeout     = 15
        interval    = 30
        startPeriod = 10
      }
      environment = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
    }
    
    admin = {
      service_domain   = "api-worker"
      connect_to_lb    = true
      container_image  = "nginx:latest"
      container_name   = "worker"
      container_cpu    = 256
      container_memory = 256
      container_depends_on = [
        {
          containerName = "backend"
          condition     = "START"
        }
      ]
      containerPort = 3050
      healthcheck = {
        retries     = 5
        command     = ["CMD-SHELL", "curl -f http://localhost:3050"]
        timeout     = 15
        interval    = 30
        startPeriod = 10
      }
      environment = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
    }
    }
  service_memory  = 1024
  service_cpu     = 512
}

No load balancer

module "ecs_service" {
  source = "zahornyak/ecs-service/aws"

  environment     = var.environment
  vpc_id          = var.vpc_id
  service_subnets = var.subnets
  vpc_cidr_block  = var.vpc_cidr_block

  # assign_public_ip = true # if you are using public subnets
  cluster_name = aws_ecs_cluster.ecs_cluster.name

  service_name  = "backend"
  desired_count = 1

  container_definitions = {
    proxy = {
      container_image  = "nginx:latest"
      container_name   = "proxy"
      container_cpu    = 256
      container_memory = 256
      containerPort    = 80
      environment      = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
    }
    
    backend = {
      container_image      = "nginx:latest"
      container_name       = "backend"
      container_cpu        = 256
      container_memory     = 256
      container_depends_on = [
        {
          containerName = "proxy"
          condition     = "START"
        }
      ]
      containerPort = 3000
      healthcheck   = {
        retries     = 5
        command     = ["CMD-SHELL", "curl -f http://localhost:3000"]
        timeout     = 15
        interval    = 30
        startPeriod = 10
      }
      environment = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
    }
  }

  service_memory = 1024
  service_cpu    = 512
}

Example of using environment valiables for containers(using ssm_secrets which creates ssm parameters and puts them into container definition)

module "ecs-service" {
  source  = "zahornyak/ecs-service/aws"
  # insert the 7 required variables here
  container_definitions = {
    proxy = {
      container_image  = "nginx:latest"
      container_name   = "proxy"
      container_cpu    = 256
      container_memory = 256
      containerPort    = 80
      environment      = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
      ssm_secrets = {
        DEBUG = {
          value = "true"
        }
      }
    }
  }
}

Example of using environment valiables for containers(using ssm_env_file which parses and creates ssm parameters and puts them into container definition)

module "ecs-service" {
  source  = "zahornyak/ecs-service/aws"
  # insert the 7 required variables here
  container_definitions = {
    proxy = {
      container_image  = "nginx:latest"
      container_name   = "proxy"
      container_cpu    = 256
      container_memory = 256
      containerPort    = 80
      environment      = [
        {
          "name"  = "foo"
          "value" = "bar"
        }
      ]
      ssm_env_file = "./env"
    }
  }
}


.env example

LOG_LEVEL=verbose
LOG_TARGET=console
LOG_FORMAT=json

CRONJOB_ENABLED=true
DEPLOYMENT=develop

Autoscaling with scaling values

module "ecs-service" {
  source = "zahornyak/ecs-service/aws"
  # insert the 7 required variables here

  min_service_tasks = 1
  max_service_tasks = 6

  cpu_scaling_target_value = 40
  cpu_scale_in_cooldown    = 350
  cpu_scale_out_cooldown   = 200

  memory_scaling_target_value = 90
  memory_scale_in_cooldown    = 350
  memory_scale_out_cooldown   = 300
}

Autoscaling with scaling values (no memory or cpu scaling)

module "ecs-service" {
  source = "zahornyak/ecs-service/aws"
  # insert the 7 required variables here

  min_service_tasks = 1
  max_service_tasks = 6

  cpu_scaling_target_value = 40
  cpu_scale_in_cooldown    = 350
  cpu_scale_out_cooldown   = 200
  
}

Capacity provider strategy, ordered placement, placement_constraints strategy example configuration

module "ecs-service" {
  source = "zahornyak/ecs-service/aws"
  # insert the 7 required variables here

  capacity_provider_strategy = {
    main = {
      capacity_provider = "FARGATE_SPOT"
      base              = 1
      weight            = 1
    }
  }


  ordered_placement_strategy = {
    test = {
      type  = "binpack"
      field = "cpu"
    }
  }


  placement_constraints = {
    example = {
      type       = "memberOf"
      expression = "attribute:ecs.availability-zone in [us-west-2a, us-west-2b]"
    }
  }
}

Service discovery example

module "ecs-service" {
  source = "zahornyak/ecs-service/aws"
  # insert the 7 required variables here

  create_service_discovery = true
  discovery_registry_id    = "service_discovery_registry_id"
  
}

Requirements

Name Version
terraform >= 1.4
aws >= 4.37

Providers

Name Version
aws >= 4.37

Modules

Name Source Version
acm terraform-aws-modules/acm/aws ~> 3.3
ecs_task_exec_policy terraform-aws-modules/iam/aws//modules/iam-policy ~> 4.4
ecs_task_execution_role terraform-aws-modules/iam/aws//modules/iam-assumable-role ~> 4.4
ecs_task_policy terraform-aws-modules/iam/aws//modules/iam-policy ~> 4.4
ecs_task_role terraform-aws-modules/iam/aws//modules/iam-assumable-role ~> 4.4
env_variables zahornyak/multiple-ssm-parameters/aws 0.0.11
service_container_definition registry.terraform.io/cloudposse/ecs-container-definition/aws ~> 0.58
service_container_sg registry.terraform.io/terraform-aws-modules/security-group/aws ~> 4.3

Resources

Name Type
aws_appautoscaling_policy.target_tracking_scaling_cpu_service resource
aws_appautoscaling_policy.target_tracking_scaling_memory_service resource
aws_appautoscaling_target.service_scaling resource
aws_cloudwatch_log_group.service_logs resource
aws_ecs_service.service resource
aws_ecs_task_definition.service resource
aws_lb_listener_certificate.this resource
aws_lb_listener_rule.service resource
aws_lb_target_group.service resource
aws_route53_record.lb_records resource
aws_service_discovery_service.service resource
aws_caller_identity.current data source
aws_iam_policy_document.ecs_task_exec_policy data source
aws_iam_policy_document.ecs_task_policy data source
aws_lb.this data source
aws_region.current data source
aws_route53_zone.this data source
aws_vpc.this data source

Inputs

Name Description Type Default Required
assign_public_ip Assign_public_ip set true if you are using public subnets. bool false no
capacity_provider_strategy capacity_provider_strategy any {} no
cluster_name Name of the ECS Cluster. string n/a yes
container_definitions Custom container definitions. any {} no
cpu_scale_in_cooldown cpu scale_in_cooldown number null no
cpu_scale_out_cooldown cpu scale_out_cooldown number null no
cpu_scaling_target_value cpu_scaling target_value number null no
create_service_discovery creates service discovery service and connects in to ecs service bool false no
create_ssl defines if create ssl for services domains bool true no
deployment_circuit_breaker deployment_circuit_breaker configuration any {} no
deployment_maximum_percent deployment_maximum_percent. For example 200 will create twice more container and if everything is ok, deployment is succesfull. number 200 no
deployment_minimum_healthy_percent deployment_minimum_healthy_percent. number 100 no
deregistration_delay Deregistration delay for target group. number 5 no
desired_count Desired count for service. number null no
discovery_registry_id service discovery registry_id string null no
docker_volume docker volume any null no
efs_volume efs volume any null no
environment Environment name. For example 'production' string n/a yes
external_dns when you dont have route53 zone, you can use external dns any null no
health_check Custom healthcheck for target group. any null no
health_check_grace_period_seconds health_check_grace_period_seconds number null no
launch_type Launch type for service: 'FARGATE', 'EC2' etc. string "FARGATE" no
lb_arn Load balancer arn. string null no
lb_dns_name Load balancer dns name. Use only if you dont have previously created Load Balancer string null no
lb_listener_arn Listener arn for load balancer connection string null no
lb_zone_id load balancer zone id string null no
max_service_tasks Maximum service tasks. number null no
memory_scale_in_cooldown memory scale_in_cooldown number null no
memory_scale_out_cooldown memory scale_out_cooldown number null no
memory_scaling_target_value memory scaling_target_value number null no
min_service_tasks Minimum service tasks. number null no
network_mode Network mode for task. For example 'awsvpc' or 'bridge' etc. string "awsvpc" no
ordered_placement_strategy ordered_placement_strategy any {} no
parameter_prefix prefix for parameter store parameter. For example '/develop/service/'. So parameter 'DEBUG' will have '/develop/service/DEBUG' name on the parameter store string null no
placement_constraints placement_constraints any {} no
protocol_version target group protocol version string null no
requires_compatibilities Compatibilities for ECS task. Available: 'FARGATE', 'FARGATE_SPOT', 'EC2' etc. list(string)
[
"FARGATE"
]
no
retention_in_days retention_in_days number 60 no
route_53_zone_id Route 53 zone id. string null no
route_53_zone_name route 53 zone name. Use only when you dont have previously created Route53 zone string null no
runtime_platform runtime platform any null no
security_groups additional security_groups for service list(string) [] no
service_cpu CPU amount for the service. number n/a yes
service_memory Memory amount for the service. number n/a yes
service_name Name of the service. string n/a yes
service_subnets Subnets for service list(string) n/a yes
task_exec_role_policy_arns Additional policies to attach to task execution role of ECS container. list(string) [] no
task_role_policy_arns Additional policies to attach to task role of ECS container. list(string) [] no
tg_protocol target group protocol(for example 'HTTP' or 'TCP') string "HTTP" no
tg_target_type target group target type(ip or instance etc) string "ip" no
vpc_cidr_block cidr block for vpc. Use that variable when you dont have previously created VPC string null no
vpc_id VPC id. string n/a yes

Outputs

Name Description
acm_arn acm arn
cloudwatch_log_group_arns aws cloudwatch log group arns
container_definitions container definitions of your task definition
ecs_service_arn ecs_service_arn
ecs_service_name ecs service name
ecs_service_security_group_ids ecs service security group ids
ecs_task_definition_arn task definition arn
ecs_task_execution_role_arn ecs task execution role arn
ecs_task_policy_arn ecs task policy arn
ecs_task_role_arn ecs task role arn
lb_listener_certificate lb listener certificate
lb_listener_rule_arns load balancer listener rules arns
records_lb_names load balancers records names
service_container_sg_ids service container sg ids
target_group_arns target group arns