如何在 GCP 中按用户用量计费并限制 quota?
结论
GCP 无法原生按”用户”或任意 cost code 对共享项目中的资源统一计费,也无法据此设置硬性 quota。Cloud Billing、Service Quotas、预算和大多数资源上限仍以 project 为主要边界。IAM 控制的是”能否操作”,不是”最多能用多少”。
通常可以采用以下设计:
- 优先为每个 cost code 建立独立 project 或一组 projects,通过 folder、billing account、IAM 和组织策略统一治理。
- 将 Cloud Billing 数据导出到 BigQuery,再结合 project labels、resource labels 或 tags 分摊成本。
- 使用 project-level quotas、IAM、组织策略和自动化策略限制资源使用。
- 只有必须共用同一批资源,并且所有资源创建都能经过统一入口时,才考虑建设集中式资源代理 API。一般不需要为每个 cost code 单独开发一套 API。
为什么共享项目难以按用户精确管理
Billing 不等于用户审计
Cloud Billing 记录计费 SKU、项目、资源和标签等信息,但不会为所有服务稳定记录”最终由哪个用户产生了这笔费用”。
Cloud Audit Logs 可以说明”谁调用了某个 API”,却无法可靠地把每条审计日志换算成账单金额。例如:
- 用户创建 VM 后,VM 可能继续运行数周;
- 多个用户可能访问同一个 Cloud SQL、GKE 集群或共享存储;
- 资源可能由 service account 创建,而实际操作来自多个用户;
- 网络、日志、快照和存储费用会在创建操作完成后继续产生。
因此,不能简单地将 Audit Logs 中的 principal 与 Cloud Billing 费用逐行关联,并据此生成通用且准确的用户账单。
Quota 通常以 quota project 或资源所属 project 为边界
大多数 Google Cloud quota 按 project、region、zone 或特定服务维度管理。即使多个用户位于同一个项目中,通常也无法配置以下规则:
user-a: 最多 20 个 vCPU
user-b: 最多 50 个 vCPU
部分 API 支持 per-user quota 或 consumer quota,但是否可用取决于具体服务,不能作为 Compute Engine、GKE、Cloud SQL 等资源的统一用户配额机制。
IAM Conditions 可以限制用户能访问哪些资源,以及在什么条件下访问,但它不是用量计数器,无法表达”累计使用达到 100 小时后禁止继续使用”。
推荐方案:以 cost code 划分项目
如果不同 cost code 需要独立核算并实施硬限制,project 是最清晰的隔离边界:
Organization
└── Folder: Shared Platform
├── Project: cost-code-a-dev
├── Project: cost-code-a-prod
├── Project: cost-code-b-dev
└── Project: cost-code-b-prod
每个 project 都可以:
- 关联同一个 Billing Account;
- 使用 project label 标记
cost_code、environment和owner; - 单独配置服务 quota;
- 单独设置预算和告警;
- 向对应用户组授予 IAM 权限;
- 通过 Shared VPC、VPC Network Peering 或服务接口共享平台能力。
例如,为项目添加成本标签:
gcloud projects update PROJECT_ID \
--update-labels=cost_code=cc1234,environment=prod
权限建议授予 Google Group,不要逐个绑定用户:
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="group:cc1234-users@example.com" \
--role="roles/compute.instanceAdmin.v1"
生产环境还应限制用户自行修改项目和资源标签,避免成本归属字段被误改或删除。
在共享项目中进行成本分摊
如果因为 GKE 集群、数据库、网络或许可证等原因必须使用共享项目,可以通过标签和账单导出管理成本。
统一成本归属字段
为支持标记的资源统一约定以下字段:
cost_code=cc1234
owner=team-a
environment=prod
最好在创建资源时写入标签,并通过 Terraform、内部平台或策略检查维持一致。实施时需要注意:
- 并非所有服务和计费条目都支持相同的资源级标签;
- 标签通常不会自动传播到所有子资源;
- 网络、支持费用、承诺使用折扣和共享平台费用可能无法直接归属;
- GKE 等共享平台还需要结合 namespace、workload 或 pod 维度的成本数据。
对于无法直接归属的成本,应提前定义分摊规则,例如按照 CPU-hours、内存 GiB-hours、请求数或固定比例分配,避免事后猜测。
导出账单到 BigQuery
启用 Cloud Billing export 后,可以按项目或资源标签聚合数据。实际表名以及字段是否存在取决于启用的导出类型,查询时应以当前导出表的 schema 为准。
以下查询按 cost_code 资源标签统计净成本:
SELECT
COALESCE(
(
SELECT ANY_VALUE(label.value)
FROM UNNEST(labels) AS label
WHERE label.key = 'cost_code'
),
'unallocated'
) AS cost_code,
SUM(cost) + SUM(
IFNULL(
(SELECT SUM(credit.amount) FROM UNNEST(credits) AS credit),
0
)
) AS net_cost
FROM
`billing_project.billing_dataset.gcp_billing_export_v1_BILLING_ACCOUNT_ID`
WHERE
usage_start_time >= TIMESTAMP('2026-08-01')
AND usage_start_time < TIMESTAMP('2026-09-01')
GROUP BY
cost_code
ORDER BY
net_cost DESC;
如果资源没有 cost_code 标签,还可以使用项目标签作为后备。不过,资源标签和项目标签在导出 schema 中使用不同字段,查询前需要检查实际表结构。
未分配费用也应单独统计:
SELECT
service.description AS service,
sku.description AS sku,
SUM(cost) AS gross_cost
FROM
`billing_project.billing_dataset.gcp_billing_export_v1_BILLING_ACCOUNT_ID`
WHERE
NOT EXISTS (
SELECT 1
FROM UNNEST(labels) AS label
WHERE label.key = 'cost_code'
)
GROUP BY
service,
sku
ORDER BY
gross_cost DESC;
不要忽略 unallocated。它通常意味着标签缺失、服务不支持标签,或者共享费用还没有明确的分摊规则。
如何实施资源限制
使用项目级 quota
将不同 cost code 放在独立项目后,可以分别申请或调整各项目的服务 quota。具体 quota 名称、是否允许降低以及调整方式取决于服务,并非所有 quota 都能设置任意下限。
这些机制的用途不同:
- Quota:限制 API 请求速率或某类资源的数量;
- Budget:监控预计费用或实际费用,并发送通知;
- IAM:控制谁能创建、修改或删除资源;
- Organization Policy:限制可使用的资源类型、区域或配置;
- Billing Account:负责结算,不提供通用的用户消费硬上限。
不要把 Budget 当作硬限额
Cloud Billing Budget 默认只发送告警,不会自动停止 VM、禁用 API 或冻结项目。如需自动处理,可以让预算通知触发 Pub/Sub、Cloud Run 或 Cloud Functions,再执行预先定义的动作。
自动停服有几项明显风险:
- 账单数据可能存在延迟;
- 禁用 billing 或 API 可能影响共享资源;
- 关闭资源后,不一定会立即停止所有费用;
- 强制停机可能造成数据损坏或业务中断。
自动化动作更适合采用分级策略:
达到 70%:通知负责人
达到 90%:禁止创建高成本资源
达到 100%:停止非关键 workload
超过阈值:人工审批后执行进一步限制
要实现”禁止继续创建”,通常需要撤销创建权限、修改内部平台策略,或者让统一代理拒绝新请求。预算本身不会执行这些操作。
是否需要建设 API
如果所有用户都能直接调用 GCP API,内部 API 就无法实施可靠限制,因为用户可以绕过它。使用代理 API 时,必须同时满足以下条件:
- 最终用户没有直接创建底层资源的 IAM 权限;
- 只有代理使用的 service account 拥有创建权限;
- 代理验证用户身份及其 cost code;
- 代理维护用量台账,并执行并发量、资源数量或预算策略;
- 所有创建、更新和删除操作都经过同一个控制面;
- 防止用户通过 service account impersonation 等方式绕过入口。
通常只需要建设一个集中式控制面,再根据调用者所属 group、请求中的 cost code 和相关策略选择目标 project,不必为每个 cost code 复制一套 API。例如:
def create_instance(request, principal):
cost_code = resolve_cost_code(principal)
if not is_member(principal, cost_code):
raise PermissionError("principal is not authorized for this cost code")
policy = load_policy(cost_code)
usage = read_current_usage(cost_code)
if usage.vcpu + request.vcpu > policy.max_vcpu:
raise QuotaExceededError("cost code vCPU limit exceeded")
project_id = select_project(cost_code, request.environment)
return compute.instances().insert(
project=project_id,
zone=request.zone,
body=build_instance(request, cost_code),
).execute()
这段逻辑只是架构示意。实际实现还必须处理并发预留、创建失败后的额度释放、异步资源状态、幂等请求和账单数据延迟。如果只在创建前读取一次当前用量,并发请求可能同时通过检查,最终突破上限。
必须保留共享项目时的折中方案
如果无法拆分项目,可以组合使用以下措施:
- 用户只能查看和使用已有资源,不能直接创建高成本资源。
- 资源由集中式平台或 service account 创建。
- 创建请求必须携带
cost_code,平台还要验证调用者是否属于对应成本中心。 - 将资源标签、Audit Logs 和平台台账写入统一数据仓库。
- 使用 Billing export 计算实际费用,使用平台台账计算实时或近实时用量。
- 为共享成本制定明确、可审计的分摊公式。
- 通过自动化任务定期查找无标签、越权和闲置资源。
这套方案可以支持内部 chargeback/showback 和业务级限制,但它属于自建治理系统,并不是 GCP 原生的按用户 billing 或统一 user quota。
如果能够按 cost code 拆分 project,应优先拆分。共享项目加标签适合成本统计,但不适合严格隔离。代理 API 则适用于无法拆分项目,同时又确实需要自定义实时配额的场景。
备注:内容仅供参考。
