cost management
90 TopicsNews and updates from FinOps X 2024: How Microsoft is empowering organizations
Last year, I shared a broad set of updates that showcased how Microsoft is embracing FinOps practitioners through education, product improvements, and innovative solutions that help organizations achieve more. with AI-powered experiences like Copilot and Microsoft Fabric. Whether you’re an engineer working in the Azure portal or part of a business or finance team collaborating in Microsoft 365 or analyzing data in Power BI, Microsoft Cloud has the tools you need to accelerate business value for your cloud investments.11KViews8likes0CommentsGetting started with FinOps hubs: Multicloud cost reporting with Azure and Google Cloud
Microsoft’s FinOps hubs offer a powerful and trusted foundation for managing, analyzing, and optimizing cloud costs. Built on Azure Data Explorer (ADX) and Azure Data Lake Storage (ADLS), FinOps hubs provide a scalable platform to unify billing data across providers leveraging FOCUS datasets. In this post, we’ll take a hands-on technical walkthrough of how to connect Microsoft FinOps hubs to Google Cloud, enabling you to export and analyze Google Cloud billing data with Azure billing data directly within your FinOps hub instance. This walk through will focus on using only storage in the hub for accessing data and is designed to get you started and understand multicloud connection and reporting from FinOps hubs. For large datasets Azure Data Explorer or Microsoft Fabric is recommended. With the introduction of the FinOps Open Cost and Usage Specification (FOCUS), normalizing billing data and reporting it through a single-pane-of-glass experience has never been easier. As a long-time contributor to the FOCUS working group, I’ve spent the past few years helping define standards to make multicloud reporting simpler and more actionable. FOCUS enables side-by-side views of cost and usage data—such as compute hours across cloud providers—helping organizations make better decisions around workload placement and right-sizing. Before FOCUS, this kind of unified analysis was incredibly challenging due to the differing data models used by each provider. To complete this technical walk through, you’ll need access to both Azure and Google Cloud—and approximately 2 to 3 hours of your time. Getting started: The basics you’ll need Before diving in, ensure you have the following prerequisites in place: ✅ Access to your Google Cloud billing account. ✅ A Google Cloud project with BigQuery and other APIs enabled and linked to your billing account. ✅ All required IAM roles and permissions for working with BigQuery, Cloud Functions, Storage, and billing data (detailed below). ✅ Detailed billing export and pricing export configured in Google Cloud. ✅ An existing deployment of FinOps hubs. What you’ll be building In this walk-through, you’ll set up Google billing exports and an Azure Data Factory pipeline to fetch the exported data, convert to parquet and ingest into your FinOps hub storage, following the FOCUS 1.0 standard for normalization. Through the process you should end up creating: Configure a BigQuery view to convert detailed billing exports into the FOCUS 1.0 schema. Create a metadata table in BigQuery to track export timestamps to enable incremental data exports reducing file export sizes and avoiding duplicate data. Set up a GCS bucket to export FOCUS-formatted data in CSV format. Deploy a Google Cloud Function that performs incremental exports from BigQuery to GCS. Create a Google Cloud Schedule to automate the export of your billing data to Google Cloud Storage. Build a pipeline in Azure Data Factory to: Fetch the CSV billing exports from Google Cloud Storage. Convert them to Parquet. Ingest the transformed data into your FinOps hub ingestion container. Let's get started. We're going to start in Google Cloud before we jump back into Azure to setup the Data Factory pipeline. Enabling FOCUS exports in Google Prerequisite: Enable detailed billing & pricing exports ⚠️ You cannot complete this guide without billing data enabled in BigQuery if you have not enabled detailed billing exports and pricing exports, do this now and come back to the walk-through in 24 hours. Steps to enabled detailed billing exports and pricing exports: Navigate to Billing > Billing Export. Enable Detailed Cost Export to BigQuery. Select the billing project and a dataset - if you have not created a project for billing do so now. Enable Pricing Export to the same dataset. 🔊 “This enables daily cost and usage data to be streamed to BigQuery for granular analysis.” Detailed guidance and information on billing exports in Google can be found here: Google Billing Exports. What you'll create: Service category table Metadata table FOCUS View - you must have detailed billing export and pricing exports enabled to create this view Cloud Function Cloud Schedule What you'll need An active GCP Billing Account (this is your payment method). A GCP Project (new or existing) linked to your GCP billing account. All required APIs enabled. All required IAM roles enabled. Enable Required APIs: BigQuery API Cloud Billing API Cloud Build API Cloud Scheduler Cloud Functions Cloud Storage Cloud Run Admin Pub/Sub (optional, for event triggers) Required IAM Roles Assign the following roles to your user account: roles/billing.viewer or billing.admin roles/bigquery.dataEditor roles/storage.admin roles/cloudfunctions.admin roles/cloudscheduler.admin roles/iam.serviceAccountTokenCreator roles/cloudfunctions.invoker roles/run.admin (if using Cloud Run) roles/project.editor Create a service account (e.g., svc-bq-focus) Assign the following roles to your service account: roles/bigquery.dataOwner roles/storage.objectAdmin roles/cloudfunctions.invoker roles/cloudscheduler.admin roles/serviceAccount.tokenCreator roles/run.admin Your default compute service account will also require access to run cloud build services. Ensure you apply the cloud build role to your default compute service account in your project, it may look like this: projectID-compute@developer.gserviceaccount.com → roles/cloudbuild.builds.builder Create FOCUS data structure and View This section will create two new tables in Big Query, one for service category mappings and one for metadata related to export times. These are important to get in before we create the FOCUS view to extract billing data in FOCUS format. Create a service category mapping table I removed service category from the original google FOCUS view to reduce the size of the SQL Query, therefore, to ensure we mapped Service category properly, I created a new service category table and joined it to the FOCUS view. In this step we will create a new table using open data to map GCP services to service category. Doing this helps reduce the size of the SQL query and simplifies management of Service Category mapping. Leveraging open source data we can easily update service category mappings if they ever change or new categories are added without impacting the FOCUS view query. Process: Download the latest service_category_mapping.csv from the FOCUS Converter repo Go to BigQuery > Your Dataset > Create Table Upload the CSV Table name: service_category Schema: Auto-detect Create a metadata table This table will be used to track the last time detailed billing data was added to your detailed billing export, we use this to enable incremental exports of billing data through the FOCUS view to ensure we only export the latest set of data and not everything all the time. Process: Go to BigQuery > Create Table Table name: metadata_focus_export Schema: Field Name : Format last_export_time: TIMESTAMP export_message: STRING Enter your field name and then choose field format, do not add : 🔊 “Ensures each export only includes new data since the last timestamp.” Create the FOCUS-aligned view Creating a view in BigQuery allows us to un-nest the detailed billing export tables into the format of FOCUS 1.0*. To use Power BI we must un-nest the tables so this step is required. It also ensures we map the right columns in Google Cloud detailed billing export to the right columns in FOCUS. ⚠️ The FOCUS SQL code provided in this walk-through has been altered from the original Google provided code. I believe this new code is better formatted for FOCUS 1.0 than the original however it does contain some nuances that suit my personal views. Please evaluate this carefully before using this in a production system and adjust the code accordingly to your needs. Steps: Navigate to BigQuery > New Query Paste and update the FOCUS view SQL query which is provided below Replace: yourexporttable with detailed export dataset ID and table name that will look like " yourpricingexporttable with pricing export and table name your_billing_dataset with your detailed export dataset ID and table name FOCUS SQL Query: WITH usage_export AS ( SELECT *, ( SELECT AS STRUCT type, id, full_name, amount, name FROM UNNEST(credits) LIMIT 1 ) AS cud, FROM "Your-Detailed-Billing-Export-ID" -- replace with your detailed usage export table path ), prices AS ( SELECT export_time, sku.id AS sku_id, sku.description AS sku_description, service.id AS service_id, service.description AS service_description, tier.* FROM "your_pricing_export_id", UNNEST(list_price.tiered_rates) AS tier ) SELECT "111111-222222-333333" AS BillingAccountId, "Your Company Name" AS BillingAccountName, COALESCE((SELECT SUM(x.amount) FROM UNNEST(usage_export.credits) x),0) + cost as BilledCost, usage_export.currency AS BillingCurrency, DATETIME(PARSE_DATE("%Y%m", invoice.month)) AS BillingPeriodStart, DATETIME(DATE_SUB(DATE_ADD(PARSE_DATE("%Y%m", invoice.month), INTERVAL 1 MONTH), INTERVAL 1 DAY)) AS BillingPeriodEnd, CASE WHEN usage_export.adjustment_info.type IS NOT NULL and usage_export.adjustment_info.type !='' THEN 'Adjustment' WHEN usage_export.cud.type = 'PROMOTION' AND usage_export.cost_type = 'regular' AND usage_export.cud.id IS NOT NULL THEN 'Credit' WHEN usage_export.sku.description LIKE '%Commitment - 3 years - dollar based VCF%' or usage_export.sku.description LIKE '%Prepay Commitment%' THEN 'Purchase' WHEN usage_export.cud.id IS NOT NULL AND usage_export.cud.id != '' THEN 'Credit' WHEN usage_export.cost_type = 'regular' THEN 'Usage' WHEN usage_export.cost_type = 'tax' THEN 'Tax' WHEN usage_export.cost_type = 'adjustment' THEN 'Adjustment' WHEN usage_export.cost_type = 'rounding_error' THEN 'Adjustment' ELSE usage_export.cost_type END AS ChargeCategory, IF(COALESCE( usage_export.adjustment_info.id, usage_export.adjustment_info.description, usage_export.adjustment_info.type, usage_export.adjustment_info.mode) IS NOT NULL, "correction", NULL) AS ChargeClass, CASE WHEN usage_export.adjustment_info.type IS NOT NULL AND usage_export.adjustment_info.type != '' THEN usage_export.adjustment_info.type ELSE usage_export.sku.description END AS ChargeDescription, CAST(usage_export.usage_start_time AS DATETIME) AS ChargePeriodStart, CAST(usage_export.usage_end_time AS DATETIME) AS ChargePeriodEnd, CASE usage_export.cud.type WHEN "COMMITTED_USAGE_DISCOUNT_DOLLAR_BASE" THEN "Spend" WHEN "COMMITTED_USAGE_DISCOUNT" THEN "Usage" END AS CommitmentDiscountCategory, usage_export.cud.id AS CommitmentDiscountId, COALESCE (usage_export.cud.full_name, usage_export.cud.name) AS CommitmentDiscountName, usage_export.cud.type AS CommitmentDiscountType, CAST(usage_export.usage.amount_in_pricing_units AS numeric) AS ConsumedQuantity, usage_export.usage.pricing_unit AS ConsumedUnit, -- review CAST( CASE WHEN usage_export.cost_type = "regular" THEN usage_export.price.effective_price * usage_export.price.pricing_unit_quantity ELSE 0 END AS NUMERIC ) AS ContractedCost, -- CAST(usage_export.price.effective_price AS numeric) AS ContractedUnitPrice, usage_export.seller_name AS InvoiceIssuerName, COALESCE((SELECT SUM(x.amount) FROM UNNEST(usage_export.credits) x),0) + cost as EffectiveCost, CAST(usage_export.cost_at_list AS numeric) AS ListCost, prices.account_currency_amount AS ListUnitPrice, IF( usage_export.cost_type = "regular", IF( LOWER(usage_export.sku.description) LIKE "commitment%" OR usage_export.cud IS NOT NULL, "committed", "standard"), null) AS PricingCategory, IF(usage_export.cost_type = 'regular', usage_export.price.pricing_unit_quantity, NULL) AS PricingQuantity, IF(usage_export.cost_type = 'regular', usage_export.price.unit, NULL) AS PricingUnit, 'Google'AS ProviderName, usage_export.transaction_type AS PublisherName, usage_export.location.region AS RegionId, usage_export.location.region AS RegionName, usage_export.service.id AS ResourceId, REGEXP_EXTRACT (usage_export.resource.global_name, r'[^/]+$') AS ResourceName, usage_export.sku.description AS ResourceType, COALESCE(servicemapping.string_field_1, 'Other') AS ServiceCategory, usage_export.service.description AS ServiceName, usage_export.sku.id AS SkuId, CONCAT("SKU ID:", usage_export.sku.id, ", Price Tier Start Amount: ", price.tier_start_amount) AS SkuPriceId, usage_export.project.id AS SubAccountId, usage_export.project.name AS SubAccountName, (SELECT CONCAT('{', STRING_AGG(FORMAT('%s:%s', kv.key, kv.value), ', '), '}') FROM ( SELECT key, value FROM UNNEST(usage_export.project.labels) UNION ALL SELECT key, value FROM UNNEST(usage_export.tags) UNION ALL SELECT key, value FROM UNNEST(usage_export.labels) ) AS kv) AS Tags, FORMAT_DATE('%B', PARSE_DATE('%Y%m', invoice.month)) AS Month, usage_export.project.name AS x_ResourceGroupName, CAST(usage_export.export_time AS TIMESTAMP) AS export_time, FROM usage_export LEFT JOIN "Your-Service-Category-Id".ServiceCategory AS servicemapping ON usage_export.service.description = servicemapping.string_field_0 LEFT JOIN prices ON usage_export.sku.id = prices.sku_id AND usage_export.price.tier_start_amount = prices.start_usage_amount AND DATE(usage_export.export_time) = DATE(prices.export_time); 🔊 "This creates a FOCUS-aligned view of your billing data using FOCUS 1.0 specification, this view does not 100% conform to FOCUS 1.0. Create GCS Bucket for CSV Exports Your GCS bucket is where you will place your incremental exports to be exported to Azure. Once your data is exported to Azure you may elect to delete the files in the bucket, the metaata table keeps a record of the last export time. Steps: Go to Cloud Storage > Create Bucket Name: focus-cost-export (or any name you would like) Region: Match your dataset region Storage Class: Nearline (cheaper to use Earline but standard will also work just fine) Enable Interoperability settings > create access/secret key The access key and secret are tied to your user account, if you want to be able to use this with multiple people, create an access key for your service account - recommended for Prod, for this guide purpose, an access key linked to your account is fine. Save the access key and secret to a secure location to use later as part of the ADF pipeline setup. 🔊 “This bucket will store daily CSVs. Consider enabling lifecycle cleanup policies.” Create a Cloud Function for incremental export A cloud function is used here to enable incremental exports of your billing data in FOCUS format on a regular schedule. At present there is no known supported on demand export service from Google, so we came up with this little workaround. The function is designed to evaluate your billing data for last export time that is equals to or greater than the last time the function ran. To do this we look at the export_time column and the metadata table for the last time we ran this. This ensure we only export the most recent billing data which aids in reducing data export costs to Azure. This process is done through the GCP GUI using an inline editor to create the cloud function in the cloud run service. Steps: Go to Cloud Run > Write Function Select > Use Inline editor to create a function Service Name: daily_focus_export Region, the same region as your dataset - in our demo case us-central1 Use settings: Runtime: Python 3.11 (you cannot use anything later than 3.11) Trigger: Optional Authentication: Require Authentication Billing: Request based Service Scaling: Auto-scaling set to 0 Ingress: All Containers: leave all settings as set Volumes: Leave all settings as set Networking: Leave all settings as set Security: Choose the service account you created earlier Save Create main.py file and requirements.txt files through the inline editor For requirements.txt copy and paste the below: functions-framework==3.* google-cloud-bigquery Google-cloud-storage For main.py your function entry point is: export_focus_data Paste the code below into your main.py inline editor window import logging import time import json from google.cloud import bigquery, storage logging.basicConfig(level=logging.INFO) def export_focus_data(request): bq_client = bigquery.Client() storage_client = storage.Client() project_id = "YOUR-ProjectID" dataset = "YOUR-Detailed-Export-Dataset" view = "YOUR-FOCUS-View-Name" metadata_table = "Your-Metadata-Table" job_name = "The name you want to call the job for the export" bucket_base = "gs://<your bucketname>/<your foldername>" bucket_name = "Your Bucket Name" metadata_file_path = "Your-Bucket-name/export_metadata.json" try: logging.info("🔁 Starting incremental export based on export_time...") # Step 1: Get last export_time from metadata metadata_query = f""" SELECT last_export_time FROM `{project_id}.{dataset}.{metadata_table}` WHERE job_name = '{job_name}' LIMIT 1 """ metadata_result = list(bq_client.query(metadata_query).result()) if not metadata_result: return "No metadata row found. Please seed export_metadata.", 400 last_export_time = metadata_result[0].last_export_time logging.info(f"📌 Last export_time from metadata: {last_export_time}") # Step 2: Check for new data check_data_query = f""" SELECT COUNT(*) AS row_count FROM `{project_id}.{dataset}.{view}` WHERE export_time >= TIMESTAMP('{last_export_time}') """ row_count = list(bq_client.query(check_data_query).result())[0].row_count if row_count == 0: logging.info("✅ No new data to export.") return "No new data to export.", 204 # Step 3: Get distinct export months folder_query = f""" SELECT DISTINCT FORMAT_DATETIME('%Y%m', BillingPeriodStart) AS export_month FROM `{project_id}.{dataset}.{view}` WHERE export_time >= TIMESTAMP('{last_export_time}') AND BillingPeriodStart IS NOT NULL """ export_months = [row.export_month for row in bq_client.query(folder_query).result()] logging.info(f"📁 Exporting rows from months: {export_months}") # Step 4: Export data for each month for export_month in export_months: export_path = f"{bucket_base}/{export_month}/export_{int(time.time())}_*.csv" export_query = f""" EXPORT DATA OPTIONS( uri='{export_path}', format='CSV', overwrite=true, header=true, field_delimiter=';' ) AS SELECT * FROM `{project_id}.{dataset}.{view}` WHERE export_time >= TIMESTAMP('{last_export_time}') AND FORMAT_DATETIME('%Y%m', BillingPeriodStart) = '{export_month}' """ bq_client.query(export_query).result() # Step 5: Get latest export_time from exported rows max_export_time_query = f""" SELECT MAX(export_time) AS new_export_time FROM `{project_id}.{dataset}.{view}` WHERE export_time >= TIMESTAMP('{last_export_time}') """ new_export_time = list(bq_client.query(max_export_time_query).result())[0].new_export_time # Step 6: Update BigQuery metadata table update_query = f""" MERGE `{project_id}.{dataset}.{metadata_table}` T USING ( SELECT '{job_name}' AS job_name, TIMESTAMP('{new_export_time}') AS new_export_time ) S ON T.job_name = S.job_name WHEN MATCHED THEN UPDATE SET last_export_time = S.new_export_time WHEN NOT MATCHED THEN INSERT (job_name, last_export_time) VALUES (S.job_name, S.new_export_time) """ bq_client.query(update_query).result() # Step 7: Write metadata JSON to GCS blob = storage_client.bucket(bucket_name).blob(metadata_file_path) blob.upload_from_string( json.dumps({"last_export_time": new_export_time.isoformat()}), content_type="application/json" ) logging.info(f"📄 Metadata file written to GCS: gs://{bucket_name}/{metadata_file_path}") return f"✅ Export complete. Metadata updated to {new_export_time}", 200 except Exception as e: logging.exception("❌ Incremental export failed:") return f"Function failed: {str(e)}", 500 Before saving, update variables like project_id, bucket_name, etc. 🔊 “This function exports new billing data based on the last timestamp in your metadata table.” Test the Cloud Function Deploy and click Test Function Ensure a seed export_metadata.json file is uploaded to your GCS bucket (if you have not uploaded a seed export file the function will not run. Example file is below { "last_export_time": "2024-12-31T23:59:59.000000+00:00" } Confirm new CSVs appear in your target folder Automate with Cloud Scheduler This step will set up an automated schedule to run your cloud function on a daily or hourly pattern, you may adjust the frequency to your desired schedule, this demo uses the same time each day at 11pm. Steps: Go to Cloud Scheduler > Create Job Region: Same region as your Function - This Demo us-central1 Name: daily-focus-export Frequency: 0 */6 * * * (every 6 hours) or Frequency: 0 23 * * * (daily at 11 PM) Time zone: your desired time zone Target: HTTP Auth: OIDC Token → Use service account Click CREATE 🔊 "This step automates the entire pipeline to run daily and keep downstream platforms updated with the latest billing data." Wrapping up Google setup Wow—there was a lot to get through! But now that you’ve successfully enabled FOCUS-formatted exports in Google Cloud, you're ready for the next step: connecting Azure to your Google Cloud Storage bucket and ingesting the data into your FinOps hub instance. This is where everything comes together—enabling unified, multi-cloud reporting in your Hub across both Azure and Google Cloud billing data. Let’s dive into building the Data Factory pipeline and the associated datasets needed to fetch, transform, and load your Google billing exports. Connecting Azure to Google billing data With your Google Cloud billing data now exporting in FOCUS format, the next step is to bring it into your Azure environment for centralized FinOps reporting. Using Data Factory, we'll build a pipeline to fetch the CSVs from your Google Cloud Storage bucket, convert them to Parquet, and land them in your FinOps Hub storage account. Azure access and prerequisites Access to the Azure Portal Access to the resource group your hub has been deployed to Contributor access to your resource group At a minimum storage account contributor and storage blob data owner roles An existing deployment of the Microsoft FinOps Hub Toolkit Admin rights on Azure Data Factory (or at least contributor role on ADF) Services and tools required Make sure the following Azure resources are in place: Azure Data Factory instance A linked Azure Storage Account (this is where FinOps Hub is expecting data) (Optional) Azure Key Vault for secret management Pipeline Overview We’ll be creating the following components in Azure Data Factory Component Purpose GCS Linked Service Connects ADF to your Google Cloud Storage bucket Azure Blob Linked Service Connects ADF to your Hub’s ingestion container Source Dataset Reads CSV files from GCS Sink Dataset Writes Parquet files to Azure Data Lake (or Blob) in Hub's expected format Pipeline Logic Orchestrates the copy activity, format conversion, and metadata updates Secrets and authentication To connect securely from ADF to GCS, you will need: The access key and secret from GCS Interoperability settings (created earlier) Store them securely in Azure Key Vault or in ADF pipeline parameters (if short-lived) 💡 Best Practice Tip: Use Azure Key Vault to securely store GCS access credentials and reference them from ADF linked services. This improves security and manageability over hardcoding values in JSON. Create Google Cloud storage billing data dataset Now that we're in Azure, it's time to connect to your Google Cloud Storage bucket and begin building your Azure Data Factory pipeline. Steps Launch Azure Data Factory Log in to the Azure Portal Navigate to your deployed Azure Data Factory instance Click “Author” from the left-hand menu to open the pipeline editor In the ADF Author pane, click the "+" (plus) icon next to Datasets Select “New dataset” Choose Google Cloud Storage as the data source Select CSV as the file format Set the Dataset Name, for example: gcs_focus_export_dataset Click “+ New” next to the Linked Service dropdown Enter a name like: GCS-Billing-LinkedService Under Authentication Type, choose: Access Key (for Interoperability access key) Or Azure Key Vault (recommended for secure credential storage) Fill in the following fields: Access Key: Your GCS interoperability key Secret: Your GCS interoperability secret Bucket Name: focus-cost-export (Optional) Point to a folder path like: focus-billing-data/ Click Test Connection to validate access Click Create Adjust Dataset Properties Now that the dataset has been created, make the following modifications to ensure proper parsing of the CSVs: SettingValue Column delimiter; (semicolon) Escape character" (double quote) You can find these under the “Connection” and “Schema” tabs of the dataset editor. If your connection fails you may need to enable public access on your GCS bucket or check your firewall restrictions from azure to the internet! Create a Google Cloud Storage metadata dataset To support incremental loading, we need a dedicated dataset to read the export_metadata.json file that your Cloud Function writes to Google Cloud Storage. This dataset will be used by the Lookup activity in your pipeline to get the latest export timestamp from Google. Steps: In the Author pane of ADF, click "+" > Dataset > New dataset Select Google Cloud Storage as the source Choose JSON as the format Click Continue Configure the Dataset Setting Value Name gcs_export_metadata_dataset Linked Service Use your existing GCS linked service File path e.g., focus-cost-export/metadata/export_metadata.json Import schema Set to From connection/store or manually define if needed File pattern Set to single file (not folder) Create the sink dataset – "ingestion_gcp" Now that we’ve connected to Google Cloud Storage and defined the source dataset, it’s time to create the sink dataset. This will land your transformed billing data in Parquet format into your Azure FinOps Hub’s ingestion container. Steps: In Azure Data Factory, go to the Author section Under Datasets, click the "+" (plus) icon and select “New dataset” Choose Azure Data Lake Storage Gen2 (or Blob Storage, depending on your Hub setup) Select Parquet as the file format Click Continue Configure dataset properties Name: ingestion_gcp Linked Service: Select your existing linked service that connects to your FinOps Hub’s storage account. (this will have been created when you deployed the hub) File path: Point to the container and folder path where you want to store the ingested files (e.g., ingestion-gcp/cost/billing-provider/gcp/focus/) Optional Settings: Option Recommended Value Compression type snappy Once configured, click Publish All again to save your new sink dataset. Create the sink dataset – "adls_last_import_metadata" The adls_last_import_metadata dataset (sinks dataset) is the location you use to copy the export time json file from google to azure, this location is sued for the pipeline to check the last time the import of data was run by reading the json file you coped from google and comparing it to the new json file from google Steps: In Azure Data Factory, go to the Author section Under Datasets, click the "+" (plus) icon and select “New dataset” Choose Azure Data Lake Storage Gen2 (or Blob Storage, depending on your Hub setup) Select JSON as the file format Click Continue Configure dataset properties Name: adsl_last_import_metadata Linked Service: Select your existing linked service that connects to your FinOps Hub’s storage account. (this will have been created when you deployed the hub) File path: Point to the container and folder path where you want to store the ingested files (e.g., ingestion/cost/metadata) Build the ADF pipeline for incremental import With your datasets created, the final step is building the pipeline logic that orchestrates the data flow. The goal is to only import newly exported billing data from Google, avoiding duplicates by comparing timestamps from both clouds. In this pipeline, we’ll: Fetch Google’s export timestamp from the JSON metadata file Fetch the last successful import time from the Hub’s metadata file in Azure Compare timestamps to determine whether there is new data to ingest If new data exists: Run the Copy Activity to fetch GCS CSVs, convert to Parquet, and write to the ingestion_gcp container Write an updated metadata file in the hub, with the latest import timestamp Pipeline components: Activity Purpose Lookup - GCS Metadata Reads the last export time from Google's metadata JSON in GCS Lookup - Hub Metadata Reads the last import time from Azure’s metadata JSON in ADLS If Condition Compares timestamps to decide whether to continue with copy Copy Data Transfers files from GCS (CSV) to ADLS (Parquet) Set Variable Captures the latest import timestamp Web/Copy Activity Writes updated import timestamp JSON file to ingestion_gcp container What your pipeline will look like: Step-by-Step Create two lookup activities: GCS Metadata Hub Metadata Lookup1 – GCS Metadata Add an activity called Lookup to your new pipeline Select the Source Dataset: gcs_export_metadata_dataset or whatever you named it earlier This lookup reads the the export_metadata.json file created by your Cloud Function in GCS for the last export time available. Configuration view of lookup for GCS metadata file Lookup2 – Hub Metadata Add an activity called lookup and name it Hub Metadata Select the source dataset: adls_last_import_metadata This lookup reads the last import time data from the hub metadata file to compare it to the last export time from GCS Configuration view of Metadata lookup activity Add conditional logic In this step we will add the condition logic Add activity called If Condition Add the expression below to the condition Go to the Expression tab and paste the following into Dynamic Content: @greater(activity('Lookup1').output.last_export_time, activity('Lookup2').output.last_import_time) Configuration view of If Condition Activity Next Add Copy Activity (If Condition = True) next to True condition select edit and add activity Copy Data Configure the activity with the following details Setting Value Source Dataset gcs_focus_export_dataset (CSV from GCS) Sink Dataset ingestion_gcp (Parquet to ADLS) Merge Files Enabled (reduce file count) Filter Expression @activity('Lookup1').output.firstRow.last_export_time Ensure you add filter expression for filter by last modified. This is important, if you do not add the filter by last modified expression your pipeline will not function properly. Finally we create an activity to update the metadata file in the hub Add another copy activity to your if condition and ensure it is linked to the previous copy activity, this ensure it runs after the import activity is completed. Copy metadata activity settings: Source gcs_export_metadata_dataset Sink adls_last_import_dataset Destination Path ingestion_gcp/metadata/last_import.json This step ensures the next run of your pipeline uses the updated import time to decide whether new data exists. Your pipeline now ingests only new billing data from Google and records each successful import to prevent duplicate processing. Wrapping up – A unified FinOps reporting solution Congratulations — you’ve just built a fully functional multi-cloud FinOps data pipeline using Microsoft’s FinOps Hub Toolkit and Google Cloud billing data, normalized with the FOCUS 1.0 standard. By following this guide, you’ve: ✅ Enabled FOCUS billing exports in Google Cloud using BigQuery, GCS, and Cloud Functions ✅ Created normalized, FOCUS-aligned views to unify your GCP billing data ✅ Automated metadata tracking to support incremental exports ✅ Built an Azure Data Factory pipeline to fetch, transform, and ingest GCP data into your Hub ✅ Established a reliable foundation for centralized, multi-cloud cost reporting This solution brings side-by-side visibility into Azure and Google Cloud costs, enabling informed decision-making, workload optimization, and true multi-cloud FinOps maturity. Next steps 🔁 Schedule the ADF pipeline to run daily or hourly using triggers 📈 Build Power BI dashboards or use templates from the Microsoft FinOps Toolkit visualise unified cloud spend 🧠 Extend to AWS by applying the same principles using the AWS FOCUS export and AWS S3 storage Feedback? Have questions or want to see more deep dives like this? Let me know — or connect with me if you’re working on FinOps, FOCUS, or multi-cloud reporting. This blog is just the beginning of what's possible.2KViews4likes1CommentMicrosoft Cost Management: Billing & trust relationships explained
As a Microsoft Cloud Solution Architect supporting our global enterprise customers on FinOps and Microsoft Cost Management topics I am often involved in conversations with customers explaining the different relationships existing in Microsoft Cost Management. In this article I will shed some light on key relationships for commercial enterprise customers to provide clarity. The knowledge provided in this article can be especially helpful for those customers currently planning to transition from a Microsoft Enterprise Agreement contract towards a Microsoft Customer Agreement. Building blocks for Cost Management relationships Before we start looking into the individual relationships, we need to understand the building blocks of these Cost Management relationships: Billing contracts – Enterprise Agreement (EA) and Microsoft Customer Agreement (MCA) providing legal and commercial terms as well as certain technical capabilities like Billing Roles. Microsoft Azure Consumption Commitment (MACC) as a contractual agreement where an organization commits to spending a certain amount on Azure services over a specified period and might gets a certain discount on their usage. Billing roles provided by each contract like Enterprise Administrator or Billing Account Owner to manage the contract. Tenants based on Microsoft Entra ID providing entities access to Billing Roles and Azure Resources Azure subscriptions as containers to deploy and manage Azure Resources at specific cost. Azure customer price sheet (CPS) determining the current Customer prices for specific Azure Resources. Key Cost Management relationships in EA and MCA contracts Customer to contract Customer Organizations can sign multiple contracts with Microsoft. Even though it is generally advised to have a 1:1 relationship between Customer and Microsoft contract, Customer can theoretically use multiple contracts at the same time. Examples Best example for this is during a transition period from EA to MCA, where the customer has an active EA and an active MCA contract. During M&A activities a customer organization might end up with multiple EA or MCA contracts. Contract to price sheet Every contract is associated with a customer specific price sheet determining the individual customer prices in the agreed billing currency. In MCA the default associated price sheet basically equals the Azure Retail price list in USD. The price sheet can be accessed in the Azure portal. Contract to Microsoft Azure Consumption Commitment (MACC) Customers can sign a MACC. There is usually a 1:1 relationship between a contract and a MACC. As a benefit of this commitment, Customers might get discounted pricing on Azure Resources (usage). This potential discount will be reflected in the price sheet associated with the contract. Contract to billing roles Both EA and MCA provide Customers with roles, that can manage certain aspects of the contract. These roles are called billing roles. Billing roles differ from EA to MCA and are described in detail here for EA and here for MCA. A key difference between EA and MCA is, that Customers can associate any valid work, school, or Microsoft account to EA billing roles but only work accounts of an approved tenant for MCA. Contract to tenant To manage billing roles customers must associate exactly one Entra ID tenant with the contract. This happens at contract setup. Identities within this Tenant can be assigned to billing roles within the contract. Example User Dirk from the contoso.com tenant can be assigned to the EA admin role in Contoso’s EA contract. In MCA this tenant is called the primary billing tenant. Only users from this tenant can be assigned to billing roles within the MCA contract. CAUTION: The tenant global administrator role is above the billing account administrator. Global administrators in a Microsoft Entra ID tenant can add or remove themselves as billing account administrators at any time to the Microsoft Customer Agreement. If you want to assign identities from tenants other than the primary billing tenant, you can add associated billing tenants. NOTE: Even though customers should strive for a single tenant, there is no restriction in how many tenants a customer can create within a contract. Contract to subscription An Azure subscription is a logical container used to provision and manage Azure resources. Resource access is managed by a trust relationship to an Entra ID tenant and billing is managed by a billing relationship to a Microsoft contract (EA or MCA). Every subscription can only have one billing relationship to one contract. This billing relationship can be moved to a different contract based on certain conditions (e.g. in EA/MCA transition scenarios or M&A scenarios). Every contract can manage 5000 subscriptions by default. NOTE: The billing relationship determines the prices for the consumed resources within the subscription. If you have a subscription that is associated with a contract that uses Azure retail prices you pay the retail price. If the associated contract has customer specific prices (e.g. by signing a MACC with applicable discounts), the resources within this subscription will be charged at these prices. Subscription to tenant Every subscription has a 1:1 trust relationship to an Entra ID tenant. This trust relationship determines, which identities can manage the resources within the subscription. Tenant to subscription Every tenant can manage trust relationships with virtually unlimited number of subscriptions. These subscriptions can use billing relationships to multiple contracts which might lead to different prices for resources deployed to these subscriptions. Example Contoso is using a single Entra ID tenant, has just signed a new MACC agreement and is migrating to MCA. At signing the MCA contract, the MACC is associated with the MCA account and not with the EA contract. During an EA to MCA transition period, some subscriptions still have a billing relationship to the old EA contract and others are already moved to the MCA contract. In this situation the same VM in the same region might be charged differently, depending on the subscription the VM is deployed to. If the subscription is still using the EA billing relationship, the price might be different (e.g. due to lack of applied discounts). Summary & key takeaways Relationships on a page Let’s summarize the described relationships: Fully understanding the described relationships can be very helpful in a variety of scenarios, especially when you are currently discussing the transition from an Enterprise Agreement contract towards a Microsoft Customer Agreement. Key takeaways Three key takeaways: Tenants do not determine the price of an Azure resource! Only the billing relationship between a subscription and a contract determines the price of a resource! Customers can use multiple tenants to manage their Azure resources. Only for managing billing roles there is usually a 1:1 relationship between the contract and an Entra ID tenant. Next steps If the provided information was helpful for you: Share this article within your FinOps, Finance and Cloud Competence Center teams. If you need in-depth support for planning the proper relationships and billing structures for your organization, please reach out to your local Microsoft representative. If you have a unified support contract, you can ask your Customer Success Account Manager (CSAM) to reach out to our global team of FinOps experts from the Culture & Cloud Experience (CCX) practice. This article lays the foundation for an upcoming post about how to manage the transition from EA to MCA from a FinOps perspective.1.3KViews4likes1CommentWhat’s new in FinOps toolkit 12 – July 2025
This month, you’ll find support for FOCUS 1.2, autostart in FinOps hubs which can reduce your hub costs, a new page in the Cost summary Power BI report, and various small fixes, improvements, and documentation updates across the board. Read on for details.511Views3likes0CommentsLearn to manage investments and cost efficiency of Azure and AI workloads
As your business and workloads grow, cloud costs can increase. That’s why it’s vital to learn to manage investments and cost efficiency in every step of your Azure workloads, empowering your team with the right skills to manage cloud spending. With self-paced learning in our new Plan on Microsoft Learn titled Manage Azure and AI investments for Cost Efficiency, you’ll be guided through learning milestones to help you build financial best practices into your AI and cloud computing management process and build a culture of cost efficiency. In this blog, we’ll share what you’ll learn in the modules of this Microsoft Learn Plan and show you how to put learning into action with Azure tools that help you combine pricing offers, create a customized cost efficiency plan, and monitor your costs. Azure savings plans help The Carlsberg Group Many industry leaders are already taking advantage of savings plans to help them design and manage cost-optimized workloads across their whole organizations. Take the Carlsberg Group, who built cost efficiency into their migration process resulting in significant savings. With a presence in 150 markets, multinational brewer The Carlsberg Group required a cloud migration process with zero downtime to avoid any production slowdown. By taking advantage of Azure pricing offerings, not only did they save between 40-65% on compute services, but they also built a culture of cost efficiency using FinOps principles and Azure cost management tools. Build a cost efficiency culture with FinOps Learning FinOps best practices gives your organization autonomy over your financial health, allowing you to manage and optimize your cloud spend. FinOps best practices spread cost efficiency responsibilities across your entire organization and requires skilling to be effective. “The solution supports financial awareness,” said Sonal Gupta, Senior Manager of FinOps and Hosting Service Delivery at The Carlsberg Group. “Everyone feels accountable for their cloud spend and avoids additional costs. We can budget and forecast, investing in just what we need, without cost spikes.” With the FinOps focused modules in our Plan on Microsoft Learn, Manage Azure and AI investments, you’ll explore how to get started with FinOps and then apply your learning to adopt FinOps principles on Azure. With an interactive guide in the Microsoft Learn modules, you’ll see how to manage cost and usage in the Azure Portal. Learn about opportunities to save Beyond FinOps best practices, Azure offers a variety of cost-saving options, from Azure Reservations to Azure Savings Plan for Compute to Azure OpenAI Service Provisioned Reservation, that allow you to optimize your cloud budget based on your workload needs, and Azure Hybrid Benefit, that builds savings into your migration process. Let’s look at each offering and see how to best match them to your business needs. Azure Reserved Instances Azure Reserved Instances are a great way to lock in a price and save for your compute workloads that are stable and have specific requirements like VM type or region. The Carlsberg Group, for example, optimizes Azure costs through a strategic mix of three-year Reserved Instances for stable workloads, one-year Reserved Instances for moderately flexible needs, and three-year Savings Plans for dynamic or seasonal requirements. Learn more with the “Save money with Azure Reserved Instances” module, with guidance on: How to use Azure Advisor and the Cost Management app to identify use patterns and get purchase recommendation. View and manage your reservations on Azure Create and analyze reports Azure Savings Plan for Compute For customers that want to save on their workloads but need flexibility across their compute resources or regions, Azure savings plan for compute is a great way to optimize spend. Learn more about Azure savings plan for compute with the “Purchase Azure savings plan for compute” module, which will guide you through: Determining if Azure Savings plan for compute matches your business needs Preparing for and purchasing Azure savings plan for compute Managing and monitoring Azure savings plan for compute Azure Hybrid Benefit Azure Hybrid Benefit is an Azure offer that allows organizations to maximize savings in their migration journey by giving a discount on server licenses and subscriptions and granting outsourcing and hybrid capabilities. The skilling module provides you with step-by-step guidance on how to use AHB, including: Identifying if you are eligible for AHB and understanding how it can benefit you Understand how to apply and manage AHB Combine AHB with other savings programs to get the best price Azure OpenAI Service Provisioned Reservations For Azure OpenAI Service customers, provisioned reservations are an opportunity for additional savings. Customers can save up to 70%* compared to pay-as-you-go with Azure OpenAI Service provisioned reservations. You can learn how to use PTU reservations with the “optimize spend and performance with Azure OpenAI Service provisioned reservations” module. It is designed to help you: Chose when to use a provisioned model Decide how many PTUs to reserve Manage and deploy your reservations Monitor costs with Microsoft Cost Management Cost information is an important factor for any cloud decision and Microsoft Azure has tools to help you pick the best offerings for your organization and continually monitor and optimize them. Learn how to make informed budget decisions with cost management training on: Cost management on Azure: Use the pricing calculator to estimate the cost of your workloads on Azure. Learn the functions of Microsoft Cost Management like monitoring your budget and sending you alerts when you are close to exceeding it and visualizing your Azure spending. Use resource tags to organize your cloud resource and manage costs. Introduction to analyzing costs and creating budgets with Microsoft Cost Management: Apply cost management principles using Microsoft Cost Management. Learn how to use cost analysis to find trends in your cloud spending. Build a budget and create alerts to help you manage your expenses. Get started Whether you’ve just started your migration journey or want to reduce your cloud costs, Azure has cost savings offerings and trainings. Manage Azure and AI investments Learn Plan walks you through building a FinOps culture, selecting and combining Azure strategic pricing offerings, and using Microsoft Cost Management to monitor and manage your budget. _________________________________________________________ * The 70% savings is based on the GPT-4o Global provisioned throughput hourly rate of approximately $1/hour, compared to the reduced rate of a 1-year reservation at approximately $0.3028/hour. Azure pricing as of January 1, 2025 (prices subject to change. Actual savings may vary depending on the specific Large Language Model and region availability.)932Views3likes0CommentsUnderstanding Cloud Cost Fluctuations with Power BI
Staying on top of your cloud costs requires regular reviews. There are many ways to slice and dice your cloud costs; one approach I find helpful is comparing daily and monthly cost deltas. Below is a visual from my Power BI report showing how my previous month’s costs compare to the month prior. The visual is filtered to only show delta increases/decreases over $1K. I can quickly see we spent $5K more on Azure SQL Database in the selected month compared to the previous month. I call this my 'large cost swings' graph. I understand that everything is not linear, nor do things translate nicely from one day or month to the next. However, the data has a story to tell. What I ask my team to focus on is the story the data is telling. In this case, we made some modifications to ADF and SQL, leading to a $4K net reduction in costs. Some stories explain the outcome of one or more actions. Then there are those stories which can help shape your future consumption and spending.425Views3likes9CommentsUnlock Cost Savings with Azure AI Foundry Provisioned Throughput reservations
In the ever-evolving world of artificial intelligence, businesses are constantly seeking ways to optimize their costs and streamline their operations while leveraging cutting-edge technologies. To help, Microsoft recently announced Azure AI Foundry Provisioned Throughput reservations which provide an innovative solution to achieve both. This offering is coming soon and will enable organizations to save significantly on their AI deployments by committing to specific throughput usage. Here’s a high-level look at what this offer is, how it works, and the benefits it brings. What are Azure AI Foundry Provisioned Throughput reservations? Prior to this announcement, Azure reservations could only apply to AI workloads running Azure OpenAI Service models. These Azure reservations were called “Azure OpenAI Service Provisioned reservations”. Now that more models are available on Azure AI Foundry and Azure reservations can apply to these models, Microsoft launched “Azure AI Foundry Provisioned Throughput reservations”. Azure AI Foundry Provisioned Throughput reservations is a strategic pricing offer for businesses using Provisioned Throughput Units (PTUs) to deploy AI models. Reservations enable businesses to reduce AI workload costs on predictable consumption patterns by locking in significant discounts compared to hourly pay-as-you-go pricing. How It Works The concept is simple yet powerful: instead of paying the PTU hourly rate for your AI model deployments, you pre-purchase a set quantity of PTUs for a specific term—either one month or one year in a specific region and deployment to receive a discounted price. The reservation applies to the deployment type (e.g., Global, Data Zone, or Regional*), and region. Azure AI Foundry Provisioned Throughput reservations are not model dependent, meaning that you do not have to commit to a model when purchasing. For example, if you deploy 3 Global PTUs in East US, you can purchase 3 Global PTU reservations in East US to significantly reduce your costs. It’s important to note that reservations are tied to deployment types and region, meaning a Global reservation won’t apply to Data Zone or Regional deployments and East US reservation won’t apply to West US deployments. Key Benefits Azure AI Foundry Provisioned Throughput reservations offer several benefits that make them an attractive option for organizations: Cost Savings: By committing to a reservation, businesses can save up to 70% compared to hourly pricing***. This makes it an ideal choice for production workloads, large-scale deployments, and steady usage patterns. Budget Control: Reservations are available for one-month or one-year terms, allowing organizations to align costs with their budget goals. Flexible terms ensure businesses can choose what works best for their financial planning. Streamlined Billing: The reservation discount applies automatically to matching deployments, simplifying cost management and ensuring predictable expenditures. How to Purchase a reservation Purchasing an Azure AI Foundry Provisioned Throughput reservation is straightforward: Sign in to the Azure Portal and navigate to the Reservations section. Select the scope you want the reservation to apply to (shared, management group, single subscription, single resource group) Select the deployment type (Global, Data Zone, or Regional) and the Azure region you want to cover. Specify the quantity of PTUs and the term (one month or one year). Add the reservation to your cart and complete the purchase. Reservations can be paid for upfront or through monthly payments, depending on your subscription type. The reservation begins immediately upon purchase and applies to any deployments matching the reservation's attributes. Best Practices Important: Azure reservations are NOT deployments —they are entirely related to billing. The Azure reservation itself doesn’t guarantee capacity, and capacity availability is very dynamic. To maximize the value of your reservation, follow these best practices: Deploy First: Create your deployments before purchasing a reservation to ensure you don’t overcommit to PTUs you may not use. Match Deployment Attributes: Ensure the scope, region, and deployment type of your reservation align with your actual deployments. Plan for Renewal: Reservations can be set to auto-renew, ensuring continuous cost savings without service interruptions. Monitor and manage: Post purchase of reservations it is important to regularly monitor your reservation utilization and setup budget alerts. Exchange reservations: Exchange your reservations if your workloads change throughout your term. Why Choose Azure AI Foundry Provisioned Throughput reservations? Azure AI Foundry Provisioned Throughput reservations are a perfect blend of cost efficiency and flexibility. Whether you’re deploying AI models for real-time processing, large-scale data transformations, or enterprise applications, this offering helps you reduce costs while maintaining high performance. By committing to a reservation, you can not only save money but also streamline your billing and gain better control over your AI expenses. Conclusion As businesses continue to adopt AI technologies, managing costs becomes a critical factor in ensuring scalability and success. Azure AI Foundry Provisioned Throughput reservations empower organizations to achieve their AI goals without breaking the bank. By aligning your workload requirements with this innovative offer, you can unlock significant savings while maintaining the flexibility and capabilities needed to drive innovation. Ready to get started? Learn more about Azure reservations and be on the lookout for Azure AI Foundry Provisioned Throughput reservations to be available to purchase in your Azure portal and get started with Additional Resources: What are Azure Reservations? - Microsoft Cost Management | Microsoft Learn Azure Pricing Overview | Microsoft Azure Azure Essentials | Microsoft Azure Azure AI Foundry | Microsoft Azure *Not all models will be available regionally. **Not all models will be available for Azure AI Foundry Provisioned Throughput reservations. *** The 70% savings is based on the GPT-4o Global provisioned throughput Azure hourly rate of approximately $1/hour, compared to the reduced rate of a 1-year Azure reservation at approximately $0.3027/hour. Azure pricing as of May 1, 2025 (prices subject to change. Actual savings may vary depending on the specific Large Language Model and region availability.)2.7KViews2likes0Comments