Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
16 changes: 12 additions & 4 deletions terraform/fastly-exporter/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
# Prometheus Exporter for Fastly

This module deploys a Prometheus exporter for Fastly using the official
[fastly/fastly-exporter] Docker image. The implementation uses the [`ecs-task`]
and [`ecs-service`] modules to deploy the exporter to ECS.
[fastly/fastly-exporter] Docker image.

## ECS Express Migration

**Note**: This service has been migrated to **AWS ECS Express** (November 2025),
which simplifies deployment by automatically managing load balancing, networking,
and scaling infrastructure. See [MIGRATION_TO_ECS_EXPRESS.md](MIGRATION_TO_ECS_EXPRESS.md)
for details about the migration.

The implementation now uses `aws_ecs_express_gateway_service` instead of the
traditional `ecs-task` and `ecs-service` modules, reducing complexity while
maintaining the same functionality.

[`ecs-service`]: ../../terragrunt/modules/ecs-service
[`ecs-task`]: ../../terragrunt/modules/ecs-task
[fastly/fastly-exporter]: https://github.com/fastly/fastly-exporter
6 changes: 5 additions & 1 deletion terraform/fastly-exporter/_terraform.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.64"
version = "~> 5.82"
}
time = {
source = "hashicorp/time"
version = "~> 0.12"
}
}

Expand Down
182 changes: 113 additions & 69 deletions terraform/fastly-exporter/main.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# ECS Express migration for fastly-exporter
# This simplifies the configuration by using aws_ecs_express_gateway_service
# which automatically manages load balancing, networking, and scaling.

locals {
name = "fastly-exporter"
}
Expand All @@ -6,104 +10,144 @@ data "aws_ssm_parameter" "fastly_api_token" {
name = "/prod/fastly-exporter/fastly/api-token"
}

resource "aws_iam_policy" "read_fastly_api_token" {
name = "ecs--${local.name}"
# CloudWatch log group for the service
resource "aws_cloudwatch_log_group" "fastly_exporter" {
name = "/ecs/${local.name}"
retention_in_days = 7
}

# IAM role for ECS task execution (pulling images, writing logs, accessing secrets)
resource "aws_iam_role" "execution" {
name = "ecs-express-execution--${local.name}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}

resource "aws_iam_role_policy" "execution" {
role = aws_iam_role.execution.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowReadingFastlyApiToken"
Sid = "AllowParameterStore"
Effect = "Allow"
Action = "ssm:GetParameters"
Resource = data.aws_ssm_parameter.fastly_api_token.arn
},
{
Sid = "AllowLogs"
Effect = "Allow"
Action = [
"logs:PutLogEvents",
"logs:CreateLogStream"
]
Resource = "${aws_cloudwatch_log_group.fastly_exporter.arn}:*"
},
{
Sid = "ECRAuthentication"
Effect = "Allow"
Action = "ecr:GetAuthorizationToken"
Resource = "*"
}
]
})
}

resource "aws_iam_role_policy_attachment" "read_fastly_api_token" {
role = module.ecs_task.execution_role_name
policy_arn = aws_iam_policy.read_fastly_api_token.arn
# IAM role for ECS Express infrastructure management (ALB, target groups, scaling)
resource "aws_iam_role" "infrastructure" {
name = "ecs-express-infrastructure--${local.name}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ecs.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}

module "ecs_task" {
source = "../shared/modules/ecs-task"

name = local.name
cpu = 256
memory = 512
resource "aws_iam_role_policy_attachment" "infrastructure" {
role = aws_iam_role.infrastructure.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVolumes"
}

log_retention_days = 7
ecr_repositories_arns = [
# This repository does not exist, since we're pulling the imge directly from GitHub.
# But the task module cannot be applied without providing at least one ARN here.
"arn:aws:ecr:us-west-1:890664054962:repository/fastly-exporter"
# Wait for IAM roles to propagate
resource "time_sleep" "wait_for_iam" {
depends_on = [
aws_iam_role_policy.execution,
aws_iam_role_policy_attachment.infrastructure
]

containers = <<EOF
[
{
"name": "${local.name}",
"image": "ghcr.io/fastly/fastly-exporter:v7.4.0",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"hostPort": 8080
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/${local.name}",
"awslogs-region": "us-west-1",
"awslogs-stream-prefix": "${local.name}"
}
},
"environment": [],
"secrets": [
{
"name": "FASTLY_API_TOKEN",
"valueFrom": "${data.aws_ssm_parameter.fastly_api_token.arn}"
}
]
}
]
EOF
create_duration = "10s"
}

# Security group for the service
resource "aws_security_group" "fastly_exporter" {
name = "fastly-exporter"
description = "Allow Prometheus to scrape the fastly-exporter"
name = "ecs-express-fastly-exporter"
description = "Allow Prometheus to scrape the fastly-exporter via ECS Express"
vpc_id = data.terraform_remote_state.shared.outputs.ecs_cluster_config.vpc_id

ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
# Elastic IP of the monitoring server
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["52.9.166.219/32"]
# Load balancer security group, required for health checks
security_groups = ["sg-0ddcb954525a46b70"]
description = "Elastic IP of the monitoring server"
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
}
}

module "ecs_service" {
source = "../shared/modules/ecs-service"
# ECS Express Gateway Service
resource "aws_ecs_express_gateway_service" "fastly_exporter" {
service_name = local.name
execution_role_arn = aws_iam_role.execution.arn
infrastructure_role_arn = aws_iam_role.infrastructure.arn

cluster_config = data.terraform_remote_state.shared.outputs.ecs_cluster_config
platform_version = "1.4.0"
primary_container {
image = "ghcr.io/fastly/fastly-exporter:v7.4.0"
container_port = 8080

name = local.name
task_arn = module.ecs_task.arn
tasks_count = 1
aws_logs_configuration {
log_group = aws_cloudwatch_log_group.fastly_exporter.name
}

http_container = local.name
http_port = 8080
secrets {
name = "FASTLY_API_TOKEN"
value_from = data.aws_ssm_parameter.fastly_api_token.arn
}
}

domains = ["fastly-exporter.infra.rust-lang.org"]
network_configuration {
subnets = data.terraform_remote_state.shared.outputs.ecs_cluster_config.subnet_ids
security_groups = [aws_security_group.fastly_exporter.id]
}

additional_security_group_ids = [
aws_security_group.fastly_exporter.id,
]
scaling_target {
min_task_count = 1
max_task_count = 2
auto_scaling_metric = "CPU"
auto_scaling_target_value = 70
}

depends_on = [time_sleep.wait_for_iam]
}