跳转至

CloudWatch OpenTelemetry Metrics 实战:原生 OTel 指标摄入与 PromQL 查询全解析

Lab 信息

  • 难度: ⭐⭐ 中级
  • 预估时间: 45 分钟
  • 预估费用: $0(Preview 期间免费)
  • Region: us-east-1(Preview 支持 5 个 Region)
  • 最后验证: 2026-04-04

背景

在 CloudWatch 原生支持 OpenTelemetry (OTel) metrics 之前,将 OTel 指标发送到 CloudWatch 需要借助 CloudWatch Agent 或 ADOT Collector 的 awsemf exporter 将 OTel 数据转换为 CloudWatch Embedded Metric Format——这意味着额外的转换逻辑、Namespace/Dimension 的手动映射,以及丢失 OTel 丰富的 label 语义。

2026 年 4 月,CloudWatch 推出了原生 OTLP metrics endpoint(Public Preview),你可以通过标准 OpenTelemetry Protocol 直接将指标发送到 CloudWatch,并使用 PromQL 查询——不需要自建 Prometheus,不需要转换逻辑,还能与 70+ AWS 服务的 vended metrics 联合查询。

本文通过实测验证从 OTel Collector 配置、指标发送、PromQL 查询到边界条件的完整链路,揭示了几个官方文档未明确提及的关键行为。

前置条件

  • AWS 账号(Region 需在 Preview 支持范围内)
  • AWS CLI v2 已配置
  • awscurl(用于向 PromQL API 发送 SigV4 签名请求)

Preview Region

目前支持:US East (N. Virginia)、US West (Oregon)、Asia Pacific (Sydney)、Asia Pacific (Singapore)、Europe (Ireland)

IAM 权限:CloudWatchAgentServerPolicy(点击展开) OTel Collector 发送指标需要 `CloudWatchAgentServerPolicy` 托管策略。如果在 EC2 上运行,将该策略附加到实例的 IAM Role 即可。

核心概念

架构一览

应用(OTel SDK)→ OTel Collector → OTLP HTTP endpoint → CloudWatch → PromQL(Query Studio)
                                   SigV4 认证(必须)

关键参数

参数
Metrics endpoint https://monitoring.{region}.amazonaws.com/v1/metrics
认证方式 AWS SigV4(service=monitoring
协议 HTTP only(不支持 gRPC)
格式 Binary (protobuf)、JSON
压缩 gzip、none
最大 TPS 500/账号
最大 datapoints/请求 1,000
最大 labels/datapoint 150
最大请求大小 1 MB(未压缩)
定价 Preview 期间免费

三种接入方式

特性 OTel Collector Custom OTel Collector ADOT SDK(无 Collector)
支持信号 Logs, Metrics, Traces Logs, Traces, Metrics Metrics, Traces
AWS 基础设施属性增强
Runtime 指标关联
部署复杂度 最低

PromQL label 结构

OTel 指标进入 CloudWatch 后,PromQL 查询时的 label 遵循 UTF-8 命名规范:

OTLP 作用域 前缀 示例
Resource @resource. @resource.service.name="myservice"
Instrumentation Scope @instrumentation. @instrumentation.@name="otel-go/metrics"
Datapoint 无前缀(向后兼容) method="GET"
AWS 系统 @aws. @aws.account="123456789"

动手实践

Step 1: 安装并配置 OTel Collector

下载 OTel Collector Contrib 发行版:

curl -sL https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.120.0/otelcol-contrib_0.120.0_linux_amd64.tar.gz \
  -o otelcol-contrib.tar.gz
tar xzf otelcol-contrib.tar.gz otelcol-contrib
chmod +x otelcol-contrib
./otelcol-contrib --version
otelcol-contrib version 0.120.1

创建 Collector 配置文件 otel-config-metrics.yaml

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    send_batch_size: 100
    timeout: 5s

exporters:
  otlphttp/metrics:
    compression: gzip
    metrics_endpoint: https://monitoring.us-east-1.amazonaws.com/v1/metrics
    auth:
      authenticator: sigv4auth/metrics
  debug:
    verbosity: detailed

extensions:
  sigv4auth/metrics:
    region: "us-east-1"
    service: "monitoring"

service:
  extensions: [sigv4auth/metrics]
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/metrics, debug]

sigv4auth 是必需的

CloudWatch OTLP endpoint 只支持 SigV4 认证。sigv4authextension 会自动从 AWS credentials chain 获取凭证(EC2 Instance Profile、环境变量或 ~/.aws/credentials)。

启动 Collector:

AWS_PROFILE=your-profile ./otelcol-contrib --config otel-config-metrics.yaml
2026-04-04T17:02:25.956Z  info  service@v0.120.0/service.go:258  Starting otelcol-contrib...
2026-04-04T17:02:25.956Z  info  otlpreceiver  Starting HTTP server  {"endpoint": "0.0.0.0:4318"}
2026-04-04T17:02:25.956Z  info  Everything is ready. Begin running and processing data.

Step 2: 发送 OTel Metrics(Counter + Gauge + Histogram)

通过 OTLP HTTP 协议发送三种指标类型:

import json, time, subprocess, random

ts_ns = str(int(time.time() * 1e9))

payload = {
    "resourceMetrics": [{
        "resource": {
            "attributes": [
                {"key": "service.name", "value": {"stringValue": "otel-hands-on-test"}},
                {"key": "host.name", "value": {"stringValue": "dev-server"}}
            ]
        },
        "scopeMetrics": [{
            "scope": {"name": "hands-on-lab", "version": "1.0.0"},
            "metrics": [
                {
                    "name": "http_requests_total",
                    "unit": "1",
                    "sum": {
                        "dataPoints": [{
                            "attributes": [
                                {"key": "method", "value": {"stringValue": "GET"}},
                                {"key": "status_code", "value": {"stringValue": "200"}}
                            ],
                            "startTimeUnixNano": ts_ns,
                            "timeUnixNano": ts_ns,
                            "asInt": "42"
                        }],
                        "aggregationTemporality": 2,
                        "isMonotonic": True
                    }
                },
                {
                    "name": "cpu_utilization",
                    "unit": "%",
                    "gauge": {
                        "dataPoints": [{
                            "attributes": [{"key": "cpu", "value": {"stringValue": "cpu0"}}],
                            "timeUnixNano": ts_ns,
                            "asDouble": 65.5
                        }]
                    }
                }
            ]
        }]
    }]
}

# 发送到 Collector 的 OTLP HTTP receiver
result = subprocess.run(
    ["curl", "-s", "-X", "POST", "http://localhost:4318/v1/metrics",
     "-H", "Content-Type: application/json",
     "-d", json.dumps(payload)],
    capture_output=True, text=True
)
print(result.stdout)
{"partialSuccess":{}}

partialSuccess 为空对象 = 所有指标都成功接收。

Step 3: 用 PromQL 查询 OTel 指标

CloudWatch 提供 Prometheus 兼容的 PromQL API,endpoint 格式:

https://monitoring.{region}.amazonaws.com/api/v1/query

awscurl 使用

PromQL API 同样需要 SigV4 签名。使用 awscurl 或 AWS SDK 的 SigV4 签名模块发送请求。

即时查询 — Counter 指标

awscurl --service monitoring --region us-east-1 \
  -X POST "https://monitoring.us-east-1.amazonaws.com/api/v1/query" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "query=http_requests_total"
{
  "status": "success",
  "data": {
    "resultType": "vector",
    "result": [{
      "metric": {
        "__name__": "http_requests_total",
        "@resource.service.name": "otel-hands-on-test",
        "@resource.host.name": "dev-server",
        "@aws.account": "595842667825",
        "@aws.region": "us-east-1",
        "method": "GET",
        "status_code": "200",
        "__type__": "Sum",
        "__temporality__": "cumulative"
      },
      "value": [1775322356.053, "92"]
    }]
  }
}

注意 label 结构:OTel resource attributes 用 @resource. 前缀,datapoint attributes(methodstatus_code)直接使用原名。

Rate 计算

# 计算 5 分钟内的请求速率
awscurl ... -d "query=rate(http_requests_total[5m])"
{"value": [1775322368.161, "0.18333333333333332"]}

Label 过滤查询

awscurl ... -d 'query=http_requests_total{method="GET"}'

Range 查询

awscurl ... -X POST ".../api/v1/query_range" \
  -d "query=cpu_utilization&start=1775321580&end=1775322360&step=60"
{
  "resultType": "matrix",
  "result": [{
    "metric": {"__name__": "cpu_utilization", "cpu": "cpu0", ...},
    "values": [
      [1775322180.0, "65.5"],
      [1775322240.0, "87.3"],
      [1775322300.0, "87.3"],
      [1775322360.0, "87.3"]
    ]
  }]
}

Step 4: 查看统一命名空间 — OTel + AWS Vended Metrics

查询所有可用 metric 名称:

awscurl --service monitoring --region us-east-1 \
  "https://monitoring.us-east-1.amazonaws.com/api/v1/label/__name__/values"

实测输出(部分):

{
  "status": "success",
  "data": [
    "ApproximateAgeOfOldestMessage",
    "CPUUtilization",
    "NetworkIn",
    "NetworkOut",
    "StatusCheckFailed",
    "cpu_utilization",
    "http_requests_total",
    "request_duration_seconds"
  ]
}

AWS vended metrics(CPUUtilizationNetworkIn)和自定义 OTel metrics(http_requests_totalcpu_utilization)出现在同一个 PromQL 命名空间中。这验证了公告中 "combine your custom OpenTelemetry metrics with AWS vended metrics" 的声明。

启用 Vended Metrics in PromQL

要在 PromQL 中查询 AWS vended metrics 的数据,需要先启用 OTel Enrichment:

# 方式 1:CloudWatch Console → Settings → Enable OTel Enrichment for AWS Metrics
# 方式 2:API 调用
awscurl --service monitoring --region us-east-1 \
  -X POST "https://monitoring.us-east-1.amazonaws.com" \
  -d "Action=StartOTelEnrichment&Version=2010-08-01"

启用后还需要开启 Resource Tags:

awscurl --service monitoring --region us-east-1 \
  -X POST "https://monitoring.us-east-1.amazonaws.com" \
  -d "Action=PutManagedInsightRules&ManagedRules.member.1.TemplateName=CloudWatch-Resource-Tags&Version=2010-08-01"

Step 5: 边界测试 — 超过 150 个 Labels

构造一个包含 151 个 attribute 的 metric datapoint:

attrs = [{"key": f"label_{i:03d}", "value": {"stringValue": f"value_{i}"}} for i in range(151)]
# ... 构造 payload 并发送

OTel Collector 本地接收成功(HTTP 200),但转发到 CloudWatch 时被拒绝:

error  Exporting failed. Dropping data.
error: "Permanent error: HTTP Status Code 400, 
  Message=Maximum number of attributes exceeded: 150 
  [ResourceMetrics.1.ScopeMetrics.1.Metrics.1.Datapoint.1]."
dropped_items: 1

Collector 不做 label 数量校验

OTel Collector 的 OTLP receiver 会接受任意数量的 attributes(返回 200),但 CloudWatch endpoint 会在服务端拒绝超过 150 个 labels 的 datapoint。这意味着数据在 Collector 端看似成功,实际已被丢弃。建议在 Collector 配置中添加 attributes processor 来提前限制 label 数量。

Step 6: Histogram 指标的特殊行为

发送 OTel Histogram 类型指标后,查询结果揭示了一个重要差异:

awscurl ... -d "query=request_duration_seconds"
{
  "result": [{
    "metric": {
      "__name__": "request_duration_seconds",
      "__type__": "Histogram",
      ...
    },
    "histogram": [1775322587.471, {
      "count": "40",
      "sum": "10",
      "buckets": [
        [0, "0.00506...", "0.00552...", "3"],
        [0, "0.01562...", "0.01703...", "2"],
        ...
      ]
    }]
  }]
}

OTel Histogram ≠ Prometheus Histogram

CloudWatch 将 OTel Histogram 转换为原生直方图格式,而非标准 Prometheus 的 _bucket/_count/_sum 后缀 convention。查询时使用原始 metric name(request_duration_seconds),不能使用 request_duration_seconds_bucket。返回的 histogram 字段包含 exponential bucket boundaries,而非 Prometheus 的固定边界 le label。

测试结果

# 测试场景 结果 关键数据 备注
1 OTel Collector → OTLP endpoint(3 种指标类型) ✅ 通过 11 轮全部 HTTP 200 Counter/Gauge/Histogram 均支持
2 PromQL 查询(即时/范围/rate/过滤) ✅ 通过 5 种查询类型全部返回正确数据 标准 PromQL 语法兼容
3 超 150 labels 边界测试 ✅ 预期拒绝 HTTP 400, "Maximum number of attributes exceeded" Collector 不校验,CloudWatch 端拒绝
4 OTel + AWS vended 统一命名空间 ✅ 通过 label values API 返回两类 metrics 需启用 OTel Enrichment
5 CloudWatch Alarm on OTel metrics ⚠️ 部分 Alarm 创建成功,但 Metrics Insights SQL 无法读取 OTel 数据 OTel metrics 只在 PromQL 域
6 Histogram 查询格式 ✅ 通过 返回原生直方图格式 非 Prometheus _bucket convention

踩坑记录

踩坑 1: OTel 指标不出现在 CloudWatch 传统指标 API 中

OTel OTLP 发送的指标存在于一个独立的数据平面,不能通过 aws cloudwatch get-metric-data 或 Metrics Insights SQL 查询。这意味着:

  • 现有的 CloudWatch Dashboard widgets 如果使用 Metrics Insights SQL,无法查询 OTel 指标
  • 基于 Metrics Insights SQL 的 Alarm 无法触发 OTel 指标
  • 必须使用 PromQL API(/api/v1/query)或 Query Studio 查询

实测发现,官方未明确记录。

踩坑 2: Histogram 指标格式与 Prometheus 标准不同

如果你习惯了 Prometheus 的 histogram_quantile(0.99, rate(xxx_bucket[5m])) 查询模式,在 CloudWatch 中需要调整——这里没有 _bucket 后缀的 metric,Histogram 作为原生类型存储,bucket 边界是指数分布而非固定边界。

实测发现,官方未明确记录。

踩坑 3: CLI 尚未支持 PromQL 相关操作

截至 AWS CLI v2.34.14,尚无 PromQL 查询或 start-otel-enrichment 等命令。需要使用 awscurl 或直接调用 API endpoint。Preview 阶段的功能 CLI 支持通常会滞后。

费用明细

资源 单价 用量 费用
OTel Metrics 摄入 $0(Preview) ~60 datapoints $0
PromQL 查询 $0(Preview) ~20 次查询 $0
合计 $0

清理资源

# 1. 停止 OTel Collector
pkill -f otelcol-contrib

# 2. 删除测试告警(如已创建)
aws cloudwatch delete-alarms \
  --region us-east-1 \
  --alarm-names "otel-http-requests-high"

# 3. 清理临时文件
rm -f otel-config-metrics.yaml otelcol-contrib.tar.gz otelcol-contrib
rm -f /tmp/otel-metrics-*.json /tmp/otel-boundary-test.json

OTel 指标数据无需清理

Preview 期间的 OTel 指标数据不产生费用,且没有手动删除 PromQL 指标的 API。数据会按照保留策略自动过期。

结论与建议

适用场景推荐

场景 是否推荐 理由
已有 OTel 基础设施,想统一到 CloudWatch ✅ 强烈推荐 零转换逻辑,直接发送
需要 PromQL + AWS 原生指标联合查询 ✅ 推荐 唯一能在同一 PromQL 中查 OTel + AWS vended metrics 的方案
替代自建 Prometheus ✅ 推荐 免运维,Preview 免费,GA 后按量付费
需要 CloudWatch Alarm on OTel metrics ⚠️ 等 GA 目前 PromQL-native alarm 支持有限
需要 gRPC 协议 ❌ 不适用 仅支持 HTTP

从 EMF 方案迁移的建议

如果你目前使用 awsemf exporter 将 OTel 指标转换为 CloudWatch Embedded Metric Format:

  1. 新项目:直接使用 OTLP endpoint,享受原生 label 语义和 PromQL 查询
  2. 迁移中:可以同时保留两个 exporter(awsemf + otlphttp),逐步验证 PromQL 查询等价性
  3. 暂不迁移:如果依赖 Metrics Insights SQL 查询或现有 Dashboard,保持 EMF 方案直到 GA

生产部署注意事项

  1. TPS 限制:500 TPS/账号,高吞吐场景需要提前规划 batch 策略
  2. Label 数量:150 上限比 Prometheus 默认宽松,但仍需在 Collector 端做好过滤
  3. 仅 HTTP:如果你的 SDK 默认使用 gRPC,需要修改 exporter 配置
  4. Region 限制:Preview 仅 5 个 Region,生产部署前确认你的 Region 在列

参考链接