diff --git a/modules/ecs-service/README.md b/modules/ecs-service/README.md index 754586cb2..d6c4d7e65 100644 --- a/modules/ecs-service/README.md +++ b/modules/ecs-service/README.md @@ -191,6 +191,58 @@ We can then create a pointer to this service in the `acme.come` hosted zone. This will create a CNAME record in the `acme.com` hosted zone that points `echo.acme.com` to `echo-server.dev-acme.com`. +### EFS + +EFS is supported by `ecs-service`. You can use either `efs_volumes` or `efs_component_volumes` in your task definition. + +This example shows how to use `efs_component_volumes` which remote looks up efs component and uses the `efs_id` to mount the volume. +And how to use `efs_volumes` +```yaml +components: + terraform: + ecs-services/my-service: + metadata: + component: ecs-service + inherits: + - ecs-services/defaults + vars: + containers: + service: + name: app + image: my-image:latest + log_configuration: + logDriver: awslogs + options: {} + port_mappings: + - containerPort: 8080 + hostPort: 8080 + protocol: tcp + mount_points: + - containerPath: "/var/lib/" + sourceVolume: "my-volume-mount" + + task: + efs_component_volumes: + - name: "my-volume-mount" + host_path: null + efs_volume_configuration: + - component: efs/my-volume-mount + root_directory: "/var/lib/" + transit_encryption: "ENABLED" + transit_encryption_port: 2999 + authorization_config: [] + efs_volumes: + - name: "my-volume-mount-2" + host_path: null + efs_volume_ configuration: + - file_system_id: "fs-1234" + root_directory: "/var/lib/" + transit_encryption: "ENABLED" + transit_encryption_port: 2998 + authorization_config: [] +``` + + ## Requirements @@ -224,6 +276,7 @@ This will create a CNAME record in the `acme.com` hosted zone that points `echo. | [ecs\_cloudwatch\_autoscaling](#module\_ecs\_cloudwatch\_autoscaling) | cloudposse/ecs-cloudwatch-autoscaling/aws | 0.7.3 | | [ecs\_cloudwatch\_sns\_alarms](#module\_ecs\_cloudwatch\_sns\_alarms) | cloudposse/ecs-cloudwatch-sns-alarms/aws | 0.12.3 | | [ecs\_cluster](#module\_ecs\_cluster) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | +| [efs](#module\_efs) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | | [gha\_assume\_role](#module\_gha\_assume\_role) | ../account-map/modules/team-assume-role-policy | n/a | | [gha\_role\_name](#module\_gha\_role\_name) | cloudposse/label/null | 0.25.0 | | [iam\_role](#module\_iam\_role) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | @@ -274,7 +327,7 @@ This will create a CNAME record in the `acme.com` hosted zone that points `echo. | [autoscaling\_enabled](#input\_autoscaling\_enabled) | Should this service autoscale using SNS alarams | `bool` | `true` | no | | [chamber\_service](#input\_chamber\_service) | SSM parameter service name for use with chamber. This is used in chamber\_format where /$chamber\_service/$name/$container\_name/$parameter would be the default. | `string` | `"ecs-service"` | no | | [cluster\_attributes](#input\_cluster\_attributes) | The attributes of the cluster name e.g. if the full name is `namespace-tenant-environment-dev-ecs-b2b` then the `cluster_name` is `ecs` and this value should be `b2b`. | `list(string)` | `[]` | no | -| [containers](#input\_containers) | Feed inputs into container definition module |
map(object({
name = string
ecr_image = optional(string)
image = optional(string)
memory = optional(number)
memory_reservation = optional(number)
cpu = optional(number)
essential = optional(bool, true)
readonly_root_filesystem = optional(bool, null)
privileged = optional(bool, null)
container_depends_on = optional(list(object({
containerName = string
condition = string # START, COMPLETE, SUCCESS, HEALTHY
})), null)

port_mappings = optional(list(object({
containerPort = number
hostPort = number
protocol = string
})), [])
command = optional(list(string), null)
entrypoint = optional(list(string), null)
healthcheck = optional(object({
command = list(string)
interval = number
retries = number
startPeriod = number
timeout = number
}), null)
ulimits = optional(list(object({
name = string
softLimit = number
hardLimit = number
})), null)
log_configuration = optional(object({
logDriver = string
options = optional(map(string), {})
}))
docker_labels = optional(map(string), null)
map_environment = optional(map(string), {})
map_secrets = optional(map(string), {})
volumes_from = optional(list(object({
sourceContainer = string
readOnly = bool
})), null)
mount_points = optional(list(object({
sourceVolume = string
containerPath = string
readOnly = bool
})), [])
}))
| `{}` | no | +| [containers](#input\_containers) | Feed inputs into container definition module |
map(object({
name = string
ecr_image = optional(string)
image = optional(string)
memory = optional(number)
memory_reservation = optional(number)
cpu = optional(number)
essential = optional(bool, true)
readonly_root_filesystem = optional(bool, null)
privileged = optional(bool, null)
container_depends_on = optional(list(object({
containerName = string
condition = string # START, COMPLETE, SUCCESS, HEALTHY
})), null)

port_mappings = optional(list(object({
containerPort = number
hostPort = number
protocol = string
})), [])
command = optional(list(string), null)
entrypoint = optional(list(string), null)
healthcheck = optional(object({
command = list(string)
interval = number
retries = number
startPeriod = number
timeout = number
}), null)
ulimits = optional(list(object({
name = string
softLimit = number
hardLimit = number
})), null)
log_configuration = optional(object({
logDriver = string
options = optional(map(string), {})
}))
docker_labels = optional(map(string), null)
map_environment = optional(map(string), {})
map_secrets = optional(map(string), {})
volumes_from = optional(list(object({
sourceContainer = string
readOnly = bool
})), null)
mount_points = optional(list(object({
sourceVolume = optional(string)
containerPath = optional(string)
readOnly = optional(bool)
})), [])
}))
| `{}` | no | | [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | | [cpu\_utilization\_high\_alarm\_actions](#input\_cpu\_utilization\_high\_alarm\_actions) | A list of ARNs (i.e. SNS Topic ARN) to notify on CPU Utilization High Alarm action | `list(string)` | `[]` | no | | [cpu\_utilization\_high\_evaluation\_periods](#input\_cpu\_utilization\_high\_evaluation\_periods) | Number of periods to evaluate for the alarm | `number` | `1` | no | @@ -352,7 +405,7 @@ This will create a CNAME record in the `acme.com` hosted zone that points `echo. | [stickiness\_type](#input\_stickiness\_type) | The type of sticky sessions. The only current possible value is `lb_cookie` | `string` | `"lb_cookie"` | no | | [stream\_mode](#input\_stream\_mode) | Stream mode details for the Kinesis stream | `string` | `"PROVISIONED"` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | -| [task](#input\_task) | Feed inputs into ecs\_alb\_service\_task module |
object({
task_cpu = optional(number)
task_memory = optional(number)
task_role_arn = optional(string, "")
pid_mode = optional(string, null)
ipc_mode = optional(string, null)
network_mode = optional(string)
propagate_tags = optional(string)
assign_public_ip = optional(bool, false)
use_alb_security_groups = optional(bool, true)
launch_type = optional(string, "FARGATE")
scheduling_strategy = optional(string, "REPLICA")
capacity_provider_strategies = optional(list(object({
capacity_provider = string
weight = number
base = number
})), [])

deployment_minimum_healthy_percent = optional(number, null)
deployment_maximum_percent = optional(number, null)
desired_count = optional(number, 0)
min_capacity = optional(number, 1)
max_capacity = optional(number, 2)
wait_for_steady_state = optional(bool, true)
circuit_breaker_deployment_enabled = optional(bool, true)
circuit_breaker_rollback_enabled = optional(bool, true)

ecs_service_enabled = optional(bool, true)
bind_mount_volumes = optional(list(object({
name = string
host_path = string
})), [])
efs_volumes = optional(list(object({
host_path = string
name = string
efs_volume_configuration = list(object({
file_system_id = string
root_directory = string
transit_encryption = string
transit_encryption_port = string
authorization_config = list(object({
access_point_id = string
iam = string
}))
}))
})), [])
docker_volumes = optional(list(object({
host_path = string
name = string
docker_volume_configuration = list(object({
autoprovision = bool
driver = string
driver_opts = map(string)
labels = map(string)
scope = string
}))
})), [])
fsx_volumes = optional(list(object({
host_path = string
name = string
fsx_windows_file_server_volume_configuration = list(object({
file_system_id = string
root_directory = string
authorization_config = list(object({
credentials_parameter = string
domain = string
}))
}))
})), [])
})
| `{}` | no | +| [task](#input\_task) | Feed inputs into ecs\_alb\_service\_task module |
object({
task_cpu = optional(number)
task_memory = optional(number)
task_role_arn = optional(string, "")
pid_mode = optional(string, null)
ipc_mode = optional(string, null)
network_mode = optional(string)
propagate_tags = optional(string)
assign_public_ip = optional(bool, false)
use_alb_security_groups = optional(bool, true)
launch_type = optional(string, "FARGATE")
scheduling_strategy = optional(string, "REPLICA")
capacity_provider_strategies = optional(list(object({
capacity_provider = string
weight = number
base = number
})), [])

deployment_minimum_healthy_percent = optional(number, null)
deployment_maximum_percent = optional(number, null)
desired_count = optional(number, 0)
min_capacity = optional(number, 1)
max_capacity = optional(number, 2)
wait_for_steady_state = optional(bool, true)
circuit_breaker_deployment_enabled = optional(bool, true)
circuit_breaker_rollback_enabled = optional(bool, true)

ecs_service_enabled = optional(bool, true)
bind_mount_volumes = optional(list(object({
name = string
host_path = string
})), [])
efs_volumes = optional(list(object({
host_path = string
name = string
efs_volume_configuration = list(object({
file_system_id = string
root_directory = string
transit_encryption = string
transit_encryption_port = string
authorization_config = list(object({
access_point_id = string
iam = string
}))
}))
})), [])
efs_component_volumes = optional(list(object({
host_path = string
name = string
efs_volume_configuration = list(object({
component = optional(string, "efs")
tenant = optional(string, null)
environment = optional(string, null)
stage = optional(string, null)

root_directory = string
transit_encryption = string
transit_encryption_port = string
authorization_config = list(object({
access_point_id = string
iam = string
}))
}))
})), [])
docker_volumes = optional(list(object({
host_path = string
name = string
docker_volume_configuration = list(object({
autoprovision = bool
driver = string
driver_opts = map(string)
labels = map(string)
scope = string
}))
})), [])
fsx_volumes = optional(list(object({
host_path = string
name = string
fsx_windows_file_server_volume_configuration = list(object({
file_system_id = string
root_directory = string
authorization_config = list(object({
credentials_parameter = string
domain = string
}))
}))
})), [])
})
| `{}` | no | | [task\_enabled](#input\_task\_enabled) | Whether or not to use the ECS task module | `bool` | `true` | no | | [task\_iam\_role\_component](#input\_task\_iam\_role\_component) | A component that outputs an iam\_role module as 'role' for adding to the service as a whole. | `string` | `null` | no | | [task\_policy\_arns](#input\_task\_policy\_arns) | The IAM policy ARNs to attach to the ECS task IAM role | `list(string)` |
[
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
"arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess"
]
| no | diff --git a/modules/ecs-service/main.tf b/modules/ecs-service/main.tf index f34c34873..5000fc207 100644 --- a/modules/ecs-service/main.tf +++ b/modules/ecs-service/main.tf @@ -40,6 +40,28 @@ locals { } : {} task = merge(var.task, local.task_s3) + + efs_component_volumes = lookup(local.task, "efs_component_volumes", []) + efs_component_map = { + for efs in local.efs_component_volumes : efs["name"] => efs + } + efs_component_remote_state = { + for efs in local.efs_component_volumes : efs["name"] => module.efs[efs["name"]].outputs + } + efs_component_merged = [for efs_volume_name, efs_component_output in local.efs_component_remote_state : { + host_path = local.efs_component_map[efs_volume_name].host_path + name = efs_volume_name + efs_volume_configuration = [ #again this is a hardcoded array because AWS does not support multiple configurations per volume + { + file_system_id = efs_component_output.efs_id + root_directory = local.efs_component_map[efs_volume_name].efs_volume_configuration[0].root_directory + transit_encryption = local.efs_component_map[efs_volume_name].efs_volume_configuration[0].transit_encryption + transit_encryption_port = local.efs_component_map[efs_volume_name].efs_volume_configuration[0].transit_encryption_port + authorization_config = local.efs_component_map[efs_volume_name].efs_volume_configuration[0].authorization_config + } + ] + }] + efs_volumes = concat(lookup(local.task, "efs_volumes", []), local.efs_component_merged) } data "aws_s3_objects" "mirror" { @@ -268,7 +290,7 @@ module "ecs_alb_service_task" { task_role_arn = lookup(local.task, "task_role_arn", one(module.iam_role[*]["outputs"]["role"]["arn"])) capacity_provider_strategies = lookup(local.task, "capacity_provider_strategies") - efs_volumes = lookup(local.task, "efs_volumes", []) + efs_volumes = local.efs_volumes docker_volumes = lookup(local.task, "docker_volumes", []) fsx_volumes = lookup(local.task, "fsx_volumes", []) bind_mount_volumes = lookup(local.task, "bind_mount_volumes", []) diff --git a/modules/ecs-service/remote-state.tf b/modules/ecs-service/remote-state.tf index 4ca467c7f..a32de427f 100644 --- a/modules/ecs-service/remote-state.tf +++ b/modules/ecs-service/remote-state.tf @@ -181,3 +181,20 @@ module "iam_role" { context = module.this.context } + +module "efs" { + for_each = local.efs_component_map + + source = "cloudposse/stack-config/yaml//modules/remote-state" + version = "1.5.0" + + # Here we can use [0] because aws only allows one efs volume configuration per volume + component = each.value.efs_volume_configuration[0].component + + context = module.this.context + + tenant = each.value.efs_volume_configuration[0].tenant + stage = each.value.efs_volume_configuration[0].stage + environment = each.value.efs_volume_configuration[0].environment + +} diff --git a/modules/ecs-service/variables.tf b/modules/ecs-service/variables.tf index 55d5c9274..2df296d5f 100644 --- a/modules/ecs-service/variables.tf +++ b/modules/ecs-service/variables.tf @@ -92,9 +92,9 @@ variable "containers" { readOnly = bool })), null) mount_points = optional(list(object({ - sourceVolume = string - containerPath = string - readOnly = bool + sourceVolume = optional(string) + containerPath = optional(string) + readOnly = optional(bool) })), []) })) description = "Feed inputs into container definition module" @@ -148,6 +148,24 @@ variable "task" { })) })) })), []) + efs_component_volumes = optional(list(object({ + host_path = string + name = string + efs_volume_configuration = list(object({ + component = optional(string, "efs") + tenant = optional(string, null) + environment = optional(string, null) + stage = optional(string, null) + + root_directory = string + transit_encryption = string + transit_encryption_port = string + authorization_config = list(object({ + access_point_id = string + iam = string + })) + })) + })), []) docker_volumes = optional(list(object({ host_path = string name = string