Approach (summary)- Create a reusable Terraform module that selects instance sizes by environment, validates allowed classes, and exposes a metadata-only output used by Infracost to estimate monthly cost during plan. Enforce runtime guards with variable validation + policy-as-code (Conftest/Sentinel) and CI gates for cost thresholds and exception approvals.Terraform module pattern (key files + snippets)1) variables.tf — env + requested size with validationhcl
variable "env" {
type = string
default = "dev"
validation {
condition = contains(["dev","staging","prod"], var.env)
error_message = "env must be dev, staging, or prod."
}
}
variable "requested_instance_type" {
type = string
validation {
condition = (
var.env == "dev" ? can(regex("^t[2-3]|t4g", var.requested_instance_type)) : true
)
error_message = "Dev only allows burstable (t2/t3/t4g) instance classes."
}
}
2) locals.tf — normalize defaults per envhcl
locals {
default_instance_by_env = {
dev = "t3.micro"
staging = "t3.medium"
prod = "m5.large"
}
instance_type = coalesce(var.requested_instance_type, local.default_instance_by_env[var.env])
}
3) resource use (example AWS)hcl
resource "aws_instance" "app" {
ami = var.ami
instance_type = local.instance_type
tags = merge(var.tags, { Environment = var.env })
}
4) Output metadata for cost toolinghcl
output "infracost_resources" {
value = [{
name = "aws_instance.app"
instance_type = local.instance_type
region = var.region
quantity = 1
}]
sensitive = false
}
Policy-as-code (Conftest/OPA) example to block expensive classes in devrego
package terraform.iac
deny[msg] {
input.resource_changes[_].type == "aws_instance"
input.resource_changes[_].change.after.tags.Environment == "dev"
startswith(input.resource_changes[_].change.after.instance_type, "m5")
msg = sprintf("Disallowed instance class %v in dev", [input.resource_changes[_].change.after.instance_type])
}
CI integration (Infracost + policy):- Run terraform init/plan in CI.- Use Infracost CLI to generate cost estimate from plan and module outputs; post diff as PR comment.- Fail pipeline if monthly cost diff > configured threshold OR if policy denies resources.Example GitHub Actions steps (outline)yaml
- uses: actions/checkout@v4
- uses: infracost/infracost-action@v2
with:
command: comment --path plan.out --threshold 50
- run: terraform plan -out=plan.out
- run: infracost breakdown --path plan.out --format json --out-file infracost.json
- run: conftest test plan.json
Safeguards & exception workflow- Explicit allowlist: maintain a small exceptions file (YAML) with ticket/owner/expiry; Conftest checks exceptions.- Short-lived overrides: require PR label like "cost-exception" + an approved governance sign-off (GitHub CODEOWNERS or required reviewers).- Audit logging: tag resources with approval ticket ID and automatically expire overrides after a date; CI rejects expired exceptions.- Monitoring: feed Infracost reports to Slack/Cost dashboard; create alerts for drifting monthly spend.Why this works- Variable validation + locals provide sensible defaults and immediate feedback to devs.- Policy-as-code enforces rules centrally and is testable.- Infracost in CI gives actionable, approximate monthly cost before apply.- Exception workflow balances agility with governance via short-lived, auditable approvals.Edge cases & notes- Infracost approximates public price lists; for exact billing, integrate cloud billing APIs post-deploy.- Ensure IAM keys used by CI have read access for pricing APIs if needed.- Keep cost thresholds environment-aware (e.g., allow higher absolute spend in prod).