diff --git a/grafana/non-prod/terraform/.gitignore b/grafana/non-prod/terraform/.gitignore new file mode 100644 index 000000000..ad9e03dcc --- /dev/null +++ b/grafana/non-prod/terraform/.gitignore @@ -0,0 +1,7 @@ +# Compiled files +*.tfstate +*.tfstate.backup +.terraform.tfstate.lock.info + +# Module directory +.terraform/ \ No newline at end of file diff --git a/grafana/non-prod/terraform/.terraform.lock.hcl b/grafana/non-prod/terraform/.terraform.lock.hcl new file mode 100644 index 000000000..397338b48 --- /dev/null +++ b/grafana/non-prod/terraform/.terraform.lock.hcl @@ -0,0 +1,74 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.91.0" + constraints = "~> 5.0" + hashes = [ + "h1:bZ3AM8Qd1veQoUJqOb7OVp/ElmLg/hkz3bHL2DZZ5Tk=", + "zh:03ee14261b25aee94c735ed6ef7cce47900ab7bdf462335432ca034d0ba74ca2", + "zh:32a3759049c9c2cd041d1257cf16cb90a5ce586e1d0a6fe5f8ebd0ec1ba8e071", + "zh:334db69bc6d8643ec4ea432f0e54e851c2394bbe889cca29ca5029db0e4699e8", + "zh:39957a4a900f100ea8d85845a42164a44c9efea8559a9e74ab4f6a1193e20c3e", + "zh:8831396c764815eb367601a522c51c2e9e8fc38bcaa5f5e83f21de771778e9ba", + "zh:8e71ab68c27f909892a063f845d92faa487297ad9bbc67c77a67194e509781e6", + "zh:944df1084a7ea37a4feea0ee6654fd15891ef4829c5453bc149ffbcc0ab9bad7", + "zh:964391527624f2e66a4eb387ad0a30a1b67a896e9395b6d01353f2572723ea03", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:bb956c660185161e8ce1ed1dbf187c9f549d1779673fe798211dd5b02b98c737", + "zh:c237199ca8cd88f4aab4c673f848c77670b90d98a484fef6bcd31a71ff63d9b9", + "zh:c7522f6072f8ba29f4a6d0f994eda8a381ed2f4a41dbe44c4d989c44852cfe63", + "zh:d412a852ced01433c44f222952b60974f7c297a8a21bef62c9a627b050084134", + "zh:e420266b772041fa89e5868594ed21c8c3090d76b3ec0262054f768a7807f5a7", + "zh:ecfcc7844e9e01123920a4b0e667a4688654e3f22f00890ec80ddd78e7312eda", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.3" + hashes = [ + "h1:I0Um8UkrMUb81Fxq/dxbr3HLP2cecTH2WMJiwKSrwQY=", + "zh:22d062e5278d872fe7aed834f5577ba0a5afe34a3bdac2b81f828d8d3e6706d2", + "zh:23dead00493ad863729495dc212fd6c29b8293e707b055ce5ba21ee453ce552d", + "zh:28299accf21763ca1ca144d8f660688d7c2ad0b105b7202554ca60b02a3856d3", + "zh:55c9e8a9ac25a7652df8c51a8a9a422bd67d784061b1de2dc9fe6c3cb4e77f2f", + "zh:756586535d11698a216291c06b9ed8a5cc6a4ec43eee1ee09ecd5c6a9e297ac1", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:9d5eea62fdb587eeb96a8c4d782459f4e6b73baeece4d04b4a40e44faaee9301", + "zh:a6355f596a3fb8fc85c2fb054ab14e722991533f87f928e7169a486462c74670", + "zh:b5a65a789cff4ada58a5baffc76cb9767dc26ec6b45c00d2ec8b1b027f6db4ed", + "zh:db5ab669cf11d0e9f81dc380a6fdfcac437aea3d69109c7aef1a5426639d2d65", + "zh:de655d251c470197bcbb5ac45d289595295acb8f829f6c781d4a75c8c8b7c7dd", + "zh:f5c68199f2e6076bce92a12230434782bf768103a427e9bb9abee99b116af7b5", + ] +} + +provider "registry.terraform.io/hashicorp/template" { + version = "2.2.0" + constraints = "~> 2.2.0" + hashes = [ + "h1:44Le5zVomtfOfKUsH+utlMXOcHwPe4CWyCi+0f8y0XQ=", + ] +} + +provider "registry.terraform.io/kreuzwerker/docker" { + version = "3.0.2" + constraints = "3.0.2" + hashes = [ + "h1:XjdpVL61KtTsuPE8swok3GY8A+Bu3TZs8T2DOEpyiXo=", + "zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f", + "zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95", + "zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138", + "zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da", + "zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2", + "zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06", + "zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1", + "zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710", + "zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005", + "zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d", + "zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792", + "zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387", + "zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62", + "zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d", + ] +} diff --git a/grafana/non-prod/terraform/all.tf b/grafana/non-prod/terraform/all.tf new file mode 100644 index 000000000..20dba833d --- /dev/null +++ b/grafana/non-prod/terraform/all.tf @@ -0,0 +1,551 @@ +# alb.tf + +resource "aws_alb" "main" { + name = "${var.prefix}-alb" + subnets = aws_subnet.grafana_public[*].id + security_groups = [aws_security_group.lb.id] +} + +resource "aws_alb_target_group" "app" { + name = "${var.prefix}-alb-tg" + port = 3000 + protocol = "HTTP" + vpc_id = aws_vpc.grafana_main.id + target_type = "ip" + + health_check { + healthy_threshold = 3 + interval = 30 + protocol = "HTTP" + matcher = "200" + timeout = 3 + path = "/api/health" # Grafana health check endpoint + unhealthy_threshold = 2 + } +} + +# Redirect all traffic from the ALB to the target group +resource "aws_alb_listener" "front_end" { + load_balancer_arn = aws_alb.main.id + port = var.app_port + protocol = "HTTP" + default_action { + target_group_arn = aws_alb_target_group.app.id + type = "forward" + } + tags = merge(var.tags, { + Name = "${var.prefix}-alb-listener" + }) +} +############################################################################################################ +# auto_scaling.tf + +resource "aws_appautoscaling_target" "target" { + service_namespace = "ecs" + resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.main.name}" + scalable_dimension = "ecs:service:DesiredCount" + role_arn = aws_iam_role.ecs_auto_scale_role.arn + min_capacity = 1 + max_capacity = 1 + tags = merge(var.tags, { + Name = "${var.prefix}-aas-tgt" + }) +} + +# Automatically scale capacity up by one +resource "aws_appautoscaling_policy" "up" { + name = "grafana_scale_up" + service_namespace = "ecs" + resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.main.name}" + scalable_dimension = "ecs:service:DesiredCount" + + step_scaling_policy_configuration { + adjustment_type = "ChangeInCapacity" + cooldown = 60 + metric_aggregation_type = "Maximum" + + step_adjustment { + metric_interval_lower_bound = 0 + scaling_adjustment = 1 + } + } + + depends_on = [aws_appautoscaling_target.target] + +} + +# Automatically scale capacity down by one +resource "aws_appautoscaling_policy" "down" { + name = "grafana_scale_down" + service_namespace = "ecs" + resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.main.name}" + scalable_dimension = "ecs:service:DesiredCount" + + step_scaling_policy_configuration { + adjustment_type = "ChangeInCapacity" + cooldown = 60 + metric_aggregation_type = "Maximum" + + step_adjustment { + metric_interval_lower_bound = 0 + scaling_adjustment = -1 + } + } + + depends_on = [aws_appautoscaling_target.target] +} +############################################################################################################ +# ecs.tf +# ecs.tf + +resource "aws_ecs_cluster" "main" { + name = "grafana-cluster" +} + +data "template_file" "grafana_app" { + template = file("${path.module}/templates/ecs/grafana_app.json.tpl") + + vars = { + app_image = local.app_image + app_port = var.app_port + fargate_cpu = var.fargate_cpu + fargate_memory = var.fargate_memory + aws_region = var.aws_region + log_group = var.log_group + health_check_path = var.health_check_path + } +} + +resource "aws_ecs_task_definition" "app" { + family = "grafana-app-task" + execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.fargate_cpu + memory = var.fargate_memory + container_definitions = data.template_file.grafana_app.rendered + tags = merge(var.tags, { + Name = "${var.prefix}-ecs-task" + }) + +} + + +resource "aws_ecs_service" "main" { + name = "${var.prefix}-ecs-svc" + cluster = aws_ecs_cluster.main.id + task_definition = aws_ecs_task_definition.app.arn + desired_count = var.app_count + launch_type = "FARGATE" + + network_configuration { + security_groups = [aws_security_group.ecs_tasks.id] + subnets = aws_subnet.grafana_private[*].id + assign_public_ip = true + } + + load_balancer { + target_group_arn = aws_alb_target_group.app.id + container_name = "grafana-app" + container_port = var.app_port + } +} +############################################################################################################ +# iam.tf +resource "aws_iam_policy" "route53resolver_policy" { + name = "${var.prefix}-route53resolver-policy" + description = "Policy to allow Route 53 Resolver DNS Firewall actions" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "route53resolver:ListFirewallRuleGroupAssociations", + "route53resolver:ListFirewallRuleGroups", + "route53resolver:ListFirewallRules" + ], + Resource = "*" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "route53resolver_policy_attachment" { + role = aws_iam_role.ecs_task_execution_role.name + policy_arn = aws_iam_policy.route53resolver_policy.arn +} + +## Task Execution Role (ecs_task_execution_role): +## This role is used by the ECS agent to pull container images from +## Amazon ECR, and to store and retrieve logs in Amazon CloudWatch. +## It grants permissions needed for ECS to start and manage tasks +resource "aws_iam_role" "ecs_task_execution_role" { + name = "${var.prefix}-ecs-task-execution-role" + + assume_role_policy = <