CSV Pricing
Kubecost allows users to apply custom prices to individual assets (e.g. nodes) via a CSV pipeline. Common uses are for on-premises clusters such as OpenShift, service-providers, or for external enterprise discounts. This feature allows for greater resource specification than is provided by Custom Pricing. This page shows how to create and configure a CSV pricing file.
Depending on which version of Kubecost you are running, there may be differing options available. Kubecost starting in version 2.8 supports a revamped custom pricing experience with greater flexibility and control. Kubecost prior to 2.8 has more limited options.
CSV Pricing 2.8+
Kubecost 2.8+ comes with an overhauled CSV-based pricing experience referred to as Enterprise Custom Pricing (ECP) going forward. Follow this section if you are on 2.8 or greater.
Enterprise Custom Pricing (ECP) gives Kubecost users the ability to define custom pricing for their infrastructure, according to properties of their own choosing. Currently, ECP uses CSV pricing specs to define custom pricing. Compared to simple custom pricing, which requires configuration on each and every cluster, ECP provides an enterprise-grade solution that only needs to be configured on the primary in multi-cluster federated environment.
-
ECP gives the ability to price many asset classes (nodes, volumes, and load balancers), as well as the containers that have those asset resources allocated to them.
-
ECP only needs to be configured on the primary cluster, but can apply pricing to all clusters in a federated multi-cluster environment.
-
ECP supports “retroactive pricing” meaning that users can overwrite their historical costs according to a freshly constructed price spec.Note: Retroactive pricing is currently an implicit option and cannot be deactivated. This will be configurable in a later version.
Enabling ECP is fairly simple. Just create a spec, load it into a ConfigMap, and enable the feature in Helm. To learn more about how to set up ECP, follow the instructions below.
Setup and Configuration
Learn how to set up and configure a pricing specification.
In the next section we’ll cover detailed instructions for building a pricing spec. For now, we’ll show an example and briefly explain each column.
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | node | a4 | hour | 0.24 | |||
| v1 | node | a4 | na-east-2 | hour | 0.26 | ||
| v1 | node | my.org/instance | a6 | cpucorehour | 0.06 | ||
| v1 | node | my.org/instance | a6 | ramgbhour | 0.006 | ||
| v1 | gpu | T4 | hour | 3.82 | |||
| v1 | volume | standard | gbhour | 0.05 | |||
| v1 | volume | __local__ | gbhour | 0.08 | |||
| v1 | loadbalancer | hour | 0.42 |
-
Version: specifies the pricing spec version (for now, always
v1) -
AssetClass: specifies the category of asset to price (
node,gpu,volume, orloadbalancer) -
InstanceType: specifies an asset’s instance type, which varies by asset class.
-
For nodes, this comes from the known label
node.kubernetes.io/instance-type. -
For GPUs, this comes from the
nvidia.com/gpu.productlabel -
For volumes, this comes from the volume
spec.StorageClass
-
-
Region: specifies a value of the
topology.kubernetes.io/regionlabel for nodes and GPUs -
LabelName: specifies a custom label name to use to specify nodes and GPUs.
-
LabelValue: specifies a value of the associated LabelName.
-
Unit: specifies the unit to price.
-
For nodes, this can be
hourorcpucorehourandramgbhour -
For GPUs, this is
hour -
For volumes, this is
gbhour -
For load balancers, this is
hour
-
-
PricePerUnit: specifies the price of the given unit for the specified assets
Generally, Version, AssetClass, Unit and PricePerUnit are required for each row. Additionally, at least one mode of further specification is required – InstanceType or LabelName and LabelValue. In the section, below, Building a price spec, we share more details about which combinations of columns are valid for each asset class.
Once a CSV has been defined per the pricing spec. options, it must be provided to Kubecost via a ConfigMap. The ConfigMap can be pre-created or defined as part of your Helm values. To pre-create a ConfigMap in the kubecost namespace, see the below command.
kubectl create configmap -n kubecost kubecost-enterprise-pricing --from-file spec.csv
If your Kubecost install is not in namespace kubecost, make sure you alter the above command. We recommend using the default ConfigMap name given above, but you may change it. Just be certain to enter that name in the Helm values, below.
To enable enterprise custom pricing, use the enterpriseCustomPricing values. Enable the feature by setting the enabled value to true. Additionally, make sure that your configMapName matches and location.URI match the ConfigMap and spec file names used previously.
enterpriseCustomPricing:
enabled: true
configMapName: kubecost-enterprise-pricing
location:
URI: /var/configs/enterprise-pricing/spec.csv
kubecostAggregator:
useDBv3: true
Note that in version 2.8 you must additionally set kubecostAggregator.useDBv3 to true. In future releases, that setting will become the default.
Building a Pricing Spec
Above, we saw an example of a pricing spec. Here, we will give detailed instructions on how to create a new pricing spec from scratch.
Begin with the following empty CSV template.
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 |
Each row in the CSV will represent pricing for a particular instance type (e.g. “m5.large”, “standard”) of the given asset class (e.g. “node”, “volume”). Below we will provide instruction and examples for how to define pricing and identifying information for each asset class.
Nodes
-
Version (
v1) -
AssetClass (
node) -
Unit (
hourorcpucorehourandramgbhour) -
PricePerUnit
hour, and the total hourly price of the entire node as PricePerUnit. Second, you can supply two rows, one for CPU (Unit cpucorehour) and one for RAM (Unit ramgbhour), each with PricePerUnit set to the hourly price of a single CPU and a single RAM GiB, respectively.For example, for a node of InstanceType a4 with 4 CPUs and 8 GiB RAM, the following two methods express the same total hourly price:
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | node | a4 | hour | 0.24 |
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | node | cpucorehour | 0.05 | ||||
| v1 | node | ramgbhour | 0.005 |
Users can identify categories of nodes for pricing according to three methods: InstanceType, InstanceType and Region, or Label. If a node matches multiple rows in the spec, then we have to decide on the priority of the spec rows. The priority, from highest to lowest, is Label, then InstanceType and Region, then InstanceType.
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | node | a4 | hour | 0.24 |
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | node | a4 | na-east-1 | hour | 0.26 |
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | node | nodeclass | a4 | hour | 0.28 |
Given the above example, a node that matches all three of the spec rows will be assigned an hourly price of 0.28, as Label is the strongest match.
GPUs
-
Version (
v1) -
AssetClass (
gpu) -
Unit (
hour) -
PricePerUnit
nvidia.com/gpu.product. For custom labels, users can provide their own label name (instead of nvidia.com/gpu.product) and value.| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | gpu | t4 | hour | 3.78 |
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | gpu | my.org/gpu | t4 | hour | 4.12 |
For any given GPU, the priority is to use Label first, then InstanceType. So, given the above example, a GPU that matches both of the spec rows will be assigned an hourly price of 4.120, as Label is the strongest match.
Volumes
-
Version (
v1) -
AssetClass (
volume) -
Unit (
gbhour) -
PricePerUnit
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | volume | g2 | gbhour | 0.005 |
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | volume | g2 | na-east-1 | gbhour | 0.006 |
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | volume | storageclass | g4 | gbhour | 0.008 |
Pricing volumes with the GB-hour unit means that the total resultant cost of a volume will be computed as VolumeCapacity * Hours * PricePerUnit, e.g., a 100 GB volume that runs for 24 hours at a price-per-unit of 0.008 will cost 100.0 * 24.0 * 0.008 = 19.20.For any given volume, the priority is to use Label first, then InstanceType and Region, then InstanceType. So, given the above example, a volume that matches all three of the spec rows will be assigned a per-GB hourly price of 0.008, as Label is the strongest match.
Load Balancers
-
Version (
v1) -
AssetClass (
loadbalancer) -
Unit (
hour) -
PricePerUnit
| Version | AssetClass | InstanceType | Region | LabelName | LabelValue | Unit | PricePerUnit |
|---|---|---|---|---|---|---|---|
| v1 | loadbalancer | hour | 0.4 |
Troubleshooting
Read the following section for some troubleshooting information.
It is possible to enable Enterprise Custom Pricing, but to have an invalid or unparsable pricing spec. In that case, Kubecost will not utilize custom pricing. To check if this is the case, you can look on the Settings page, under Enterprise Custom Pricing, and you can look for error logs.
A successful Kubecost installation with Enterprise Custom Pricing enabled will look as below.
The logs for a successful install will show the following.
INF enterprise custom pricing: enabled, will load from CSV file at '/var/configs/enterprise-pricing/pricing.csv'
INF enterprise custom pricing: successfully loaded CSV
INF enterprise custom pricing: clearing pricing records not matching spec checksum 0d567e56e35a109534a3629ae2d5da81
Missing Enterprise Key
Enterprise Custom Pricing is an enterprise feature, so deploying with the feature enabled, but without a valid enterprise key, will lock the application.
To enable Enterprise Custom Pricing, be sure to sign up for an enterprise contract and include your product key during installation.
Invalid Pricing Spec.
If your pricing spec is invalid, you will see warning messages on the Settings page.
You will also see logs, reflecting the same information.
INF enterprise custom pricing: enabled, will load from CSV file at '/var/configs/enterprise-pricing/pricing.csv'
INF enterprise custom pricing: successfully loaded CSV
ERR enterprise custom pricing: pricing spec is invalid with 2 issues:
line 9: invalid node pricing: node pricing must use total pricing (unit 'hour') or resource pricing (units 'cpucorehour' and 'ramgbhour')
line 10: invalid node pricing: node pricing must use total pricing (unit 'hour') or resource pricing (units 'cpucorehour' and 'ramgbhour')
Unparsable Pricing Spec.
It may be the case that the pricing spec cannot be parsed at all. In that case, you will see an error on the Settings page.
You will also see logs, reflecting the same errors.
INF enterprise custom pricing: enabled, will load from CSV file at '/var/configs/enterprise-pricing/pricing.csv'
ERR enterprise custom pricing: failed to load CSV: failed to parse file '/var/configs/enterprise-pricing/pricing.csv': error reading pricing spec CSV file '/var/configs/enterprise-pricing/pricing.csv': record on line 2: wrong number of fields
CSV Pricing Pre-2.8
For versions of Kubecost prior to 2.8, the previous version of CSV custom pricing is documented below.
Creating a pricing file
-
Create a CSV file in this format (also in the below table). CSV changes are picked up hourly by default.
-
EndTimeStamp: currently unused -
InstanceID: identifier used to match asset -
Region: filter match based on topology.kubernetes.io/region -
AssetClass: node pv, gpu are supported -
InstanceIDField: field in spec or metadata that will contain the relevant InstanceID. For nodes, often spec.providerID , for pv’s often metadata.name -
InstanceType: optional field to define the asset type, e.g. m5.12xlarge -
MarketPriceHourly: hourly price to charge this asset -
Version: field for schema version, currently unused
-
If the node label topology.kubernetes.io/region is present, it must also be in the Region column.
GPU pricing
- The node the GPU is attached to must be matched by a CSV node price. Typically this will be matched on instance type (node.kubernetes.io/instance-type)
- Supported GPU labels are currently:
- gpu.nvidia.com/class
- nvidia.com/gpu_type
- Verification:
- Connect to the Kubecost Prometheus:
kubectl port-forward --namespace kubecost services/kubecost-cost-analyzer 9090:9090 - Run the following query:
curl localhost:9090/model/prometheusQuery?query=node_gpu_hourly_cost- You should see output similar to this:
{instance="ip-192-168-34-166.us-east-2.compute.internal",instance_type="test.xlarge",node="ip-192-168-34-166.us-east-2.compute.internal",provider_id="aws:///us-east-2b/i-055274d3576800444",region="us-east-2"} 10 | YOUR_HOURLY_COST
- You should see output similar to this:
- Connect to the Kubecost Prometheus:
Kubecost configuration
Provide a file path for your CSV pricing data in your values.yaml . This path can reference a local PV or an S3 bucket.
pricingCsv:
enabled: true
location:
provider: "AWS|GCP"
region: "us-east-1"
URI: s3://YOUR_BUCKET/path/custom-pricing.csv
csvAccessCredentials: pricing-schema-access-secret
Alternatively, mount a ConfigMap with the CSV:
kubectl create configmap csv-pricing --from-file custom-pricing.csv
Then set the following Helm values:
pricingCsv:
enabled: true
location:
URI: /var/kubecost-csv/custom-pricing.csv
csvAccessCredentials: ""
extraVolumes:
- name: kubecost-csv
configMap:
name: csv-pricing
extraVolumeMounts:
- name: kubecost-csv
mountPath: /var/kubecost-csv
For S3 locations, provide file access. Required IAM permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:List*"
],
"Resource": [
"arn:aws:s3::: /*",
"arn:aws:s3::: "
]
}
]
}
There are two options for adding the credentials to the Kubecost pod:
- Service key: Create an S3 service key with the permissions above, then add its ID and access key as a K8s secret:
-
kubectl create secret generic pricing-schema-access-secret -n kubecost --from-literal=AWS_ACCESS_KEY_ID=id --from-literal=AWS_SECRET_ACCESS_KEY=key - The name of this secret should be the same as
csvAccessCredentialsin values.yaml above
-
- AWS IAM (IRSA) service account annotation
Pricing discounts
Negotiated discounts are applied after cost metrics are written to Prometheus. Discounts will apply to all node pricing data, including pricing data read directly from the custom provider CSV pipeline. Additionally, all discounts can be updated at any time and changes are applied retroactively.
Pricing inference
The following logic is used to match node prices accurately:
- First, search for an exact match in the CSV pipeline
- If an exact match is not available, search for an existing CSV data point that matches region, instanceType, and AssetClass
- If neither is available, fall back to pricing estimates
You can check a summary of the number of nodes that have matched with the CSV by visiting /model/pricingSourceCounts. The response is a JSON object of the form:
{
"code": 200,
"status": "success",
"data": {
"TotalNodes": 10,
"PricingType": {
"csvExact": 5, // exact matches by the providerID field
"csvClass": 4, // matches where the region and instanceType match
"": 1 // matches that use our default pricing
}
}
}