Skip to content

Latest commit

 

History

History
341 lines (275 loc) · 16 KB

README.md

File metadata and controls

341 lines (275 loc) · 16 KB

Terraform module that creates an ECS service with the following features

  • Runs an ECS service with or without an AWS load balancer.
  • Stream logs to a CloudWatch log group encrypted with a KMS key.
  • Associate multiple target groups with Network Load Balancers (NLB) and Application Load Balancers (ALB).
  • Supports running ECS tasks on EC2 instances or Fargate.

Default container definition (hello world app)

We create an initial task definition using the golang:alpine image as a way to validate the initial infrastructure is working: visiting the site shows a simple Go hello world page listening on two configurable ports. This is meant to get a proof of concept instance up and running and to help with testing.

If you want to customize the listener ports for the hello world app, you can modify the hello_world_container_ports variable.

In production usage, we expect deployment tooling to manage the container definitions going forward, not Terraform.

Terraform Versions

Terraform 0.12. Pin module version to ~> 3.0. Submit pull-requests to master branch.

Terraform 0.11. Pin module version to ~> 1.14. Submit pull-requests to terraform011 branch.

Usage

ECS service associated with an Application Load Balancer (ALB)

module "app_ecs_service" {
  source = "trussworks/ecs-service/aws"

  name        = "app"
  environment = "prod"

  ecs_cluster                   = aws_ecs_cluster.mycluster
  ecs_vpc_id                    = module.vpc.vpc_id
  ecs_subnet_ids                = module.vpc.private_subnets
  kms_key_id                    = aws_kms_key.main.arn
  tasks_desired_count           = 2

  associate_alb      = true
  alb_security_group = module.security_group.id
  target_groups =
  [
    {
      container_port             = 8443
      container_healthcheck_port = 8443
      lb_target_group_arn        = module.alb.arn
    }
  ]
}

ECS Service associated with a Network Load Balancer(NLB)

module "app_ecs_service" {
  source = "trussworks/ecs-service/aws"

  name        = "app"
  environment = "prod"

  ecs_cluster                   = aws_ecs_cluster.mycluster
  ecs_vpc_id                    = module.vpc.vpc_id
  ecs_subnet_ids                = module.vpc.private_subnets
  kms_key_id                    = aws_kms_key.main.arn
  tasks_desired_count           = 2

  associate_nlb          = true
  nlb_subnet_cidr_blocks = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]

  lb_target_groups =
  [
    {
      container_port             = 8443
      container_healthcheck_port = 8080
      lb_target_group_arn        = module.nlb.arn
    }
  ]
}

ECS Service without any AWS load balancer

module "app_ecs_service" {
  source = "trussworks/ecs-service/aws"

  name        = "app"
  environment = "prod"

  ecs_cluster                   = aws_ecs_cluster.mycluster
  ecs_vpc_id                    = module.vpc.vpc_id
  ecs_subnet_ids                = module.vpc.private_subnets
  kms_key_id                    = aws_kms_key.main.arn
}

Requirements

Name Version
terraform >= 0.12

Providers

Name Version
aws n/a

Inputs

Name Description Type Default Required
additional_security_group_ids In addition to the security group created for the service, a list of security groups the ECS service should also be added to. list(string) [] no
alb_security_group Application Load Balancer (ALB) security group ID to allow traffic from. string "" no
assign_public_ip Whether this instance should be accessible from the public internet. Default is false. bool false no
associate_alb Whether to associate an Application Load Balancer (ALB) with the ECS service. bool false no
associate_nlb Whether to associate a Network Load Balancer (NLB) with the ECS service. bool false no
cloudwatch_alarm_actions The list of actions to take for cloudwatch alarms list(string) [] no
cloudwatch_alarm_cpu_enable Enable the CPU Utilization CloudWatch metric alarm bool true no
cloudwatch_alarm_cpu_threshold The CPU Utilization threshold for the CloudWatch metric alarm number 80 no
cloudwatch_alarm_mem_enable Enable the Memory Utilization CloudWatch metric alarm bool true no
cloudwatch_alarm_mem_threshold The Memory Utilization threshold for the CloudWatch metric alarm number 80 no
cloudwatch_alarm_name Generic name used for CPU and Memory Cloudwatch Alarms string "" no
command The command that is passed to the container list(string) [] no
container_definitions Container definitions provided as valid JSON document. Default uses golang:alpine running a simple hello world. string "" no
container_image The image of the container. string n/a yes
cost_tags Additional tags for cost tracking map(string) {} no
disableNetworking When this parameter is true, networking is disabled within the container bool false no
dnsSearchDomains A list of DNS search domains that are presented to the container list(string) [] no
dnsServers A list of DNS servers that are presented to the container list(string) [] no
dockerLabels A key/value map of labels to add to the container map(string) {} no
dockerSecurityOptions A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems list(string) [] no
ecr_repo_arns The ARNs of the ECR repos. By default, allows all repositories. list(string)
[
"*"
]
no
ecs_cluster ECS cluster object for this task.
object({
arn = string
name = string
})
n/a yes
ecs_instance_role The name of the ECS instance role. string "" no
ecs_subnet_ids Subnet IDs for the ECS tasks. list(string) n/a yes
ecs_use_fargate Whether to use Fargate for the task definition. bool false no
ecs_vpc_id VPC ID to be used by ECS. string n/a yes
entryPoint The entry point that is passed to the container list(string) [] no
environment Environment tag, e.g prod. string n/a yes
extraHosts A list of hostnames and IP address mappings to append to the /etc/hosts file on the container list(string) [] no
family The image family for the task definition string n/a yes
fargate_task_cpu Number of cpu units used in initial task definition. Default is minimum. number 256 no
fargate_task_memory Amount (in MiB) of memory used in initial task definition. Default is minimum. number 512 no
healthCheck The health check command and associated configuration parameters for the container any {} no
hostname The hostname to use for your container string "" no
interactive When this parameter is true, this allows you to deploy containerized applications that require stdin or a tty to be allocated bool false no
ipc_mode The IPC resource namespace to use for the containers in the task string n/a yes
kms_key_id KMS customer managed key (CMK) ARN for encrypting application logs. string n/a yes
lb_target_groups List of load balancer target group objects containing the lb_target_group_arn, container_port and container_health_check_port. The container_port is the port on which the container will receive traffic. The container_health_check_port is an additional port on which the container can receive a health check. The lb_target_group_arn is either Application Load Balancer (ALB) or Network Load Balancer (NLB) target group ARN tasks will register with.
list(
object({
container_port = number
container_health_check_port = number
lb_target_group_arn = string
}
)
)
[] no
links The link parameter allows containers to communicate with each other without the need for port mappings list(string) [] no
linuxParameters Linux-specific modifications that are applied to the container, such as Linux KernelCapabilities any {} no
logs_cloudwatch_group CloudWatch log group to create and use. Default: /ecs/{name}-{environment} string "" no
logs_cloudwatch_retention Number of days you want to retain log events in the log group. number 90 no
memoryReservation The soft limit (in MiB) of memory to reserve for the container number 0 no
mountPoints The mount points for data volumes in your container list(any) [] no
name The service name. string n/a yes
nlb_subnet_cidr_blocks List of Network Load Balancer (NLB) CIDR blocks to allow traffic from. list(string) [] no
pid_mode The process namespace to use for the containers in the task string n/a yes
placement_constraints An array of placement constraint objects to use for the task list(string) [] no
portMappings The list of port mappings for the container list(any) [] no
privileged When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user) bool false no
pseudoTerminal When this parameter is true, a TTY is allocated bool false no
readonlyRootFilesystem When this parameter is true, the container is given read-only access to its root file system bool false no
register_task_definition Registers a new task definition from the supplied family and containerDefinitions bool true no
repositoryCredentials The private repository authentication credentials to use map(string) {} no
resourceRequirements The type and amount of a resource to assign to a container list(string) [] no
secrets The secrets to pass to the container list(string) [] no
systemControls A list of namespaced kernel parameters to set in the container list(string) [] no
tags The metadata that you apply to the task definition to help you categorize and organize them map(string) {} no
target_container_name Name of the container the Load Balancer should target. Default: {name}-{environment} string "" no
tasks_desired_count The number of instances of a task definition. number 1 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
ulimits A list of ulimits to set in the container list(any) [] no
user The user name to use inside the container string "" no
volumes A list of volume definitions in JSON format that containers in your task may use list(any) [] no
volumesFrom Data volumes to mount from another container list(string) [] no
workingDirectory The working directory in which to run commands inside the container string "" no

Outputs

Name Description
aws_ecs_cluster_name Name of the ECS cluster
aws_ecs_service_name Name of the ECS service
awslogs_group Name of the CloudWatch Logs log group containers should use.
awslogs_group_arn ARN of the CloudWatch Logs log group containers should use.
ecs_security_group_id Security Group ID assigned to the ECS tasks.
instance_role_policy ARN of the Instance Role Policy
task_container_definitions A list of container definitions in JSON format that describe the different containers that make up your task
task_definition_arn Full ARN of the Task Definition (including both family and revision).
task_definition_family The family of the Task Definition.
task_definition_revision The revision of the task in a particular family
task_execution_role The role object of the task execution role that the Amazon ECS container agent and the Docker daemon can assume.
task_execution_role_arn The ARN of the task execution role that the Amazon ECS container agent and the Docker daemon can assume.
task_execution_role_name The name of the task execution role that the Amazon ECS container agent and the Docker daemon can assume.
task_role The IAM role object assumed by Amazon ECS container tasks.
task_role_arn The ARN of the IAM role assumed by Amazon ECS container tasks.
task_role_name The name of the IAM role assumed by Amazon ECS container tasks.

Upgrade Path

2.x.x to 3.0.0

In 3.0.0 the module added support for multiple load balancer target groups. To support this change, container_port, container_health_check_port and lb_target_group are being replaced with lb_target_groups

Without a load balancer

If you are using this module without an ALB or NLB then you can remove any references to container_port, container_health_check_port and lb_target_group if you were doing so.

Using with ALB or NLB target groups

If you are using an NLB or NLB target groups with this module then you will need replace the values of container_port, container_health_check_port and lb_target_group with

Below is an example of how the module would be instantiated prior to version 3.0.0

module "app_ecs_service" {
  source = "trussworks/ecs-service/aws"
  ...
  container_port                  = 8443
  container_health_check_port     = 8080
  lb_target_group_arn             = module.alb.arn
  ...
}

In 3.0.0 the same example will look like the following

module "app_ecs_service" {
  source = "trussworks/ecs-service/aws"
  ...
  lb_target_groups =
  [
    {
      container_port                  = 8443
      container_health_check_port     = 8080
      lb_target_group_arn             = module.alb.arn
    }
  ]
  ...
}

2.0.0 to 2.1.0

In 2.1.0 KMS log encryption is required by default. This requires that you create and attach a new AWS KMS key ARN. As an example here is how to set that up (please review on your own):

data "aws_iam_policy_document" "cloudwatch_logs_allow_kms" {
  statement {
    sid    = "Enable IAM User Permissions"
    effect = "Allow"

    principals {
      type = "AWS"
      identifiers = [
        "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root",
      ]
    }

    actions = [
      "kms:*",
    ]
    resources = ["*"]
  }

  statement {
    sid    = "Allow logs KMS access"
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["logs.us-west-2.amazonaws.com"]
    }

    actions = [
      "kms:Encrypt*",
      "kms:Decrypt*",
      "kms:ReEncrypt*",
      "kms:GenerateDataKey*",
      "kms:Describe*"
    ]
    resources = ["*"]
  }
}

resource "aws_kms_key" "main" {
  description         = "Key for ECS log encryption"
  enable_key_rotation = true

  policy = data.aws_iam_policy_document.cloudwatch_logs_allow_kms.json
}

NOTE: Best practice is to use a separate KMS key per ECS Service. Do not re-use KMS keys if it can be avoided.

1.15.0 to 2.0.0

v2.0.0 of this module is built against Terraform v0.12. In addition to requiring this upgrade, the v1.15.0 version of the module took the name of the ECS cluster as a parameter; v2.0.0 takes the actual object of the ECS cluster as a parameter instead. You will need to update previous instances of this module with the altered parameter.

Developer Setup

Install dependencies (macOS)

brew install pre-commit go terraform terraform-docs

Testing

Terratest is being used for automated testing with this module. Tests in the test folder can be run locally by running the following command:

make test

Or with aws-vault:

AWS_VAULT_KEYCHAIN_NAME=<NAME> aws-vault exec <PROFILE> -- make test