From ea0b59e2d04a2ee282be7f02e06ef15a161c8b3d Mon Sep 17 00:00:00 2001 From: Jikan7 Date: Tue, 28 May 2024 09:54:09 +0200 Subject: [PATCH 1/6] add modules --- modules/ecs-cluster/data.tf | 10 + modules/ecs-cluster/iam.tf | 48 +++ modules/ecs-cluster/main.tf | 282 ++++++++++++ modules/ecs-cluster/outputs.tf | 53 +++ modules/ecs-cluster/scripts/user_data.sh.tpl | 8 + modules/ecs-cluster/variables.tf | 178 ++++++++ modules/ecs-cluster/versions.tf | 25 ++ modules/ecs-service/main.tf | 428 +++++++++++++++++++ modules/ecs-service/outputs.tf | 34 ++ modules/ecs-service/variables.tf | 139 ++++++ modules/ecs-service/versions.tf | 15 + 11 files changed, 1220 insertions(+) create mode 100644 modules/ecs-cluster/data.tf create mode 100644 modules/ecs-cluster/iam.tf create mode 100644 modules/ecs-cluster/main.tf create mode 100644 modules/ecs-cluster/outputs.tf create mode 100644 modules/ecs-cluster/scripts/user_data.sh.tpl create mode 100644 modules/ecs-cluster/variables.tf create mode 100644 modules/ecs-cluster/versions.tf create mode 100644 modules/ecs-service/main.tf create mode 100644 modules/ecs-service/outputs.tf create mode 100644 modules/ecs-service/variables.tf create mode 100644 modules/ecs-service/versions.tf diff --git a/modules/ecs-cluster/data.tf b/modules/ecs-cluster/data.tf new file mode 100644 index 0000000..80cb94e --- /dev/null +++ b/modules/ecs-cluster/data.tf @@ -0,0 +1,10 @@ +data "aws_ami" "ecs_optimized" { + owners = ["amazon"] + + most_recent = true + + filter { + name = "name" + values = ["amzn2-ami-ecs-hvm-*-x86_64-ebs"] + } +} diff --git a/modules/ecs-cluster/iam.tf b/modules/ecs-cluster/iam.tf new file mode 100644 index 0000000..a88c494 --- /dev/null +++ b/modules/ecs-cluster/iam.tf @@ -0,0 +1,48 @@ +resource "aws_iam_role" "instance_role" { + name = "${random_id.prefix.hex}-cluster-instance" + assume_role_policy = data.aws_iam_policy_document.instance_role.json + + tags = merge(local.tags, { "resource.group" = "identity" }) +} + +data "aws_iam_policy_document" "instance_role" { + statement { + effect = "Allow" + actions = [ + "sts:AssumeRole", + ] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_instance_profile" "instance_profile" { + name = "${random_id.prefix.hex}-cluster-instance" + role = aws_iam_role.instance_role.name +} + +resource "aws_iam_role_policy" "ecs_instance" { + name = "${random_id.prefix.hex}-ecs-instance" + role = aws_iam_role.instance_role.name + policy = data.aws_iam_policy_document.ecs_instance.json +} + +data "aws_iam_policy_document" "ecs_instance" { + statement { + effect = "Allow" + actions = [ + "ecs:RegisterContainerInstance", + "ecs:DeregisterContainerInstance", + "ecs:DiscoverPollEndpoint", + "ecs:Poll", + "ecs:Submit*", + "ecs:StartTelemetrySession", + "ecs:TagResource", + ] + + resources = ["*"] + } +} diff --git a/modules/ecs-cluster/main.tf b/modules/ecs-cluster/main.tf new file mode 100644 index 0000000..c63c84b --- /dev/null +++ b/modules/ecs-cluster/main.tf @@ -0,0 +1,282 @@ +locals { + ami = var.ami == "" ? data.aws_ami.ecs_optimized.id : var.ami + + tags = merge({ + "context.namespace" = var.context.namespace + "context.stage" = var.context.stage + "context.name" = var.context.name + }, var.tags) +} + +resource "random_id" "prefix" { + byte_length = 4 + prefix = "${var.name_prefix}-" +} + +resource "aws_launch_template" "this" { + name_prefix = "${random_id.prefix.hex}-" + image_id = local.ami + instance_type = var.instance_type + key_name = aws_key_pair.this.key_name + + block_device_mappings { + device_name = "/dev/xvda" + + ebs { + volume_type = var.root_block_configuration.volume_type + volume_size = var.root_block_configuration.volume_size + } + } + + network_interfaces { + associate_public_ip_address = var.associate_public_ip_address + security_groups = concat([aws_security_group.instance_sg.id], var.security_groups) + } + + iam_instance_profile { + name = aws_iam_instance_profile.instance_profile.name + } + + user_data = data.cloudinit_config.this.rendered +} + +resource "aws_ecs_cluster" "this" { + name = random_id.prefix.hex + + setting { + name = "containerInsights" + value = var.enable_container_insights ? "enabled" : "disabled" + } + + tags = merge(local.tags, { "resource.group" = "compute" }) +} + +resource "aws_placement_group" "this" { + name = random_id.prefix.hex + strategy = var.placement_group.strategy # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html + spread_level = var.placement_group.spread_level + + tags = merge(local.tags, { "resource.group" = "compute" }) +} + +resource "aws_autoscaling_group" "this" { + name = random_id.prefix.hex + vpc_zone_identifier = var.subnet_ids + + min_size = var.autoscaling_group.min_size + desired_capacity = var.autoscaling_group.desired_capacity + max_size = var.autoscaling_group.max_size + protect_from_scale_in = var.protect_from_scale_in + + placement_group = aws_placement_group.this.id + termination_policies = ["OldestInstance"] + + default_cooldown = 300 + wait_for_capacity_timeout = "480s" + health_check_grace_period = 15 + health_check_type = "EC2" + + launch_template { + id = aws_launch_template.this.id + version = "$Latest" + } + + tag { + key = "Name" + value = random_id.prefix.hex + propagate_at_launch = true + } + + tag { + key = var.ssm_tag_key + value = var.ssm_tag_value + propagate_at_launch = true + } + + dynamic "tag" { + for_each = merge(local.tags, { "resource.group" = "compute" }) + content { + key = tag.key + value = tag.value + propagate_at_launch = true + } + } + + force_delete = false + + lifecycle { + create_before_destroy = true + ignore_changes = [target_group_arns] + } +} + +resource "aws_security_group" "instance_sg" { + description = "Controls direct access to application instances" + vpc_id = var.vpc_id + name = "${random_id.prefix.hex}-instance" + + tags = merge(local.tags, { "resource.group" = "network" }) +} + +resource "aws_security_group_rule" "ephemeral_port_range" { + description = "Allow dynamic port mapping for ECS" + type = "ingress" + from_port = 1 + to_port = 65535 + protocol = "tcp" + source_security_group_id = var.lb_security_group_id + security_group_id = aws_security_group.instance_sg.id +} + +resource "aws_security_group_rule" "allow_all_outbound_ec2_instance" { + description = "Allow outgoing traffic" + type = "egress" + from_port = 0 + to_port = 0 + protocol = "all" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.instance_sg.id +} + +resource "aws_iam_role_policy" "efs" { + count = var.efs == null ? 0 : 1 + + name = "${random_id.prefix.hex}-efs" + role = aws_iam_role.instance_role.name + policy = data.aws_iam_policy_document.efs[count.index].json +} + +data "aws_iam_policy_document" "efs" { + count = var.efs == null ? 0 : 1 + + statement { + effect = "Allow" + actions = [ + "elasticfilesystem:DescribeMountTargets", + "elasticfilesystem:DescribeAccessPoints", + "elasticfilesystem:DescribeFileSystems", + "elasticfilesystem:DescribeTags", + ] + + resources = [ + var.efs.arn + ] + } + + statement { + effect = "Allow" + actions = [ + "ec2:DescribeAvailabilityZones", + ] + + resources = ["*"] + } +} + + +data "cloudinit_config" "this" { + gzip = true + base64_encode = true + + part { + filename = "ecs-init.sh" + content_type = "text/x-shellscript" + content = templatefile("${path.module}/scripts/user_data.sh.tpl", + { + ecs_cluster = aws_ecs_cluster.this.name, + ecs_loglevel = var.ecs_loglevel, + ecs_tags = jsonencode(merge(var.tags, { + "Name" = random_id.prefix.hex, + "ssm.group" = var.ssm_tag_value, + })) + } + ) + } + + dynamic "part" { + for_each = var.efs == null ? [] : ["efs"] + + content { + filename = "efs.sh" + content = <> /etc/ecs/ecs.config +ECS_CLUSTER=${ecs_cluster} +ECS_LOGLEVEL=${ecs_loglevel} +ECS_CONTAINER_INSTANCE_TAGS=${ecs_tags} +CONFIG diff --git a/modules/ecs-cluster/variables.tf b/modules/ecs-cluster/variables.tf new file mode 100644 index 0000000..38e49fb --- /dev/null +++ b/modules/ecs-cluster/variables.tf @@ -0,0 +1,178 @@ +# required + +variable "context" { + description = "Project context." + + type = object({ + namespace = string + stage = string + name = string + }) +} + +variable "name_prefix" { + type = string + description = "Name prefix (hyphen suffix should be skipped)." +} + +variable "vpc_id" { + type = string + description = "AWS VPC id." +} + +variable "subnet_ids" { + type = list(string) + description = "List of AWS subent IDs for Autoscaling group." +} + +variable "instance_type" { + type = string + description = "EC2 instance type i.e. t3.medium." +} + +variable "lb_security_group_id" { + type = string + description = "Load balancer security group id." +} + +# optional + +variable "autoscaling_group" { + type = object({ + min_size = number + max_size = number + desired_capacity = number + }) + description = "Autoscaling group configuration." + default = { + min_size = 0 + max_size = 5 + desired_capacity = 0 + } +} + +variable "tags" { + type = map(string) + description = "Additional tags attached to resources." + default = {} +} + +variable "ssm_tag_key" { + type = string + description = "Tag key to add for SSM access" + default = "ssm.group" +} + +variable "ssm_tag_value" { + type = string + description = "Tag value to add for SSM access" + default = "true" +} + +variable "protect_from_scale_in" { + type = bool + description = "If protect from scale in is enabled, newly launched instances will be protected from scale in by default." + default = false +} + +variable "ami" { + type = string + description = "Image ID for Autoscaling group. If left blank, latest ECS-optimized version will be used." + default = "" +} + +variable "security_groups" { + type = list(string) + description = "List of security groups attached to launch configuration." + default = [] +} + +variable "cloudinit_parts" { + type = list(object({ + content = string + filename = string + content_type = string + })) + description = "Parts for cloud-init config that are added to the final MIME document." + default = [] +} + +variable "cloudinit_scripts" { + type = list(string) + description = "Shell scripts added to cloud-init." + default = [] +} + +variable "enable_container_insights" { + type = bool + description = "Enable container insights for the cluster." + default = true +} + +variable "ecs_loglevel" { + type = string + description = "ECS Cluster log level." + default = "info" + + validation { + condition = can(regex("^crit|error|warn|info|debug$", var.ecs_loglevel)) + error_message = "The ecs_loglevel must be one of crit, error, warn, info, debug." + } +} + +variable "associate_public_ip_address" { + type = bool + default = true + description = "Associate a public ip address with an instance in a VPC." +} + +variable "root_block_configuration" { + type = object({ + volume_type = string + volume_size = number + }) + default = { + volume_type = "gp2" + volume_size = 30 + } + description = "Configuration for root block device block." +} + +variable "placement_group" { + description = "Placement group strategy." + + type = object({ + strategy = string + spread_level = string + }) + default = { + strategy = "spread" + spread_level = "rack" + } +} + +variable "allow_ssh" { + description = "Allow SSH port in SG." + type = bool + default = false +} + +variable "efs" { + type = object({ + arn = string + }) + description = "EFS volume to mount to ECS" + default = null +} + +variable "ssh_cidr_ipv4" { + type = list(string) + description = "IPv4 CIDR block that will be granted access to SSH on ECS instances." + default = [] +} + +variable "ssh_cidr_ipv6" { + type = list(string) + description = "IPv6 CIDR block that will be granted access to SSH on ECS instances." + default = [] +} diff --git a/modules/ecs-cluster/versions.tf b/modules/ecs-cluster/versions.tf new file mode 100644 index 0000000..483a559 --- /dev/null +++ b/modules/ecs-cluster/versions.tf @@ -0,0 +1,25 @@ +terraform { + required_version = "~> 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + + cloudinit = { + source = "hashicorp/cloudinit" + version = "~> 2.0" + } + + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} diff --git a/modules/ecs-service/main.tf b/modules/ecs-service/main.tf new file mode 100644 index 0000000..2bcaa50 --- /dev/null +++ b/modules/ecs-service/main.tf @@ -0,0 +1,428 @@ +data "aws_region" "this" {} +data "aws_caller_identity" "this" {} + +locals { + tags = merge({ + "terraform.module" = "Selleo/terraform-aws-ecs" + "terraform.submodule" = "service" + "context.namespace" = var.context.namespace + "context.stage" = var.context.stage + "context.name" = var.context.name + }, var.tags) + + # when no ports are specified, we assume this is a worker + is_worker = var.port == null ? true : false + # when LB is used ports must be specified + needs_lb = var.create_alb_target_group && !local.is_worker + + ordered_placement_strategy = [ + { + type = "spread" + field = "attribute:ecs.avaiability-zones" + }, + { + type = "spread" + field = "instanceId" + } + ] +} + +resource "random_id" "prefix" { + byte_length = 4 + prefix = "${var.name}-" +} + +resource "aws_cloudwatch_log_group" "this" { + name = "${var.context.namespace}/${var.context.stage}/${var.context.name}/ecs/${var.name}" + retention_in_days = var.log_retention_in_days + + tags = merge(local.tags, { "resource.group" = "log" }) +} + +resource "aws_cloudwatch_log_group" "one_off" { + for_each = var.one_off_commands + + name = "${var.context.namespace}/${var.context.stage}/${var.context.name}/ecs/${var.name}-${each.key}" + retention_in_days = var.log_retention_in_days + + tags = merge(local.tags, { "resource.group" = "log" }) +} + +resource "aws_ecs_task_definition" "this" { + family = random_id.prefix.hex + network_mode = "bridge" + requires_compatibilities = ["EC2"] + execution_role_arn = aws_iam_role.task_execution.arn + task_role_arn = aws_iam_role.task_role.arn + + container_definitions = jsonencode([ + { + essential = true, + memoryReservation = 32 + memory = 64 + cpu = 64 + name = var.name + image = "qbart/go-http-server-noop:0.3.0" + # workers do not need port mappings + portMappings = local.is_worker ? [] : [ + { + containerPort = var.port, + hostPort = 0, + protocol = "tcp", + }, + ], + environment = [ + { + name = "APP_ENV" + value = var.context.stage + }, + { + name = "ADDR" + value = var.port == null ? ":3000" : ":${var.port}" + }, + ], + + logConfiguration = { + logDriver = "awslogs", + options = { + awslogs-group = aws_cloudwatch_log_group.this.name, + awslogs-region = data.aws_region.this.name, + awslogs-stream-prefix = "ecs", + }, + }, + } + ]) + + tags = merge(local.tags, { "resource.group" = "compute" }) +} + +resource "aws_ecs_task_definition" "one_off" { + for_each = var.one_off_commands + + family = "${random_id.prefix.hex}-${each.key}" + network_mode = "bridge" + requires_compatibilities = ["EC2"] + execution_role_arn = aws_iam_role.task_execution.arn + task_role_arn = aws_iam_role.task_role.arn + + container_definitions = jsonencode([ + { + essential = true, + memoryReservation = 32 + memory = 64 + cpu = 64 + name = var.name + image = "busybox:latest" + command = ["sh", "-c", "echo 'Hi'"] + + logConfiguration = { + logDriver = "awslogs", + options = { + awslogs-group = aws_cloudwatch_log_group.one_off[each.key].name, + awslogs-region = data.aws_region.this.name, + awslogs-stream-prefix = "ecs", + }, + }, + } + ]) + + tags = merge(local.tags, { "resource.group" = "compute" }) +} + +resource "aws_ecs_service" "this" { + name = var.name + cluster = var.cluster_id + task_definition = "${aws_ecs_task_definition.this.family}:${aws_ecs_task_definition.this.revision}" + enable_execute_command = var.enable_execute_command + + launch_type = "EC2" + + dynamic "load_balancer" { + for_each = local.needs_lb ? [1] : [] + + content { + target_group_arn = aws_alb_target_group.this[0].arn + container_name = var.name + container_port = var.port + } + } + + desired_count = var.desired_count + deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent + deployment_maximum_percent = var.deployment_maximum_percent + + dynamic "ordered_placement_strategy" { + for_each = local.ordered_placement_strategy + + content { + type = ordered_placement_strategy.value.type + field = ordered_placement_strategy.value.field + } + } + + tags = merge(local.tags, { "resource.group" = "compute" }) + + lifecycle { + ignore_changes = [ + task_definition, + ] + } +} + +resource "aws_iam_role" "task_role" { + name = "${random_id.prefix.hex}-task" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + }, + ] + }) + + tags = merge(local.tags, { "resource.group" = "identity" }) +} + +resource "aws_iam_role_policy" "task_role" { + name = "${random_id.prefix.hex}-task" + role = aws_iam_role.task_role.name + policy = data.aws_iam_policy_document.task_role.json +} + + +resource "aws_iam_role" "task_execution" { + name = "${random_id.prefix.hex}-task-execution" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + }, + ] + }) + + tags = merge(local.tags, { "resource.group" = "identity" }) +} + +resource "aws_iam_role_policy" "task_execution" { + name = "${random_id.prefix.hex}-task-execution" + role = aws_iam_role.task_execution.name + policy = data.aws_iam_policy_document.task_execution.json +} + +resource "aws_iam_role_policy" "ssm_get" { + count = length(var.secrets) == 0 ? 0 : 1 + + name = "${random_id.prefix.hex}-ssm-get" + role = aws_iam_role.task_execution.name + policy = data.aws_iam_policy_document.task_execution_ssm_get.json +} + +data "aws_iam_policy_document" "task_execution_ssm_get" { + statement { + sid = "GetSSMParams" + effect = "Allow" + actions = [ + "ssm:GetParameters", + ] + + resources = [ + for secret in var.secrets : + "arn:aws:ssm:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:parameter${secret}/*" + ] + } +} + +data "aws_iam_policy_document" "task_role" { + statement { + sid = "Task" + effect = "Allow" + actions = [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel", + ] + + resources = ["*"] + } +} + +data "aws_iam_policy_document" "task_execution" { + statement { + sid = "Task" + effect = "Allow" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + + resources = ["*"] + } +} + +resource "aws_iam_role_policy" "cloudwatch" { + name = "${random_id.prefix.hex}-role-cloudwatch-policy" + role = aws_iam_role.task_execution.id + policy = data.aws_iam_policy_document.cloudwatch.json +} + +resource "aws_iam_role_policy" "cloudwatch_one_off" { + for_each = var.one_off_commands + + name = "${random_id.prefix.hex}-${each.key}-cloudwatch-policy" + role = aws_iam_role.task_execution.id + policy = data.aws_iam_policy_document.cloudwatch_one_off[each.key].json +} + +data "aws_iam_policy_document" "cloudwatch" { + statement { + effect = "Allow" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + + resources = ["${aws_cloudwatch_log_group.this.arn}:*"] + } +} + +data "aws_iam_policy_document" "cloudwatch_one_off" { + for_each = var.one_off_commands + + statement { + effect = "Allow" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + + resources = ["${aws_cloudwatch_log_group.one_off[each.key].arn}:*"] + } +} + +resource "aws_alb_target_group" "this" { + count = local.needs_lb ? 1 : 0 + + name = random_id.prefix.hex + port = var.port + protocol = "HTTP" + vpc_id = var.vpc_id + deregistration_delay = var.deregistration_delay # draining time + target_type = "instance" + + health_check { + path = var.health_check.path + protocol = "HTTP" + timeout = var.health_check_threshold.timeout + interval = var.health_check_threshold.interval + healthy_threshold = var.health_check_threshold.healthy + unhealthy_threshold = var.health_check_threshold.unhealthy + matcher = var.health_check.matcher + } + + tags = merge(local.tags, { "resource.group" = "network" }) +} + +# deployment group that can be attached to user deployer + +resource "aws_iam_group" "deployment" { + name = "${random_id.prefix.hex}-deployment" +} + +resource "aws_iam_group_policy_attachment" "update_service" { + group = aws_iam_group.deployment.name + policy_arn = aws_iam_policy.update_service.arn +} + +resource "aws_iam_group_policy_attachment" "pass_role" { + group = aws_iam_group.deployment.name + policy_arn = aws_iam_policy.pass_role.arn +} + +resource "aws_iam_group_policy_attachment" "run_one_off_task" { + for_each = var.one_off_commands + + group = aws_iam_group.deployment.name + policy_arn = aws_iam_policy.deployment_run_one_off_task[each.key].arn +} + +# policy for updating service + +resource "aws_iam_policy" "update_service" { + name = "${random_id.prefix.hex}-update-service" + policy = data.aws_iam_policy_document.update_service.json + + tags = merge(local.tags, { "resource.group" = "identity" }) +} + +data "aws_iam_policy_document" "update_service" { + statement { + actions = [ + "ecs:DescribeTaskDefinition", + "ecs:RegisterTaskDefinition", + ] + + resources = ["*"] + } + + statement { + actions = [ + "ecs:DescribeServices", + "ecs:UpdateService", + ] + + resources = [aws_ecs_service.this.id] + } +} + +# policy for registering new task (IAM needs to pass role to task/execution role) + +resource "aws_iam_policy" "pass_role" { + name = "${random_id.prefix.hex}-pass-role" + policy = data.aws_iam_policy_document.pass_role.json + + tags = merge(local.tags, { "resource.group" = "identity" }) +} + +data "aws_iam_policy_document" "pass_role" { + statement { + actions = ["iam:PassRole", "iam:GetRole"] + + resources = [ + aws_iam_role.task_role.arn, + aws_iam_role.task_execution.arn + ] + } +} + +# policy for starting new one off task + +resource "aws_iam_policy" "deployment_run_one_off_task" { + for_each = var.one_off_commands + + name = "${random_id.prefix.hex}-one-off-run-${each.key}" + policy = data.aws_iam_policy_document.run_task[each.key].json +} + +data "aws_iam_policy_document" "run_task" { + for_each = var.one_off_commands + + statement { + actions = ["ecs:RunTask"] + + resources = [ + "arn:aws:ecs:${data.aws_region.this.id}:${data.aws_caller_identity.this.id}:task-definition/${random_id.prefix.hex}-${each.key}" + ] + } +} \ No newline at end of file diff --git a/modules/ecs-service/outputs.tf b/modules/ecs-service/outputs.tf new file mode 100644 index 0000000..580c875 --- /dev/null +++ b/modules/ecs-service/outputs.tf @@ -0,0 +1,34 @@ +output "lb_target_group_id" { + value = try(aws_alb_target_group.this[0].id, null) + description = "ARN of the Target Group." +} + +output "name" { + value = var.name + description = "Service name." +} + +output "service_id" { + value = aws_ecs_service.this.id + description = "ARN that identifies the service." +} + +output "task_role_id" { + value = aws_iam_role.task_role.id + description = "ECS task role ID" +} + +output "task_execution_role_id" { + value = aws_iam_role.task_execution.id + description = "ECS task execution role ID" +} + +output "deployment_group" { + value = aws_iam_group.deployment.name + description = "Deployment group name" +} + +output "deployment_group_arn" { + value = aws_iam_group.deployment.arn + description = "Deployment group ARN" +} \ No newline at end of file diff --git a/modules/ecs-service/variables.tf b/modules/ecs-service/variables.tf new file mode 100644 index 0000000..3c50db3 --- /dev/null +++ b/modules/ecs-service/variables.tf @@ -0,0 +1,139 @@ +# required + +variable "context" { + description = "Project context." + + type = object({ + namespace = string + stage = string + name = string + }) +} + +variable "vpc_id" { + type = string + description = "VPC id." +} + +variable "subnet_ids" { + type = list(string) + description = "List of AWS subent IDs for service." +} + +variable "name" { + type = string + description = "ECS Service name." +} + +variable "cluster_id" { + type = string + description = "ECS Cluster id." +} + +variable "desired_count" { + type = number + description = "Desired task count." +} + +# optional + +variable "port" { + description = "Container port" + type = number + default = null +} + +variable "enable_execute_command" { + description = "Allow to exec into containers." + type = bool + default = true +} + +variable "create_alb_target_group" { + description = "Register service as targer for load balancer." + type = bool + default = true +} + +variable "secrets" { + description = "Paths to secret. All secrets are read under the path." + type = set(string) + default = [] +} + +variable "tags" { + type = map(string) + description = "Additional tags attached to resources." + default = {} +} + +variable "one_off_commands" { + type = set(string) + description = "Set of commands that the tasks are created for." + default = [] +} + +variable "health_check" { + type = object({ + path = string + matcher = string + }) + description = "Health check config for ALB target group." + default = { + path = "/" + matcher = "200" + } +} + +variable "health_check_threshold" { + type = object({ + timeout = number + interval = number + healthy = number + unhealthy = number + }) + description = "Health check thresholds for ALB target group." + default = { + timeout = 10 + interval = 15 + healthy = 3 + unhealthy = 3 + } +} + +variable "deregistration_delay" { + description = "Deregistration delay (draining time) from LB." + + type = number + default = 30 +} + +variable "deployment_minimum_healthy_percent" { + description = "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 = 50 +} + +variable "deployment_maximum_percent" { + description = "Upper limit (as a percentage of the service's `desired_count`) 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 "log_retention_in_days" { + type = string + description = "Log retention in days for Cloudwatch." + default = 365 +} + +variable "efs" { + type = object({ + id = string + mount_path = string + volume = string + }) + description = "EFS volume to mount to ECS" + default = null +} \ No newline at end of file diff --git a/modules/ecs-service/versions.tf b/modules/ecs-service/versions.tf new file mode 100644 index 0000000..0567f49 --- /dev/null +++ b/modules/ecs-service/versions.tf @@ -0,0 +1,15 @@ +terraform { + required_version = "~> 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} \ No newline at end of file From bae357df39ff8a2d5e2e636a44198e0a06b6b984 Mon Sep 17 00:00:00 2001 From: Jikan7 Date: Tue, 28 May 2024 10:18:44 +0200 Subject: [PATCH 2/6] add example --- examples/ecs-app/.terraform.lock.hcl | 85 ++++++++++++++++++++++ examples/ecs-app/main.tf | 103 +++++++++++++++++++++++++++ examples/ecs-app/versions.tf | 15 ++++ 3 files changed, 203 insertions(+) create mode 100644 examples/ecs-app/.terraform.lock.hcl create mode 100644 examples/ecs-app/main.tf create mode 100644 examples/ecs-app/versions.tf diff --git a/examples/ecs-app/.terraform.lock.hcl b/examples/ecs-app/.terraform.lock.hcl new file mode 100644 index 0000000..aae824b --- /dev/null +++ b/examples/ecs-app/.terraform.lock.hcl @@ -0,0 +1,85 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.67.0" + constraints = ">= 4.0.0, ~> 4.0, >= 4.35.0" + hashes = [ + "h1:P43vwcDPG99x5WBbmqwUPgfJrfXf6/ucAIbGlRb7k1w=", + "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", + "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", + "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", + "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", + "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", + "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", + "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", + "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", + "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", + "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", + "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", + "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", + "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", + ] +} + +provider "registry.terraform.io/hashicorp/cloudinit" { + version = "2.3.4" + constraints = "~> 2.0" + hashes = [ + "h1:/Ty/HXg0Bti5T+Zk6XvhwEHyKGiOV5LzCrbLiekjuLU=", + "zh:09f1f1e1d232da96fbf9513b0fb5263bc2fe9bee85697aa15d40bb93835efbeb", + "zh:381e74b90d7a038c3a8dcdcc2ce8c72d6b86da9f208a27f4b98cabe1a1032773", + "zh:398eb321949e28c4c5f7c52e9b1f922a10d0b2b073b7db04cb69318d24ffc5a9", + "zh:4a425679614a8f0fe440845828794e609b35af17db59134c4f9e56d61e979813", + "zh:4d955d8608ece4984c9f1dacda2a59fdb4ea6b0243872f049b388181aab8c80a", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a48fbee1d58d55a1f4c92c2f38c83a37c8b2f2701ed1a3c926cefb0801fa446a", + "zh:b748fe6631b16a1dafd35a09377c3bffa89552af584cf95f47568b6cd31fc241", + "zh:d4b931f7a54603fa4692a2ec6e498b95464babd2be072bed5c7c2e140a280d99", + "zh:f1c9337fcfe3a7be39d179eb7986c22a979cfb2c587c05f1b3b83064f41785c5", + "zh:f58fc57edd1ee3250a28943cd84de3e4b744cdb52df0356a53403fc240240636", + "zh:f5f50de0923ff530b03e1bca0ac697534d61bb3e5fc7f60e13becb62229097a9", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.2" + constraints = "~> 3.0" + hashes = [ + "h1:R5qdQjKzOU16TziCN1vR3Exr/B+8WGK80glLTT4ZCPk=", + "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec", + "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53", + "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114", + "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad", + "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b", + "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916", + "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150", + "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544", + "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7", + "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.5" + constraints = "~> 4.0" + hashes = [ + "h1:yLqz+skP3+EbU3yyvw8JqzflQTKDQGsC9QyZAg+S4dg=", + "zh:01cfb11cb74654c003f6d4e32bbef8f5969ee2856394a96d127da4949c65153e", + "zh:0472ea1574026aa1e8ca82bb6df2c40cd0478e9336b7a8a64e652119a2fa4f32", + "zh:1a8ddba2b1550c5d02003ea5d6cdda2eef6870ece86c5619f33edd699c9dc14b", + "zh:1e3bb505c000adb12cdf60af5b08f0ed68bc3955b0d4d4a126db5ca4d429eb4a", + "zh:6636401b2463c25e03e68a6b786acf91a311c78444b1dc4f97c539f9f78de22a", + "zh:76858f9d8b460e7b2a338c477671d07286b0d287fd2d2e3214030ae8f61dd56e", + "zh:a13b69fb43cb8746793b3069c4d897bb18f454290b496f19d03c3387d1c9a2dc", + "zh:a90ca81bb9bb509063b736842250ecff0f886a91baae8de65c8430168001dad9", + "zh:c4de401395936e41234f1956ebadbd2ed9f414e6908f27d578614aaa529870d4", + "zh:c657e121af8fde19964482997f0de2d5173217274f6997e16389e7707ed8ece8", + "zh:d68b07a67fbd604c38ec9733069fbf23441436fecf554de6c75c032f82e1ef19", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/examples/ecs-app/main.tf b/examples/ecs-app/main.tf new file mode 100644 index 0000000..a938e74 --- /dev/null +++ b/examples/ecs-app/main.tf @@ -0,0 +1,103 @@ +resource "random_id" "example" { + byte_length = 4 + + prefix = "ec2-" +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 4.0" + + name = random_id.example.hex + cidr = "10.0.0.0/16" + + azs = ["eu-central-1a", "eu-central-1b"] + private_subnets = ["10.0.1.0/24"] + public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] + + single_nat_gateway = true + enable_nat_gateway = false + enable_vpn_gateway = false +} + +module "cluster" { + source = "../../modules/ecs-cluster" + + context = { + namespace = "selleo" + stage = "dev" + name = "example" + } + + name_prefix = random_id.example.hex + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.public_subnets + instance_type = "t3.nano" + lb_security_group_id = module.lb.security_group_id + + autoscaling_group = { + min_size = 1 + max_size = 5 + desired_capacity = 1 + } +} + +module "lb" { + source = "Selleo/backend/aws//modules/load-balancer" + version = "0.23.0" + + name = random_id.example.hex + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.public_subnets + force_https = false +} + +module "service" { + source = "../../modules/ecs-service" + + context = { + namespace = "selleo" + stage = "dev" + name = "example" + } + + name = random_id.example.hex + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.public_subnets + cluster_id = module.cluster.id + desired_count = 1 + secrets = ["/example/staging/api/terraform"] + + port = 3000 + depends_on = [module.secrets] +} + +resource "aws_alb_listener" "http" { + load_balancer_arn = module.lb.id + port = 80 + protocol = "HTTP" + + default_action { + target_group_arn = module.service.lb_target_group_id + type = "forward" + } +} + +module "secrets" { + source = "Selleo/ssm/aws//modules/parameters" + version = "0.4.0" + + context = { + namespace = "example" + stage = "staging" + name = "api" + } + + secrets = { + APP_ENV = "staging" + } +} + +output "lb_dns" { + value = "http://${module.lb.dns_name}" +} diff --git a/examples/ecs-app/versions.tf b/examples/ecs-app/versions.tf new file mode 100644 index 0000000..51493ec --- /dev/null +++ b/examples/ecs-app/versions.tf @@ -0,0 +1,15 @@ +terraform { + required_version = "~> 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = "eu-central-1" +} + From 5b5e69792d3ef7ba4da018daa94945f0f211456a Mon Sep 17 00:00:00 2001 From: Jikan7 Date: Tue, 28 May 2024 10:21:05 +0200 Subject: [PATCH 3/6] delete secrets module --- examples/ecs-app/main.tf | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/examples/ecs-app/main.tf b/examples/ecs-app/main.tf index a938e74..d10dc8a 100644 --- a/examples/ecs-app/main.tf +++ b/examples/ecs-app/main.tf @@ -66,7 +66,6 @@ module "service" { subnet_ids = module.vpc.public_subnets cluster_id = module.cluster.id desired_count = 1 - secrets = ["/example/staging/api/terraform"] port = 3000 depends_on = [module.secrets] @@ -83,21 +82,6 @@ resource "aws_alb_listener" "http" { } } -module "secrets" { - source = "Selleo/ssm/aws//modules/parameters" - version = "0.4.0" - - context = { - namespace = "example" - stage = "staging" - name = "api" - } - - secrets = { - APP_ENV = "staging" - } -} - output "lb_dns" { value = "http://${module.lb.dns_name}" } From 6448f173c33cf7578f72e02995f99fbf59c421cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 May 2024 08:22:53 +0000 Subject: [PATCH 4/6] chore: Update terraform docs --- examples/ecs-app/README.md | 37 ++++++++++++++ modules/ecs-cluster/README.md | 93 +++++++++++++++++++++++++++++++++++ modules/ecs-service/README.md | 88 +++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 examples/ecs-app/README.md create mode 100644 modules/ecs-cluster/README.md create mode 100644 modules/ecs-service/README.md diff --git a/examples/ecs-app/README.md b/examples/ecs-app/README.md new file mode 100644 index 0000000..5b1a979 --- /dev/null +++ b/examples/ecs-app/README.md @@ -0,0 +1,37 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.0 | +| [aws](#requirement\_aws) | ~> 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 4.67.0 | +| [random](#provider\_random) | 3.6.2 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cluster](#module\_cluster) | ../../modules/ecs-cluster | n/a | +| [lb](#module\_lb) | Selleo/backend/aws//modules/load-balancer | 0.23.0 | +| [service](#module\_service) | ../../modules/ecs-service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_alb_listener.http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/alb_listener) | resource | +| [random_id.example](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | + +## Outputs + +| Name | Description | +|------|-------------| +| [lb\_dns](#output\_lb\_dns) | n/a | + \ No newline at end of file diff --git a/modules/ecs-cluster/README.md b/modules/ecs-cluster/README.md new file mode 100644 index 0000000..1065514 --- /dev/null +++ b/modules/ecs-cluster/README.md @@ -0,0 +1,93 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.0 | +| [aws](#requirement\_aws) | ~> 4.0 | +| [cloudinit](#requirement\_cloudinit) | ~> 2.0 | +| [random](#requirement\_random) | ~> 3.0 | +| [tls](#requirement\_tls) | ~> 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 4.0 | +| [cloudinit](#provider\_cloudinit) | ~> 2.0 | +| [random](#provider\_random) | ~> 3.0 | +| [tls](#provider\_tls) | ~> 4.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_autoscaling_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource | +| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_iam_group.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource | +| [aws_iam_group_policy_attachment.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_instance_profile.instance_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_policy.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.instance_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.ecs_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_key_pair.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource | +| [aws_launch_template.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | +| [aws_placement_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/placement_group) | resource | +| [aws_security_group.instance_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.allow_all_outbound_ec2_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.allow_ssh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_security_group_rule.ephemeral_port_range](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [random_id.prefix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [tls_private_key.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [aws_ami.ecs_optimized](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_iam_policy_document.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ecs_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.instance_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [cloudinit_config.this](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allow\_ssh](#input\_allow\_ssh) | Allow SSH port in SG. | `bool` | `false` | no | +| [ami](#input\_ami) | Image ID for Autoscaling group. If left blank, latest ECS-optimized version will be used. | `string` | `""` | no | +| [associate\_public\_ip\_address](#input\_associate\_public\_ip\_address) | Associate a public ip address with an instance in a VPC. | `bool` | `true` | no | +| [autoscaling\_group](#input\_autoscaling\_group) | Autoscaling group configuration. |
object({
min_size = number
max_size = number
desired_capacity = number
})
|
{
"desired_capacity": 0,
"max_size": 5,
"min_size": 0
}
| no | +| [cloudinit\_parts](#input\_cloudinit\_parts) | Parts for cloud-init config that are added to the final MIME document. |
list(object({
content = string
filename = string
content_type = string
}))
| `[]` | no | +| [cloudinit\_scripts](#input\_cloudinit\_scripts) | Shell scripts added to cloud-init. | `list(string)` | `[]` | no | +| [context](#input\_context) | Project context. |
object({
namespace = string
stage = string
name = string
})
| n/a | yes | +| [ecs\_loglevel](#input\_ecs\_loglevel) | ECS Cluster log level. | `string` | `"info"` | no | +| [efs](#input\_efs) | EFS volume to mount to ECS |
object({
arn = string
})
| `null` | no | +| [enable\_container\_insights](#input\_enable\_container\_insights) | Enable container insights for the cluster. | `bool` | `true` | no | +| [instance\_type](#input\_instance\_type) | EC2 instance type i.e. t3.medium. | `string` | n/a | yes | +| [lb\_security\_group\_id](#input\_lb\_security\_group\_id) | Load balancer security group id. | `string` | n/a | yes | +| [name\_prefix](#input\_name\_prefix) | Name prefix (hyphen suffix should be skipped). | `string` | n/a | yes | +| [placement\_group](#input\_placement\_group) | Placement group strategy. |
object({
strategy = string
spread_level = string
})
|
{
"spread_level": "rack",
"strategy": "spread"
}
| no | +| [protect\_from\_scale\_in](#input\_protect\_from\_scale\_in) | If protect from scale in is enabled, newly launched instances will be protected from scale in by default. | `bool` | `false` | no | +| [root\_block\_configuration](#input\_root\_block\_configuration) | Configuration for root block device block. |
object({
volume_type = string
volume_size = number
})
|
{
"volume_size": 30,
"volume_type": "gp2"
}
| no | +| [security\_groups](#input\_security\_groups) | List of security groups attached to launch configuration. | `list(string)` | `[]` | no | +| [ssh\_cidr\_ipv4](#input\_ssh\_cidr\_ipv4) | IPv4 CIDR block that will be granted access to SSH on ECS instances. | `list(string)` | `[]` | no | +| [ssh\_cidr\_ipv6](#input\_ssh\_cidr\_ipv6) | IPv6 CIDR block that will be granted access to SSH on ECS instances. | `list(string)` | `[]` | no | +| [ssm\_tag\_key](#input\_ssm\_tag\_key) | Tag key to add for SSM access | `string` | `"ssm.group"` | no | +| [ssm\_tag\_value](#input\_ssm\_tag\_value) | Tag value to add for SSM access | `string` | `"true"` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of AWS subent IDs for Autoscaling group. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | Additional tags attached to resources. | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | AWS VPC id. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_group](#output\_autoscaling\_group) | Autoscaling Group data. | +| [deployment\_group](#output\_deployment\_group) | Deployment group name | +| [deployment\_group\_arn](#output\_deployment\_group\_arn) | Deployment group ARN | +| [id](#output\_id) | ECS cluster ID (contains randomized suffix). | +| [instance\_role](#output\_instance\_role) | IAM role that is attached to EC2 instances. | +| [instance\_security\_group\_id](#output\_instance\_security\_group\_id) | ID of the security group attached to an instance. | +| [key\_name](#output\_key\_name) | Key pair name for SSH access. | +| [name](#output\_name) | ECS cluster name. | +| [prefix](#output\_prefix) | Random prefix to use for associated resources. | +| [private\_key\_pem](#output\_private\_key\_pem) | Private key in PEM format. | + \ No newline at end of file diff --git a/modules/ecs-service/README.md b/modules/ecs-service/README.md new file mode 100644 index 0000000..2e298f5 --- /dev/null +++ b/modules/ecs-service/README.md @@ -0,0 +1,88 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.0 | +| [aws](#requirement\_aws) | ~> 4.0 | +| [random](#requirement\_random) | ~> 3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 4.0 | +| [random](#provider\_random) | ~> 3.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_alb_target_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/alb_target_group) | resource | +| [aws_cloudwatch_log_group.one_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.one_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_iam_group.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource | +| [aws_iam_group_policy_attachment.pass_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_group_policy_attachment.run_one_off_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_group_policy_attachment.update_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource | +| [aws_iam_policy.deployment_run_one_off_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.pass_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.update_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.cloudwatch_one_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.ssm_get](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [random_id.prefix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cloudwatch_one_off](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.pass_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.run_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_execution_ssm_get](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.update_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_id](#input\_cluster\_id) | ECS Cluster id. | `string` | n/a | yes | +| [context](#input\_context) | Project context. |
object({
namespace = string
stage = string
name = string
})
| n/a | yes | +| [create\_alb\_target\_group](#input\_create\_alb\_target\_group) | Register service as targer for load balancer. | `bool` | `true` | no | +| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Upper limit (as a percentage of the service's `desired_count`) 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](#input\_deployment\_minimum\_healthy\_percent) | 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` | `50` | no | +| [deregistration\_delay](#input\_deregistration\_delay) | Deregistration delay (draining time) from LB. | `number` | `30` | no | +| [desired\_count](#input\_desired\_count) | Desired task count. | `number` | n/a | yes | +| [efs](#input\_efs) | EFS volume to mount to ECS |
object({
id = string
mount_path = string
volume = string
})
| `null` | no | +| [enable\_execute\_command](#input\_enable\_execute\_command) | Allow to exec into containers. | `bool` | `true` | no | +| [health\_check](#input\_health\_check) | Health check config for ALB target group. |
object({
path = string
matcher = string
})
|
{
"matcher": "200",
"path": "/"
}
| no | +| [health\_check\_threshold](#input\_health\_check\_threshold) | Health check thresholds for ALB target group. |
object({
timeout = number
interval = number
healthy = number
unhealthy = number
})
|
{
"healthy": 3,
"interval": 15,
"timeout": 10,
"unhealthy": 3
}
| no | +| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | Log retention in days for Cloudwatch. | `string` | `365` | no | +| [name](#input\_name) | ECS Service name. | `string` | n/a | yes | +| [one\_off\_commands](#input\_one\_off\_commands) | Set of commands that the tasks are created for. | `set(string)` | `[]` | no | +| [port](#input\_port) | Container port | `number` | `null` | no | +| [secrets](#input\_secrets) | Paths to secret. All secrets are read under the path. | `set(string)` | `[]` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of AWS subent IDs for service. | `list(string)` | n/a | yes | +| [tags](#input\_tags) | Additional tags attached to resources. | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | VPC id. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [deployment\_group](#output\_deployment\_group) | Deployment group name | +| [deployment\_group\_arn](#output\_deployment\_group\_arn) | Deployment group ARN | +| [lb\_target\_group\_id](#output\_lb\_target\_group\_id) | ARN of the Target Group. | +| [name](#output\_name) | Service name. | +| [service\_id](#output\_service\_id) | ARN that identifies the service. | +| [task\_execution\_role\_id](#output\_task\_execution\_role\_id) | ECS task execution role ID | +| [task\_role\_id](#output\_task\_role\_id) | ECS task role ID | + \ No newline at end of file From b66c1395419e9aed1d5811a26500ae348a7b5b11 Mon Sep 17 00:00:00 2001 From: Jikan7 Date: Wed, 29 May 2024 09:09:52 +0200 Subject: [PATCH 5/6] change default value for log retention days --- modules/ecs-service/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ecs-service/variables.tf b/modules/ecs-service/variables.tf index 3c50db3..241fb0b 100644 --- a/modules/ecs-service/variables.tf +++ b/modules/ecs-service/variables.tf @@ -125,7 +125,7 @@ variable "deployment_maximum_percent" { variable "log_retention_in_days" { type = string description = "Log retention in days for Cloudwatch." - default = 365 + default = 60 } variable "efs" { From d3383c703690fa02cae87b36ba90a2f5762e7856 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 May 2024 07:11:31 +0000 Subject: [PATCH 6/6] chore: Update terraform docs --- modules/ecs-service/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ecs-service/README.md b/modules/ecs-service/README.md index 2e298f5..b8b8870 100644 --- a/modules/ecs-service/README.md +++ b/modules/ecs-service/README.md @@ -65,7 +65,7 @@ | [enable\_execute\_command](#input\_enable\_execute\_command) | Allow to exec into containers. | `bool` | `true` | no | | [health\_check](#input\_health\_check) | Health check config for ALB target group. |
object({
path = string
matcher = string
})
|
{
"matcher": "200",
"path": "/"
}
| no | | [health\_check\_threshold](#input\_health\_check\_threshold) | Health check thresholds for ALB target group. |
object({
timeout = number
interval = number
healthy = number
unhealthy = number
})
|
{
"healthy": 3,
"interval": 15,
"timeout": 10,
"unhealthy": 3
}
| no | -| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | Log retention in days for Cloudwatch. | `string` | `365` | no | +| [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | Log retention in days for Cloudwatch. | `string` | `60` | no | | [name](#input\_name) | ECS Service name. | `string` | n/a | yes | | [one\_off\_commands](#input\_one\_off\_commands) | Set of commands that the tasks are created for. | `set(string)` | `[]` | no | | [port](#input\_port) | Container port | `number` | `null` | no |