GCP Billing Export 与 GKE Cost Allocation
Cloud Billing 总额突然上升,平台组在 GKE 里能查到 Pod 和 requests,却无法回答费用属于哪个 namespace;数据团队已经打开 standard usage export,BigQuery 里仍没有 Kubernetes 分摊标签。问题不在 SQL:GKE 成本下钻需要同时打开集群 Cost Allocation 和可承载资源明细的 detailed 或 FOCUS export。任意一边缺失,链路都不完整。
GKE Cost Allocation 按资源 requests 拆分支持的 vCPU、内存、GPU、Persistent Disk 和 TPU 费用,并把 cluster、namespace、workload 与 Pod labels 写入 Cloud Billing 数据。它不是实时利用率计费器;BigQuery export 也不是监控系统。正确做法是把账单事实、Kubernetes 分摊和实时指标分别保存,再以稳定的时间窗与口径对账。
两个开关决定数据能否落地
第一条链在 GKE cluster:
Pod requests + owner + labels
↓
GKE Cost Allocation
↓
cluster / namespace / workload billing labels第二条链在 Cloud Billing account:
Cloud Billing usage and credits
↓
Detailed usage export 或 FOCUS usage cost export
↓
BigQuery raw table / immutable linked dataset
↓
adapter view → showback / chargebackStandard usage export 不提供 GKE allocation 明细。Detailed export 的表名形如 gcp_billing_export_resource_v1_<BILLING_ACCOUNT_ID>;FOCUS export 当前是 Preview,创建 Google 管理的 immutable linked dataset,规范交付基线为 FOCUS 1.2。FOCUS 规范自身可能继续演进,不能把规范版本与云厂商当前交付版本混成一句话。
先建立独立 FinOps 项目与数据集
账单 export 覆盖同一 Billing Account 支付的所有项目,不应直接放在普通应用项目。建立独立 FinOps project,固定数据 location、加密和消费权限。Detailed export 需要 Billing Account Costs Manager 或 Billing Account Administrator;项目侧通常需要 BigQuery User,并需具备创建或选择 dataset 的权限。FOCUS linked dataset 的启用边界更高:项目侧需要 Project IAM Admin 与 BigQuery Admin。
先保存身份与项目基线:
gcloud auth list --filter=status:ACTIVE
gcloud config get-value project
gcloud billing projects describe <finops-project-id>
gcloud services enable bigquery.googleapis.com --project=<finops-project-id>
bq --location=<location> mk --dataset <finops-project-id>:billing_detailedDataset location 创建后不能迁移。需要 CMEK 时应在启用 export 前设计 dataset default key;VPC Service Controls、跨区域和组织策略也要先验证。更换 project、dataset 或 location 不会自动把旧账单合并到新位置,消费层必须显式 UNION 并处理重叠窗口。
在 Cloud Billing 的 Billing export 页面选择目标 Billing Account:
启用 Detailed usage cost export,指向自建 dataset。需要跨云规范模型时,单独启用 Preview 的 FOCUS usage cost export,选择 project 和 location。记录 billing account、project、dataset、location、export 类型、启用身份和变更单。
确认系统账号 billing-export-bigquery@system.gserviceaccount.com 被自动加入 dataset。
不要移除该系统账号。它负责创建表并持续写入;移除后 table 停止更新并产生数据丢失风险。若原始表使用 row-level security,必须给这个系统账号建立 FILTER USING (TRUE) 的写入通路,再对人类消费者设置受限策略。
逐集群启用 Cost Allocation
Cost Allocation 面向 GKE Standard cluster。先查询 location、版本、网络模式和当前配置:
gcloud container clusters describe <cluster-name> \
--location=<cluster-location> \
--format='yaml(name,location,currentMasterVersion,costManagementConfig,status)'启用操作需要包含 container.clusters.update 的权限,Billing 管理员不应默认获得集群管理员权限:
gcloud container clusters update <cluster-name> \
--location=<cluster-location> \
--enable-cost-allocation
gcloud container clusters describe <cluster-name> \
--location=<cluster-location> \
--format='value(costManagementConfig.enabled)'预期输出 True。只有集群开关而没有 detailed/FOCUS export,数据不会形成可持久查询的 BigQuery 账本;只有 export 而没有集群开关,表里有 Compute Engine 资源费用,却没有 GKE workload 分摊。
启用后新增行从该时点开始生成,不回填历史。数据最长可能等待三天才出现在 Cloud Billing。FOCUS linked dataset 通常数小时开始出现;若包含当前月和上月回溯,追平可能需要更久。把这些窗口记录为观测信号,不要承诺固定 SLA。
分摊口径来自 requests
GKE 使用 CPU、memory 等 resource requests,而不是 Prometheus 的实际消耗。某 Pod 实际只用 50m CPU,但 request 为 2 core,分摊按 request 反映它占用的可调度容量;反之,没写合理 requests 的高负载 Pod 可能把运行压力留给其他团队,却没有得到相称的 request-based 成本。
当前支持的主要 SKU 包括 VM vCPU、RAM、Custom Extended RAM、GPU、Persistent Disk Capacity 与 Cloud TPU。A4/A4X VM 及其 GPU、绕过 GKE node pool 的外部节点管理方案不支持。Persistent Disk 还受动态 PVC/Generic Ephemeral Volume、驱动、访问模式和至少约 30 分钟存活等条件约束。
因此账单分摊、容量治理和实时效率必须三表并存:
request allocation → 谁预留了容量
usage telemetry → 谁实际消耗了容量
provider billing → 供应商最终收了多少钱创建正向验证工作负载
使用稳定、无敏感信息的 namespace 与 labels:
apiVersion: v1
kind: Namespace
metadata:
name: gke-cost-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: allocation-probe
namespace: gke-cost-lab
spec:
replicas: 2
selector:
matchLabels:
app: allocation-probe
template:
metadata:
labels:
app: allocation-probe
team: platform-lab
cost-center: shared-engineering
spec:
containers:
- name: pause
image: registry.k8s.io/pause:3.10.1
resources:
requests: {cpu: 100m, memory: 64Mi}
limits: {cpu: 100m, memory: 64Mi}kubectl apply -f gke-cost-probe.yaml
kubectl -n gke-cost-lab rollout status deployment/allocation-probe --timeout=120s
kubectl -n gke-cost-lab get pod --show-labels
kubectl -n gke-cost-lab get pod -o custom-columns='NAME:.metadata.name,UID:.metadata.uid,NODE:.spec.nodeName,CPU:.spec.containers[*].resources.requests.cpu,MEM:.spec.containers[*].resources.requests.memory'保存 cluster name、namespace、workload、Pod UID、node、requests、labels 和运行窗口。账单到达后先检查 schema,而不是直接复制固定 SQL:
bq show --schema --format=prettyjson \
'<finops-project-id>:billing_detailed.gcp_billing_export_resource_v1_<billing-account-id>'Detailed export 的 labels 是重复 RECORD。查询 cluster、namespace 和 workload 时,应先展开 labels,再把 credits 作为重复字段聚合。示例骨架:
WITH base AS (
SELECT
usage_start_time,
service.description AS service,
sku.description AS sku,
resource.name AS resource_name,
cost,
IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0) AS credits,
(SELECT value FROM UNNEST(labels) WHERE key = 'goog-k8s-cluster-name') AS cluster_name,
(SELECT value FROM UNNEST(labels) WHERE key = 'k8s-namespace') AS namespace,
(SELECT value FROM UNNEST(labels) WHERE key = 'k8s-workload-type') AS workload_type,
(SELECT value FROM UNNEST(labels) WHERE key = 'k8s-workload-name') AS workload_name
FROM `<project>.<dataset>.gcp_billing_export_resource_v1_<account>`
WHERE usage_start_time >= TIMESTAMP('<window-start>')
AND usage_start_time < TIMESTAMP('<window-end>')
)
SELECT cluster_name, namespace, workload_type, workload_name,
SUM(cost) AS gross_cost,
SUM(credits) AS credits,
SUM(cost + credits) AS net_cost
FROM base
WHERE cluster_name = '<cluster-name>'
GROUP BY 1,2,3,4;预期能看到 gke-cost-lab、Deployment 类型和名称。字段结构应以当前实际 schema 为准;生产报表应读取 adapter view,避免 export 新增列或嵌套结构变化时所有消费者同时失败。
特殊 namespace 是成本证据
无法直接归给业务 workload 的成本不会天然消失:
kube:system-overhead:node capacity 与 allocatable 的差额。kube:unallocated:未被 workload request 或 system overhead 占用的容量。goog-k8s-unknown:Cloud Billing 暂时无法处理的 SKU,节点创建或关闭窗口可能出现。
goog-k8s-unsupported-sku 或空值:当前不支持或未追踪的成本。
打开 CUD discount sharing 时,部分实例成本可能不能完整下钻并进入 unsupported。财务守恒必须把这些类别放在分母里:
allocated workloads
+ system overhead
+ unallocated
+ unknown
+ unsupported
= supported GKE parent cost under the same basis and windowPersistent Disk 成本继承使用它的 Pod labels 与 namespace,直到 PVC 删除。PVC 删除但 PV 按 reclaim policy 保留时,磁盘成本进入 unallocated;直接写在 PVC 上的 labels 不进入 Billing export。存储归属因此要同时检查 Pod、PVC、PV 与 reclaim policy。
反向实验一:移除 requests
在测试 Deployment 中删除 memory request:
kubectl -n gke-cost-lab patch deployment allocation-probe \
--type=json \
-p='[{"op":"remove","path":"/spec/template/spec/containers/0/resources/requests/memory"}]'
kubectl -n gke-cost-lab rollout status deployment/allocation-probe --timeout=120s
kubectl -n gke-cost-lab get pod -o yamlPod 仍可能 Running,但成本分摊语义已经改变。等待相同账单窗口后,对比 workload 行、kube:unallocated 与父资源成本;预期不是 Kubernetes 报错,而是 request-based allocation 发生缺口或偏移。恢复 request 后再次验证新窗口。生产上应由 admission/CI 拒绝缺失 CPU 或 memory requests 的工作负载。
反向实验二:超过标签上限
Pod 超过 50 个 Kubernetes labels 时,这些 Pod labels 全部不会进入 Cloud Billing 控制台或 detailed export,并非只丢第 51 个。不要给生产 Pod 填充垃圾标签;在隔离 namespace 生成 51 个无敏感值的实验标签,保存 Pod YAML 和 export 查询结果,再立即回滚。
预期 cluster、namespace、workload 的系统标签仍按产品规则处理,而 k8s-label/<key> 用户标签集合不出现。标签冲突时 Pod Kubernetes label 值覆盖 VM label 值。这个实验说明成本维度需要中央 allowlist、基数预算和命名治理,不能把任意业务元数据都变成账单列。
FOCUS export 与 detailed export 如何协作
FOCUS export 是 Google 管理的 immutable linked dataset,命名形如 gcp_billing_immutable_<BILLING_ACCOUNT_ID>_<Location>,查询计费但 Google 承担该 linked dataset 的存储。当前 Preview 数据保留约两年;更长审计期要复制到自有表,并承担存储、CMEK、查询、删除与跨境责任。
Detailed export 保留 Google 原生资源和 label 结构,适合 GKE 深度下钻;FOCUS 适合跨云标准化。两者可以共享适配层,但不能假定 GKE 的 namespace/workload 一定出现在某个固定 FOCUS 标准列。保留三层模型:
Google 原始 detailed/FOCUS 表与 delivery metadata。版本化 adapter view,统一字段但保留原始 labels、credits 和 schema version。Kubernetes allocation 表,保存 cluster、namespace、workload、特殊 namespace 和 allocation basis。
FOCUS linked dataset 重启时必须选择原 project 与 location 才能继续使用原不可变 dataset;停用期间不回填。选择新 project 或 region 会创建新 dataset,旧数据不会自动并入。
查询成本和数据容量也要预算
启用 Cost Allocation 不改变 GKE 总成本,却会按 distinct label 与 namespace 组合增加 BigQuery 行数、存储与扫描量。容量估算至少包含:集群数、Pod/Job 每小时新 UID、namespace 数、用户标签基数、export 保留期、分区裁剪率和报表刷新频率。
生产查询必须限制 partition/time window、只选择需要列,并将常用团队聚合物化到受控派生表。给 BigQuery project 设置预算、查询 bytes 上限和审计日志。不能因为 FOCUS linked dataset 存储免费,就忽略每次全表扫描的 compute 费用。
常见失败沿两条链排查
| 现象 | 证据 | 原因 | 处理 |
|---|---|---|---|
| standard export 无 namespace | export 类型 | 产品边界 | 启用 detailed 或 FOCUS export |
| detailed 表有 Compute 行但无 GKE 标签 | costManagementConfig.enabled | 集群未启用 allocation | 逐集群开启并等待新数据,不期待回填 |
| 表停止更新 | dataset IAM 与 last modified | 系统账号被移除 | 恢复 billing-export-bigquery@system.gserviceaccount.com |
| 部分 labels 全缺 | Pod label 数量 | 超过 50 个 | 收敛 allowlist,在新账单窗口复验 |
| namespace 金额偏低 | requests、unknown/unsupported | requests 失真或 SKU 不支持 | 治理 requests,单列未知与不支持项 |
| 磁盘落入 unallocated | PVC/PV/reclaim policy | PVC 删除但 PV/PD 保留 | 查资产生命周期并明确残留 owner |
| 查询费用暴涨 | bytes processed、行数、标签基数 | 全表扫描或高基数 | 分区裁剪、adapter/aggregate、预算门禁 |
| 更换 dataset 后历史断层 | export 配置与两侧时间窗 | 不自动迁移/回填 | 显式 UNION、去重并保存 lineage |
权限、敏感信息和团队职责
集群更新者、Billing export 管理员、BigQuery 原始表管理员、派生视图消费者必须拆开。应用团队不能读取整个 Billing Account 的真实折扣、项目层级和资源 ID,只读取自身 cluster/namespace 的脱敏视图。Billing 管理身份也不应因此获得 Kubernetes 管理权限。
项目名、资源 ID、namespace、workload、labels、credits 与真实成本都可能暴露组织和客户信息。禁止把客户名、邮箱、工单、密钥片段或个人标识写入 Kubernetes labels。BigQuery 使用授权视图、行列级策略、查询审计和最小 dataset 权限;设置 row access policy 时仍要为 export 系统账号保留 TRUE 写入通路。
平台团队维护集群开关、requests 与标签门禁;FinOps 团队维护账单口径、特殊 namespace 分配政策和守恒阈值;数据团队维护 export、adapter、迟到修正和 schema 兼容;业务 owner 只对自身可解释维度负责。把这四类责任都交给一个 dashboard owner,会让数据中断和政策争议无人接管。
升级、回滚与退出
Export schema 或 FOCUS 交付升级时,先创建兼容 view 并双读,比较字段、行数、credits、净成本、特殊 namespace 和迟到修正,再切换报表。不要直接修改唯一生产 SQL,也不要用 FOCUS 最新规范强校验云厂商尚未交付的字段。
退出顺序如下:
保存最后一个稳定窗口的 schema、查询、守恒结果和数据到达时间。逐集群关闭 Cost Allocation:
gcloud container clusters update <cluster-name> \
--location=<cluster-location> \
--no-enable-cost-allocation确认关闭后不再新增 allocation 行;历史行不会被自动删除,停用窗口不会回填。决定是否停用 detailed/FOCUS export。停用前冻结消费层,停用后观察 table last modified 与最后一批记录。按审计策略保留或删除自有 dataset、授权视图、长期副本、CMEK 与服务账号授权。
清理实验:
kubectl delete namespace gke-cost-lab
kubectl get namespace gke-cost-lab完成退出的证据是新分摊停止、旧账可追溯、查询消费者已迁移、系统与人工权限已核销,并且历史成本没有因为关闭功能而从财务记录中消失。
官方操作和字段应以 GKE Cost Allocation、Detailed Billing Export、FOCUS Billing Export 与 BigQuery Billing 查询示例 为准。
