Skip to content

Commit

Permalink
[feature] Add ECS modules to cztack (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbarrien authored Sep 25, 2019
1 parent 4185303 commit 6918848
Show file tree
Hide file tree
Showing 28 changed files with 2,205 additions and 0 deletions.
45 changes: 45 additions & 0 deletions aws-ecs-job-fargate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ECS Job on Fargate

This creates an ECS service running on Fargate with no load balancer in front of it. Good for
background worker daemon sort of things.

## Terraform managed task definition vs czecs

If the user sets `var.manage_task_definition = true`, Terraform will manage the lifecycle
of the container definition; any external changes are reset on the next Terraform run.

If var.manage_task_definition = false, the user is expected to manage the
container definition external to Terraform (e.g. using [czecs](https://github.com/chanzuckerberg/czecs)). Upon creation,
Terraform will use a stub definition, but from that point forward will ignore any
changes to the definition, allowing external task definition management.

<!-- START -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|:----:|:-----:|:-----:|
| cluster\_id | | string | n/a | yes |
| container\_name | Name of the container. Must match name in task definition. If omitted, defaults to name derived from project/env/service. | string | `null` | no |
| cpu | CPU units for Fargate task. Used if task_definition provided, or for initial stub task if externally managed. | number | `256` | no |
| deployment\_maximum\_percent | (Optional) The 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 | (Optional) The 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 | | number | n/a | yes |
| env | Env for tagging and naming. See [doc](../README.md#consistent-tagging). | string | n/a | yes |
| memory | Memory in megabytes for Fargate task. Used if task_definition provided, or for initial stub task if externally managed. | number | `512` | no |
| project | Project for tagging and naming. See [doc](../README.md#consistent-tagging) | string | n/a | yes |
| registry\_secretsmanager\_arn | ARN for AWS Secrets Manager secret for credentials to private registry | string | `null` | no |
| security\_group\_ids | Security group to use for the Fargate task. | list | `<list>` | no |
| service | Service for tagging and naming. See [doc](../README.md#consistent-tagging). | string | n/a | yes |
| task\_definition | JSON to describe task. If omitted, defaults to a stub task that is expected to be managed outside of Terraform. | string | `null` | no |
| task\_role\_arn | | string | n/a | yes |
| task\_subnets | Subnets to launch Fargate task in. | list | `<list>` | no |

## Outputs

| Name | Description |
|------|-------------|
| ecs\_service\_arn | ARN for the ECS service. |
| ecs\_task\_definition\_family | The family of the task definition defined for the given/generated container definition. |
| task\_execution\_role\_arn | Task execution role for Fargate task. |

<!-- END -->
58 changes: 58 additions & 0 deletions aws-ecs-job-fargate/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
data "aws_iam_policy_document" "execution_role" {
statement {
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}

actions = ["sts:AssumeRole"]
}
}

# Always create and attach, Fargate requires task definition to have execution role ARN to support log driver awslogs.
resource "aws_iam_role" "task_execution_role" {
name = "${local.name}-execution-role"
assume_role_policy = data.aws_iam_policy_document.execution_role.json
}

# TODO(mbarrien): We can probably narrow this down to allowing access to only
# the specific ECR arn if applicable, and the specific cloudwatch log group.
# Either pass both identifiers in, or pass the entire role ARN as an argument
resource "aws_iam_role_policy_attachment" "task_execution_role" {
count = var.registry_secretsmanager_arn != null ? 1 : 0
role = aws_iam_role.task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

data "aws_iam_policy_document" "registry_secretsmanager" {
count = var.registry_secretsmanager_arn != null ? 1 : 0

statement {
actions = [
"kms:Decrypt",
]

resources = [var.registry_secretsmanager_arn]
}

statement {
actions = [
"secretsmanager:GetSecretValue",
]

# Limit to only current version of the secret
condition {
test = "ForAnyValue:StringEquals"
variable = "secretsmanager:VersionStage"
values = ["AWSCURRENT"]
}

resources = [var.registry_secretsmanager_arn]
}
}

resource "aws_iam_role_policy" "task_execution_role_secretsmanager" {
count = var.registry_secretsmanager_arn != null ? 1 : 0
role = aws_iam_role.task_execution_role.name
policy = data.aws_iam_policy_document.registry_secretsmanager[0].json
}
87 changes: 87 additions & 0 deletions aws-ecs-job-fargate/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
locals {
name = "${var.project}-${var.env}-${var.service}"

container_name = var.container_name == null ? local.name : var.container_name

task_definition = "${aws_ecs_task_definition.job.family}:${aws_ecs_task_definition.job.revision}"

tags = {
managedBy = "terraform"
Name = local.name
project = var.project
env = var.env
service = var.service
owner = var.owner
}
}

# Only one of the following is active at a time, depending on var.manage_task_definition
resource "aws_ecs_service" "job" {
name = local.name
cluster = var.cluster_id
count = var.manage_task_definition ? 1 : 0
launch_type = "FARGATE"

task_definition = local.task_definition
desired_count = var.desired_count
deployment_maximum_percent = var.deployment_maximum_percent
deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
scheduling_strategy = "REPLICA"

network_configuration {
subnets = var.task_subnets
security_groups = var.security_group_ids
}

tags = local.tags
}

resource "aws_ecs_service" "unmanaged-job" {
name = local.name
cluster = var.cluster_id
count = var.manage_task_definition ? 0 : 1
launch_type = "FARGATE"

task_definition = local.task_definition
desired_count = var.desired_count
deployment_maximum_percent = var.deployment_maximum_percent
deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
scheduling_strategy = "REPLICA"

network_configuration {
subnets = var.task_subnets
security_groups = var.security_group_ids
}

lifecycle {
ignore_changes = [task_definition]
}

tags = local.tags
}

# Default container definition if var.manage_task_definition == false
# Defaults to a minimal hello-world implementation; should be updated separately from
# Terraform, e.g. using ecs deploy or czecs
locals {
template = <<TEMPLATE
[
{
"name": "${local.container_name}",
"image": "library/busybox:1.29",
"command": ["sh", "-c", "while true; do { echo -e 'HTTP/1.1 200 OK\r\n\nRunning stub server'; date; } | nc -l -p 8080; done"]
}
]
TEMPLATE
}

resource "aws_ecs_task_definition" "job" {
family = local.name
container_definitions = var.manage_task_definition ? var.task_definition : local.template
task_role_arn = var.task_role_arn
requires_compatibilities = ["FARGATE"]
cpu = var.cpu
memory = var.memory
network_mode = "awsvpc"
execution_role_arn = aws_iam_role.task_execution_role.arn
}
17 changes: 17 additions & 0 deletions aws-ecs-job-fargate/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
output "ecs_service_arn" {
description = "ARN for the ECS service."

# Awful hack modified from https://github.com/hashicorp/terraform/issues/16726
# Since we know exactly one of the following has count > 0, we just concatenate all the mostly empty lists, and return the first element.
value = concat(aws_ecs_service.unmanaged-job.*.id, aws_ecs_service.job.*.id)[0]
}

output "ecs_task_definition_family" {
description = "The family of the task definition defined for the given/generated container definition."
value = aws_ecs_task_definition.job.family
}

output "task_execution_role_arn" {
description = "Task execution role for Fargate task."
value = aws_iam_role.task_execution_role.arn
}
91 changes: 91 additions & 0 deletions aws-ecs-job-fargate/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
variable "project" {
type = string
description = "Project for tagging and naming. See [doc](../README.md#consistent-tagging)"
}

variable "service" {
type = string
description = "Service for tagging and naming. See [doc](../README.md#consistent-tagging)."
}

variable "env" {
type = string
description = "Env for tagging and naming. See [doc](../README.md#consistent-tagging)."
}

variable "owner" {
type = string
description = "Owner for tagging and naming. See [doc](../README.md#consistent-tagging)."
}

variable "cluster_id" {
type = string
}

variable "desired_count" {
type = number
}

variable "deployment_maximum_percent" {
description = "(Optional) The 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."
type = number
default = 200
}

variable "deployment_minimum_healthy_percent" {
description = "(Optional) The 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."
type = number
default = 100
}

variable "task_role_arn" {
type = string
}

variable "task_definition" {
description = "JSON to describe task. If omitted, defaults to a stub task that is expected to be managed outside of Terraform."
type = string
default = null
}

variable "cpu" {
description = "CPU units for Fargate task. Used if task_definition provided, or for initial stub task if externally managed."
type = number
default = 256
}

variable "memory" {
description = "Memory in megabytes for Fargate task. Used if task_definition provided, or for initial stub task if externally managed."
type = number
default = 512
}

variable "task_subnets" {
description = "Subnets to launch Fargate task in."
type = list(string)
default = []
}

variable "security_group_ids" {
description = "Security group to use for the Fargate task."
type = list(string)
default = []
}

variable "container_name" {
description = "Name of the container. Must match name in task definition. If omitted, defaults to name derived from project/env/service."
type = string
default = null
}

variable "registry_secretsmanager_arn" {
description = "ARN for AWS Secrets Manager secret for credentials to private registry"
type = string
default = null
}

variable "manage_task_definition" {
description = "If false, Terraform will not touch the task definition for the ECS service after initial creation"
type = bool
default = true
}
40 changes: 40 additions & 0 deletions aws-ecs-job/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ECS Job

This creates an ECS service with no load balancer in front of it. Good for
background worker daemon sort of things.

## Terraform managed task definition vs czecs

If the user sets `var.manage_task_definition = true`, Terraform will manage the lifecycle
of the container definition; any external changes are reset on the next Terraform run.

If var.manage_task_definition = false, the user is expected to manage the
container definition external to Terraform (e.g. using [czecs](https://github.com/chanzuckerberg/czecs)). Upon creation,
Terraform will use a stub definition, but from that point forward will ignore any
changes to the definition, allowing external task definition management.

<!-- START -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|:----:|:-----:|:-----:|
| cluster\_id | | string | n/a | yes |
| container\_name | Name of the container. Must match name in task definition. If omitted, defaults to name derived from project/env/service. | string | `null` | no |
| deployment\_maximum\_percent | (Optional) The 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 | (Optional) The 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 | | number | n/a | yes |
| env | Env for tagging and naming. See [doc](../README.md#consistent-tagging). | string | n/a | yes |
| project | Project for tagging and naming. See [doc](../README.md#consistent-tagging) | string | n/a | yes |
| scheduling\_strategy | Scheduling strategy for the service: REPLICA or DAEMON. | string | `"REPLICA"` | no |
| service | Service for tagging and naming. See [doc](../README.md#consistent-tagging). | string | n/a | yes |
| task\_definition | JSON to describe task. If omitted, defaults to a stub task that is expected to be managed outside of Terraform. | string | `null` | no |
| task\_role\_arn | | string | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| ecs\_service\_arn | ARN for the ECS service. |
| ecs\_task\_definition\_family | The family of the task definition defined for the given/generated container definition. |

<!-- END -->
Loading

0 comments on commit 6918848

Please sign in to comment.