08 · FinOps & Enterprise Cost Optimization¶
Level 3's cost module covered budgets and tagging for one account. FinOps at enterprise scale is the ongoing practice built on top of that visibility: reserved capacity decisions, anomaly detection, showback/chargeback across teams, and rightsizing as a recurring process rather than a one-time task.
Enterprise-wide cost visibility¶
ibmcloud enterprise account-group-usage-report \
--account-group-id $(ibmcloud enterprise account-group product-teams --output json | jq -r .id) \
--billing-month 2026-08
This is the enterprise-level view that Module 01's account-group
structure makes possible — one report spans every account under
product-teams without needing per-account login.
Showback and chargeback with cost tags¶
ibmcloud billing account-usage --output json | \
jq -r '.resources[] | select(.tags[]? | startswith("team:")) |
"\(.tags[] | select(startswith("team:")))\t\(.charges)"' | \
awk -F'\t' '{sum[$1]+=$2} END {for (t in sum) print t, sum[t]}'
Showback (reporting spend per team without billing them internally) is the easy first step; chargeback (actually debiting a team's internal budget) needs the tag discipline from Level 3, Module 07 enforced without exception — one untagged large resource breaks the whole report's accuracy silently.
Detect cost anomalies before the monthly bill does¶
ibmcloud billing account-usage --output json > usage-current.json
python3 - <<'EOF'
import json
with open("usage-current.json") as f:
data = json.load(f)
for r in data["resources"]:
if r["charges"] > r.get("last_month_charges", 0) * 1.5 and r["charges"] > 50:
print(f"ANOMALY: {r['resource_name']} jumped to ${r['charges']:.2f}")
EOF
A 50%+ month-over-month jump on a specific service is usually one of a
few causes: a forgotten dev resource left running at production size, an
autoscaler's max-size set too high and actually being hit, or a genuine
traffic increase — the anomaly check's job is to surface it quickly, not
diagnose which cause it is.
Reserved capacity and committed-use discounts¶
For steady-state workloads whose sizing has stabilized (the opposite of Module 05's autoscaled traffic-following workloads), a committed-use agreement with IBM trades flexibility for a lower rate — check actual utilization trends over at least a full quarter before committing, since a mis-sized reservation costs more than paying list price for variable usage.
Rightsizing as a recurring job, not a one-time audit¶
ibmcloud is instances --output json | \
jq -r '.[] | select(.profile.name | test("bx2-8x32|bx2-16x64")) | .name'
Cross-reference instances provisioned at large profiles against their
actual ibmcloud ob monitoring CPU/memory utilization over the trailing
30 days (Level 4, Module 05's baseline habit, applied as a recurring
report instead of a one-off check) — a bx2-16x64 instance sitting at 8%
average CPU for a month is a rightsizing candidate, not a capacity
decision that was necessarily wrong when originally made.
Idle and orphaned resource cleanup¶
An unattached floating IP still bills — a common, easy-to-miss leftover from a deleted instance whose floating IP wasn't explicitly released. Schedule a monthly sweep (a scripted check, not a manual click-through) for unattached floating IPs, unattached block storage volumes, and snapshots past their intended retention.
FinOps dashboard: pull it together¶
resource "ibm_resource_instance" "cost_dashboard_functions" {
name = "finops-anomaly-check"
service = "functions"
plan = "lite"
location = "us-south"
resource_group_id = data.ibm_resource_group.mastery_path.id
}
A scheduled Code Engine job or Cloud Function (from Level 1's serverless module) running the anomaly-detection script monthly and posting results to a chat channel turns this module's manual commands into an actual recurring FinOps practice rather than something remembered only when the bill is surprising.
Gotchas¶
- Showback numbers are only as good as tag coverage — a single large, untagged resource silently understates every team's real spend in a showback report; audit tag coverage percentage as its own metric.
- Committed-use discounts lock in a minimum spend — committing based on a temporary traffic spike (rather than sustained baseline) can leave an account paying for capacity it no longer uses once the spike passes.
- Instance profile changes need a reboot — rightsizing a running production instance is a planned-maintenance-window activity, not something to script as a silent background change.
- Free-tier and lite-plan resources still show as $0 in billing but count against account-level quotas — a cost review focused only on billed dollars can miss quota exhaustion building up on the free side.
How It Actually Works¶
- The enterprise account-group usage report is possible only because billing data is rolled up along the same account-group hierarchy tree Module 01 built, not recomputed separately — every account's metering records already carry their account ID, and the enterprise's billing system aggregates by walking the account-group parentage from Module 01 the way an org chart aggregates headcount. That's why the account-group structure decision made at provisioning time directly determines what cost views are possible later without custom tooling.
- An unattached floating IP keeps billing because a floating IP is a
reserved, routable public address allocated against the account's
address pool independent of whatever instance it's currently bound
to — detaching it from a deleted instance doesn't release the
allocation itself, it just clears the
targetfield; the IP is still held in reserve (so it can be immediately re-attached to a replacement instance) and billed for exactly that reservation, not for traffic or compute. Only an explicit release call actually frees it back to IBM's pool and stops the charge. - A month-over-month anomaly script works because metering records
accumulate into per-service-instance monthly totals that the billing
API exposes as a queryable time series, not just a final invoice
number. Comparing
chargesagainstlast_month_chargesper resource is directly reading two adjacent points on that time series — the same underlying data source Level 3's budget alerts poll, just compared resource-by-resource instead of against one aggregate threshold, which is why this script catches a single service's spike even when total account spend still looks unremarkable. - Instance profile resizing needs a reboot because vCPU and memory allocation are properties of the underlying VSI's hypervisor placement, not something the guest OS can renegotiate live — changing profile requires the VPC infrastructure layer to deallocate the instance's current compute resources and reschedule it onto a host slot matching the new profile's shape, which is a stop/reallocate/start cycle from the hypervisor's perspective regardless of how briefly the CLI command appears to run.
Cheat sheet¶
| Task | Command |
|---|---|
| Enterprise usage report | ibmcloud enterprise account-group-usage-report --account-group-id <id> --billing-month <YYYY-MM> |
| List unattached floating IPs | ibmcloud is floating-ips --output json \| jq '.[] \| select(.target == null)' |
| Resize an instance profile | ibmcloud is instance-update <name> --profile <profile> |
| List subscription/reserved offerings | ibmcloud billing offering-list |
| View pod resource usage cluster-wide | oc adm top pods -A |
Exercise¶
- Write a script (Python or
jq) that flags any resource whose current month charge exceeds 150% of the prior month. - Compute a showback total per
team:tag from a sample billing usage JSON export. - Find (or simulate) an unattached floating IP and describe the cleanup steps and their cost impact.
- Given a hypothetical instance sitting at 8% average CPU for a month, propose a rightsized profile and describe the maintenance-window process for applying it safely.