OCI Cost Management: Budgets, Alerts, and Rightsizing Recommendations with Terraform

Cloud cost surprises follow a predictable pattern. Something gets deployed, nobody sets a budget, utilization grows quietly, and the first signal is the invoice. By then, the workload has been running at unexpected scale for weeks. OCI Cost Management gives you the tools to catch this early: budgets that alert before the threshold is hit, cost allocation through defined tags, and rightsizing recommendations from actual utilization data.

Step 1: Monthly Budget with Forecast Alert

resource "oci_budget_budget" "production_monthly" {
  compartment_id                        = var.tenancy_ocid
  display_name                          = "production-monthly-budget"
  amount                                = 10000
  budget_processing_period_start_offset = 1
  processing_period_type                = "MONTH"
  reset_period                          = "MONTHLY"
  target_type                           = "COMPARTMENT"
  targets                               = [var.compartment_id]
}

resource "oci_budget_alert_rule" "warning" {
  budget_id      = oci_budget_budget.production_monthly.id
  display_name   = "75-percent-warning"
  type           = "ACTUAL"
  threshold      = 75
  threshold_type = "PERCENTAGE"
  recipients     = var.finance_email
  message        = "Production has consumed 75 percent of the monthly budget."
}

resource "oci_budget_alert_rule" "critical" {
  budget_id      = oci_budget_budget.production_monthly.id
  display_name   = "90-percent-critical"
  type           = "ACTUAL"
  threshold      = 90
  threshold_type = "PERCENTAGE"
  recipients     = join(",", [var.finance_email, var.eng_manager_email])
  message        = "Production has consumed 90 percent of the monthly budget. Immediate review required."
}

resource "oci_budget_alert_rule" "forecast" {
  budget_id      = oci_budget_budget.production_monthly.id
  display_name   = "forecast-overrun"
  type           = "FORECAST"
  threshold      = 100
  threshold_type = "PERCENTAGE"
  recipients     = var.finance_email
  message        = "Forecast: production spend will exceed the monthly budget before month end."
}

Step 2: Tag Namespace for Cost Allocation

resource "oci_identity_tag_namespace" "operations" {
  compartment_id = var.tenancy_ocid
  description    = "Operational tags for cost allocation"
  name           = "Operations"
}

resource "oci_identity_tag" "team" {
  description      = "Owning team for cost chargeback"
  name             = "Team"
  tag_namespace_id = oci_identity_tag_namespace.operations.id
  validator {
    validator_type = "ENUM"
    values         = ["platform", "orders", "payments", "notifications", "data"]
  }
}

resource "oci_identity_tag" "environment" {
  description      = "Deployment environment"
  name             = "Environment"
  tag_namespace_id = oci_identity_tag_namespace.operations.id
  validator {
    validator_type = "ENUM"
    values         = ["production", "staging", "development", "sandbox"]
  }
}

# Tag default automatically applies the Environment tag to every resource created in this compartment
resource "oci_identity_tag_default" "production_env" {
  compartment_id    = var.compartment_id
  tag_definition_id = oci_identity_tag.environment.id
  value             = "production"
  is_required       = true
}

Step 3: Query Costs via Usage API

import oci
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta

def get_cost_by_service(compartment_id: str) -> list:
    config = oci.config.from_file()
    client = oci.usage_api.UsageapiClient(config)
    now    = datetime.now(timezone.utc)
    start  = (now - relativedelta(months=1)).replace(day=1, hour=0, minute=0, second=0)

    resp = client.request_summarized_usages(
        request_summarized_usages_details=oci.usage_api.models.RequestSummarizedUsagesDetails(
            tenant_id=config["tenancy"],
            time_usage_started=start,
            time_usage_ended=now,
            granularity="MONTHLY",
            is_aggregate_by_time=False,
            group_by=["service", "skuName"],
            filter=oci.usage_api.models.Filter(
                operator="AND",
                dimensions=[oci.usage_api.models.Dimension(key="compartmentId", value=compartment_id)]
            )
        )
    )

    items = [{
        "service":  i.service,
        "sku":      i.sku_name,
        "cost_usd": round(float(i.computed_amount or 0), 2)
    } for i in resp.data.items]

    return sorted(items, key=lambda x: x["cost_usd"], reverse=True)

for item in get_cost_by_service("ocid1.compartment.oc1..yourcompartmentocid")[:10]:
    print(f"${item['cost_usd']:>10.2f} | {item['service']:<30} | {item['sku']}")

Step 4: Rightsizing via OCI Optimizer

import oci

def get_rightsizing_recommendations(compartment_id: str) -> list:
    config = oci.config.from_file()
    client = oci.optimizer.OptimizerClient(config)

    recs = client.list_recommendations(
        compartment_id=compartment_id,
        compartment_id_in_subtree=True,
        status="PENDING_IMPLEMENTATION"
    ).data.items

    results = [
        {"name": r.name, "savings": r.estimated_cost_saving, "priority": r.importance}
        for r in recs
        if "right" in r.name.lower() or "underutilized" in r.name.lower()
    ]

    return sorted(results, key=lambda x: x["savings"], reverse=True)

for rec in get_rightsizing_recommendations("ocid1.compartment.oc1..yourcompartmentocid"):
    print(f"Save ${rec['savings']:.2f}/month | {rec['priority']:<10} | {rec['name']}")

Step 5: OCI CLI Cost Queries

# Get current month spend by service
oci usage-api usage-summary request-summarized-usages \
  --tenant-id ${TENANCY_OCID} \
  --time-usage-started "$(date -u +%Y-%m-01T00:00:00Z)" \
  --time-usage-ended "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --granularity MONTHLY \
  --group-by '["service"]' \
  --query 'data.items[*].{service:service, cost:"computedAmount"}' \
  --output table

# Get current budget status
oci budgets budget list \
  --compartment-id ${TENANCY_OCID} \
  --query 'data[*].{name:"display-name", amount:amount, actual:"actual-spend", forecast:"forecast-spend"}' \
  --output table

# List pending Optimizer recommendations sorted by savings
oci optimizer recommendation list \
  --compartment-id ${COMPARTMENT_OCID} \
  --compartment-id-in-subtree true \
  --status PENDING_IMPLEMENTATION \
  --query 'data.items[*].{name:name, savings:"estimatedCostSaving", importance:importance}' \
  --output table | sort -t"$" -k2 -rn

Operational Notes

Tag defaults on compartments automatically apply tags to every resource created there, including resources created by automated pipelines. Without tag defaults, tag compliance depends on every engineer and automation script remembering to set tags. With tag defaults, the baseline tags are always present and only team-specific tags need to be set explicitly per resource.

FORECAST budget alerts fire before month end when projected spend is on track to exceed the budget. Set forecast alerts at 100 percent of the budget. The forecast model uses current run-rate, so if 60 percent of the budget is consumed in the first half of the month, the forecast alert fires immediately rather than waiting for actual spend to reach 90 percent.

OCI Optimizer rightsizing recommendations are based on 30 days of utilization data. A recommendation to downsize a compute instance only appears after 30 days of consistently low CPU and memory utilization. Check recommendations monthly, not daily, and validate that the utilization data covers your peak usage periods before acting on a downsize recommendation.

Regards,
Osama

#OCI #OracleCloud #CostManagement #FinOps #Terraform #IaC #OracleCloudInfrastructure #CloudCost #TechBlog #Oracle #Budgets #CloudGovernance #CostAllocation #Rightsizing #CloudOptimization #TagManagement #PlatformEngineering #DevOps #UsageAPI #CloudFinance

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.