Orange 的 FinOps 实践:先让工程师愿意行动,再用 AI Agent 扩大影响

2026-09-17 15 预计阅读时间: 1 分钟
来源: cloud.google.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:12 分钟

云成本治理最难的部分,往往不是生成账单报表,而是让工程团队真正采取行动。法国电信企业 Orange 的做法很直接:暂时放下交付待办,把专门的一天留给云成本清理;资深成员现场指导,排行榜展示成果,业务赞助者当天就能看到节省效果。

这些被称为 FinOps Clean Days 的活动,与游戏化黑客松和持续运营的实践社区结合后,为 Orange 超过 100 人的 FinOps 社区带来了高于 70 的内部净推荐值。更值得关注的不是活动形式本身,而是它揭示了一条可复用的路径:FinOps 要先成为组织行为,再由 AI Agent 将这种行为扩展到更多团队。

为什么一张成本报表通常改变不了行为

在持续部署的敏捷团队中,成本优化天然处于劣势。产品需求、故障、技术债和交付期限都比“检查闲置资源”更紧迫。即使工程师知道某个环境可能存在浪费,这项工作也很容易被继续推回 backlog。

Orange 的 Clean Days 改变了三个条件:

  • 时间受到保护:成本优化不再与正常迭代争抢零散时间。
  • 工作具有协作性:新成员可以跟随有经验的实践者边做边学。
  • 结果公开可见:排行榜、奖励和当天可展示的成果形成即时反馈。

这也符合组织变革常见的四个支点:

变革支点 Clean Days 中的体现 Agent 可以如何增强
理解与认同 分享账单变化、优化案例和支出来源 在日常工具中推送与团队直接相关的成本解释
正式机制 预留活动时间,建立固定社区和沟通渠道 自动生成任务、审批记录和治理报告
榜样作用 排行榜让优秀实践和成果变得可见 识别可复用的优化,并推荐给其他团队
技能与机会 资深成员现场指导新人 提供上下文说明、修复步骤和待审核变更

关键点是:排行榜并不是为了把 FinOps 变成一次竞赛,而是让原本不可见的行为变得可见。Agent 也不应该只是增加一个聊天入口,而应该减少从“发现问题”到“完成修改”的摩擦。

Agent 应该填补哪一种断点

当 FinOps 社区扩大到上百人,而企业拥有数千名工程师时,中心团队不可能直接服务每个人。此时可以沿着 FinOps 生命周期寻找参与度下降的位置,而不是一开始就追求全自动化。

认知断点:团队不知道自己的影响

可以部署只读的洞察 Agent,将成本、环比变化和异常资源推送到团队已有的聊天工具、工单系统或开发者门户。通知必须能回答“这与我有什么关系”,而不只是发送全公司账单。

带宽断点:团队知道问题,但没有时间处理

修复型 Agent 可以把优化建议转换成可审核的变更,例如:

  • 调整非生产环境的实例规格;
  • 为临时环境补充自动关闭策略;
  • 删除已经确认无归属的存储卷;
  • 生成基础设施即代码的合并请求,而不是直接修改线上资源。

“准备好合并的变更”比“这里可能节省 20%”更容易进入工程流程。

复杂度断点:报告和归属需要大量手工工作

编排型 Agent 可以收集账单、标签、资源所有者和历史工单,生成统一报告。它适合处理机械但耗时的工作,但最终结论仍应保留数据来源,避免让不可解释的摘要成为财务依据。

可以这样实践:先做一个只读成本洞察 Agent

下面是一个可直接改造的最小示例。它从 Google Cloud Billing 导出的 BigQuery 表中读取最近几天的项目净成本,然后发送到支持 {"text": "..."} 请求格式的团队 Webhook。

这不是对 Orange 内部实现的复刻,而是一种低风险起步方式。运行前需要满足以下假设:

  1. 已将 Cloud Billing 数据导出到 BigQuery;
  2. 执行身份拥有目标数据集的只读权限和 BigQuery Job 执行权限;
  3. 团队 Webhook 接受 JSON POST 请求;
  4. 将示例中的表名替换为自己的完整表名。

创建 requirements.txt

google-cloud-bigquery==3.25.0
requests==2.32.3

创建 cost_insight_agent.py

import os
import requests
from google.cloud import bigquery

BILLING_TABLE = os.environ["BILLING_TABLE"]
WEBHOOK_URL = os.environ["WEBHOOK_URL"]
LOOKBACK_DAYS = int(os.getenv("LOOKBACK_DAYS", "7"))
TOP_N = int(os.getenv("TOP_N", "10"))


def load_costs():
    client = bigquery.Client()
    query = f"""
    SELECT
      COALESCE(project.id, 'unassigned') AS project_id,
      ROUND(
        SUM(
          cost + IFNULL(
            (SELECT SUM(credit.amount) FROM UNNEST(credits) AS credit),
            0
          )
        ),
        2
      ) AS net_cost
    FROM `{BILLING_TABLE}`
    WHERE usage_start_time >= TIMESTAMP_SUB(
      CURRENT_TIMESTAMP(), INTERVAL @days DAY
    )
    GROUP BY project_id
    ORDER BY net_cost DESC
    LIMIT @top_n
    """

    config = bigquery.QueryJobConfig(
        query_parameters=[
            bigquery.ScalarQueryParameter("days", "INT64", LOOKBACK_DAYS),
            bigquery.ScalarQueryParameter("top_n", "INT64", TOP_N),
        ]
    )
    return list(client.query(query, job_config=config).result())


def build_message(rows):
    lines = [f"Cloud cost insight: last {LOOKBACK_DAYS} days"]
    if not rows:
        lines.append("No billing records found.")
    else:
        for index, row in enumerate(rows, start=1):
            lines.append(f"{index}. {row.project_id}: {row.net_cost:.2f}")

    lines.extend([
        "",
        "Read-only report: verify ownership and workload context before remediation.",
    ])
    return "\n".join(lines)


def main():
    message = build_message(load_costs())
    response = requests.post(
        WEBHOOK_URL,
        json={"text": message},
        timeout=15,
    )
    response.raise_for_status()
    print(message)


if __name__ == "__main__":
    main()

安装并运行:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

export BILLING_TABLE="my-billing-project.billing_export.gcp_billing_export_v1_XXXXXX"
export WEBHOOK_URL="https://example.internal/hooks/finops"
export LOOKBACK_DAYS="7"
export TOP_N="10"

# 本地开发时可以使用 gcloud 的应用默认凭据;生产环境应使用专用服务账号。
gcloud auth application-default login
python cost_insight_agent.py

这个示例故意不使用大模型,也不修改资源。它先验证三个更基础的问题:数据是否可信、通知是否送达正确团队、团队是否会根据通知采取行动。确认这些环节有效后,再考虑让 Agent 解释异常、创建工单或生成合并请求。

可以进一步把脚本部署为定时任务,并加入成本环比、标签缺失和资源所有者映射。若要使用 LLM 生成摘要,应先删除账号标识、合同价格等敏感字段,并让模型输出附带查询时间、数据范围和原始记录链接。

从建议到执行:自治能力要逐级开放

直接执行云资源修改可能带来停机、数据丢失和合规风险。更稳妥的路线是按信任程度逐级开放能力:

finops_agent_policy:
  stage_1_observe:
    permissions:
      - billing.read
      - asset_inventory.read
    actions:
      - send_insight
    approval_required: false

  stage_2_propose:
    permissions:
      - repository.pull_request.create
      - ticket.create
    actions:
      - propose_rightsizing
      - generate_iac_change
    approval_required: true

  stage_3_execute:
    permissions:
      - approved_resource_update
    actions:
      - apply_preapproved_change
    approval_required: true
    safeguards:
      - maintenance_window
      - rollback_plan
      - maximum_daily_savings_scope
      - audit_log

这里的 YAML 是治理策略示例,不对应某个现成产品的固定格式。实践时应将它映射到 IAM、代码仓库保护规则、审批系统和审计日志。

无代码或低代码团队可以通过企业级 Agent 构建工具快速验证只读场景;需要精细控制的开发团队则可以选择支持代码框架、身份治理和工具调用控制的平台。无论采用哪种产品,权限边界都应该由确定性的策略控制,而不是交给模型自己判断。

值得复制的是顺序,而不只是工具

Orange 的经验说明,FinOps 是业务变革问题,也是工程效率问题。Clean Days、实践社区和游戏化机制先建立了共同语言、技能与责任感,Agent 才有机会把这些能力送到中心团队无法直接触达的人群。

落地时可以用下面的检查清单控制节奏:

  • 是否为优化工作提供了不会被 sprint 挤占的时间?
  • 每条成本洞察是否能定位到团队、项目和负责人?
  • 建议是否包含可执行步骤,而不只是金额和趋势?
  • 只读 Agent 的数据准确率和团队采纳率是否已经得到验证?
  • 自动修改前是否具备审批、回滚、限额和审计机制?
  • 是否同时衡量节省金额、工程投入和业务风险?

不要从“Agent 能自动关闭多少资源”开始,而要从“团队为什么没有采取行动”开始。文化建立责任,流程保护时间,Agent 降低摩擦。三者按这个顺序组合,FinOps 才可能真正成为每个人的工作。


相关推荐