- 人工智能
- AI Agent
- Agent 沙箱
- 云原生
- 容器运行时
- 零信任
【免费下载链接】substrate
Agent Substrate: the core system
导读
本文以 substrate 仓库中随 vendored 依赖引入的 opentelemetry-operations-go/exporter/metric 文档为主体,讲解如何在 Go 应用中把 OpenTelemetry 采集的指标(Metrics)导出到 Google Cloud Monitoring(GCM)。读完本文你将掌握:Google Cloud Monitoring 的前置 Workspace 设置、基于应用默认凭据(ADC)的认证机制、WithProjectID等核心配置项的用法,以及该 exporter 在底层如何完成 MetricDescriptor 创建、TimeSeries 批量上传与指标类型映射。文末还会结合仓库源码给出全部可用的Option配置项说明与真实调用链。
一、Exporter 是什么:把 OTel 指标送达 Google Cloud Monitoring
本小节内容基于 exporter/metric/README.md 的开篇描述。
OpenTelemetry Google Cloud Monitoring Exporter是一个标准的 OpenTelemetry Metric Exporter,它允许用户把程序内采集并聚合好的指标数据发送到 Google Cloud(即 Google Cloud Monitoring 服务)。在 substrate 仓库中,该模块以 vendored 形式存在于 vendor/github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric/ 目录下,包含以下源文件:
cloudmonitoring.go:导出器入口构造函数New(),负责默认凭据探测与metricExporter初始化;option.go:定义Option函数类型与全部可配置项;metric.go:核心实现,完成指标描述符创建、时间序列转换与批量上传;constants.go:历史遗留的资源标签键与受监控资源类型常量(多数已标记 Deprecated);error.go:导出器相关错误定义;version.go:当前版本号0.55.0。
在 Go 模块层面,该包以间接依赖的形式被仓库引用,见 go.mod:
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect其中internal/resourcemapping被 metric.go 用来完成 OTel Resource 到 Cloud Monitoring MonitoredResource 的自动映射,这将在后文详述。
Google Cloud Monitoring 本身是一个托管服务,它收集来自 Google Cloud、AWS、托管可用性探测、应用埋点以及 Cassandra、Nginx、Apache、Elasticsearch 等常见中间件的指标、事件和元数据,并通过仪表盘、图表和告警(可对接 Slack、PagerDuty 等)把数据转化为可操作的洞察。
二、前置准备:先创建 Monitoring Workspace
对应 README 的 Setup 一节。
Google Cloud Monitoring 是 GCP 提供的托管服务,使用前必须预先创建 Workspace。Workspace 是 Cloud Monitoring 中的数据组织单元,指标、仪表盘与告警策略都归属于某个 Workspace。官方要求在使用该 exporter 前完成 Workspace 的创建,创建入口位于 Google Cloud 控制台的 Monitoring 页面("Create Workspace" 流程)。
提示:本文档仅确认"必须预先创建 Workspace"这一事实。创建 Workspace 的具体操作步骤属于 GCP 控制台交互流程,需以 Google Cloud 官方文档为准,本仓库并未包含该流程的实现代码。
三、认证机制:应用默认凭据(ADC)的自动探测
对应 README 的 Authentication 一节。
该 exporter 的认证完全依赖 Go 生态的google.FindDefaultCredentials(golang.org/x/oauth2/google包)。也就是说:默认情况下,服务账号会自动被探测到,无需手工传凭据。README 引用其官方说明,ADC 的探测顺序如下:
- 读取环境变量
GOOGLE_APPLICATION_CREDENTIALS指定的 JSON 文件路径(即通常所说的service_account_key.json服务账号密钥文件); - 读取 gcloud 命令行工具已知位置的 JSON 文件:
- Windows:
%APPDATA%/gcloud/application_default_credentials.json - 其他系统:
$HOME/.config/gcloud/application_default_credentials.json
- Windows:
源码层面,这一探测逻辑体现在 cloudmonitoring.go 的New()中:
func New(opts ...Option) (sdkmetric.Exporter, error) { o := options{ context: context.Background(), resourceAttributeFilter: DefaultResourceAttributesFilter, } for _, opt := range opts { opt(&o) } if o.projectID == "" { creds, err := google.FindDefaultCredentials(o.context, monitoring.DefaultAuthScopes()...) if err != nil { return nil, fmt.Errorf("failed to find Google Cloud credentials: %v", err) } if creds.ProjectID == "" { return nil, errors.New("google cloud monitoring: no project found with application default credentials") } o.projectID = creds.ProjectID } return newMetricExporter(&o) }两个值得注意的实现细节:
- 只有当用户没有通过
WithProjectID显式指定项目时,才会触发google.FindDefaultCredentials探测;探测成功后,项目 ID 会直接取自凭据中的ProjectID字段; - 如果凭据探测失败或凭据中不包含项目 ID,
New()会分别返回 "failed to find Google Cloud credentials" 与 "no project found with application default credentials" 两类错误,而非静默使用空项目。
本地运行时需要额外指定项目 ID
README 特别指出:在本地开发环境运行时,仅有GOOGLE_APPLICATION_CREDENTIALS往往还不够,还需要显式指定 Google Project ID。最佳实践是借助环境变量(例如GOOGLE_CLOUD_PROJECT)配合metric.WithProjectID方法完成:
projectID := os.Getenv("GOOGLE_CLOUD_PROJECT") opts := []mexporter.Option{ mexporter.WithProjectID(projectID), }这样构造出的opts后续可传给mexporter.New(opts...),确保导出目标项目与凭据归属项目一致。
四、核心配置项全景:Option 与默认行为
本小节基于 option.go 的完整实现展开。
Option是一个函数类型func(*options),所有配置都以函数式选项的方式传入New()。options结构体(option.go#L42-L94)完整定义如下:
| 配置项 | 类型 | 默认行为 | 对应 Option 函数 |
|---|---|---|---|
context | context.Context | context.Background() | 无(仅内部字段,见New()初始化) |
projectID | string | 从 ADC 凭据自动探测 | WithProjectID(id) |
metricDescriptorTypeFormatter | func(metricdata.Metrics) string | "workload.googleapis.com/[metric name]" | WithMetricDescriptorTypeFormatter(f) |
resourceAttributeFilter | attribute.Filter | 仅保留service.name、service.namespace、service.instance.id | WithFilteredResourceAttributes(filter) |
monitoredResourceDescription | MonitoredResourceDescription | 关闭,走通用 resourcemapping | WithMonitoredResourceDescription(mrType, mrLabels) |
compression | string | 不压缩 | WithCompression(c) |
monitoringClient | *monitoring.MetricClient | 自动创建 | WithMonitoringClient(cl) |
monitoringClientOptions | []apioption.ClientOption | 空 | WithMonitoringClientOptions(opts...) |
destinationProjectQuota | bool | 关闭 | WithDestinationProjectQuota() |
disableCreateMetricDescriptors | bool | 关闭(自动创建描述符) | WithDisableCreateMetricDescriptors() |
enableSumOfSquaredDeviation | bool | 关闭 | WithSumOfSquaredDeviation() |
createServiceTimeSeries | bool | 关闭 | WithCreateServiceTimeSeries() |
逐一说明各配置项的实际影响:
1.WithProjectID(id)— 指定目标项目
设置指标上传的目标 GCP 项目。若不调用此选项,项目 ID 会从默认凭据探测流程中自动获取(见上一节)。在metricExporter内部,该值被拼进三类请求的资源名:
- 指标描述符查询/创建:
projects/{projectID}/metricDescriptors/...与projects/{projectID}(metric.go#L216-L233); - 时间序列上传:
projects/{projectID}(metric.go#L244); - 直方图 Exemplar 中的 Span 关联:
projects/{projectID}/traces/{traceID}/spans/{spanID}(metric.go#L785-L795)。
同时,该 projectID 会被用于 GCM 受监控资源的project_id标签——当资源本身不属于某个特定项目时(例如本地/自建环境下的k8s_container或generic_task),这一标签尤为关键。
2.WithMetricDescriptorTypeFormatter(f)— 自定义指标类型命名
控制 MetricDescriptor 的Type字段格式。默认格式串为"workload.googleapis.com/[metric name]",定义于 metric.go#L61:
cloudMonitoringMetricDescriptorNameFormat = "workload.googleapis.com/%s"自定义 formatter 的签名是func(metricdata.Metrics) string,实际生效逻辑见 metric.go#L276-L281。注意:自定义格式必须遵守 GCM 自定义指标命名约定(官方要求形如workload.googleapis.com/xxx、custom.googleapis.com/xxx等域前缀格式),否则 GCM API 会拒绝创建。
3.WithFilteredResourceAttributes(filter)— 控制资源属性是否下沉为指标标签
决定 OTel Resource 的哪些属性会作为额外的 metric label 附加到指标上。默认过滤器是DefaultResourceAttributesFilter(option.go#L157-L161),仅保留三个语义化约定属性且要求值非空:
func DefaultResourceAttributesFilter(kv attribute.KeyValue) bool { return (kv.Key == semconv.ServiceNameKey || kv.Key == semconv.ServiceNamespaceKey || kv.Key == semconv.ServiceInstanceIDKey) && len(kv.Value.AsString()) > 0 }这样做是为了避免针对同一受监控资源写入重复的时间序列。若想完全关闭资源属性下沉,可传入NoAttributes():
mexporter.WithFilteredResourceAttributes(mexporter.NoAttributes())4.WithDisableCreateMetricDescriptors()— 关闭自动描述符创建
默认情况下,导出器在遇到尚未注册的指标时会自动调用 GCM 的CreateMetricDescriptor。调用此选项可关闭该行为——适用于预先手工创建好 MetricDescriptor 的场景(例如为控制权限或严格管控指标 schema)。
5.WithCreateServiceTimeSeries()— 使用服务时间序列接口
配置导出器使用CreateServiceTimeSeries(而非CreateTimeSeries)写入时间序列。该接口适用于服务拥有者写入自身服务的时序数据。注意:使用该选项会隐式将disableCreateMetricDescriptors置为 true(option.go#L192-L199),即不再导出指标描述符。
6.WithCompression("gzip")— 开启 gRPC 压缩
对 gRPC 请求启用 gzip 压缩。在 metric.go#L122-L131 中,压缩器会被附加到GetMetricDescriptor、CreateMetricDescriptor、CreateTimeSeries、CreateServiceTimeSeries四个调用的 gRPC CallOptions 上。
7.WithMonitoringClient(cl)/WithMonitoringClientOptions(opts...)— 定制底层客户端
前者直接注入一个现成的monitoring.MetricClient;后者追加apioption.ClientOption(如自定义 endpoint、超时、拦截器等)供内部创建客户端时使用。两者互斥:若同时提供,WithMonitoringClient优先生效(option.go#L115-L131)。未注入客户端时,导出器会使用带opentelemetry-go <ver>; google-cloud-metric-exporter <ver>User-Agent 的 gRPC 客户端(option.go#L30)。
8.WithDestinationProjectQuota()— 使用目标项目配额
启用后,每次请求都会携带x-goog-user-project元数据头,值为projectID(去掉projects/前缀),从而使用目标项目的配额而非调用方项目的配额(metric.go#L154-L156)。典型场景是给指标设置gcp.project.id资源属性,将时序写入其他项目。
9.WithSumOfSquaredDeviation()— 直方图平方偏差估算
为直方图启用 SumOfSquaredDeviation 字段的计算。源码注释明确指出:该值是估算值而非真实平方偏差——它假设每个桶内的数据点都落在桶区间的中点(见 metric.go#L834-L849 的setSumOfSquaredDeviation),因此默认不发送。
10.WithMonitoredResourceDescription(mrType, mrLabels)— 指定受监控资源映射
配置导出器尝试把 OTel Resource 映射为指定的 GCM MonitoredResource。使用时需满足两个条件:
- OTel Resource 中存在
gcp.resource_type属性,且其值等于mrType; - Resource 属性中包含
mrLabels中列出的标签键。
命中后,这些标签会被组装进 MonitoredResource 的 Labels(metric.go#L374-L391);未配置或未命中时,则回落到resourcemapping.ResourceAttributesToMonitoringMonitoredResource的通用映射逻辑(metric.go#L393-L404)。
五、完整接入示例:从埋点到导出
综合 README 给出的WithProjectID示例与 OTel SDK 标准用法,一个最小可用的接入流程如下:
import ( "context" "os" mexporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric" "go.opentelemetry.io/otel/sdk/metric" ) func main() { ctx := context.Background() // 1. 构造 exporter(本地运行时显式指定项目 ID) projectID := os.Getenv("GOOGLE_CLOUD_PROJECT") exporter, err := mexporter.New( mexporter.WithProjectID(projectID), // mexporter.WithCompression("gzip"), // 可选:开启 gRPC gzip 压缩 // mexporter.WithFilteredResourceAttributes(mexporter.NoAttributes()), // 可选:禁止资源属性下沉 // mexporter.WithDisableCreateMetricDescriptors(), // 可选:关闭自动创建描述符 ) if err != nil { panic(err) // 常见于凭据探测失败或项目为空 } // 2. 注册到 OTel metric SDK(默认周期导出) provider := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(exporter)), ) defer func() { _ = provider.Shutdown(ctx) }() }在此之后,程序中通过 Meter 创建的任何 Counter、UpDownCounter、Histogram、ObservableGauge 等指标,都会在 SDK 周期导出时经过该 exporter 上传到 Google Cloud Monitoring。
需要说明的版本前提:本仓库 vendored 的 exporter 版本为0.55.0(见 version.go),其面向的 OTel Go SDK 为go.opentelemetry.io/otel/sdk/metric的 metricdata 模型,直接使用于 substrate 仓库的 Go 工具链(见 go.mod)。如需在其他项目中复用,请以对应 Go module 版本的 API 为准。
六、底层导出流程:描述符缓存、批量上传与类型映射
本小节深入 metric.go 的核心实现,回答"导出器内部到底做了什么"。
6.1 导出入口:两阶段处理
每次导出周期,Export依次执行两个阶段(metric.go#L147-L161):
func (me *metricExporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) error { // 若已 Shutdown 则直接返回错误 ... if me.o.destinationProjectQuota { ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{"x-goog-user-project": strings.TrimPrefix(me.o.projectID, "projects/")})) } return errors.Join( me.exportMetricDescriptor(ctx, rm), // 阶段一:确保 MetricDescriptor 存在 me.exportTimeSeries(ctx, rm), // 阶段二:上传 TimeSeries ) }6.2 阶段一:MetricDescriptor 的创建与缓存
exportMetricDescriptor(metric.go#L175-L214)以(指标名, instrumentation scope 名)为键维护一个进程内缓存mdCache。对每个新出现的指标:
- 先调用
GetMetricDescriptor查询该类型是否已存在于 GCM; - 若已存在则跳过创建(GCM 的描述符一旦创建便无法原地修改,只能删除重建,因此跳过是唯一合理路径);
- 若不存在则调用
CreateMetricDescriptor创建,成功后写入缓存。
描述符内容由recordToMdpb组装(metric.go#L293-L312):包括Type(默认workload.googleapis.com/名称)、DisplayName(去掉域前缀后的路径)、MetricKind、ValueType、Unit、Description以及从资源属性与数据点属性收集的Labels。
6.3 阶段二:TimeSeries 转换与 200 条批量上传
exportTimeSeries(metric.go#L238-L267)把聚合数据逐点转换为 GCM 的TimeSeries,然后按每批最多 200 条分组发送:
const sendBatchSize = 200 // GCM API 硬性上限,绝不超发这个 200 是 Cloud Monitoring API 的硬性限制(metric.go#L56-L59),因此导出器永远不会在一次请求中超过该值。
6.4 OTel 聚合类型 → GCM 指标类型映射
recordToMdpbKindType(metric.go#L409-L430)定义了完整映射关系:
| OTel 聚合类型 | MetricKind | ValueType |
|---|---|---|
Gauge[int64] | GAUGE | INT64 |
Gauge[float64] | GAUGE | DOUBLE |
Sum[int64](单调) | CUMULATIVE | INT64 |
Sum[int64](非单调) | GAUGE | INT64 |
Sum[float64](单调) | CUMULATIVE | DOUBLE |
Sum[float64](非单调) | GAUGE | DOUBLE |
Histogram[int64]/Histogram[float64] | CUMULATIVE | DISTRIBUTION |
| 未知类型 | 返回 UNSPECIFIED,并由errUnexpectedAggregationKind(error.go)报告错误 |
关键设计点:非单调 Sum 会被当作 GAUGE 上传(metric.go#L494-L497),因为 GCM 的 CUMULATIVE 语义要求单调递增;直方图则映射为DISTRIBUTION,并支持把 Exemplar 中的 span 上下文编码为projects/{projectID}/traces/{traceID}/spans/{spanID}形式的附件,实现指标与链路关联。
6.5 时间间隔与标签规范化
- 时间间隔:
toNonemptyTimeIntervalpb(metric.go#L684-L705)保证所有非 GAUGE 类型的时间间隔"结束时间至少比开始时间晚 1 毫秒"——若采样间隔过短,会自动把结束时间推进 1ms,满足 GCM API 约束; - 标签规范化:
normalizeLabelKey(metric.go#L880-L889)将标签键中所有非字母数字字符替换为下划线,且当键以数字开头时自动加key_前缀,以符合 GCM "仅允许字母、数字、下划线,且以字母或数字开头"的标签命名约束; - UTF-8 清洗:所有标签值经
sanitizeUTF8处理,无效字节替换为替换符�。
6.6 生命周期管理
metricExporter提供ForceFlush(无状态,直接返回 ctx 错误)与Shutdown(sync.Once保证只关闭一次,并关闭底层 gRPC 客户端连接),见 metric.go#L90-L101。接入 SDK 后,MeterProvider.Shutdown会顺带触发 exporter 的关闭。
七、版本与集成现状小结
- 当前 vendored 版本:
v0.55.0,其Version()返回值与 go.mod 中的间接依赖版本完全一致,可作为"仓库中实际使用的 exporter 版本"的可验证事实; - 该包为间接依赖,说明 substrate 仓库并未直接在业务代码中 import 此 exporter,而是经由其他依赖(如 OTel 相关工具链)带入;真正面向 GCP 观测的集成入口以仓库内 docs/observability.md 及 internal/otlprelay 等模块为准;
- 相关资源标签常量的历史定义(
k8s_container、gce_instance、aws_ec2_instance、generic_task等)仍保留在 constants.go 中,但均已标记 Deprecated,新代码应优先使用 OpenTelemetry Semantic Conventions(semconv)中的对应键。
八、快速参考:接入清单
要在自己的 Go 服务中通过该 exporter 向 Google Cloud Monitoring 上报指标,按以下清单核对即可:
- 创建 Workspace:在 Google Cloud 控制台 Monitoring 中预先创建 Workspace;
- 准备凭据:本地开发设置
GOOGLE_APPLICATION_CREDENTIALS指向服务账号 JSON;生产环境使用默认服务账号或 Workload Identity; - 指定项目:设置
GOOGLE_CLOUD_PROJECT环境变量并调用mexporter.WithProjectID(projectID)(本地运行时必需); - 选择可选配置:按需启用 gzip 压缩、关闭自动描述符创建、自定义指标类型前缀、指定受监控资源映射等;
- 注册 SDK:将 exporter 挂入
metric.NewPeriodicReader,构造MeterProvider; - 验证:在 GCM 控制台的 Metrics Explorer 中检索
workload.googleapis.com/前缀下对应指标名,确认数据已落库。
- 人工智能
- AI Agent
- Agent 沙箱
- 云原生
- 容器运行时
- 零信任
【免费下载链接】substrate
Agent Substrate: the core system
相关推荐
kOps 仓库中的 OpenTelemetry Google Cloud Monitoring Exporter:将 Go 指标接入 Cloud Monitoring 的完整指南
kOps 仓库中的 OpenTelemetry Google Cloud Monitoring Exporter:将 Go 指标接入 Cloud Monitor
云原生集群管理运维IaC思源笔记插件开发入门指南:从搭好环境到发布第一个插件的完整路径
思源笔记插件开发入门指南:从搭好环境到发布第一个插件的完整路径 思源笔记(SiYuan)是一个开源、隐私优先、可自托管的知识工作空间,你可以通过插件系统(pet
知识管理知识库使用 Telegraf Yandex Cloud Monitoring 输出插件将自定义指标写入 Yandex Cloud Monitoring
使用 Telegraf Yandex Cloud Monitoring 输出插件将自定义指标写入 Yandex Cloud Monitoring 本文以 plu
可观测性指标监控运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考