Skip to content
NewFinOps, Kubernetes and AI, on one platform
Book a demo
UnitVane docs
API referenceReleases

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

  1. 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

  2. 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

  3. 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

  4. 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

In Integrations > Connect a cloud, choose your cloud and select Keyless. You create no key, password or secret. Your cloud checks that the request is for your organization ID, which KubeOn sets and no organization can change. The page fills that ID into the commands.

1. Create the export

Billing and Cost Management
# 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

Only CUR 2.0 has the 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.

CloudFormation
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 text

What the role can do

Policy
{  "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

Trust policy
{  "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 itemCost used
Savings Plan covered usagesavings_plan_net_savings_plan_effective_cost
Reserved Instance usagereservation_net_effective_cost
Everything elseline_item_net_unblended_cost
Credits, refunds, EDP and bundled discounts, taxIncluded as their own lines
Need help with this guide?Contact support