diff --git a/infrastructure/applications/applications.tf b/infrastructure/applications/applications.tf index a99c34c1f5..3edc952cdb 100644 --- a/infrastructure/applications/applications.tf +++ b/infrastructure/applications/applications.tf @@ -39,6 +39,13 @@ module "pycon_frontend" { cf_hosted_zone_id = module.cluster.cf_hosted_zone_id } +module "resonate" { + source = "./resonate" + cluster_id = module.cluster.cluster_id + logs_group_name = module.cluster.logs_group_name + database_settings = module.database.database_settings +} + module "clamav" { source = "./clamav" cluster_id = module.cluster.cluster_id diff --git a/infrastructure/applications/cluster/security.tf b/infrastructure/applications/cluster/security.tf index 9c050a3231..f53dd6d37d 100644 --- a/infrastructure/applications/cluster/security.tf +++ b/infrastructure/applications/cluster/security.tf @@ -64,6 +64,25 @@ resource "aws_security_group_rule" "out_clamav" { security_group_id = aws_security_group.server.id } +resource "aws_security_group_rule" "in_resonate" { + type = "egress" + from_port = 8001 + to_port = 8001 + protocol = "tcp" + source_security_group_id = aws_security_group.server.id + security_group_id = aws_security_group.server.id +} + +resource "aws_security_group_rule" "out_resonate" { + # needed by fargate to connect to the server with resonate + type = "ingress" + from_port = 8001 + to_port = 8001 + protocol = "tcp" + source_security_group_id = aws_security_group.server.id + security_group_id = aws_security_group.server.id +} + resource "aws_security_group_rule" "web_http" { type = "ingress" from_port = 80 diff --git a/infrastructure/applications/pycon_backend/resonate_worker.tf b/infrastructure/applications/pycon_backend/resonate_worker.tf new file mode 100644 index 0000000000..60d0a1045a --- /dev/null +++ b/infrastructure/applications/pycon_backend/resonate_worker.tf @@ -0,0 +1,64 @@ +resource "aws_ecs_task_definition" "resonate_worker" { + family = "pythonit-${terraform.workspace}-resonate-worker" + + container_definitions = jsonencode([ + { + name = "resonate-worker" + image = "${data.aws_ecr_repository.be_repo.repository_url}@${data.aws_ecr_image.be_arm_image.image_digest}" + memoryReservation = local.is_prod ? 200 : 10 + essential = true + + # Reloading is off outside DEBUG anyway; saying so keeps the deployed + # process a single one, with no reloader parent to sit between ECS and + # the worker's own signal handling. + command = [ + "python", "manage.py", "resonate_worker", "--no-reload" + ] + + environment = local.env_vars + + mountPoints = [] + systemControls = [ + { + "namespace" : "net.core.somaxconn", + "value" : "4096" + } + ] + + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = var.logs_group_name + "awslogs-region" = "eu-central-1" + "awslogs-stream-prefix" = "resonate-worker" + } + } + + healthCheck = { + retries = 3 + command = [ + "CMD-SHELL", + "echo 1" + ] + timeout = 3 + interval = 10 + } + + # Long enough for the worker to hand its tasks back to the server + # instead of letting their leases lapse. + stopTimeout = 120 + } + ]) + + requires_compatibilities = [] + tags = {} +} + +resource "aws_ecs_service" "resonate_worker" { + name = "backend-resonate-worker" + cluster = var.cluster_id + task_definition = aws_ecs_task_definition.resonate_worker.arn + desired_count = 1 + deployment_minimum_healthy_percent = 0 + deployment_maximum_percent = 100 +} diff --git a/infrastructure/applications/pycon_backend/worker.tf b/infrastructure/applications/pycon_backend/worker.tf index 7af5ac89b0..e75aa8e391 100644 --- a/infrastructure/applications/pycon_backend/worker.tf +++ b/infrastructure/applications/pycon_backend/worker.tf @@ -188,6 +188,11 @@ locals { { name = "SNS_WEBHOOK_SECRET", value = module.common_secrets.value.sns_webhook_secret + }, + { + # Both the workflows' worker and whoever starts one (the admin) need it. + name = "RESONATE_URL", + value = "http://${var.server_ip}:8001" } ] } diff --git a/infrastructure/applications/resonate/task.tf b/infrastructure/applications/resonate/task.tf new file mode 100644 index 0000000000..f20fabae2e --- /dev/null +++ b/infrastructure/applications/resonate/task.tf @@ -0,0 +1,86 @@ +locals { + is_prod = terraform.workspace == "production" + + # Resonate keeps its promises on the database instance the backend already + # uses, in a database of its own. The instance is private, so that database + # is not created from here: it has to exist before this runs. + database = "resonate" + + database_url = "postgres://${var.database_settings.username}:${var.database_settings.password}@${var.database_settings.address}:${var.database_settings.port}/${local.database}?sslmode=require" +} + +resource "aws_ecs_task_definition" "resonate" { + family = "pythonit-${terraform.workspace}-resonate" + + container_definitions = jsonencode([ + { + name = "resonate" + image = "resonatehqio/resonate:v0.9.8" + memoryReservation = local.is_prod ? 200 : 10 + essential = true + + environment = [ + { + name = "RESONATE_SERVER__BIND" + value = "0.0.0.0" + }, + { + name = "RESONATE_STORAGE__TYPE" + value = "postgres" + }, + { + name = "RESONATE_STORAGE__POSTGRES__URL" + value = local.database_url + } + ] + + portMappings = [ + { + containerPort = 8001 + hostPort = 8001 + name = "resonate" + } + ] + + mountPoints = [] + systemControls = [] + + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = var.logs_group_name + "awslogs-region" = "eu-central-1" + "awslogs-stream-prefix" = "resonate" + } + } + + healthCheck = { + retries = 10 + command = [ + "CMD-SHELL", + "wget -qO- http://127.0.0.1:8001/health || exit 1" + ] + timeout = 3 + interval = 10 + # The server spends up to 30 seconds waiting on the database pool + # before it gives up, and creates its schema on first boot, so it + # needs more room than the default none before checks start counting. + startPeriod = 60 + } + + stopTimeout = 300 + } + ]) + + requires_compatibilities = [] + tags = {} +} + +resource "aws_ecs_service" "resonate" { + name = "resonate" + cluster = var.cluster_id + task_definition = aws_ecs_task_definition.resonate.arn + desired_count = 1 + deployment_minimum_healthy_percent = 0 + deployment_maximum_percent = 100 +} diff --git a/infrastructure/applications/resonate/variables.tf b/infrastructure/applications/resonate/variables.tf new file mode 100644 index 0000000000..0d0ce653d4 --- /dev/null +++ b/infrastructure/applications/resonate/variables.tf @@ -0,0 +1,3 @@ +variable "cluster_id" {} +variable "logs_group_name" {} +variable "database_settings" {}