Connect AWS billing
Give KubeOn read access to your CUR 2.0 export. Billing is queried in place with Amazon Athena, and the role's only write is the query results.
Who does what
How AWS data reaches KubeOn
- 01
Billing export
CUR 2.0 Data Export with resource IDs and split cost allocation data, cataloged in AWS Glue, queried with Amazon Athena.
Your billing account
- 02
Read-only access
A role deployed with CloudFormation in your billing account, assumed with an ExternalId. It reads the export, and its only write is Athena query output.
KubeOn billing role
- 03
Cluster snapshots
A cluster token from KubeOn. No IAM role: the agent sends snapshots to your organization in KubeOn and nowhere else.
Agent in each cluster
- 04
Allocate + reconcile
The hub joins hourly snapshots with the bill, allocates each node's cost to pods, teams and labels, and reconciles every cluster to its bill.
KubeOn hub
The KubeOn agent never changes your clusters. In your AWS account, the billing role's only write is Athena query output.
On this page
No keys needed
1. Create the export
# In Billing and Cost Management:# 1. Cost Management preferences > enable "Split cost allocation data" for Amazon EKS# 2. Data Exports > Create > Standard data export > CUR 2.0# - Include resource IDs: on# - Split cost allocation data: on# - Format: Parquet, delivered daily to s3://acme-cur-exports/# 3. Catalog the export with an AWS Glue crawler (or reuse your existing CUR table)Why split cost allocation data
split_line_item_* columns. With them, KubeOn uses AWS's own per-pod cost and cross-checks its allocation. Without them, allocation still works from the agent's measurements.2. Deploy the billing role
Deploy the stack in the account that owns the export, usually the management (payer) account. Keep the ExternalId; you will paste it into KubeOn.
aws cloudformation deploy \ --stack-name kubeon-billing-access \ --template-url https://downloads.kubeon.io/aws/kubeon-billing-access.yml \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides \ KubeOnAccountId=444455556666 \ KubeOnRoleName=kubeon-prod-hub-task \ ExternalId="$(openssl rand -hex 24)" \ CurBucketName=acme-cur-exports \ AthenaResultsBucketName=acme-athena-results \ GlueDatabaseName=cur_database \ AthenaWorkgroupName=primaryaws cloudformation describe-stacks --stack-name kubeon-billing-access \ --query "Stacks[0].Outputs[?OutputKey=='RoleArn'].OutputValue" --output textWhat the role can do
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AthenaQuery", "Effect": "Allow", "Action": [ "athena:StartQueryExecution", "athena:GetQueryExecution", "athena:GetQueryResults", "athena:StopQueryExecution", "athena:GetWorkGroup" ], "Resource": "arn:aws:athena:us-east-1:111122223333:workgroup/primary" }, { "Sid": "GlueCatalogRead", "Effect": "Allow", "Action": [ "glue:GetDatabase", "glue:GetDatabases", "glue:GetTable", "glue:GetTables", "glue:GetPartition", "glue:GetPartitions" ], "Resource": [ "arn:aws:glue:us-east-1:111122223333:catalog", "arn:aws:glue:us-east-1:111122223333:database/cur_database", "arn:aws:glue:us-east-1:111122223333:table/cur_database/*" ] }, { "Sid": "CurBucketRead", "Effect": "Allow", "Action": ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"], "Resource": ["arn:aws:s3:::acme-cur-exports", "arn:aws:s3:::acme-cur-exports/*"] }, { "Sid": "AthenaResultsReadWrite", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:AbortMultipartUpload", "s3:ListBucket", "s3:GetBucketLocation" ], "Resource": ["arn:aws:s3:::acme-athena-results", "arn:aws:s3:::acme-athena-results/*"] }, { "Sid": "STSAccess", "Effect": "Allow", "Action": "sts:GetCallerIdentity", "Resource": "*" } ]}Who can assume it
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::444455556666:role/kubeon-prod-hub-task" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "<external-id>" } } } ]}3. Add the integration
In Settings > Integrations, choose AWS and enter the Athena region, database, table, workgroup and results bucket, the role ARN and the ExternalId. Test integration runs a small query. On save, KubeOn backfills 180 days in 3-day chunks.
Partition keys
Set the CUR 2.0 partition keys (source account, report name, data partition, billing period) in the integration. Athena then reads only the partitions it needs, which keeps each daily query small and cheap.
Cost basis
| Line item | Cost used |
|---|---|
| Savings Plan covered usage | savings_plan_net_savings_plan_effective_cost |
| Reserved Instance usage | reservation_net_effective_cost |
| Everything else | line_item_net_unblended_cost |
| Credits, refunds, EDP and bundled discounts, tax | Included as their own lines |