Overview

At the end of the month, a CTO stared at a 350K RMB cloud bill and asked: “Where did all this money go?”

Nobody could answer.

120 ECS instances, 3 K8s clusters, 50+ SLBs, scattered across two cloud accounts (Alibaba Cloud and Tencent Cloud). Finance only saw the aggregate bill. The engineering team only knew the instances they had created themselves. There was no unified view. Worse, after a full inventory, more than 40% of resources were running at under 30% utilization — idle ECS instances humming along, over-provisioned CPUs wasting cycles, Pods spinning empty during off-peak hours, and SLBs with no backend still billing by the hour.

This is not an isolated case. The FinOps Foundation 2025 annual report shows that enterprises waste 30-35% of cloud spend on average. The problem is not that the cloud is too expensive; it’s that cost is invisible, unattributable, and uncontrollable.

I ran a cloud cost governance initiative at a ride-hailing project before, pulling a runaway monthly bill back onto a track that was predictable, attributable, and optimizable. This article breaks down the 6 key engineering decisions from that practice — from tagging strategy to cost-as-code, from K8s cost breakdown to anomaly detection, with pitfalls and measured data at every step.

一、The Cost Blind Spot: Why Cloud Bills Always Run Out of Control

1.1 Three properties of cloud cost make it inherently uncontrollable

Cloud resources have three characteristics that are fundamentally different from traditional data centers:

PropertyTraditional Data CenterCloud PlatformCost Impact
Acquisition speed2-4 weeks for procurement approval30 seconds via APIExtremely low barrier to provisioning; people spin up instances they don’t need
Billing granularityAsset depreciation, fixed monthlyPer-second/hour, dynamicBills fluctuate and are hard to predict
Resource ownershipRegistered in the asset management systemCreator equals user; tags are optionalCost attribution is hard; the “big bill” problem

The FinOps maturity model (from the FinOps Foundation) roughly maps to these stages:

StageNameTypical BehaviorAdoption in the Industry
CrawlBill-firstWait for the bill to arrive, then reconcile passively~60%
WalkProactive monitoringDeploy Kubecost/OpenCost and watch trends daily~25%
RunCost tied to businessAllocate cost by team/product, set budget alerts~12%
FlyAutomated optimizationAuto-scaling based on load, Spot scheduling, idle resource recycling~3%

Most teams are stuck at “export the bill to Excel and reconcile at month end.” The problem is that by the time the bill arrives, the money is already spent. Reconciliation only records history; it cannot change the outcome.

Real FinOps pushes cost control forward — tag resources at creation time, embed budget gates in the deployment pipeline, and detect anomalies at runtime in real time. This is not a finance department job; it’s an engineering team job.

If you’ve read SRE’s Perspective on FinOps: Cloud Cost Visibility and Optimization Strategy, that article focused on the theoretical framework of FinOps and the SRE role. This one focuses on engineering implementation — the complete chain from tags to code to alerting.

二、Tagging Strategy: The First Gate of Cost Attribution

2.1 Why tags are the foundation of FinOps

Cost attribution on cloud platforms entirely depends on tags. Without tags, you only see a “big bill” — you know you spent 350K, but not how much each team, product line, or environment spent.

When I took over cost governance at the ride-hailing project, the first thing I did was inventory the existing tags. The results were alarming:

ProblemCountImpact
ECS without tags47/120 instances39% of compute cost unattributable
Inconsistent tags (case, spelling)23 instancesFragmented attribution data, cannot aggregate
Missing business-line tag68 instancesCannot allocate cost by product line
Missing environment tag52 instancesCannot separate production/testing/development cost

47 out of 120 instances had no tags — the creators had probably left the company long ago, and no one knew what those machines were running.

2.2 Tag taxonomy design: 5 mandatory dimensions

Based on practice, I recommend a minimal viable tag taxonomy:

# Mandatory tags (5 dimensions)
Owner       = "Zhang San"     # Person responsible; you know who to call when something breaks
Team        = "payment"       # Team, used for cost allocation
Project     = "order-service" # Project / product line
Environment = "production"    # Environment: production/staging/development
CostCenter  = "CC-001"        # Cost center, linked to the finance system

Why these 5 and not more? If you define too many tags, nobody fills them in and the system becomes dead weight. These 5 dimensions exactly cover “who is responsible, which team, what project, what environment, how finance books it” — fewer breaks attribution, more raises the cost of compliance.

2.3 Enforcing the tagging policy

Defining tags is useless without enforcement. On Alibaba Cloud, use Tag Policy; on Tencent Cloud, use Tag Association. The core logic is the same: no tag, no resource creation.

#!/usr/bin/env python3
"""
Tag compliance check script: scans all ECS instances and checks whether
the mandatory tags are present. Used as a resource creation gate in CI/CD
pipelines, or as a scheduled inspection.
"""
import json
import sys
from alibabacloud_ecs20140526.client import Client as EcsClient
from alibabacloud_tea_openapi.models import Config

REQUIRED_TAGS = ["Owner", "Team", "Project", "Environment", "CostCenter"]

def check_tag_compliance(access_key_id, access_key_secret, region_id="cn-hangzhou"):
    """Scan tag compliance for all ECS instances in the specified region"""
    config = Config(
        access_key_id=access_key_id,
        access_key_secret=access_key_secret,
        region_id=region_id,
        endpoint=f"ecs.{region_id}.aliyuncs.com"
    )
    client = EcsClient(config)

    # Get all instances
    instances = client.describe_instances(
        describe_instances_request={}
    ).body.instances.instance

    violations = []
    for inst in instances:
        instance_id = inst.instance_id
        instance_name = inst.instance_name
        existing_tags = {tag.tag_key: tag.tag_value for tag in (inst.tags.tag or [])}

        missing = [t for t in REQUIRED_TAGS if t not in existing_tags]
        if missing:
            violations.append({
                "instance_id": instance_id,
                "instance_name": instance_name,
                "missing_tags": missing,
                "existing_tags": existing_tags
            })

    return violations

if __name__ == "__main__":
    violations = check_tag_compliance(
        access_key_id=sys.argv[1],
        access_key_secret=sys.argv[2]
    )
    if violations:
        print(f"[FAIL] Found {len(violations)} instances with non-compliant tags:")
        for v in violations:
            print(f"  {v['instance_id']} ({v['instance_name']}): missing {v['missing_tags']}")
        sys.exit(1)
    else:
        print("[PASS] All instances have compliant tags")

2.4 Pitfall: the case-sensitivity trap of tags

A real pitfall: Alibaba Cloud tag keys are case-sensitive — Team and team are two different tags. In one project, resources belonging to the same team were tagged in two different cases, so cost attribution split them into two entries, and the report showed that team’s cost as half of what it actually was.

The fix is tag standardization — normalize to UpperCamelCase at the creation entry point, and clean up existing tags in one pass:

# Batch fix tag case (Alibaba Cloud CLI example)
# First list all tags and find instances that need fixing
aliyun ecs DescribeTags --RegionId cn-hangzhou | \
  jq '.Tags.Tag[] | select(.TagKey | test("team"))' | \
  jq -r '.InstanceId'

# For each instance, delete the old tag and add the standardized one
for instance_id in $(aliyun ecs DescribeInstances --RegionId cn-hangzhou | \
  jq -r '.Instances.Instance[] | select(.Tags.Tag[]? | .TagKey == "team") | .InstanceId'); do
    # Delete the lowercase tag
    aliyun ecs RemoveTags --ResourceType instance --ResourceId "$instance_id" \
        --Tag.1.Key team --RegionId cn-hangzhou
    # Read the value of the lowercase tag
    tag_value=$(aliyun ecs DescribeInstances --InstanceIds "[\"$instance_id\"]" | \
        jq -r '.Instances.Instance[0].Tags.Tag[] | select(.TagKey == "team") | .TagValue')
    # Add the uppercase tag
    aliyun ecs AddTags --ResourceType instance --ResourceId "$instance_id" \
        --Tag.1.Key Team --Tag.1.Value "$tag_value" --RegionId cn-hangzhou
done

The cleanup took 2 days and fixed tags on 23 instances. After that, we enforced tag case normalization at both the cloud console and API gateway layers to prevent recurrence.

三、Kubernetes Cost Breakdown: From “Big Bill” to “Itemized Bill”

3.1 The K8s cost black box

One of the hardest parts of cloud cost governance is Kubernetes. A cluster runs dozens of microservices, and the cloud bill only shows cluster-level charges (nodes, storage, load balancers). How much each namespace, Pod, or service costs is invisible on the bill.

This is the classic “big bill” problem — the whole cluster costs 80K a month, but you don’t know whether the order service spent 30K or 5K.

At the ride-hailing project, we had 3 K8s clusters with 120+ microservices. The tech center’s monthly cloud bill was 200K, all charged to the tech center, while business departments had zero visibility into their costs. The CTO demanded that costs be broken down by business line so that each business line owner knew exactly what they were spending.

3.2 OpenCost: the native K8s cost metric standard

OpenCost is a CNCF sandbox project open-sourced by the Kubecost team, and it’s currently the de facto standard for K8s cost measurement. Its core value: break down cluster costs by namespace, Pod, tag, Service, and other dimensions, turning the “big bill” into an “itemized bill.”

How OpenCost works:

┌─────────────────────────────────────────────────────┐
│                   OpenCost Architecture              │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Prometheus  ──→  OpenCost  ──→  Cost data          │
│  (metrics)        (cost engine)   (API + UI)        │
│                                                     │
│  Inputs:                                            │
│  • node-exporter → node price, specs                │
│  • kube-state-metrics → Pod resource requests       │
│  • cAdvisor → actual resource usage                 │
│  • cloud pricing API → node unit price              │
│                                                     │
│  Outputs:                                           │
│  • namespace cost                                   │
│  • Pod-level cost                                   │
│  • tag-dimension cost                               │
│  • resource efficiency (usage vs requests)          │
│                                                     │
└─────────────────────────────────────────────────────┘

Deploying OpenCost is simple, provided you already have Prometheus:

# Install OpenCost (requires an existing Prometheus)
kubectl apply -f https://raw.githubusercontent.com/opencost/opencost/develop/kubernetes/opencost.yaml -n opencost

# Verify the deployment
kubectl get pods -n opencost
# Expected output: opencost 1/1 Running

# Port-forward to access the UI
kubectl port-forward -n opencost service/opencost 9003 9090

# Visit http://localhost:9090 for the cost dashboard

3.3 Namespace-level cost allocation

OpenCost’s core capability is splitting cost by namespace. But namespace alone is not enough — multiple business lines can run inside the same namespace. Truly usable cost allocation requires combining namespaces with tags:

# OpenCost custom cost allocation rules
# Business-line cost attribution via Pod labels
apiVersion: v1
kind: ConfigMap
metadata:
  name: opencost-config
  namespace: opencost
data:
  # Allocate cost by labels
  costLabels: "team,project,env"
  # Node price overrides (required for self-hosted IDC or non-cloud environments)
  # Otherwise OpenCost fetches node prices automatically via the cloud API
  pricing: |
    {
      "defaultCPUHourlyCost": 0.0,
      "defaultRAMHourlyCost": 0.0,
      "customPricesEnabled": true
    }    

Once Pods carry the team, project, and env labels, OpenCost can aggregate cost along these dimensions:

# Pod label example: cost attribution labels
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: production
spec:
  template:
    metadata:
      labels:
        app: order-service
        team: payment          # team
        project: order-system  # project
        env: production        # environment

3.4 Measured data: before and after cost breakdown

We ran a cost breakdown at the ride-hailing project, with these results:

Business lineMonthly cost (before breakdown)Monthly cost (after breakdown)UtilizationStatus
Order system200K (all)72K62%Reasonable
Payment system—48K71%Reasonable
User center—31K45%Low
Log service—28K28%Severe waste
Test environment—21K15%Severe waste

The breakdown exposed two cost black holes:

  1. Log service cost 28K/month at 28% utilization — the Elasticsearch cluster was over-provisioned; 4 of 6 nodes had CPU < 10% year-round
  2. Test environment cost 21K/month at 15% utilization — it ran 24/7, but was actually only used during working hours (9:00-21:00)

These two alone accounted for 24.5% of total cost — and only 40% of the allocated resources were actually needed.

For the fundamentals of K8s resource management, see Kubernetes Resource Management: Requests and Limits — understanding the relationship between requests and limits is a prerequisite for K8s cost optimization.

四、Resource Right-sizing: From Over-provisioning to Precise Fit

4.1 Over-provisioning is the biggest hidden waste

Right-sizing means adjusting cloud resource specs to match actual load. It is the highest ROI optimization in FinOps — no architecture changes, no code changes, just spec adjustments.

The ECS resource inventory at the ride-hailing project:

Spec problemInstancesMonthly wasteTypical symptom
CPU over-provisioning3552K8-core instances at CPU < 10% year-round
Memory over-provisioning2838K32G of RAM holding 4G of data
Oversized disks158K500G SSD with 30G used
Total7898K28% of total bill

78 instances had spec problems, wasting 98K per month. This is not a “should we save” question; it’s a “should we spend at all” question.

4.2 The right-sizing decision framework: smaller is not always better

Right-sizing is not simply swapping large specs for small ones. The core is finding the balance between “cost” and “performance headroom.”

My decision framework:

MetricThresholdAction
CPU utilization P95 < 15%Sustained 7 daysDownsize 50%
CPU utilization P95 15-30%Sustained 7 daysDownsize 30%
CPU utilization P95 30-60%Sustained 7 daysKeep as-is
CPU utilization P95 > 60%Sustained 7 daysUpgrade or scale out
Memory utilization < 30%Sustained 7 daysReduce memory 50%
Memory utilization > 80%Sustained 3 daysScale up immediately

Why P95 instead of the average? Averages hide spikes — an instance with a 20% average CPU may spike to 80%. If you downsize based on the average alone, it will buckle under the spike. P95 covers 95% of the time window, leaving reasonable headroom.

4.3 Automated right-sizing inspection script

Manually inspecting 120 instances is unrealistic. I wrote an automated script that periodically scans and generates right-sizing recommendations:

#!/usr/bin/env python3
"""
ECS right-sizing inspection: generates downsizing recommendations
based on CloudMonitor metrics.
Dependencies: alibabacloud_cloudmonitor20190801
"""
import json
from datetime import datetime, timedelta
from alibabacloud_cloudmonitor20190801.client import Client as CmsClient
from alibabacloud_tea_openapi.models import Config

def get_cpu_utilization(client, instance_id, days=7):
    """Get the CPU utilization P95 of an instance over the past N days"""
    end_time = datetime.now()
    start_time = end_time - timedelta(days=days)
    period = 300  # one data point every 5 minutes

    response = client.describe_metric_list(
        describe_metric_list_request={
            "MetricName": "CPUUtilization",
            "Dimensions": json.dumps({"instanceId": instance_id}),
            "StartTime": start_time.strftime("%Y-%m-%d %H:%M:%S"),
            "EndTime": end_time.strftime("%Y-%m-%d %H:%M:%S"),
            "Period": str(period)
        }
    )

    values = []
    for datapoint in response.body.datapoints:
        dp = json.loads(datapoint) if isinstance(datapoint, str) else datapoint
        if "Average" in dp:
            values.append(dp["Average"])

    if not values:
        return None

    # Compute P95
    values.sort()
    p95_index = int(len(values) * 0.95)
    return values[p95_index]

def generate_rightsizing_report(client, instances):
    """Generate a right-sizing recommendation report"""
    report = []
    for inst in instances:
        instance_id = inst["instance_id"]
        instance_type = inst["instance_type"]

        cpu_p95 = get_cpu_utilization(client, instance_id)
        if cpu_p95 is None:
            continue

        if cpu_p95 < 15:
            suggestion = "Downsize 50%"
            priority = "High"
        elif cpu_p95 < 30:
            suggestion = "Downsize 30%"
            priority = "Medium"
        elif cpu_p95 < 60:
            suggestion = "Keep as-is"
            priority = "Low"
        else:
            suggestion = "Upgrade or scale out"
            priority = "Urgent"

        report.append({
            "instance_id": instance_id,
            "instance_type": instance_type,
            "cpu_p95": round(cpu_p95, 1),
            "suggestion": suggestion,
            "priority": priority
        })

    return report

if __name__ == "__main__":
    config = Config(
        access_key_id="YOUR_AK",
        access_key_secret="YOUR_SK",
        endpoint="metrics.cn-hangzhou.aliyuncs.com"
    )
    cms_client = CmsClient(config)

    # Instance list (actually fetched from the ECS API)
    instances = [
        {"instance_id": "i-bp1xxx", "instance_type": "ecs.c5.2xlarge"},
        {"instance_id": "i-bp1yyy", "instance_type": "ecs.r5.large"},
    ]

    report = generate_rightsizing_report(cms_client, instances)
    print(f"{'InstanceID':<20} {'Spec':<20} {'CPU P95':<10} {'Suggestion':<15} {'Priority'}")
    print("-" * 80)
    for r in report:
        print(f"{r['instance_id']:<20} {r['instance_type']:<20} {r['cpu_p95']:<10} {r['suggestion']:<15} {r['priority']}")

4.4 Executing downsizing: gray release first, never all at once

Downsizing is not just changing a spec. Downsizing in production carries risk — changing specs requires stopping and restarting, and if you cut too deep, the instance may not survive the restart under traffic.

My execution flow:

  1. Downsize the test environment first: verify the downsized instance can pass the full test suite
  2. Downsize production one instance at a time: only 1-2 instances at a time, observe for 24 hours with no anomalies before continuing
  3. Keep a rollback path: record the original spec before downsizing; restore immediately if anything goes wrong
  4. Choose a maintenance window: execute during the business off-peak (2-5 AM)

We hit a pitfall during one downsizing: after an 8-core instance was reduced to 4 cores, CPU utilization jumped from 12% to 85% — because that instance was running a scheduled batch job that did a full data sync for 1 hour every night at 2 AM. Low average CPU does not mean the instance doesn’t need the compute; the peak during the batch window is what matters.

Lesson: right-sizing must look not only at steady-state utilization, but also at periodic peaks. For bursty workloads like batch jobs, scheduled tasks, and backup windows, P95 won’t catch them — you need to look at P99 or the absolute peak.

五、Committed Discounts and Elastic Strategy: Combining RI/SP with Spot

5.1 On-demand is the most expensive option

Cloud providers typically offer three billing models:

Billing modelDiscountBest forRisk
On-demand1.0x (baseline)Short-term testing, bursty loadNone
Reserved Instances (RI) / Savings Plans (SP)0.4-0.6xLong-term stable load1-3 year commitment, upfront payment
Spot instances0.1-0.3xFault-tolerant batch processingCan be reclaimed at any time

For the same 8-core 32G instance, on-demand costs about 1200 RMB/month, RI about 540 RMB, and Spot about 180 RMB. The gap is huge.

5.2 Layered committed discount strategy

Not every workload suits the same billing model. I recommend a layered combination:

Workload typeCharacteristicsRecommended modelShare
Core databases7x24 stable, must not be interruptedRI 3-year prepaid~30%
Core services7x24 stable, brief interruption acceptableSP 1-year monthly~40%
Elastic scaling layerHighly volatile trafficOn-demand + Spot mix~20%
Batch/CIFault-tolerant, interruptibleSpot pure bidding~10%

This ratio is not pulled out of thin air. At the ride-hailing project, our cost structure was:

  • RI covering the core database layer (30% of resources) at a 55% discount, saving 42K per month
  • SP covering the core service layer (40% of resources) at a 40% discount, saving 31K per month
  • Spot covering CI/CD runners and batch processing (10% of resources) at an 80% discount, saving 8K per month
  • On-demand as the elasticity fallback (20% of resources), no discount but flexible

Combined, committed discounts and Spot drove the monthly bill from 350K down to around 260K — a 25% reduction.

5.3 The Spot trap: exit gracefully when reclaimed

The biggest risk with Spot instances is that they can be reclaimed at any time. When cloud provider resources are tight, you get 2-5 minutes of warning, then the instance is forcibly reclaimed.

If you run long-lived connection services directly on Spot, a reclamation means broken connections, failed requests, and degraded user experience. The right approach:

  1. Run only stateless services on Spot instances, keep stateful data in persistent storage
  2. Configure graceful shutdown hooks: after receiving a reclamation notice, stop accepting new requests, finish in-flight requests, then exit
  3. Use K8s Spot node pools + PDB to ensure only a limited number of Pods are reclaimed at once
# K8s Spot node pool PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: spot-service-pdb
  namespace: production
spec:
  minAvailable: 2  # keep at least 2 Pods available
  selector:
    matchLabels:
      node-role: spot
---
# Deployment on Spot nodes
apiVersion: apps/v1
kind: Deployment
metadata:
  name: batch-processor
spec:
  replicas: 6
  template:
    spec:
      nodeSelector:
        node-role: spot
      tolerations:
        - key: "spot-instance"
          operator: "Exists"
      containers:
        - name: processor
          image: registry.example.com/processor:v1.0
          lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - |
                    # Graceful exit: deregister from the load balancer, wait for in-flight requests to finish
                    curl -s http://lb-controller/deregister
                    sleep 30                    
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5

One production incident: a CI Runner on a Spot instance was mid-build when it was reclaimed. The build was interrupted, the artifacts were corrupted, and the deployment failed. We later added a checkpoint/resume mechanism for build jobs — if interrupted mid-build, resume from the last cached step instead of starting over. This change took the CI Spot interruption rate from “fails every time” to “occasionally costs an extra 2 minutes to recover.”

六、Cost-as-Code: Embedding Budget Gates into CI/CD

6.1 Discovering a sky-high bill after deployment is too late

The ultimate goal of FinOps is not post-hoc optimization but prevention. The ideal scenario: at commit time, you see how much this change will add to cloud cost, and if it exceeds the budget, the deployment is blocked.

Infracost is currently the most mature IaC cost estimation tool, supporting Terraform, Pulumi, and more. At the PR stage, it scans infrastructure code changes, estimates the cost difference, and displays it in a PR comment.

6.2 Integrating Infracost into CI/CD

# GitLab CI integration with Infracost cost estimation
# .gitlab-ci.yml
stages:
  - validate
  - cost-check
  - plan
  - apply

infracost-cost-check:
  stage: cost-check
  image: infracost/infracost:latest
  variables:
    INFRACOST_API_KEY: $INFRACOST_API_KEY
  script:
    # Generate the cost estimate report
    - infracost breakdown --path . --format json --out-file infracost.json
    # Generate a diff report (vs. the main branch baseline)
    - infracost diff --path . --compare-to infracost-base.json --format json --out-file infracost-diff.json
    # Extract the monthly cost change
    - |
      MONTHLY_COST=$(jq -r '.projects[0].breakdown.totalMonthlyCost // 0' infracost.json)
      COST_DIFF=$(jq -r '.projects[0].diff.totalMonthlyCost // 0' infracost-diff.json)
      echo "Monthly cost of this change: ${MONTHLY_COST} USD"
      echo "Change vs. baseline: ${COST_DIFF} USD"
      # Budget gate: monthly increase over 5000 USD requires approval
      THRESHOLD=5000
      if (( $(echo "${COST_DIFF} > ${THRESHOLD}" | bc -l) )); then
        echo "⚠️ Cost increase exceeds threshold (${THRESHOLD} USD/month), requires architect approval"
        exit 1
      fi      
  artifacts:
    paths:
      - infracost.json
      - infracost-diff.json
  only:
    - merge_requests

6.3 A three-tier cost gate design

Cost estimation alone is not enough. In practice, I designed a three-tier cost gate:

TierGate ruleTrigger actionApprover
L1: PR commentAll PRs containing IaC changesAuto-comment with cost estimateNone (informational)
L2: Budget alertMonthly increase > 2000 USDBlock auto-mergeArchitect approval
L3: Hard gateMonthly increase > 5000 USD, or new instance > 1000 USD/monthBlock deploymentCTO approval

L1 is the information layer — it tells developers “how much this PR will cost.” Most developers have no idea that changing one line of Terraform can cost thousands of dollars. After seeing the cost comment, many proactively adjust their specs.

L2 is the approval layer — exceeding budget requires an architect to confirm necessity. This isn’t about blocking business; it’s about making decisions traceable.

L3 is the hard gate — large cost increases require CTO sign-off. It prevents incidents like “opened 10 GPU instances in the test environment and forgot to shut them down.”

For the layered design of CI/CD pipelines, I covered the architecture decisions of our self-built DAG scheduling engine in Don’t Let Jenkins Dictate Your Release Rhythm. Cost gates are one dimension of pipeline design, complementary to release rhythm and rollback strategy.

七、Cost Anomaly Detection: From Monthly Reconciliation to Real-time Discovery

7.1 The lag problem with traditional reconciliation

The biggest problem with monthly reconciliation is lag — by the time you spot an anomaly at month end, the money has been spent for a full month.

One incident: a developer opened a 32-core 128G GPU instance in the test environment to train a model, and forgot to shut it down after finishing. The instance ran for 18 days before being discovered, adding 12K to the bill. With monthly reconciliation, it would have taken until month end at best.

7.2 The cost anomaly detection design

My cost anomaly detection design has two layers:

Layer one: budget alerts

Set a budget threshold in the cloud platform; trigger an alert when daily spend exceeds 1/30 of the budget:

#!/usr/bin/env python3
"""
Daily spend alert: checks whether today's cloud spend exceeds the daily average budget.
Fetches real-time spend via the Alibaba Cloud BSS (billing center) API.
"""
import json
from alibabacloud_bssopenapi20171214.client import Client as BssClient
from alibabacloud_tea_openapi.models import Config

def check_daily_cost(access_key_id, access_key_secret, monthly_budget=280000):
    """
    Check whether today's spend is abnormal.
    monthly_budget: monthly budget (RMB), daily budget = monthly_budget / 30
    """
    config = Config(
        access_key_id=access_key_id,
        access_key_secret=access_key_secret,
        endpoint="business.aliyuncs.com"
    )
    client = BssClient(config)

    # Query this month's bill
    from alibabacloud_bssopenapi20171214.models import QueryBillRequest
    import datetime
    now = datetime.datetime.now()
    response = client.query_bill(
        query_bill_request=QueryBillRequest(
            billing_cycle=f"{now.year}-{now.month:02d}"
        )
    )

    # Sum up today's spend
    daily_cost = 0
    for item in response.body.data.account_bill_list.items:
        if item.billing_date == now.strftime("%Y-%m-%d"):
            daily_cost += float(item.pretax_amount)

    daily_budget = monthly_budget / 30
    ratio = daily_cost / daily_budget

    if ratio > 1.5:
        level = "CRITICAL"
    elif ratio > 1.2:
        level = "WARNING"
    elif ratio > 1.0:
        level = "NOTICE"
    else:
        level = "OK"

    return {
        "daily_cost": round(daily_cost, 2),
        "daily_budget": round(daily_budget, 2),
        "ratio": round(ratio, 2),
        "level": level
    }

if __name__ == "__main__":
    result = check_daily_cost("YOUR_AK", "YOUR_SK", monthly_budget=280000)
    print(json.dumps(result, indent=2, ensure_ascii=False))
    # Send an alert via DingTalk/Feishu bot when level is CRITICAL

Layer two: day-over-day anomaly detection

Even more sensitive than budgets is day-over-day detection — compare today’s cost against the average of the past 7 days; alert if it suddenly spikes. The problem with budget alerts is that you have to know the budget first. But many cost anomalies are unexpected events (an instance mistakenly created, Spot prices surging), not a wrong budget.

def detect_cost_anomaly(client, instance_ids, days=7):
    """
    Day-over-day anomaly detection: compares today's cost with the daily
    average of the past N days. If today's cost exceeds 200% of the N-day
    average, it is flagged as an anomaly.
    """
    import datetime
    now = datetime.datetime.now()

    costs = []
    for i in range(days + 1):
        date = (now - datetime.timedelta(days=i)).strftime("%Y-%m-%d")
        daily_cost = get_daily_cost(client, date)
        costs.append({"date": date, "cost": daily_cost})

    today_cost = costs[0]["cost"]
    avg_cost = sum(c["cost"] for c in costs[1:]) / days

    if avg_cost > 0 and today_cost > avg_cost * 2:
        return {
            "anomaly": True,
            "today_cost": today_cost,
            "avg_cost": round(avg_cost, 2),
            "increase_ratio": round(today_cost / avg_cost, 2),
            "message": f"Today's cost {today_cost} RMB exceeds the {days}-day average of {round(avg_cost, 2)} RMB by {round(today_cost / avg_cost, 2)}x"
        }
    return {"anomaly": False, "today_cost": today_cost, "avg_cost": round(avg_cost, 2)}

7.3 Automated idle resource inspection

Beyond cost anomalies, idle resources need periodic inspection. This inspection logic can run as a scheduled job:

#!/usr/bin/env python3
"""
Idle resource inspection: scans low-CPU ECS instances, SLBs without backends,
and unattached cloud disks. Runs daily; results are sent via DingTalk/Feishu.
"""
import json
import datetime

def scan_idle_ecs(client, threshold=5, days=7):
    """Scan ECS instances whose CPU utilization stays below the threshold"""
    idle_instances = []
    # Get all running instances
    instances = client.describe_instances(
        describe_instances_request={"status": "Running"}
    ).body.instances.instance

    for inst in instances:
        cpu_avg = get_cpu_avg(client, inst.instance_id, days)
        if cpu_avg is not None and cpu_avg < threshold:
            idle_instances.append({
                "instance_id": inst.instance_id,
                "instance_name": inst.instance_name,
                "instance_type": inst.instance_type,
                "cpu_avg": round(cpu_avg, 1),
                "idle_days": days,
                "monthly_cost": estimate_monthly_cost(inst.instance_type),
                "suggestion": "Recommended to release or downsize"
            })
    return idle_instances

def scan_idle_slb(client):
    """Scan SLB instances with no backend servers"""
    idle_slbs = []
    slbs = client.describe_load_balancers(
        describe_load_balancers_request={}
    ).body.load_balancers.load_balancer

    for slb in slbs:
        # Get the backend server list
        backends = client.describe_health_status(
            describe_health_status_request={
                "load_balancer_id": slb.load_balancer_id
            }
        ).body.backend_servers.backend_server

        if not backends:
            idle_slbs.append({
                "slb_id": slb.load_balancer_id,
                "slb_name": slb.load_balancer_name,
                "address": slb.address,
                "monthly_cost": 30,  # SLB base fee is about 30 RMB/month
                "suggestion": "No backend servers, recommended to release"
            })
    return idle_slbs

def scan_unattached_disks(client):
    """Scan unattached cloud disks"""
    idle_disks = []
    disks = client.describe_disks(
        describe_disks_request={"status": "Available"}  # Available = unattached
    ).body.disks.disk

    for disk in disks:
        idle_disks.append({
            "disk_id": disk.disk_id,
            "disk_name": disk.disk_name,
            "size_gb": disk.size,
            "disk_type": disk.category,
            "create_time": disk.creation_time,
            "monthly_cost": estimate_disk_monthly(disk.size, disk.category),
            "suggestion": "Unattached disk, recommended to release or attach"
        })
    return idle_disks

def generate_report(idle_ecs, idle_slbs, idle_disks):
    """Generate the inspection report"""
    total_waste = sum(i["monthly_cost"] for i in idle_ecs) + \
                  sum(s["monthly_cost"] for s in idle_slbs) + \
                  sum(d["monthly_cost"] for d in idle_disks)

    report = f"""
Idle resource inspection report ({datetime.date.today()})

Idle ECS: {len(idle_ecs)} instances, ~{sum(i['monthly_cost'] for i in idle_ecs)} RMB wasted per month
Idle SLB: {len(idle_slbs)} instances, ~{sum(s['monthly_cost'] for s in idle_slbs)} RMB wasted per month
Unattached disks: {len(idle_disks)} disks, ~{sum(d['monthly_cost'] for d in idle_disks)} RMB wasted per month

Total monthly waste: {total_waste} RMB
"""
    if total_waste > 10000:
        report += "\n⚠️ Monthly waste exceeds 10K RMB, recommended to handle immediately!"
    return report

After running this inspection for 3 months at the ride-hailing project, we discovered and reclaimed:

  • 23 idle ECS instances, saving 34K/month
  • 8 SLBs without backends, saving 240 RMB/month
  • 11 unattached disks, saving 1650 RMB/month
  • 42 over-provisioned instances downsized, saving 63K/month

Total: 99K saved per month — nearly 300K over 3 months.

Summary

Runaway cloud cost is not a technical problem; it’s an engineering management problem. The core contradiction: the barrier to provisioning cloud resources is extremely low (an instance in 30 seconds via API), but the feedback loop for cost control is extremely long (the bill arrives at month end). The essence of FinOps is compressing that feedback loop from months to days, even to hours.

Priority and expected returns of the 6 decisions:

DecisionImplementation difficultyTime to effectMonthly benefit (estimate)Priority
Tagging strategy & attributionMedium1-2 weeksFoundational (no direct savings, but supports everything else)P0
K8s cost breakdownMedium1 weekUncovers wasteP1
Resource right-sizingLowImmediateDownsizing saves money directlyP1
Committed discount layeringLowImmediate20-25% cost reductionP2
Cost-as-code gatesHigh2-4 weeksPreventive (avoids new waste)P2
Anomaly detection & alertingMedium1 weekPreventive (avoids sustained waste)P1

Key takeaways from practice:

  1. Tags are the foundation — without tag attribution, every downstream optimization is blind. Spend the first 2 weeks building the tag taxonomy; sharpening the axe doesn’t delay the chopping
  2. Right-sizing is the highest-ROI move — no architecture changes, no code changes, just spec adjustments with immediate results
  3. Committed discounts are “savings without engineering” — RI/SP need no technical changes, but you must predict load accurately to commit the right amount
  4. Cost-as-code is the endgame — embed cost control into the CI/CD pipeline to stop waste at the source, not remediate after the fact
  5. Anomaly detection is not optional — without real-time detection, idle resources quietly accumulate, and by the time you notice, tens of thousands have already leaked

FinOps is not a one-off optimization project; it’s continuous operations. Cloud resources change, load changes, prices change, and cost governance must change with them. Getting this system running, bringing the monthly bill from 350K down to 170K, is not the finish line — continuous optimization is the steady state of FinOps.

References & Acknowledgments

This article references the following materials during writing; thanks to the original authors for their contributions:

  1. FinOps 云成本优化架构实战:从资源浪费到成本可控 — CSDN, practical guide to the three FinOps elements and maturity model
  2. What Is Cloud Cost Optimization? Strategy & Best Practices — Oracle official, cloud cost optimization methodology and best practices
  3. FinOps Solutions – Cloud Cost Optimization — Microsoft Azure official, FinOps solution framework
  4. 云成本治理实战:AI 弹性伸缩与闲置资源回收,月费直降 38% — Tencent Cloud developer community, quantitative analysis of cloud waste and automated governance practice
  5. 云成本进化之路:从ITAM到FinOps,再到FOCUS — Zhihu, evolution of three generations of cost management models and the FOCUS unified billing spec
  6. OpenCost部署指南:Kubernetes环境中的无缝集成方案 — CSDN, OpenCost deployment and configuration practice in K8s
  7. Kubecost 开源 OpenCost:Kubernetes 成本监控的开源标准 — Tencent Cloud developer community, OpenCost design philosophy and the CNCF ecosystem
  8. FinOps Foundation 2025 Annual Report — FinOps Foundation, source of the 30-35% average cloud waste statistic