用 Lambda 打通内网:将 CloudWatch 指标推送到 VPC 内的 OpenTelemetry Collector

2026-05-13 16 预计阅读时间:1 分钟
来源:aws.amazon.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:9 分钟

AWS CloudWatch 是公有云监控的标配,但很多企业的可观测性底座是部署在 VPC 内网的 OpenTelemetry (OTel) Collector。CloudWatch 默认没有内网推送通道,直接让内网 Collector 拉取公有云 API 既不安全又受带宽与 API 限流制约。本文拆解一种实战方案:用 AWS Lambda 做格式转换与内网穿透,把 CloudWatch 指标以 OTLP 格式直接灌进 VPC 内的 Collector。

为什么需要 Lambda 做桥接

当 OpenTelemetry Collector 部署在私有子网时,它面临一个尴尬的网络隔离:它能访问内网服务,却无法高效、安全地与 AWS 公有云 API 交互。如果让 Collector 主动通过 NAT 网关拉取 CloudWatch 指标,不仅会产生公网流量费,还会因为 GetMetricData API 的调用限制导致指标延迟。

把 Lambda 放在中间做“桥接”,能从根本上扭转数据流向: - Lambda 主动拉取或接收流:利用 CloudWatch Metric Streams 或 EventBridge 定时触发,Lambda 在 AWS 内部网络拿到原始指标。 - Lambda 转换格式:将 CloudWatch 的 JSON 结构转为 OpenTelemetry 标准的 OTLP JSON 或 Protobuf。 - Lambda 推送内网:Lambda 被挂载到 VPC 内,直接通过私有 IP 把数据 POST 到 Collector,全程不走公网。

数据流转路径设计

这套架构的核心在于触发机制与网络路由的配合。典型的流转路径如下:

  1. 指标采集:CloudWatch Metric Streams 将指标实时推送到 Kinesis Data Firehose,或者由 EventBridge 规则按分钟触发 Lambda 拉取指定指标。
  2. 格式转换:Lambda 函数解析 CloudWatch 的命名空间、维度与指标值,映射为 OTLP 的 scopeMetricsgauge/sum 数据点。
  3. 内网投递:Lambda 通过 VPC 内的 ENI,向 Collector 的 4318(HTTP)或 4317(gRPC)端口发送数据。

下面进入实操环节,看如何用代码把这条路径落地。

Lambda 转换层实战

Lambda 的核心任务是把 CloudWatch 的指标结构“翻译”成 OTLP 格式。CloudWatch Metric Streams 推送过来的 JSON 通常包含 namespacemetric_namedimensionsvalues,我们需要把它们平铺为 OTLP 的属性键值对。

以下是一个可以直接部署的 Python Lambda 函数示例,它接收 CloudWatch 指标事件,转换为 OTLP JSON 并推送到内网 Collector:

import json
import urllib.request
import os
from datetime import datetime

# 从环境变量读取内网 Collector 地址
OTEL_COLLECTOR_URL = os.environ.get('OTEL_COLLECTOR_URL', 'http://10.0.1.50:4318/v1/metrics')

def lambda_handler(event, context):
    # 假设事件来自 CloudWatch Metric Stream (经 Firehose 解码) 或定时拉取
    # 这里以简化的 CloudWatch 指标结构为例
    cw_metrics = event.get('metrics', [])

    if not cw_metrics:
        print("未收到指标,跳过处理")
        return {"status": "skipped"}

    otlp_payload = transform_to_otlp(cw_metrics)
    push_to_collector(otlp_payload)

    return {"status": "success", "metrics_processed": len(cw_metrics)}

def transform_to_otlp(cw_metrics):
    """将 CloudWatch 指标转为 OpenTelemetry OTLP JSON 格式"""
    otlp_metrics = []

    for m in cw_metrics:
        # 将 CloudWatch 命名空间转为 OTel 指标名,如 AWS/EC2 -> aws.ec2.cpu_utilization
        namespace = m['namespace'].replace('/', '.').lower()
        metric_name = m['metric_name'].lower()
        otel_metric_name = f"{namespace}.{metric_name}"

        # 将 CloudWatch Dimensions 转为 OTel Attributes
        attributes = []
        for dim_key, dim_val in m.get('dimensions', {}).items():
            attributes.append({"key": dim_key.lower(), "value": {"stringValue": dim_val}})

        # 构造 OTLP Gauge 数据点 (假设 CloudWatch 传来的 value 是单值)
        data_points = []
        timestamp_ms = m.get('timestamp', datetime.utcnow().isoformat())
        # OTLP 时间戳需为纳秒字符串
        time_unix_nano = str(int(datetime.fromisoformat(timestamp_ms).timestamp() * 1e9))

        data_points.append({
            "timeUnixNano": time_unix_nano,
            "asDouble": m['value']
        })

        otlp_metrics.append({
            "name": otel_metric_name,
            "unit": m.get('unit', '1'),
            "gauge": {
                "dataPoints": data_points
            },
            "attributes": attributes
        })

    # 组装完整的 OTLP ResourceMetrics 结构
    return {
        "resourceMetrics": [{
            "resource": {
                "attributes": [
                    {"key": "cloud.provider", "value": {"stringValue": "aws"}}
                ]
            },
            "scopeMetrics": [{
                "scope": {"name": "cloudwatch-lambda-bridge"},
                "metrics": otlp_metrics
            }]
        }]
    }

def push_to_collector(otlp_payload):
    """通过内网将 OTLP JSON 推送到 VPC 内的 Collector"""
    req = urllib.request.Request(
        OTEL_COLLECTOR_URL,
        data=json.dumps(otlp_payload).encode('utf-8'),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )

    try:
        with urllib.request.urlopen(req, timeout=5) as resp:
            print(f"推送成功,Collector 返回状态码: {resp.status}")
    except urllib.error.URLError as e:
        print(f"推送失败,请检查 Collector 是否在内网可达: {e}")
        raise e

部署与改造提示: - 如果你的数据源是 CloudWatch Metric Streams,Lambda 前面需要挂一层 Kinesis Firehose,并在 Firehose 中配置数据转换 Lambda 来解压与解码。 - OTEL_COLLECTOR_URL 环境变量必须填写 Collector 的私有 IP(如 http://10.0.1.50:4318/v1/metrics),不要填写公网域名。

网络与权限配置要点

Lambda 要能访问 VPC 内的 Collector,必须将其挂载到对应的 VPC 与私有子网。这带来一个关键副作用:挂载到 VPC 的 Lambda 会失去公网出站权限。如果 Lambda 还需要访问其他 AWS 公有服务(如读取 S3 配置或调用 CloudWatch API),必须为 VPC 配置 NAT 网关或 VPC Endpoints。

以下是使用 AWS SAM 部署该 Lambda 的 YAML 模板片段,展示了如何将 Lambda 放入 VPC 并配置安全组:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  CloudWatchToOtelFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 10
      # 关键配置:将 Lambda 挂载到 VPC 内网
      VpcConfig:
        SubnetIds:
          - subnet-0a1b2c3d4e5f # 你的私有子网 ID
        SecurityGroupIds:
          - sg-0123456789abcdef # 允许出站访问 Collector 的安全组
      Environment:
        Variables:
          OTEL_COLLECTOR_URL: http://10.0.1.50:4318/v1/metrics
      Policies:
        # 如果 Lambda 需要主动拉取 CloudWatch,需赋予相关权限
        - Statement:
            Effect: Allow
            Action:
              - cloudwatch:GetMetricData
            Resource: '*'
        # Lambda 挂载 VPC 需要创建 ENI 的权限
        - VPCAccessPolicy: {}

安全组规则提醒:Collector 所在实例的安全组,必须放行 Lambda 安全组对 4317(gRPC)或 4318(HTTP)端口的入站访问。

架构权衡与落地建议

用 Lambda 做指标桥接并不是银弹,它在解决网络隔离的同时,也引入了新的复杂度。

主要权衡: - 延迟与成本:如果使用 EventBridge 定时触发 Lambda 拉取指标,拉取频率越高,Lambda 调用费越贵;频率越低,指标时效性越差。如果使用 Metric Streams + Firehose 实时推流,数据延迟可降到秒级,但 Firehose 会产生按数据量计费的成本。 - 网络陷阱:Lambda 一旦挂载 VPC,冷启动时间会因 ENI 分配而显著增加。如果 VPC 内没有配置 NAT 网关,Lambda 将无法访问 AWS 公有 API(如 CloudWatch 与 STS),此时必须为 CloudWatch 配置 VPC Endpoint。

落地检查清单: 1. 确认 Lambda 所在子网有到 Collector 的路由(同 VPC 内直接路由,跨 VPC 需 Peering 或 TGW)。 2. 确认 Collector 的 Security Group 允许 Lambda 访问 OTLP 端口。 3. 确认 Lambda 执行角色包含 ec2:CreateNetworkInterface 等权限(SAM 的 VPCAccessPolicy 已自动包含)。 4. 评估是否需要为 CloudWatch 或 Kinesis 配置 VPC Endpoint,避免 Lambda 因失去公网而无法拉取数据。 5. 在 Collector 端配置 file_exporter 或调试日志,先验证 OTLP 数据结构是否被正确接收与解析,再对接后端 Prometheus 或 Jaeger。

这套 Lambda 桥接方案,本质上是在公有云监控与内网可观测性之间修了一条“专线”。只要网络与权限配置到位,它能让 VPC 内的 Collector 安稳地坐在内网,源源不断地接收标准化的 OTLP 指标流。


相关推荐