/projects/cost-anomaly-detection
Cost anomaly detection module
A reusable Terraform module that turns AWS Cost Anomaly Detection into Slack-and-email alerts teams actually see, without each account reinventing the wiring.
The problem
AWS Cost Anomaly Detection is useful only if someone notices. The default platform monitor sends a weekly email above a fixed threshold to the account address — easy to ignore, hard to route to the team that owns the spend, and not something you want every tribe to re-implement with slightly different SNS and Lambda glue.
I wanted a module you drop into a Terragrunt unit: point it at the existing Cost Explorer monitor, set percentage and absolute thresholds, and choose email or Slack. Same shape in every account.
What it does
The module creates an aws_ce_anomaly_subscription against a monitor ARN the
platform already provisions on account creation. Thresholds are both relative
and absolute — percentage alone fires on noise for small services; absolute
alone misses slow creep on large ones.
Delivery is either:
- Email, for daily/weekly digests (what Cost Explorer expects for those frequencies), or
- SNS → Slack, for immediate alerts, using a shared SNS module and the open-source notify-slack Lambda path.
Naming had to respect AWS length limits on subscription and topic names, so subscriber identifiers are truncated and sanitised rather than stuffed with full email local-parts.
Design decisions
Reuse the platform DefaultServiceMonitor. Creating a second monitor per team duplicates Cost Explorer state and drifts from what FinOps already configured. The module only owns the subscription and the notification path.
Thresholds as a pair. Both ANOMALY_TOTAL_IMPACT_PERCENTAGE and
ANOMALY_TOTAL_IMPACT_ABSOLUTE must match. That is stricter than either alone
and cuts false positives without making the module opinionated about a single
magic number.
Slack is SNS, not a custom webhook in the CE API. Cost Anomaly’s immediate path wants SNS. Keeping Slack behind SNS means email and Slack share the same topic lifecycle and KMS story.
What I’d do differently
The KMS key lookup for SNS encryption is region-keyed in the module. That works until a region is missing from the map. Preferring a data source or an explicit variable would have been cleaner than a hard-coded region table.
Status
Deployed across multiple accounts via Terragrunt units. Module lives in a versioned registry; consumers pass thresholds, tags, and Slack or email targets.
