diff --git a/services/terraform/modules/node_service/main.tf b/services/terraform/modules/node_service/main.tf new file mode 100644 --- /dev/null +++ b/services/terraform/modules/node_service/main.tf @@ -0,0 +1,199 @@ +locals { + environment_var_list = [ + for name, value in var.environment_vars : { + name = name + value = value + } + ] +} + +resource "aws_cloudwatch_log_group" "service" { + name = "/ecs/${var.service_name}-task-def" + retention_in_days = 7 +} + +resource "aws_ecs_task_definition" "service" { + network_mode = "awsvpc" + family = "${var.service_name}-task-def" + requires_compatibilities = ["FARGATE"] + task_role_arn = var.ecs_task_role_arn + execution_role_arn = var.ecs_task_execution_role_arn + cpu = var.cpu + memory = var.memory + + ephemeral_storage { + size_in_gib = var.ephemeral_storage + } + + container_definitions = jsonencode([ + { + name = var.container_name + image = var.image + essential = true + portMappings = [ + { + name = "${var.service_name}-port" + containerPort = 3000 + hostPort = 3000 + protocol = "tcp" + }, + ] + environment = local.environment_var_list + logConfiguration = { + "logDriver" = "awslogs" + "options" = { + "awslogs-create-group" = "true" + "awslogs-group" = aws_cloudwatch_log_group.service.name + "awslogs-stream-prefix" = "ecs" + "awslogs-region" = var.region + } + } + linuxParameters = { + initProcessEnabled = true + } + } + ]) + + runtime_platform { + cpu_architecture = "ARM64" + operating_system_family = "LINUX" + } +} + +resource "aws_security_group" "service" { + name = "${var.service_name}-service-ecs-sg" + vpc_id = var.vpc_id + + ingress { + from_port = 3000 + to_port = 3000 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "Allow inbound traffic from any IPv6 address" + from_port = 3000 + to_port = 3000 + protocol = "tcp" + ipv6_cidr_blocks = ["::/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_ecs_service" "service" { + name = var.service_name + cluster = var.cluster_id + task_definition = aws_ecs_task_definition.service.arn + launch_type = "FARGATE" + enable_execute_command = true + enable_ecs_managed_tags = true + force_new_deployment = true + desired_count = var.desired_count + deployment_maximum_percent = 200 + deployment_minimum_healthy_percent = 100 + + network_configuration { + subnets = var.vpc_subnets + security_groups = [aws_security_group.service.id] + assign_public_ip = true + } + + load_balancer { + target_group_arn = aws_lb_target_group.service.arn + container_name = var.container_name + container_port = 3000 + } + + deployment_circuit_breaker { + enable = true + rollback = true + } +} + +resource "aws_lb_target_group" "service" { + name = "${var.service_name}-ecs-tg" + port = 3000 + protocol = "HTTP" + vpc_id = var.vpc_id + + target_type = "ip" + + stickiness { + type = "lb_cookie" + cookie_duration = 86500 + enabled = true + } + + health_check { + enabled = true + healthy_threshold = 2 + unhealthy_threshold = 3 + + protocol = "HTTP" + path = "/health" + matcher = "200" + } +} + +resource "aws_lb" "service" { + load_balancer_type = "application" + name = "${var.service_name}-lb" + security_groups = [aws_security_group.lb_sg.id] + + internal = false + subnets = var.vpc_subnets +} + +resource "aws_lb_listener" "service" { + load_balancer_arn = aws_lb.service.arn + port = "443" + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = data.aws_acm_certificate.service.arn + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.service.arn + } + + lifecycle { + ignore_changes = [default_action[0].forward[0].stickiness[0].duration] + replace_triggered_by = [aws_lb_target_group.service] + } +} + +resource "aws_security_group" "lb_sg" { + name = "${var.service_name}-lb-sg" + description = "Security group for ${var.service_name} load balancer" + vpc_id = var.vpc_id + + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +data "aws_acm_certificate" "service" { + domain = var.domain_name + statuses = ["ISSUED"] +} diff --git a/services/terraform/modules/node_service/outputs.tf b/services/terraform/modules/node_service/outputs.tf new file mode 100644 --- /dev/null +++ b/services/terraform/modules/node_service/outputs.tf @@ -0,0 +1,3 @@ +output "service_load_balancer_dns_name" { + value = aws_lb.service.dns_name +} diff --git a/services/terraform/modules/node_service/variables.tf b/services/terraform/modules/node_service/variables.tf new file mode 100644 --- /dev/null +++ b/services/terraform/modules/node_service/variables.tf @@ -0,0 +1,85 @@ +# AWS Deployment Configuration Options + +variable "region" { + description = "The AWS region" + type = string +} + +variable "vpc_id" { + description = "The VPC ID" + type = string +} + +variable "vpc_subnets" { + description = "List of VPC subnet IDs" + type = list(string) +} + +variable "cluster_id" { + description = "id of ecs cluster" + type = string +} + +variable "ecs_task_role_arn" { + description = "The ARN of the ECS task role" + type = string +} + +variable "ecs_task_execution_role_arn" { + description = "The ARN of the ECS task execution role" + type = string +} + +# Service Options + +variable "service_name" { + description = "The name of the ECS service" + type = string +} + +variable "domain_name" { + description = "The domain name for the load balancer certificate" + type = string +} + +variable "container_name" { + description = "The name of the container" + type = string +} + + +variable "desired_count" { + description = "Desired number of running nodes" + type = number + # default 2 for constant uptime + default = 2 +} + +variable "image" { + description = "The Docker image for the container" + type = string +} + +variable "environment_vars" { + description = "Map of environment variables to be initialized in container" + type = map(string) +} + +# Task resources +variable "cpu" { + description = "CPU units allocated to each task" + type = number + default = 2048 +} + +variable "memory" { + description = "Memory allocated to each task in MiB" + type = number + default = 4096 +} + +variable "ephemeral_storage" { + description = "Ephemeral storage dedicated to task in GiB" + type = number + default = 40 +} diff --git a/services/terraform/self-host/outputs.tf b/services/terraform/self-host/outputs.tf --- a/services/terraform/self-host/outputs.tf +++ b/services/terraform/self-host/outputs.tf @@ -1,3 +1,7 @@ output "keyserver_service_load_balancer_dns_name" { value = aws_lb.keyserver_service.dns_name } + +output "webapp_service_load_balancer_dns_name" { + value = var.enable_webapp_service ? module.webapp_service[0].service_load_balancer_dns_name : "" +} diff --git a/services/terraform/self-host/variables.tf b/services/terraform/self-host/variables.tf --- a/services/terraform/self-host/variables.tf +++ b/services/terraform/self-host/variables.tf @@ -8,11 +8,6 @@ type = string } -variable "webapp_domain_name" { - description = "Domain name for your web app" - type = string -} - variable "region" { description = "Keyserver's AWS deployment region" type = string @@ -58,3 +53,17 @@ type = string default = null } + +# Web app + +variable "enable_webapp_service" { + description = "Whether to run webapp on AWS" + type = bool + default = false +} + +variable "webapp_domain_name" { + description = "Domain name for your web app" + type = string + default = "" +} diff --git a/services/terraform/self-host/webapp.tf b/services/terraform/self-host/webapp.tf --- a/services/terraform/self-host/webapp.tf +++ b/services/terraform/self-host/webapp.tf @@ -12,178 +12,21 @@ "COMM_NODE_ROLE" = "webapp", "COMM_JSONCONFIG_facts_run_server_config" = local.webapp_run_server_config }) - - webapp_environment = [ - for name, value in local.webapp_environment_vars : { - name = name - value = value - } - ] -} - -resource "aws_cloudwatch_log_group" "webapp_service" { - name = "/ecs/webapp-task-def" - retention_in_days = 7 -} - -resource "aws_ecs_task_definition" "webapp_service" { - network_mode = "awsvpc" - family = "webapp-task-def" - requires_compatibilities = ["FARGATE"] - task_role_arn = aws_iam_role.ecs_task_role.arn - execution_role_arn = aws_iam_role.ecs_task_execution.arn - cpu = "2048" - memory = "4096" - - ephemeral_storage { - size_in_gib = 40 - } - - container_definitions = jsonencode([ - { - name = local.webapp_container_name - image = local.keyserver_service_server_image - essential = true - portMappings = [ - { - name = "webapp-port" - containerPort = 3000 - hostPort = 3000, - protocol = "tcp" - }, - - ] - environment = local.webapp_environment - logConfiguration = { - "logDriver" = "awslogs" - "options" = { - "awslogs-create-group" = "true" - "awslogs-group" = aws_cloudwatch_log_group.webapp_service.name - "awslogs-stream-prefix" = "ecs" - "awslogs-region" = "${var.region}" - } - } - linuxParameters = { - initProcessEnabled = true - } - } - ]) - - runtime_platform { - cpu_architecture = "ARM64" - operating_system_family = "LINUX" - } - - skip_destroy = false -} - -resource "aws_ecs_service" "webapp_service" { - depends_on = [null_resource.create_comm_database] - - name = "webapp-service" - cluster = aws_ecs_cluster.keyserver_cluster.id - task_definition = aws_ecs_task_definition.webapp_service.arn - launch_type = "FARGATE" - enable_execute_command = true - enable_ecs_managed_tags = true - force_new_deployment = true - desired_count = 2 - deployment_maximum_percent = 200 - deployment_minimum_healthy_percent = 100 - - - network_configuration { - subnets = local.vpc_subnets - security_groups = [aws_security_group.keyserver_service.id] - assign_public_ip = true - } - - load_balancer { - target_group_arn = aws_lb_target_group.webapp_service.arn - container_name = local.webapp_container_name - container_port = 3000 - } - - deployment_circuit_breaker { - enable = true - rollback = true - } -} - -resource "aws_lb_target_group" "webapp_service" { - name = "webapp-service-ecs-tg" - port = 3000 - protocol = "HTTP" - vpc_id = local.vpc_id - - # "awsvpc" network mode requires target type set to ip - target_type = "ip" - - stickiness { - type = "lb_cookie" - cookie_duration = 86500 - enabled = true - } - - health_check { - enabled = true - healthy_threshold = 2 - unhealthy_threshold = 3 - - protocol = "HTTP" - path = "/health" - matcher = "200-299" - } -} - -resource "aws_lb" "webapp_service" { - load_balancer_type = "application" - name = "webapp-service-lb" - security_groups = [aws_security_group.webapp_lb_sg.id] - - internal = false - subnets = local.vpc_subnets -} - -resource "aws_lb_listener" "webapp_service" { - load_balancer_arn = aws_lb.webapp_service.arn - port = "443" - protocol = "HTTPS" - ssl_policy = "ELBSecurityPolicy-2016-08" - certificate_arn = data.aws_acm_certificate.webapp_service.arn - - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.webapp_service.arn - } - - lifecycle { - ignore_changes = [default_action[0].forward[0].stickiness[0].duration] - replace_triggered_by = [aws_lb_target_group.webapp_service] - } -} - -resource "aws_security_group" "webapp_lb_sg" { - name = "web-lb-sg" - description = "Security group for webapp load balancer" - vpc_id = local.vpc_id - - ingress { - from_port = 443 - to_port = 443 - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - } - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } } -data "aws_acm_certificate" "webapp_service" { - domain = var.webapp_domain_name - statuses = ["ISSUED"] +module "webapp_service" { + source = "../modules/node_service" + count = var.enable_webapp_service ? 1 : 0 + + container_name = "webapp" + image = local.keyserver_service_server_image + service_name = "webapp" + cluster_id = aws_ecs_cluster.keyserver_cluster.id + domain_name = var.webapp_domain_name + vpc_id = local.vpc_id + vpc_subnets = local.vpc_subnets + region = var.region + environment_vars = local.webapp_environment_vars + ecs_task_role_arn = aws_iam_role.ecs_task_role.arn + ecs_task_execution_role_arn = aws_iam_role.ecs_task_execution.arn }