diff --git a/README.md b/README.md index 799fd33..3d1f2ea 100644 --- a/README.md +++ b/README.md @@ -132,16 +132,16 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [alb](#input\_alb) | Configuration settings for the Application Load Balancer (ALB). This includes attributes related to the ALB itself, such as its name, port, protocol, and other optional settings like access logs and tags. |
object({
name = optional(string, null)
port = optional(number)
protocol = optional(string, "HTTP")
internal = optional(bool, false)
load_balancer_type = optional(string, "application")
idle_timeout = optional(number, 60)
enable_deletion_protection = optional(bool, false)
enable_http2 = optional(bool, true)
certificate_arn = optional(string, null)
create_alb = optional(bool, false)

access_logs = optional(object({
bucket = string
enabled = optional(bool, false)
prefix = optional(string, "")
}))

tags = optional(map(string), {})
})
| n/a | yes | -| [alb\_target\_group](#input\_alb\_target\_group) | List of target groups to create |
list(object({
name = optional(string, "target-group")
port = number
protocol = optional(string, null)
protocol_version = optional(string, "HTTP1")
vpc_id = optional(string, "")
target_type = optional(string, "ip")
ip_address_type = optional(string, "ipv4")
load_balancing_algorithm_type = optional(string, "round_robin")
load_balancing_cross_zone_enabled = optional(string, "use_load_balancer_configuration")
deregistration_delay = optional(number, 300)
slow_start = optional(number, 0)
tags = optional(map(string), {})

health_check = optional(object({
enabled = optional(bool, true)
protocol = optional(string, "HTTP")
path = optional(string, "/")
port = optional(string, "traffic-port")
timeout = optional(number, 6)
healthy_threshold = optional(number, 3)
unhealthy_threshold = optional(number, 3)
interval = optional(number, 30)
matcher = optional(string, "200")
}))

stickiness = optional(object({
enabled = optional(bool, true)
type = string
cookie_duration = optional(number, 86400)
})
)

}))
| n/a | yes | -| [capacity\_provider](#input\_capacity\_provider) | Configuration settings for the ECS capacity providers, including the capacity providers used for autoscaling and Fargate. This variable defines the properties of each capacity provider and how they are managed, such as scaling policies and termination protection. |
object({
autoscaling_capacity_providers = map(object({
name = optional(string)
auto_scaling_group_arn = string
managed_termination_protection = optional(string, "DISABLED")
managed_draining = optional(string, "ENABLED")
managed_scaling = optional(object({
instance_warmup_period = optional(number)
maximum_scaling_step_size = optional(number)
minimum_scaling_step_size = optional(number)
status = optional(string)
target_capacity = optional(number)
}))
tags = optional(map(string), {})
}))
use_fargate = bool
fargate_capacity_providers = any
})
| n/a | yes | -| [cidr\_blocks](#input\_cidr\_blocks) | CIDR blocks for security group ingress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | -| [ecs\_cluster](#input\_ecs\_cluster) | The ECS-specific values to use such as cluster, service, and repository names.

Keys:
- cluster\_name: The name of the ECS cluster.
- cluster\_configuration: The execute command configuration for the cluster.
- cluster\_settings: A list of cluster settings (e.g., container insights). Default is an empty list.
- cluster\_service\_connect\_defaults: Configures a default Service Connect namespace.
- create\_cloudwatch\_log\_group: Boolean flag to specify whether to create a CloudWatch log group for the ECS cluster. |
object({
name = string
configuration = optional(object({
execute_command_configuration = optional(object({
kms_key_id = optional(string, "")
logging = optional(string, "DEFAULT")
log_configuration = optional(object({
cloudwatch_encryption_enabled = optional(bool, null)
log_group_name = optional(string, null)
log_group_retention_in_days = optional(number, null)
log_group_kms_key_id = optional(string, null)
log_group_tags = optional(map(string), null)
s3_bucket_name = optional(string, null)
s3_bucket_encryption_enabled = optional(bool, null)
s3_key_prefix = optional(string, null)
}), {})
}), {})
}), {})
create_cloudwatch_log_group = bool
service_connect_defaults = optional(map(string), null)
settings = optional(any, null)
tags = optional(map(string), null)
})
| n/a | yes | -| [ecs\_service](#input\_ecs\_service) | The ECS-specific values to use such as cluster, service, and repository names. |
object({
cluster_name = string
service_name = string
repository_name = string
enable_load_balancer = bool
aws_lb_target_group_name = optional(string)
create_service = optional(bool, false)
})
| n/a | yes | +| [alb](#input\_alb) | Configuration settings for the Application Load Balancer (ALB). This includes attributes related to the ALB itself, such as its name, port, protocol, and other optional settings like access logs and tags. |
object({
name = optional(string, null)
port = optional(number)
protocol = optional(string, "HTTP")
internal = optional(bool, false)
load_balancer_type = optional(string, "application")
idle_timeout = optional(number, 60)
enable_deletion_protection = optional(bool, false)
enable_http2 = optional(bool, true)
certificate_arn = optional(string, null)
create_alb = optional(bool, false)

access_logs = optional(object({
bucket = string
enabled = optional(bool, false)
prefix = optional(string, "")
}))

tags = optional(map(string), {})
})
| n/a | yes | +| [alb\_target\_group](#input\_alb\_target\_group) | List of target groups to create |
list(object({
name = optional(string, "target-group")
port = number
protocol = optional(string, null)
protocol_version = optional(string, "HTTP1")
vpc_id = optional(string, "")
target_type = optional(string, "ip")
ip_address_type = optional(string, "ipv4")
load_balancing_algorithm_type = optional(string, "round_robin")
load_balancing_cross_zone_enabled = optional(string, "use_load_balancer_configuration")
deregistration_delay = optional(number, 300)
slow_start = optional(number, 0)
tags = optional(map(string), {})

health_check = optional(object({
enabled = optional(bool, true)
protocol = optional(string, "HTTP")
path = optional(string, "/")
port = optional(string, "traffic-port")
timeout = optional(number, 6)
healthy_threshold = optional(number, 3)
unhealthy_threshold = optional(number, 3)
interval = optional(number, 30)
matcher = optional(string, "200")
}))

stickiness = optional(object({
enabled = optional(bool, true)
type = string
cookie_duration = optional(number, 86400)
})
)

}))
| n/a | yes | +| [capacity\_provider](#input\_capacity\_provider) | Configuration settings for the ECS capacity providers, including the capacity providers used for autoscaling and Fargate. This variable defines the properties of each capacity provider and how they are managed, such as scaling policies and termination protection. |
object({
autoscaling_capacity_providers = map(object({
name = optional(string)
auto_scaling_group_arn = string
managed_termination_protection = optional(string, "DISABLED")
managed_draining = optional(string, "ENABLED")
managed_scaling = optional(object({
instance_warmup_period = optional(number)
maximum_scaling_step_size = optional(number)
minimum_scaling_step_size = optional(number)
status = optional(string)
target_capacity = optional(number)
}))
tags = optional(map(string), {})
}))
use_fargate = bool
fargate_capacity_providers = any
})
| n/a | yes | +| [cidr\_blocks](#input\_cidr\_blocks) | CIDR blocks for security group ingress rules | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [ecs\_cluster](#input\_ecs\_cluster) | The ECS-specific values to use such as cluster, service, and repository names.

Keys:
- cluster\_name: The name of the ECS cluster.
- cluster\_configuration: The execute command configuration for the cluster.
- cluster\_settings: A list of cluster settings (e.g., container insights). Default is an empty list.
- cluster\_service\_connect\_defaults: Configures a default Service Connect namespace.
- create\_cloudwatch\_log\_group: Boolean flag to specify whether to create a CloudWatch log group for the ECS cluster. |
object({
name = string
configuration = optional(object({
execute_command_configuration = optional(object({
kms_key_id = optional(string, "")
logging = optional(string, "DEFAULT")
log_configuration = optional(object({
cloudwatch_encryption_enabled = optional(bool, null)
log_group_name = optional(string, null)
log_group_retention_in_days = optional(number, null)
log_group_kms_key_id = optional(string, null)
log_group_tags = optional(map(string), null)
s3_bucket_name = optional(string, null)
s3_bucket_encryption_enabled = optional(bool, null)
s3_key_prefix = optional(string, null)
}), {})
}), {})
}), {})
create_cloudwatch_log_group = bool
service_connect_defaults = optional(map(string), null)
settings = optional(any, null)
tags = optional(map(string), null)
})
| n/a | yes | +| [ecs\_service](#input\_ecs\_service) | The ECS-specific values to use such as cluster, service, and repository names. |
object({
cluster_name = string
service_name = string
repository_name = string
enable_load_balancer = bool
aws_lb_target_group_name = optional(string)
create_service = optional(bool, false)
})
| n/a | yes | | [environment](#input\_environment) | The environment associated with the ECS service | `string` | n/a | yes | -| [lb](#input\_lb) | ALB-related information (listening port, deletion protection, security group) |
object({
name = string
listener_port = number
deregistration_delay = optional(number)
security_group_id = string
})
| n/a | yes | -| [listener\_rules](#input\_listener\_rules) | List of listener rules to create |
list(object({
priority = number

conditions = list(object({
field = string
values = list(string)
}))

actions = list(object({
type = string
target_group_arn = optional(string)
order = optional(number)
redirect = optional(object({
protocol = string
port = string
host = optional(string)
path = optional(string)
query = optional(string)
status_code = string
}), null)

fixed_response = optional(object({
content_type = string
message_body = optional(string)
status_code = optional(string)
}), null)
}))
}))
| n/a | yes | -| [task](#input\_task) | Task-related information (vCPU, memory, # of tasks, port, and health check info.) |
object({
tasks_desired = optional(number)
container_vcpu = optional(number)
container_memory = optional(number)
container_port = number
container_health_check_path = optional(string)
container_definition = optional(string)
environment_variables = optional(map(string))
task_execution_role = optional(string)
})
| n/a | yes | +| [lb](#input\_lb) | ALB-related information (listening port, deletion protection, security group) |
object({
name = string
listener_port = number
deregistration_delay = optional(number)
security_group_id = string
})
| n/a | yes | +| [listener\_rules](#input\_listener\_rules) | List of listener rules to create |
list(object({
priority = number

conditions = list(object({
field = string
values = list(string)
}))

actions = list(object({
type = string
target_group_arn = optional(string)
order = optional(number)
redirect = optional(object({
protocol = string
port = string
host = optional(string)
path = optional(string)
query = optional(string)
status_code = string
}), null)

fixed_response = optional(object({
content_type = string
message_body = optional(string)
status_code = optional(string)
}), null)
}))
}))
| n/a | yes | +| [task](#input\_task) | Task-related information (vCPU, memory, # of tasks, port, and health check info.) |
object({
tasks_desired = optional(number)
container_vcpu = optional(number)
container_memory = optional(number)
container_port = number
container_health_check_path = optional(string)
container_definition = optional(string)
environment_variables = optional(map(string))
task_execution_role = optional(string)
})
| n/a | yes | | [vpc\_id](#input\_vpc\_id) | ID of VPC in which all resources need to be created | `string` | n/a | yes | ## Outputs diff --git a/main.tf b/main.tf index 795109e..dbba6da 100644 --- a/main.tf +++ b/main.tf @@ -18,6 +18,15 @@ module "ecs_cluster" { use_fargate = var.capacity_provider.use_fargate fargate_capacity_providers = var.capacity_provider.fargate_capacity_providers } + + launch_template = { + name = "lt1" + } + + asg = { + max_size = 2 + min_size = 1 + } } diff --git a/modules/ecs_cluster/main.tf b/modules/ecs_cluster/main.tf index 71286be..a32b589 100644 --- a/modules/ecs_cluster/main.tf +++ b/modules/ecs_cluster/main.tf @@ -81,17 +81,142 @@ resource "aws_ecs_cluster" "this" { tags = merge(var.tags, var.ecs_cluster.tags) } +################################################################################ +# EC2 Launch Template +################################################################################ + +resource "aws_launch_template" "this" { + count = var.capacity_provider.use_fargate != true ? 1 : 0 + + name = var.launch_template.name + + dynamic "block_device_mappings" { + for_each = var.launch_template.block_device_mappings + content { + device_name = block_device_mappings.value.device_name + + dynamic "ebs" { + for_each = block_device_mappings.value.ebs != null ? [block_device_mappings.value.ebs] : [] + content { + volume_size = ebs.value.volume_size + } + } + } + } + + dynamic "cpu_options" { + for_each = var.launch_template.cpu_options != null ? [var.launch_template.cpu_options] : [] + content { + core_count = cpu_options.value.core_count + threads_per_core = cpu_options.value.threads_per_core + } + } + + disable_api_stop = var.launch_template.disable_api_stop + disable_api_termination = var.launch_template.disable_api_termination + ebs_optimized = var.launch_template.ebs_optimized + + dynamic "elastic_gpu_specifications" { + for_each = var.launch_template.elastic_gpu_specifications + content { + type = elastic_gpu_specifications.value.type + } + } + + dynamic "iam_instance_profile" { + for_each = var.launch_template.iam_instance_profile != null ? [var.launch_template.iam_instance_profile] : [] + content { + name = iam_instance_profile.value.name + } + } + + image_id = var.launch_template.image_id + + instance_initiated_shutdown_behavior = var.launch_template.instance_initiated_shutdown_behavior + + dynamic "monitoring" { + for_each = var.launch_template.monitoring != null ? [var.launch_template.monitoring] : [] + content { + enabled = monitoring.value.enabled + } + } + + dynamic "network_interfaces" { + for_each = var.launch_template.network_interfaces + content { + associate_public_ip_address = network_interfaces.value.associate_public_ip_address + ipv4_prefixes = network_interfaces.value.ipv4_prefixes + ipv6_prefixes = network_interfaces.value.ipv6_prefixes + ipv4_addresses = network_interfaces.value.ipv4_addresses + ipv6_addresses = network_interfaces.value.ipv6_addresses + network_interface_id = network_interfaces.value.network_interface_id + private_ip_address = network_interfaces.value.private_ip_address + security_groups = network_interfaces.value.security_groups + subnet_id = network_interfaces.value.subnet_id + } + } + + dynamic "placement" { + for_each = var.launch_template.placement != null ? [var.launch_template.placement] : [] + content { + availability_zone = placement.value.availability_zone + } + } + + vpc_security_group_ids = var.launch_template.vpc_security_group_ids + + dynamic "tag_specifications" { + for_each = var.launch_template.tag_specifications + content { + resource_type = tag_specifications.value.resource_type + tags = tag_specifications.value.tags + } + } + + user_data = var.launch_template.user_data != null ? filebase64(var.launch_template.user_data) : null +} + +################################################################################ +# Auto Scaling Group +################################################################################ + +resource "aws_autoscaling_group" "this" { + count = var.capacity_provider.use_fargate != true ? 1 : 0 + + name = var.asg.name != null ? var.asg.name : "ecs-auto-scaling-group" + min_size = var.asg.min_size + max_size = var.asg.max_size + desired_capacity = var.asg.desired_capacity != null ? var.asg.desired_capacity : var.asg.min_size + vpc_zone_identifier = var.asg.vpc_zone_identifier != null ? var.asg.vpc_zone_identifier : [] + + launch_template { + id = aws_launch_template.this[0].id + } + + health_check_type = var.asg.health_check_type != null ? var.asg.health_check_type : "EC2" + health_check_grace_period = var.asg.health_check_grace_period != null ? var.asg.health_check_grace_period : 300 + protect_from_scale_in = var.asg.protect_from_scale_in != null ? var.asg.protect_from_scale_in : false + default_cooldown = var.asg.default_cooldown != null ? var.asg.default_cooldown : 300 + + instance_refresh { + strategy = var.asg.instance_refresh.strategy + preferences { + min_healthy_percentage = var.asg.instance_refresh.preferences != null && var.asg.instance_refresh.preferences.min_healthy_percentage != null ? var.asg.instance_refresh.preferences.min_healthy_percentage : 50 + } + } +} + ################################################################################ # ECS Capacity Provider - EC2 ################################################################################ resource "aws_ecs_capacity_provider" "this" { - for_each = var.capacity_provider.autoscaling_capacity_providers != null ? var.capacity_provider.autoscaling_capacity_providers : {} + for_each = (var.capacity_provider.use_fargate != true && var.capacity_provider.autoscaling_capacity_providers != null) ? var.capacity_provider.autoscaling_capacity_providers : {} name = each.value.name != "" ? each.value.name : each.key auto_scaling_group_provider { - auto_scaling_group_arn = each.value.auto_scaling_group_arn + auto_scaling_group_arn = aws_autoscaling_group.this[0].arn # Enable managed termination protection only if managed scaling is defined managed_termination_protection = each.value.managed_scaling != null ? (each.value.managed_termination_protection != null ? each.value.managed_termination_protection : "DISABLED") : "DISABLED" diff --git a/modules/ecs_cluster/variables.tf b/modules/ecs_cluster/variables.tf index e21be67..7da391f 100644 --- a/modules/ecs_cluster/variables.tf +++ b/modules/ecs_cluster/variables.tf @@ -56,7 +56,93 @@ EOT } } +variable "launch_template" { + type = object({ + name = string + block_device_mappings = optional(list(object({ + device_name = string + ebs = optional(object({ + volume_size = number + })) + })), []) + + cpu_options = optional(object({ + core_count = number + threads_per_core = number + }), null) + + disable_api_stop = optional(bool, false) + disable_api_termination = optional(bool, false) + ebs_optimized = optional(bool, false) + + elastic_gpu_specifications = optional(list(object({ + type = string + })), []) + + iam_instance_profile = optional(object({ + name = string + }), null) + + image_id = optional(string, null) + instance_initiated_shutdown_behavior = optional(string, "stop") + + instance_type = optional(string, null) + kernel_id = optional(string, null) + key_name = optional(string, null) + + monitoring = optional(object({ + enabled = bool + }), null) + + network_interfaces = optional(list(object({ + associate_public_ip_address = optional(bool, null) + ipv4_prefixes = optional(list(string), []) + ipv6_prefixes = optional(list(string), []) + ipv4_addresses = optional(list(string), []) + ipv6_addresses = optional(list(string), []) + network_interface_id = optional(string, null) + private_ip_address = optional(string, null) + security_groups = optional(list(string), []) + subnet_id = optional(string, null) + })), []) + + placement = optional(object({ + availability_zone = string + }), null) + vpc_security_group_ids = optional(list(string), []) + + tag_specifications = optional(list(object({ + resource_type = string + tags = map(string) + })), []) + + user_data = optional(string, null) + }) +} + +variable "asg" { + description = "Auto Scaling Group configuration" + type = object({ + name = optional(string, null) + min_size = number + max_size = number + desired_capacity = optional(number) + vpc_zone_identifier = optional(list(string)) + + health_check_type = optional(string) + health_check_grace_period = optional(number, 300) + protect_from_scale_in = optional(bool) + default_cooldown = optional(number) + + instance_refresh = optional(object({ + strategy = string + preferences = optional(object({ + min_healthy_percentage = optional(number) + })) + })) + }) +} ################################################################################ # Cluster Capacity Providers diff --git a/variables.tf b/variables.tf index b2462a7..d1b9a87 100644 --- a/variables.tf +++ b/variables.tf @@ -62,6 +62,95 @@ variable "capacity_provider" { } +/* variable "launch_template" { + type = object({ + name = string + block_device_mappings = optional(list(object({ + device_name = string + ebs = optional(object({ + volume_size = number + })) + })), []) + + cpu_options = optional(object({ + core_count = number + threads_per_core = number + }), null) + + disable_api_stop = optional(bool, false) + disable_api_termination = optional(bool, false) + ebs_optimized = optional(bool, false) + + elastic_gpu_specifications = optional(list(object({ + type = string + })), []) + + iam_instance_profile = optional(object({ + name = string + }), null) + + image_id = optional(string, null) + instance_initiated_shutdown_behavior = optional(string, "stop") + + instance_type = optional(string, null) + kernel_id = optional(string, null) + key_name = optional(string, null) + + monitoring = optional(object({ + enabled = bool + }), null) + + network_interfaces = optional(list(object({ + associate_public_ip_address = optional(bool, null) + ipv4_prefixes = optional(list(string), []) + ipv6_prefixes = optional(list(string), []) + ipv4_addresses = optional(list(string), []) + ipv6_addresses = optional(list(string), []) + network_interface_id = optional(string, null) + private_ip_address = optional(string, null) + security_groups = optional(list(string), []) + subnet_id = optional(string, null) + })), []) + + placement = optional(object({ + availability_zone = string + }), null) + + vpc_security_group_ids = optional(list(string), []) + + tag_specifications = optional(list(object({ + resource_type = string + tags = map(string) + })), []) + + user_data = optional(string, null) + }) +} + +variable "asg" { + description = "Auto Scaling Group configuration" + type = object({ + name = optional(string, null) + min_size = number + max_size = number + desired_capacity = optional(number) + vpc_zone_identifier = optional(list(string)) + + health_check_type = optional(string) + health_check_grace_period = optional(number, 300) + protect_from_scale_in = optional(bool) + default_cooldown = optional(number) + + instance_refresh = object({ + strategy = string + preferences = optional(object({ + min_healthy_percentage = optional(number) + })) + }) + }) +} */ + + ################################################################################ ## ALB ################################################################################