Lambda NodeJS 运行时链路接入观测云

    背景

    为有效监控无服务器架构的业务性能,我们需要将 AWS Lambda 函数的全链路数据接入观测云进行统一可观测性分析。由于 Lambda 环境的特殊性,最佳实践是构建一个集成了 OpenTelemetry 的官方 Layer。该 Layer 能自动捕获函数调用链与性能指标,并通过标准 OTLP 协议上报。为确保数据传输的高效性与前瞻性,我们特别将社区常见的 JSON 格式调整为 Protobuf 编码,以适配观测云后端的技术演进,为函数性能优化与故障诊断提供坚实的数据基础。

    前提条件

    运行时:NodeJS 22

    安装 DataKit 并配置采集器

    进入观测云控制台 -「集成」-「DataKit」-「Linux」复制命令安装 DataKit 。

    进入 /usr/local/datakit/conf.d/samples ,将 opentelemetry.conf.sample 复制到上级目录 /conf.d 中,并修改文件后缀为 conf

    cp opentelemetry.conf.sample ../opentelemetry.conf
    

    编辑 opentelemetry 配置文件,修改如下部分,添加 enable = true ,然后保存。

    执行以下命令重启 DataKit 。

    datakit service -R
    

    打开 datakit.confhttp_api 开启 0.0.0.0:9529

    准备一个 Lambda 函数

    以下 demo 脚本调用了一个 Java 服务 52.83.66.70:8090/user

    const http = require('http');
    
    exports.handler = async (event, context) => {
        console.log('=== 开始调用Java服务验证TraceID ===');
        
        try {
            console.log('准备调用Java服务: 52.83.66.70:8090/user');
            
            // 调用您的Java服务
            const javaServiceResult = await callJavaService();
            
            console.log('Java服务调用成功');
            
            return {
                statusCode: 200,
                body: JSON.stringify({
                    success: true,
                    message: 'Java服务调用完成',
                    javaServiceResponse: javaServiceResult,
                    requestId: context.awsRequestId,
                    timestamp: new Date().toISOString()
                })
            };
            
        } catch (error) {
            console.error('调用Java服务失败:', error);
            return {
                statusCode: 500,
                body: JSON.stringify({
                    success: false,
                    message: 'Java服务调用失败',
                    error: error.message
                })
            };
        }
    };
    
    function callJavaService() {
        return new Promise((resolve, reject) => {
            console.log('开始发起HTTP请求到Java服务...');
            
            const options = {
                hostname: '52.83.66.70',
                port: 8090,
                path: '/user',
                method: 'GET',
                timeout: 5000,  // 5秒超时
                headers: {
                    'User-Agent': 'Lambda-OTEL-Test/1.0',
                    'Accept': 'application/json'
                }
            };
            
            const req = http.request(options, (res) => {
                console.log(`Java服务响应状态码: ${res.statusCode}`);
                console.log('响应头:', JSON.stringify(res.headers));
                
                let data = '';
                res.on('data', (chunk) => {
                    data += chunk;
                });
                
                res.on('end', () => {
                    console.log('Java服务响应数据长度:', data.length);
                    console.log('原始响应:', data);
                    
                    try {
                        // 尝试解析JSON响应
                        const parsedData = data ? JSON.parse(data) : {};
                        resolve({
                            statusCode: res.statusCode,
                            data: parsedData,
                            headers: res.headers,
                            rawResponse: data
                        });
                    } catch (e) {
                        // 如果JSON解析失败,返回原始数据
                        console.log('响应不是JSON格式,返回原始数据');
                        resolve({
                            statusCode: res.statusCode,
                            data: data,
                            headers: res.headers,
                            isJson: false
                        });
                    }
                });
            });
            
            req.on('error', (error) => {
                console.error('请求Java服务错误:', error.message);
                reject(error);
            });
            
            req.on('timeout', () => {
                console.error('请求Java服务超时');
                req.destroy();
                reject(new Error('请求Java服务超时'));
            });
            
            // 发送请求
            req.end();
            console.log('HTTP请求已发送到Java服务');
        });
    }
    

    测试事件:

    构建 Layer

    构建官方的 Layer 做导出器,通过 Layer 集成,无侵入式地自动捕获 Lambda 函数执行的链路数据可以自动采集 Lambda 函数的链路数据,将采集的数据转换为 OpenTelemetry(OTel)标准格式,确保与观测后端平台的兼容性。

    注意:Node.js 社区提供的默认 OpenTelemetry 导出器通常使用 HTTP/JSON 方式发送数据,需要将默认的导出协议从 HTTP/JSON 改为 HTTP/PROTOBUF,DataKit 后续可能考虑废弃 HTTP/JSON 方式。Protobuf 编码具有更高的序列化/反序列化效率,能显著降低传输数据大小和网络开销,尤其适用于 Lambda 的短时执行环境。

    具体可以参考 Opentelemetry 的社区:https://github.com/open-telemetry/opentelemetry-lambda/tree/main/nodejs

    克隆仓库

    git clone https://github.com/open-telemetry/opentelemetry-lambda
    

    修改协议

    进入项目目录,将相关文件的 @opentelemetry/exporter-trace-otlp-http 改成@opentelemetry/exporter-trace-otlp-proto,一共需要修改 3 个文件。

    cd opentelemetry-lambda/nodejs
    

    ./packages/layer/src/wrapper.ts

    ./packages/layer/package.json

    ./packages/layer/test/wrapper.spec.ts

    安装依赖

    npm  install
    

    构建项目

    npm run build
    

    ./nodejs/packages/layer/build/ 会有一个 layer.zip 文件。

    添加 Layer

    创建 Layer

    在 AWS 控制台 Lambda 进入「layer」,新建一个 Layer,选择上传 .zip 文件方式上传刚才生成的 layer.zip 文件,架构选择 x86、运行时选择 nodejs。创建好后复制 ARN 。

    添加 Layer

    在 Demo 函数中添加 Layer,选择指定一个 ARN ,将刚才的 ARN 复制进去,点击「添加」。

    配置环境变量

    配置 Lambda 环境变量,选择「配置」-「环境变量」。

    添加如下变量:

    KEY VALUE
    AWS_LAMBDA_EXEC_WRAPPER /opt/otel-handler
    OTEL_EXPORTER_OTLP_ENDPOINT http://<datakit主机地址>:9529/otel
    OTEL_EXPORTER_OTLP_TRACES_PROTOCOL http/protobuf
    OTEL_NODE_ENABLED_INSTRUMENTATIONS aws-lambda,aws-sdk,http,https,pg,mysql,redis
    OTEL_SERVICE_NAME 服务名称
    OTEL_TRACES_SAMPLER always_on

    测试函数

    回到函数点击测试

    观测云效果

    链路上报效果如下:

    联系我们

    加入社区

    微信扫码
    加入官方交流群

    立即体验

    在线开通,按量计费,真正的云服务!

    立即开始

    选择观测云版本

    代码托管平台