Tutorial Part 7: FinOps
Integrate financial data to add cost intelligence to your blueprint.
Part 7: FinOps - Cost Intelligence as Code
Goal: In this section, we will integrate financial data into our blueprint to enable cost analysis and optimization. We will enrich our resources with cost data, use templates to calculate costs, and propagate those costs up to the applications that consume them, enabling a calculation of Total Cost of Ownership (TCO).
Step 25: Ingesting Financial Data
First, let’s create a “cost catalog”. This is an external data source that defines unit costs for different resource types. In a real-world scenario, this data might come from your cloud provider’s billing API, your CMDB, or an enterprise procurement system.
Create the following asset file:
data/assets/cost_catalog.csv
name,resource_type,cost,unit,provider
onprem-vm-std,onprem_vm,50,per_core_per_month,dell
oracle-license,database,1000,per_month,oracle-corp
aws-eks-node,managed_k8s_cluster,75,per_node_per_month,aws-cloud
Now, create a model that uses enrich_property to join this cost data onto the corresponding resources in our graph.
data/models/cost.toml
# Rule 1: Enrich on-prem VMs with their cost per core
origin_resource = "onprem_vm"
[[enrich_property]]
from_resource_type = "cost_catalog"
origin_key = "resource_type"
target_key = "resource_type"
property_to_copy = "cost"
match_on = [ { property = "name", value = "onprem-vm-std" } ] # Assuming we have a way to match the catalog entry
# Rule 2: Enrich managed K8s clusters
origin_resource = "managed_k8s_cluster"
[[enrich_property]]
from_resource_type = "cost_catalog"
left_join_key = "resource_type"
right_join_key = "resource_type"
property_to_copy = "cost"
match_on = [ { property = "name", value = "aws-eks-node" } ]
# Rule 3: Enrich Oracle database with its license cost
origin_resource = "database"
[[enrich_property]]
from_resource_type = "cost_catalog"
# Here we can't join on resource_type, so we'll use a more specific match
origin_key = "vendor"
target_key = "provider"
property_to_copy = "cost"
match_on = [ { property = "name", value = "oracle-license" } ]
Simplifying for the Tutorial
The `match_on` clauses here are simplified. In a real scenario, you'd likely have a `sku` or `instance_type` property on your resources to perform a more precise join with the cost catalog.
Run rescile-ce serve.
Result: Our hybrid infrastructure resources are now enriched with their unit costs. The blueprint is now “financially aware.”
Step 26: Calculating and Propagating Costs
With unit costs in place, we can use Tera templates to perform calculations. Let’s also add some utilization data to our infrastructure nodes so we have something to calculate with.
Update hosting.toml and infrastructure.toml to add cores and node_count properties.
data/models/hosting.toml (add node_count to the managed_k8s_cluster rule)
# ...
[[create_resource]]
match_on = [
{ property = "type", value = "container" },
# ...
]
resource_type = "managed_k8s_cluster"
# ...
[create_resource.properties]
platform_provider = "aws-cloud"
node_count = 5 # Add this line
# ...
data/models/infrastructure.toml (add cores to the self-hosted onprem_vm rule)
# ...
[[create_resource]]
match_on = [ { property = "type", value = "self-hosted" } ]
resource_type = "onprem_vm"
# ...
[create_resource.properties]
os_provider = "redhat"
hardware_provider = "dell"
cores = 8 # Add this line
# ...
Now, create a new model to perform the cost calculations and use subscribe_property to propagate the results up to the applications.
data/models/cost_calculation.toml
# Rule 1: Calculate total cost for on-prem VMs and subscribe it to the parent application.
origin_resource = "onprem_vm"
# Update the VM node with a calculated total_cost property.
[[create_resource]]
resource_type = "onprem_vm"
name = "{{ origin_resource.name }}"
relation_type = "_COST_CALCULATED"
[create_resource.properties]
total_cost = "{{ origin_resource.cores | default(value=1) * origin_resource.cost | default(value=0) }}"
[[subscribe_property]]
origin_resource_type = "onprem_vm"
origin_property = "total_cost"
target_resource_type = "application"
target_property = "infra_cost"
# Rule 2: Calculate total cost for K8s clusters and subscribe it to the parent application.
origin_resource = "managed_k8s_cluster"
[[create_resource]]
resource_type = "managed_k8s_cluster"
name = "{{ origin_resource.name }}"
relation_type = "_COST_CALCULATED"
[create_resource.properties]
total_cost = "{{ origin_resource.node_count | default(value=1) * origin_resource.cost | default(value=0) }}"
[[subscribe_property]]
origin_resource_type = "managed_k8s_cluster"
origin_property = "total_cost"
target_resource_type = "application"
target_property = "infra_cost"
# Rule 3: Propagate direct costs (like licenses) from databases to applications.
origin_resource = "database"
[[subscribe_property]]
match_on = [ { property = "cost", exists = true } ] # Only for databases with a direct cost
origin_resource_type = "database"
origin_property = "cost"
target_resource_type = "application"
target_property = "direct_cost"
Run rescile-ce serve.
Result: Costs are now calculated at the infrastructure level and propagated to the (application) nodes that consume them. The order-frontend now has an infra_cost of 375 (5 nodes * 75), and the billing-legacy application has a direct_cost of 1000 from its Oracle database.
Step 27: Calculating Total Cost of Ownership (TCO)
The final step is to sum these costs at the application level to calculate a Total Cost of Ownership (TCO).
Add one more rule to your application.toml model.
data/models/application.toml (add this block)
# Calculate TCO by summing the propagated costs.
[[create_resource]]
resource_type = "application"
name = "{{ origin_resource.name }}"
relation_type = "_TCO_CALCULATED"
[create_resource.properties]
# Use Tera's default filter to handle cases where a cost property might not exist.
tco = "{{ origin_resource.infra_cost | default(value=0) + origin_resource.direct_cost | default(value=0) }}"
Run rescile-ce serve.
Result: The graph now contains a calculated tco property for each application, providing a complete financial picture that automatically updates with any architectural or cost catalog changes.
Verify with a query:
query ApplicationTCO {
application {
name
tco
infra_cost
direct_cost
}
}
This shows the power of combining enrich_property, subscribe_property, and Tera templates to build a dynamic FinOps model directly into your hybrid cloud blueprint.
Step 28: Generating a Financial Report
To aggregate these costs and generate a structured financial report for each application. Instead of just adding a tco property to the application itself, we’ll use rescile’s reporting engine to create a new financial_report resource. This pattern is ideal for creating summary artifacts, data exports, or inputs for other systems from your graph data.
Report definitions are the final step in the processing pipeline. They can query the fully built and enriched graph.
Create the following report definition file:
data/reports/financial_report.toml
origin_resource = "application"
# Define some static data available to the template
report_metadata = { version = "1.0", author = "finops-team" }
[[output]]
resource_type = "financial_report"
name = "fin-report-for-{{ origin_resource.name }}"
# The template renders a complete JSON object. The keys and values of this
# object will become the properties of the new 'financial_report' resource.
template = """
{
"report_author": "{{ report_metadata.author }}",
"application_name": "{{ origin_resource.name }}",
"total_cost_of_ownership": {{ origin_resource.infra_cost | default(value=0) + origin_resource.direct_cost | default(value=0) }},
"cost_breakdown": {
"infrastructure_cost": {{ origin_resource.infra_cost | default(value=0) }},
"direct_licensing_cost": {{ origin_resource.direct_cost | default(value=0) }}
}
}
"""
Run rescile-ce serve.
Result: The graph now contains new (financial_report) nodes, one for each application. These nodes hold a structured JSON object with the complete financial picture, which automatically updates with any architectural or cost catalog changes.
query ApplicationFinancialReports {
financial_report {
name
application_name
total_cost_of_ownership
cost_breakdown
}
}
This shows the power of using the reporting engine to create high-level, structured data artifacts from your blueprint, completing the “as-code” FinOps model.