diff --git a/modules/aws_ecs_fargate/README.md b/modules/aws_ecs_fargate/README.md new file mode 100644 index 0000000..e720b65 --- /dev/null +++ b/modules/aws_ecs_fargate/README.md @@ -0,0 +1,23 @@ +# AWS ECS + Fargate Module + +This module deploys and ECS cluster with AWS Fargate. This eliminates the need to manually provision, scale and manage compute instances. + +# Prerequisites + +- A VPC + - with 2+ private and 2+ public subnets, + - and a route from the private subnets to the Internet (perhaps via a NAT gateway with EIP) + +# Key Design Decisions + +The module will deploy an RDS instance in the same VPC as the Fargate cluster. + +# Usage + +1. Directly use our module in your existing Terraform configuration and provide the required variables + +``` +module "retool" { + ... +} +``` diff --git a/modules/aws_ecs_fargate/loadbalancers.tf b/modules/aws_ecs_fargate/loadbalancers.tf new file mode 100644 index 0000000..0efeb49 --- /dev/null +++ b/modules/aws_ecs_fargate/loadbalancers.tf @@ -0,0 +1,53 @@ +resource "aws_lb" "this" { + name = "${var.deployment_name}-alb" + idle_timeout = var.alb_idle_timeout + + security_groups = [aws_security_group.alb.id] + subnets = var.alb_subnet_ids +} + +resource "aws_lb_listener" "this" { + load_balancer_arn = aws_lb.this.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.this.arn + } +} + +resource "aws_lb_listener_rule" "this" { + listener_arn = aws_lb_listener.this.arn + priority = 1 + + action { + type = "forward" + target_group_arn = aws_lb_target_group.this.arn + } + + condition { + path_pattern { + values = ["/"] + } + } +} + +resource "aws_lb_target_group" "this" { + name = "${var.deployment_name}-target" + vpc_id = var.vpc_id + deregistration_delay = 30 + port = 80 + protocol = "HTTP" + target_type = "ip" + + + health_check { + interval = 10 + path = "/api/checkHealth" + protocol = "HTTP" + timeout = 5 + healthy_threshold = 3 + unhealthy_threshold = 2 + } +} \ No newline at end of file diff --git a/modules/aws_ecs_fargate/locals.tf b/modules/aws_ecs_fargate/locals.tf new file mode 100644 index 0000000..a13bd66 --- /dev/null +++ b/modules/aws_ecs_fargate/locals.tf @@ -0,0 +1,72 @@ +locals { + environment_variables = concat( + var.additional_env_vars, # add additional environment variables + [ + { + name = "NODE_ENV" + value = var.node_env + }, + { + name = "FORCE_DEPLOYMENT" + value = tostring(var.force_deployment) + }, + { + name = "POSTGRES_DB" + value = "hammerhead_production" + }, + { + name = "POSTGRES_HOST" + value = aws_db_instance.this.address + }, + { + name = "POSTGRES_SSL_ENABLED" + value = "true" + }, + { + name = "POSTGRES_PORT" + value = "5432" + }, + { + "name" = "POSTGRES_USER", + "value" = var.rds_username + }, + { + "name" = "POSTGRES_PASSWORD", + "value" = random_string.rds_password.result + }, + { + "name" : "JWT_SECRET", + "value" : random_string.jwt_secret.result + }, + { + "name" : "ENCRYPTION_KEY", + "value" : random_string.encryption_key.result + }, + { + "name" : "LICENSE_KEY", + "value" : var.retool_license_key + } + ] + ) + + stack_name = "${var.deployment_name}" + database_name = aws_db_instance.this.db_name + db_subnet_group_name = "${var.deployment_name}-subnet-group" + retool_image = "${var.ecs_retool_image}" + retool_alb_ingress_port = var.alb_listener_certificate_arn != null ? "443" : var.retool_alb_ingress_port + retool_alb_listener_protocol = var.alb_listener_certificate_arn != null ? "HTTPS" : var.aws_lb_listener_protocol + retool_alb_listener_ssl_policy = var.alb_listener_certificate_arn != null ? var.alb_listener_ssl_policy : null + retool_alb_listener_certificate_arn = var.alb_listener_certificate_arn + retool_url_port = local.retool_alb_ingress_port != "443" ? ":${local.retool_alb_ingress_port}" : "" + + retool_jwt_secret = { + password = aws_secretsmanager_secret_version.jwt_secret + } + retool_encryption_key_secret = { + password = random_string.encryption_key.result + } + retool_rds_secret = { + username = "retool" + password = aws_secretsmanager_secret.rds_password + } +} diff --git a/modules/aws_ecs_fargate/main.tf b/modules/aws_ecs_fargate/main.tf new file mode 100644 index 0000000..a9b647a --- /dev/null +++ b/modules/aws_ecs_fargate/main.tf @@ -0,0 +1,240 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +resource "aws_ecs_cluster" "this" { + name = "${var.deployment_name}-ecs" + + setting { + name = "containerInsights" + value = var.ecs_insights_enabled + } +} + +resource "aws_cloudwatch_log_group" "this" { + name = "${var.deployment_name}-ecs-log-group" + retention_in_days = var.log_retention_in_days +} + +resource "aws_db_subnet_group" "this" { + name = local.db_subnet_group_name + subnet_ids = var.rds_subnet_ids +} + +resource "aws_db_instance" "this" { + identifier = "${var.deployment_name}-rds-instance" + allocated_storage = 80 + instance_class = var.rds_instance_class + engine = "postgres" + engine_version = "13.7" + db_name = "hammerhead_production" + username = aws_secretsmanager_secret_version.rds_username.secret_string + password = aws_secretsmanager_secret_version.rds_password.secret_string + port = 5432 + publicly_accessible = var.rds_publicly_accessible + db_subnet_group_name = local.db_subnet_group_name + vpc_security_group_ids = [aws_security_group.rds.id] + performance_insights_enabled = var.rds_performance_insights_enabled + + skip_final_snapshot = true + apply_immediately = true + + depends_on = [ + aws_db_subnet_group.this + ] +} + +resource "aws_ecs_service" "retool" { + name = "${var.deployment_name}-main-service" + cluster = aws_ecs_cluster.this.id + task_definition = aws_ecs_task_definition.retool.arn + desired_count = var.ecs_service_count + deployment_maximum_percent = var.maximum_percent + deployment_minimum_healthy_percent = var.minimum_healthy_percent + launch_type = "FARGATE" + + load_balancer { + target_group_arn = aws_lb_target_group.this.arn + container_name = "retool" + container_port = 3000 + } + + network_configuration { + subnets = var.ecs_tasks_subnet_ids + security_groups = [aws_security_group.ecs_tasks.id] + } +} + +resource "aws_ecs_task_definition" "retool" { + family = "${var.deployment_name}-backend" + requires_compatibilities = ["FARGATE"] + network_mode = var.ecs_task_network_mode + cpu = var.ecs_task_cpu + memory = var.ecs_task_memory + task_role_arn = aws_iam_role.task_role.arn + execution_role_arn = aws_iam_role.execution_role.arn + container_definitions = <