-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adv/feat/aws ecs fargate #14
base: main
Are you sure you want to change the base?
Changes from 2 commits
9be0b44
cc62c8e
20d60a3
70e1ff4
79722d9
a3ffb59
d39116b
78237fd
01e71b2
c7efe17
5d770fa
c8422ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# 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. | ||
|
||
# Usage | ||
|
||
1. Directly use our module in your existing Terraform configuration and provide the required variables | ||
|
||
``` | ||
module "retool" { | ||
... | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
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 | ||
} | ||
] | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
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_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 | ||
vpc_security_group_ids = [aws_security_group.rds.id] | ||
performance_insights_enabled = var.rds_performance_insights_enabled | ||
|
||
skip_final_snapshot = true | ||
apply_immediately = true | ||
} | ||
|
||
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.subnet_ids | ||
security_groups = [aws_security_group.alb.id] | ||
} | ||
} | ||
|
||
resource "aws_eip" "retool_ip" { | ||
vpc = true | ||
} | ||
|
||
resource "aws_nat_gateway" "retool_nat_gateway" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so we expect the customer to come with a public subnet w/ an internet gateway inside of it? I wonder if we should just do this for them? I recommend creating a networking.tf file that sets this stuff up if these variables are not provided. don't we also need route table rules to make sure traffic from our private subnet actually makes it to this NAT gateway? i think this is a good example https://dev.betterdoc.org/infrastructure/2020/02/04/setting-up-a-nat-gateway-on-aws-using-terraform.html |
||
allocation_id = aws_eip.retool_ip.id | ||
subnet_id = var.public_subnet | ||
|
||
tags = { | ||
"Name" = "retool-nat" | ||
} | ||
|
||
depends_on = [ | ||
var.internet_gateway | ||
] | ||
} | ||
|
||
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 = <<TASK_DEFINITION | ||
[ | ||
{ | ||
"command": ["./docker_scripts/start_api.sh"], | ||
"environment": [ | ||
{"name": "NODE_ENV", "value": "${var.node_env}"}, | ||
{"name": "SERVICE_TYPE", "value": "MAIN_BACKEND,DB_CONNECTOR"}, | ||
{"name": "FORCE_DEPLOYMENT", "value": "${var.force_deployment}"}, | ||
{"name": "POSTGRES_DB", "value": "hammerhead_production"}, | ||
{"name": "POSTGRES_HOST", "value": "${aws_db_instance.this.address}"}, | ||
{"name": "POSTGRES_SSL_ENABLED", "value": "${var.postgresql_ssl_enabled}"}, | ||
{"name": "POSTGRES_PORT", "value": "${var.postgresql_db_port}"}, | ||
{"name": "POSTGRES_USER", "value": "${aws_secretsmanager_secret_version.rds_username.secret_string}"}, | ||
{"name": "POSTGRES_PASSWORD", "value": "${aws_secretsmanager_secret_version.rds_password.secret_string}"}, | ||
{"name": "JWT_SECRET", "value": "${random_string.jwt_secret.result}"}, | ||
{"name": "ENCRYPTION_KEY", "value": "${random_string.encryption_key.result}"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i always am weary about randomly generating keys like this because if a customer were to want to keep their db but for some reason re-create their retool containers, they may get a new encryption key generated and nothing will work right :') typically this stuff should be customer-provided and referenced through aws secrets manager in this case |
||
{"name": "LICENSE_KEY", "value": "${var.retool_license_key}"}, | ||
{"name": "COOKIE_INSECURE", "value": "${var.cookie_insecure}"} | ||
], | ||
"logConfiguration": { | ||
"logDriver": "${var.retool_ecs_tasks_logdriver}", | ||
"options": { | ||
"awslogs-group": "${aws_cloudwatch_log_group.this.name}", | ||
"awslogs-region": "${var.aws_region}", | ||
"awslogs-stream-prefix": "${var.retool_ecs_tasks_log_prefix}" | ||
} | ||
}, | ||
"essential": true, | ||
"image": "${local.retool_image}", | ||
"name": "${var.retool_task_container_name}", | ||
"portMappings": [ | ||
{ | ||
"containerPort": ${var.retool_task_container_port} | ||
} | ||
] | ||
} | ||
] | ||
TASK_DEFINITION | ||
} | ||
|
||
resource "aws_ecs_service" "jobs_runner" { | ||
name = "${var.deployment_name}-jobs-runner-service" | ||
cluster = aws_ecs_cluster.this.id | ||
desired_count = 1 | ||
task_definition = aws_ecs_task_definition.retool_jobs_runner.arn | ||
launch_type = "FARGATE" | ||
|
||
network_configuration { | ||
subnets = var.subnet_ids | ||
security_groups = [aws_security_group.alb.id] | ||
} | ||
} | ||
|
||
resource "aws_ecs_task_definition" "retool_jobs_runner" { | ||
family = "${var.deployment_name}-jobs-runner" | ||
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 = <<TASK_DEFINITION | ||
[ | ||
{ | ||
"command": ["./docker_scripts/start_api.sh"], | ||
"environment": [ | ||
{"name": "NODE_ENV", "value": "${var.node_env}"}, | ||
{"name": "SERVICE_TYPE", "value": "JOBS_RUNNER"}, | ||
{"name": "FORCE_DEPLOYMENT", "value": "${var.force_deployment}"}, | ||
{"name": "POSTGRES_DB", "value": "hammerhead_production"}, | ||
{"name": "POSTGRES_HOST", "value": "${aws_db_instance.this.address}"}, | ||
{"name": "POSTGRES_SSL_ENABLED", "value": "${var.postgresql_ssl_enabled}"}, | ||
{"name": "POSTGRES_PORT", "value": "${var.postgresql_db_port}"}, | ||
{"name": "POSTGRES_USER", "value": "${aws_secretsmanager_secret_version.rds_username.secret_string}"}, | ||
{"name": "POSTGRES_PASSWORD", "value": "${aws_secretsmanager_secret_version.rds_password.secret_string}"}, | ||
{"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}"}, | ||
{"name": "COOKIE_INSECURE", "value": "${var.cookie_insecure}"} | ||
], | ||
"logConfiguration": { | ||
"logDriver": "${var.retool_ecs_tasks_logdriver}", | ||
"options": { | ||
"awslogs-group": "${aws_cloudwatch_log_group.this.name}", | ||
"awslogs-region": "${var.aws_region}", | ||
"awslogs-stream-prefix": "${var.retool_ecs_tasks_log_prefix}" | ||
} | ||
}, | ||
"essential": true, | ||
"image": "${local.retool_image}", | ||
"name": "${var.retool_task_container_name}", | ||
"portMappings": [ | ||
{ | ||
"containerPort": ${var.retool_task_container_port} | ||
} | ||
] | ||
} | ||
] | ||
TASK_DEFINITION | ||
} | ||
|
||
# resource "aws_iam_role" "retool_service_role" { | ||
# name = "${var.deployment_name}-service-role" | ||
# path = "/" | ||
# assume_role_policy = jsonencode({ | ||
# Version = "2012-10-17" | ||
# Statement = [ | ||
# { | ||
# Action = "sts:AssumeRole" | ||
# Effect = "Allow" | ||
# Principal = { | ||
# Service = "ecs.amazonaws.com" | ||
# } | ||
# } | ||
# ] | ||
# }) | ||
|
||
# inline_policy { | ||
# name = "${var.deployment_name}-env-service-policy" | ||
|
||
# policy = jsonencode({ | ||
# Version = "2012-10-17" | ||
# Statement = [ | ||
# { | ||
# Action = [ | ||
# "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", | ||
# "elasticloadbalancing:DeregisterTargets", | ||
# "elasticloadbalancing:Describe*", | ||
# "elasticloadbalancing:RegisterInstancesWithLoadBalancer", | ||
# "elasticloadbalancing:RegisterTargets", | ||
# "ec2:Describe*", | ||
# "ec2:AuthorizeSecurityGroupIngress" | ||
# ] | ||
# Effect = "Allow" | ||
# Resource = "*" | ||
# }] | ||
# }) | ||
# } | ||
# } | ||
|
||
# resource "aws_iam_role" "retool_task_role" { | ||
# name = "${var.deployment_name}-task-role" | ||
# path = "/" | ||
# assume_role_policy = jsonencode({ | ||
# Version = "2012-10-17" | ||
# Statement = [ | ||
# { | ||
# Action = "sts:AssumeRole" | ||
# Effect = "Allow" | ||
# Principal = { | ||
# Service = "ecs-tasks.amazonaws.com" | ||
# } | ||
# } | ||
# ] | ||
# }) | ||
# } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably should allow people to opt-out of creating a DB with some var and then support the customer passing in the db information through env vars as normal