Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions infrastructure/live/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ No providers.
| <a name="module_frontend_build_cache"></a> [frontend\_build\_cache](#module\_frontend\_build\_cache) | ../modules/ecr-cache | n/a |
| <a name="module_kms"></a> [kms](#module\_kms) | ../modules/kms | n/a |
| <a name="module_networking"></a> [networking](#module\_networking) | ../modules/networking | n/a |
| <a name="module_observability"></a> [observability](#module\_observability) | ../modules/observability | n/a |
| <a name="module_parameters"></a> [parameters](#module\_parameters) | ../modules/parameters | n/a |
| <a name="module_security"></a> [security](#module\_security) | ../modules/security | n/a |
| <a name="module_storage"></a> [storage](#module\_storage) | ../modules/storage | n/a |
Expand Down Expand Up @@ -101,6 +102,7 @@ No resources.
| <a name="input_enable_additional_parameters"></a> [enable\_additional\_parameters](#input\_enable\_additional\_parameters) | Whether to enable additional parameters (e.g. for production). | `bool` | `false` | no |
| <a name="input_enable_cron_tasks"></a> [enable\_cron\_tasks](#input\_enable\_cron\_tasks) | Whether to enable scheduled cron tasks. | `bool` | n/a | yes |
| <a name="input_enable_nat_gateway"></a> [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Whether to enable a NAT Gateway. | `bool` | `true` | no |
| <a name="input_enable_observability"></a> [enable\_observability](#input\_enable\_observability) | Whether to create the observability stack (VictoriaMetrics). | `bool` | `false` | no |
| <a name="input_enable_rds_proxy"></a> [enable\_rds\_proxy](#input\_enable\_rds\_proxy) | Whether to create an RDS proxy. | `bool` | `false` | no |
| <a name="input_enable_vpc_cloudwatch_logs_endpoint"></a> [enable\_vpc\_cloudwatch\_logs\_endpoint](#input\_enable\_vpc\_cloudwatch\_logs\_endpoint) | Whether to create CloudWatch Logs VPC endpoint. | `bool` | `false` | no |
| <a name="input_enable_vpc_ecr_api_endpoint"></a> [enable\_vpc\_ecr\_api\_endpoint](#input\_enable\_vpc\_ecr\_api\_endpoint) | Whether to create ECR API VPC endpoint. | `bool` | `false` | no |
Expand All @@ -116,6 +118,7 @@ No resources.
| <a name="input_frontend_max_count"></a> [frontend\_max\_count](#input\_frontend\_max\_count) | The maximum number of tasks for auto scaling. | `number` | `6` | no |
| <a name="input_frontend_min_count"></a> [frontend\_min\_count](#input\_frontend\_min\_count) | The minimum number of tasks for auto scaling. | `number` | `2` | no |
| <a name="input_frontend_use_fargate_spot"></a> [frontend\_use\_fargate\_spot](#input\_frontend\_use\_fargate\_spot) | Whether to use Fargate Spot for frontend tasks. | `bool` | `true` | no |
| <a name="input_observability_vm_image"></a> [observability\_vm\_image](#input\_observability\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | `"victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70"` | no |
| <a name="input_private_subnet_cidrs"></a> [private\_subnet\_cidrs](#input\_private\_subnet\_cidrs) | A list of CIDR blocks for the private subnets. | `list(string)` | <pre>[<br/> "10.0.11.0/24",<br/> "10.0.12.0/24",<br/> "10.0.13.0/24"<br/>]</pre> | no |
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | The name of the project. | `string` | `"nest"` | no |
| <a name="input_public_subnet_cidrs"></a> [public\_subnet\_cidrs](#input\_public\_subnet\_cidrs) | A list of CIDR blocks for the public subnets. | `list(string)` | <pre>[<br/> "10.0.1.0/24",<br/> "10.0.2.0/24",<br/> "10.0.3.0/24"<br/>]</pre> | no |
Expand Down
20 changes: 20 additions & 0 deletions infrastructure/live/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,23 @@ module "tasks" {
subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids
use_fargate_spot = var.tasks_use_fargate_spot
}

module "observability" {
Comment thread
hassaansaleem28 marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets move this module in alphabetical order.

count = var.enable_observability ? 1 : 0
Comment thread
hassaansaleem28 marked this conversation as resolved.
Outdated
source = "../modules/observability"
Comment thread
hassaansaleem28 marked this conversation as resolved.
Outdated

app_security_group_ids = [
module.security.backend_sg_id,
module.security.frontend_sg_id,
module.security.tasks_sg_id,
]
assign_public_ip = local.assign_public_ip
aws_region = var.aws_region
common_tags = local.common_tags
environment = var.environment
kms_key_arn = module.kms.key_arn
project_name = var.project_name
subnet_ids = var.enable_nat_gateway ? module.networking.private_subnet_ids : module.networking.public_subnet_ids
vm_image = var.observability_vm_image
vpc_id = module.networking.vpc_id
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
12 changes: 12 additions & 0 deletions infrastructure/live/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ variable "enable_nat_gateway" {
default = true
}

variable "enable_observability" {
description = "Whether to create the observability stack (VictoriaMetrics)."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets keep this description vendor agnostic.

Comment thread
hassaansaleem28 marked this conversation as resolved.
Outdated
type = bool
default = false
}

variable "enable_rds_proxy" {
description = "Whether to create an RDS proxy."
type = bool
Expand Down Expand Up @@ -285,6 +291,12 @@ variable "frontend_use_fargate_spot" {
default = true
}

variable "observability_vm_image" {
description = "The VictoriaMetrics container image (including digest)."
type = string
default = "victoriametrics/victoria-metrics:v1.145.0@sha256:c014fb5a711d38cb24fd0673197592cd1394bb903dbb16aea565620c9c8a3d70"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are we tracking this image with dependabot?
If possible, can we move it to a Dockerfile and use dependabot to track it? I'm not sure if you can parse the Dockerfile, pseudocode : default = $$(grep -E '^FROM victoriametrics/victoria-metrics:' docker/victoriametrics/Dockerfile | sed 's/^FROM //')

Also, we must make sure this image + digest is the same as the local setup, ideally single source of truth for both.

See example --

$$(grep -E '^FROM semgrep/semgrep:' docker/semgrep/Dockerfile | sed 's/^FROM //') \

}

variable "private_subnet_cidrs" {
description = "A list of CIDR blocks for the private subnets."
type = list(string)
Expand Down
66 changes: 66 additions & 0 deletions infrastructure/modules/observability/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
| ---- | ------- |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.15.0 |

## Providers

| Name | Version |
| ---- | ------- |
| <a name="provider_aws"></a> [aws](#provider\_aws) | n/a |

## Modules

No modules.

## Resources

| Name | Type |
| ---- | ---- |
| [aws_cloudwatch_log_group.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
| [aws_ecs_cluster.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource |
| [aws_ecs_cluster_capacity_providers.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource |
| [aws_ecs_service.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
| [aws_ecs_task_definition.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
| [aws_efs_file_system.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource |
| [aws_efs_mount_target.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource |
| [aws_iam_policy.ecs_task_execution_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.ecs_task_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.ecs_task_execution_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_security_group.efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws_security_group.vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws_security_group_rule.efs_from_vm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
| [aws_security_group_rule.vm_egress_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
| [aws_security_group_rule.vm_ingest_from_apps](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
| [aws_security_group_rule.vm_to_efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |

## Inputs

| Name | Description | Type | Default | Required |
| ---- | ----------- | ---- | ------- | :------: |
| <a name="input_app_security_group_ids"></a> [app\_security\_group\_ids](#input\_app\_security\_group\_ids) | Security group IDs of the application tasks allowed to send metrics to VictoriaMetrics. | `list(string)` | n/a | yes |
| <a name="input_assign_public_ip"></a> [assign\_public\_ip](#input\_assign\_public\_ip) | Whether to assign a public IP to the VictoriaMetrics task. | `bool` | `false` | no |
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | The AWS region where the module is deployed. | `string` | n/a | yes |
| <a name="input_common_tags"></a> [common\_tags](#input\_common\_tags) | A map of common tags to apply to all resources. | `map(string)` | `{}` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | The environment (e.g., staging, production). | `string` | n/a | yes |
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the EFS file system. | `string` | n/a | yes |
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The number of days to retain VictoriaMetrics container logs. | `number` | `90` | no |
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | The name of the project. | `string` | n/a | yes |
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | The private subnet IDs for the EFS mount targets and the VictoriaMetrics task. | `list(string)` | n/a | yes |
Comment thread
hassaansaleem28 marked this conversation as resolved.
| <a name="input_vm_cpu"></a> [vm\_cpu](#input\_vm\_cpu) | The CPU units for the VictoriaMetrics Fargate task. | `number` | `512` | no |
| <a name="input_vm_image"></a> [vm\_image](#input\_vm\_image) | The VictoriaMetrics container image (including digest). | `string` | n/a | yes |
| <a name="input_vm_memory"></a> [vm\_memory](#input\_vm\_memory) | The memory (in MiB) for the VictoriaMetrics Fargate task. | `number` | `1024` | no |
| <a name="input_vm_port"></a> [vm\_port](#input\_vm\_port) | The port VictoriaMetrics listens on for ingest and queries. | `number` | `8428` | no |
| <a name="input_vm_retention_period"></a> [vm\_retention\_period](#input\_vm\_retention\_period) | The VictoriaMetrics data retention period (e.g., 12, 5y). | `string` | `"12"` | no |
| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | The VPC ID where the VictoriaMetrics security group is created. | `string` | n/a | yes |

## Outputs

| Name | Description |
| ---- | ----------- |
| <a name="output_efs_file_system_id"></a> [efs\_file\_system\_id](#output\_efs\_file\_system\_id) | The ID of the EFS file system backing VictoriaMetrics storage. |
| <a name="output_vm_cluster_name"></a> [vm\_cluster\_name](#output\_vm\_cluster\_name) | The name of the ECS cluster running VictoriaMetrics. |
| <a name="output_vm_security_group_id"></a> [vm\_security\_group\_id](#output\_vm\_security\_group\_id) | The ID of the VictoriaMetrics security group. |
<!-- END_TF_DOCS -->
258 changes: 258 additions & 0 deletions infrastructure/modules/observability/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
terraform {
Comment thread
hassaansaleem28 marked this conversation as resolved.
required_version = "~> 1.15.0"

required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
Comment on lines +4 to +10

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs a version field, see this PR for more info -- #5336


locals {
name_prefix = "${var.project_name}-${var.environment}-observability"

vm_container_definition = {
command = [
"-storageDataPath=/data",
"-retentionPeriod=${var.vm_retention_period}",
"-httpListenAddr=:${var.vm_port}",
]
essential = true
healthCheck = {
command = ["CMD-SHELL", "wget --spider -q http://localhost:${var.vm_port}/health || exit 1"]
interval = 30
retries = 3
startPeriod = 30
timeout = 5
}
image = var.vm_image
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.vm.name
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "ecs"
}
}
mountPoints = [
{
containerPath = "/data"
readOnly = false
sourceVolume = "vm-data"
}
]
name = "victoriametrics"
portMappings = [
{
containerPort = var.vm_port
hostPort = var.vm_port
protocol = "tcp"
}
]
}
}

resource "aws_security_group" "vm" {
description = "Security group for the VictoriaMetrics task"
name = "${local.name_prefix}-vm-sg"
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-vm-sg"
})
vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "vm_ingest_from_apps" {
Comment thread
hassaansaleem28 marked this conversation as resolved.
for_each = toset(var.app_security_group_ids)
Comment thread
hassaansaleem28 marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this breaks on an empty state apply. This is localstack logs but same will happen in prod/staging:

Plan: 21 to add, 0 to change, 0 to destroy.
╷
│ Error: Invalid for_each argument
│
│   on ../../modules/observability/main.tf line 67, in resource "aws_security_group_rule" "vm_ingest_from_apps":
│   67:   for_each = toset(var.app_security_group_ids)
│     ├────────────────
│     │ var.app_security_group_ids is list of string with 3 elements
│
│ The "for_each" set includes values derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the
│ instances of this resource.
│
│ When working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your configuration and where only the values contain apply-time
│ results.
│
│ Alternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge.
╵
╷
│ Error: Invalid for_each argument
│
│   on ../../modules/observability/main.tf line 130, in resource "aws_efs_mount_target" "vm":
│  130:   for_each = toset(var.subnet_ids)
│     ├────────────────
│     │ var.subnet_ids is list of string with 2 elements
│
│ The "for_each" set includes values derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the
│ instances of this resource.
│
│ When working with unknown values in for_each, it's better to use a map value where the keys are defined statically in your configuration and where only the values contain apply-time
│ results.
│
│ Alternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and then apply a second time to fully converge.
╵
make: *** [infrastructure-apply-observability] Error 1

It is because for_each needs to know its keys at plan time. But since we're passing the values from resources that haven't been created yet, those values are "known after apply". Since length of list is known at plan time, you should use count = length(var.app_security_group_ids) instead.

Example:

count = length(var.private_subnet_ids)


description = "Allow metrics ingest and queries from application tasks"
from_port = var.vm_port
protocol = "tcp"
security_group_id = aws_security_group.vm.id
source_security_group_id = each.value
to_port = var.vm_port
type = "ingress"
}

resource "aws_security_group_rule" "vm_egress_https" {
cidr_blocks = ["0.0.0.0/0"]
description = "Allow HTTPS egress for container image pulls"
from_port = 443
protocol = "tcp"
security_group_id = aws_security_group.vm.id
to_port = 443
type = "egress"
}
Comment thread
hassaansaleem28 marked this conversation as resolved.

resource "aws_security_group_rule" "vm_to_efs" {
description = "Allow NFS to the observability EFS"
from_port = 2049
protocol = "tcp"
security_group_id = aws_security_group.vm.id
source_security_group_id = aws_security_group.efs.id
to_port = 2049
type = "egress"
}

resource "aws_security_group" "efs" {
description = "Security group for the observability EFS file system"
name = "${local.name_prefix}-efs-sg"
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-efs-sg"
})
vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "efs_from_vm" {
Comment thread
hassaansaleem28 marked this conversation as resolved.
description = "Allow NFS from the VictoriaMetrics task"
from_port = 2049
protocol = "tcp"
security_group_id = aws_security_group.efs.id
source_security_group_id = aws_security_group.vm.id
to_port = 2049
type = "ingress"
}

resource "aws_efs_file_system" "vm" {
encrypted = true
kms_key_id = var.kms_key_arn
Comment thread
hassaansaleem28 marked this conversation as resolved.
Comment thread
hassaansaleem28 marked this conversation as resolved.
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-vm"
})

lifecycle {
prevent_destroy = true
}
}

resource "aws_efs_mount_target" "vm" {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
for_each = toset(var.subnet_ids)

file_system_id = aws_efs_file_system.vm.id
security_groups = [aws_security_group.efs.id]
subnet_id = each.value
}

resource "aws_cloudwatch_log_group" "vm" {
kms_key_id = var.kms_key_arn
name = "/aws/ecs/${local.name_prefix}"
retention_in_days = var.log_retention_in_days
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-logs"
})
}

resource "aws_ecs_cluster" "vm" {
name = "${local.name_prefix}-cluster"
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-cluster"
})

setting {
name = "containerInsights"
value = "enabled"
}
}

resource "aws_ecs_cluster_capacity_providers" "vm" {
capacity_providers = ["FARGATE"]
cluster_name = aws_ecs_cluster.vm.name

default_capacity_provider_strategy {
base = 0
capacity_provider = "FARGATE"
weight = 1
}
}

resource "aws_iam_role" "ecs_task_execution_role" {
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
name = "${local.name_prefix}-execution-role"
tags = var.common_tags
}

resource "aws_iam_policy" "ecs_task_execution_policy" {
description = "Policy for the VictoriaMetrics ECS task execution - CloudWatch Logs access."
name = "${local.name_prefix}-execution-policy"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "${aws_cloudwatch_log_group.vm.arn}:*"
}
]
})
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution_policy_attachment" {
policy_arn = aws_iam_policy.ecs_task_execution_policy.arn
role = aws_iam_role.ecs_task_execution_role.name
}

resource "aws_ecs_task_definition" "vm" {
container_definitions = jsonencode([local.vm_container_definition])
cpu = var.vm_cpu
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
family = local.name_prefix
memory = var.vm_memory
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
runtime_platform {
cpu_architecture = "ARM64"
operating_system_family = "LINUX"
}
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-task-def"
})

volume {
name = "vm-data"

efs_volume_configuration {
Comment thread
hassaansaleem28 marked this conversation as resolved.
file_system_id = aws_efs_file_system.vm.id
transit_encryption = "ENABLED"
}
}
Comment thread
hassaansaleem28 marked this conversation as resolved.
}

resource "aws_ecs_service" "vm" {
cluster = aws_ecs_cluster.vm.id
deployment_maximum_percent = 100
deployment_minimum_healthy_percent = 0
desired_count = 1
name = "${local.name_prefix}-service"
tags = merge(var.common_tags, {
Name = "${local.name_prefix}-service"
})
task_definition = aws_ecs_task_definition.vm.arn

capacity_provider_strategy {
base = 0
capacity_provider = "FARGATE"
weight = 1
}

network_configuration {
assign_public_ip = var.assign_public_ip
security_groups = [aws_security_group.vm.id]
subnets = var.subnet_ids
}

depends_on = [aws_efs_mount_target.vm]
}
Comment thread
hassaansaleem28 marked this conversation as resolved.
Loading
Loading