Outcome
By the end of this lesson, you will be able to choose the right billing data source on AWS, GCP, or Azure for a given question, and explain when one source is wrong for the job.
New to the cloud? The optional track Cloud, from zero covers the basics first.
| Prerequisites | L1: What’s actually in a cloud bill |
| Time | 8 minutes |
1. Concept
Each cloud provider (AWS, Google Cloud and Microsoft Azure) publishes the same bill through more than one interface: a screen in its console, the website where you manage your account, or a data file you can query. They do not all answer the same questions. Picking the wrong one wastes an afternoon, or worse, returns a number that looks right and is not.
There are four you will meet.
AWS Cost and Usage Report, the CUR. The raw data behind everything else: one row per resource, per charge type, per day, or per hour if you configure it that way. AWS drops it into a storage bucket (a folder in its S3 file storage) as compressed spreadsheet-style files, once a day, roughly 24 hours behind. You query it with SQL, the standard language for asking questions of tables, using Athena (AWS’s pay-per-query SQL service), Redshift or any data warehouse (a database built for large reports).
It is the only AWS source that carries the full picture: tags (labels you attach to resources, such as team = billing), account, region (the geographic location of the data centre), charge type, and how a reserved-instance discount was attributed. A reserved instance is a one- or three-year commitment to a machine in return for a lower hourly rate.
AWS Cost Explorer. A query screen in the console, sitting on a pre-aggregated copy of the CUR. Up to 14 months of history, quick to filter and group.
The data is mostly the same, with three caveats worth knowing before you rely on it. The console is free, but every API call costs $0.01 at any granularity (an API is the interface programs use to ask a service for data). Hourly and resource-level detail are billed extra, per usage record. And tag filtering is less flexible than querying the CUR directly.
Azure Cost Management. Azure’s main cost interface, reading from the platform’s billing service.
Two terms first. A reservation (Azure’s word for a reserved instance) or savings plan is a prepaid or committed discount. Amortized cost spreads that up-front or committed amount evenly across the hours it covers, the way you might spread a yearly insurance bill over twelve months.
It exposes two separate cost columns, ActualCost and AmortizedCost, and the difference matters more than it sounds. A reservation or savings plan shows $0 under ActualCost at subscription scope (a subscription is Azure’s billing container, much like an AWS account), and its full effective cost under AmortizedCost. Reading the wrong column is the most common mistake made on Azure bills, and it makes committed spend look free.
Google BigQuery billing export. Google has no equivalent file drop. Billing data lands directly in BigQuery, Google’s data warehouse, instead, one row per charge type per resource per day, queried with ordinary SQL.
Two versions exist. The detailed export adds machine type and, if you have labelled them, Kubernetes namespace (Kubernetes runs many applications across a group of servers, and a namespace is one team’s or app’s slice of it), in exchange for storing more data.
When to pick which
In the table, K8s is short for Kubernetes, and EKS and AKS are the AWS and Azure managed Kubernetes services (managed means the provider runs the underlying machinery for you). FinOps is the practice of managing cloud cost.
QUESTION → BEST SOURCE────────────────────────────────────────────────────────────"Quick monthly trend for a stakeholder" → Cost Explorer / Cost Mgmt / BigQuery dashboard"Per-resource cost yesterday" → CUR (AWS), BigQuery (GCP), Cost Mgmt with resource scope (Azure)"Reserved instance attribution" → CUR (AWS): RI distribution columns live here"Reservation cost for Azure resources" → Cost Mgmt with AmortizedCost: NEVER ActualCost at subscription scope"K8s namespace breakdown" → BigQuery detailed export (GCP), CUR with EKS labels (AWS), Cost Mgmt with AKS labels (Azure)"Multi-account roll-up" → CUR exported at Organization root (AWS), BigQuery billing-account export (GCP), Cost Mgmt at Billing Account scope (Azure)"Programmatic ingestion into FinOps tool" → CUR / BigQuery / Cost Mgmt Export API: pick whichever lands in object storageGranularity vs. freshness: the table
| Source | Granularity | Freshness | History | Cost to query |
|---|---|---|---|---|
| AWS CUR | Hourly, per resource, per usage type | 24 h lag | Configurable retention | S3 storage + Athena scan cost |
| AWS Cost Explorer | Daily default, hourly available | 24 h lag | 14 months | Console free; API $0.01 per request at any granularity; hourly and resource-level data billed extra per usage record |
| Azure Cost Management | Daily, per resource | 8-24 h lag | 13 months UI, 7 years via export | Free in portal |
| GCP BigQuery export | Daily | 24 h lag | Forever, you own the dataset | BigQuery query cost |
One non-obvious rule
The bill you read is behind reality, often by a day or more. Every source here lags by hours at least, and the earliest hours of yesterday are often still being written when you look.
So treat any claim of real-time cost with suspicion. A tool showing live numbers is doing one of two things. It is multiplying current usage by list prices, which gives the rack rate (the published list price, before any discount) rather than what you will be charged. Or it is extrapolating from a day that has not finished. Both are useful; neither is the bill. A well-built tool says which it is doing: in ZopNight the Rack Rate column is calculated from list prices once a day and the Billing Cost column carries the lagged actual figures. (See L1 of M0.4 for the full two-source model.)
2. Demo
The same question, “What did EC2 (AWS’s rented servers) cost yesterday across all my accounts?” answered three ways. You do not need to run these; read them to see how each source is asked.
AWS CUR via Athena (SQL):
SELECT SUM(line_item_unblended_cost) AS cost_usdFROM cur.consolidated_2026_05WHERE line_item_product_code = 'AmazonEC2' AND line_item_usage_start_date >= DATE '2026-05-19' AND line_item_usage_start_date < DATE '2026-05-20';-- → returns $4,217.83 (servers plus their disks, snapshots, NAT gateways and Elastic IPs)AWS Cost Explorer via the CLI (the AWS command-line tool):
aws ce get-cost-and-usage \ --time-period Start=2026-05-19,End=2026-05-20 \ --granularity DAILY \ --metrics UnblendedCost \ --filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Elastic Compute Cloud - Compute"]}}'# → returns $3,391.40 (server hours only)GCP BigQuery (Compute Engine equivalent):
SELECT SUM(cost) AS cost_usdFROM `billing_export.gcp_billing_export_v1_*`WHERE service.description = 'Compute Engine' AND usage_start_time >= TIMESTAMP('2026-05-19') AND usage_start_time < TIMESTAMP('2026-05-20');-- → returns the equivalent for GCEAll three answer roughly the same question, but the two AWS answers do not match, and that is expected. The CUR product code AmazonEC2 also covers the servers’ disks (EBS), snapshots, NAT gateways and Elastic IPs, which Cost Explorer reports separately as “EC2 - Other”. The Cost Explorer filter above counts server hours only. Before you compare two sources, check that they cover the same scope. They differ in what they let you ask next. The CUR through Athena drills furthest, down to a resource ID and how a discount was attributed. Cost Explorer returns quickest. On Google the BigQuery export is the only option, and it is the most flexible of the three if the team is comfortable with SQL.
3. Hands-on (6 min: uses real bill, not ZopNight)
Optional, if you have access to a cloud account’s billing: pick the source that matches your main cloud and pull yesterday’s total spend on virtual machines (EC2 on AWS, Compute Engine on Google, VMs on Azure). If you do not have access, read the queries and answer the question at the end as a thought exercise.
AWS, if CUR is configured:
SELECT SUM(line_item_unblended_cost)FROM <your_cur_database>.<your_cur_table>WHERE line_item_product_code = 'AmazonEC2' AND DATE(line_item_usage_start_date) = CURRENT_DATE - INTERVAL '1' DAY;AWS, if CUR is not configured but Cost Explorer is: Use the CLI block from the Demo, swap dates for yesterday and today.
Azure:
az consumption usage list \ --start-date 2026-05-19 \ --end-date 2026-05-20 \ --query "[?contains(meterCategory, 'Virtual Machines')].pretaxCost" \ -o tsv | awk '{s+=$1} END {print s}'GCP:
SELECT SUM(cost)FROM `<your_dataset>.gcp_billing_export_v1_*`WHERE service.description = 'Compute Engine' AND DATE(usage_start_time) = CURRENT_DATE() - 1;Note the number. Now answer the next question without re-running:
Which resource ID accounts for the most of that spend?
If the original query cannot answer that without modification, the source is too coarse for resource-level work. Re-run at the right grain. CUR with line_item_resource_id, the BigQuery detailed export (gcp_billing_export_resource_v1_*) with resource.global_name, and Azure Cost Management with resource group or resource ID scope are the right paths. The standard export table (gcp_billing_export_v1_*) used in the queries above has no resource-level columns.
4. Knowledge check
Q1
A Finance Partner asks: “How much did our three reserved AWS instances actually cost the company in May?” Which source is right?
A. Cost Explorer at the resource level
B. CUR with reservation/EffectiveCost and pricing/unit columns
C. Azure Cost Management, reading from the AmortizedCost column
D. GCP BigQuery export
Show answer
Correct: B. Only CUR exposes the RI distribution columns that explain how a reservation’s amortized cost is attributed across resources. Cost Explorer can show aggregate RI usage but is not the right source for line-item attribution. C and D are wrong-cloud answers.
Q2
An Azure-focused engineer reports: “Our reserved VM costs $0 in Cost Management. The RI is broken.” What is the most likely true cause?
A. The RI is broken
B. Azure does not report RI cost at all
C. The engineer queried ActualCost. The right column is AmortizedCost
D. Cost Management has a 48-hour lag and the data is not yet posted
Show answer
Correct: C. Reservations show $0 there at subscription scope. Azure’s ActualCost column reflects the moment of purchase, which is when the reservation was bought, not when it was consumed. The AmortizedCost column distributes the purchase across the term and shows the effective daily cost per consuming resource. This is Azure’s most common FinOps trap. (See M0.4 L3 for the full treatment.)
Q3
The team wants to ingest yesterday’s per-resource AWS bill into an external FinOps tool every morning. Which source is built for that?
A. CUR with daily delivery to S3, picked up by a downstream pipeline
B. Cost Explorer (it has an API)
C. The AWS Console screenshot
D. The Cost Anomaly Detection alerts feed, instead of the export
Show answer
Correct: A. CUR is the only AWS source designed for programmatic ingestion at per-resource grain. Cost Explorer’s API exists but is rate-limited, costs $0.01 per request, and its resource-level data covers only the last 14 days. C and D are not data sources.
5. Apply
ZopNight, the cost platform these lessons are built around, reads from these sources behind the scenes. The Billing Sync feature wires AWS Cost Explorer (or, if you opt in, your CUR 2.0 export in S3), Azure Cost Management (amortized) and GCP BigQuery billing exports into one cost table, so reports query one column regardless of provider. Reading the CUR lifts Cost Explorer’s 14-day limit on resource-level detail.
To verify which source is currently feeding your account:
- Settings → Cloud Accounts → an account’s menu → Edit → Cost source. For AWS, Azure and GCP accounts this shows the feed capture chose (for example AWS Cost and Usage Report or AWS Cost Explorer), why, and since when.
- For sync health, ask an AI assistant connected to ZopNight’s MCP server to run
get_billing_sync_status. It returns, per account, the sync state, the last successful sync, the last error and the detected billing currency. - Reports → Summary displays the active cost-source label. ZopNight picks billing cost for each resource and day where billing data exists and falls back to rack rate where it does not, so one report can mix both. The label tells you which you are reading: “Unblended Cost” (AWS, cost as charged on the day, with prepaid discounts not spread out), “Amortized Cost” (Azure), “Net of Credits” (GCP), “Mixed (by provider)” when providers differ, or “Rack Rate” when no billing data is in play.
Open ZopNight Cloud Accounts → (deep link resolves once signed in)
Related lessons
- L1: What’s actually in a cloud bill (previous)
- L3: Granularity vs. timeliness (next)
- T0.M0.4.L3: Amortized cost: Azure’s gotcha
- T4.M4.5.L1: Showback design: pick the dimension
Rule references
RC-002Orphaned EBS Volume: finds disks left unattached (statusavailable) for 7 days or more, from discovery data rather than the bill
Glossary terms touched
CUR · Cost Explorer · Amortized cost · BigQuery billing export · Unblended cost