使用OpenTelemetry对Nginx进行链路追踪

本文介绍如何使用 OpenTelemetry 对 Nginx 进行链路追踪。 Nginx 是一个高性能、轻量级的开源 Web 服务器和反向代理服务器,支持通过模块以扩展其功能。Nginx OTel 模块支持采集 Nginx 的调用链并通过 OpenTelemetry Collector 转发上报至可观测链路 OpenTelemetry 版

使用限制

  • Nginx OTel 模块目前仅支持 gRPC 方式上报,不支持 HTTP 方式上报。

  • Nginx OTel 模块暂不支持设置 gRPC 鉴权 Token,因此您需要部署 OpenTelemetry Collector。将 Nginx OTel 采集到的 Nginx 调用链通过 OpenTelemetry Collector 转发上报至可观测链路 OpenTelemetry 版。详细步骤,请参见三、部署 OpenTelemetry Collector

前提条件

获取接入点信息

  1. 登录ARMS控制台,在左侧导航栏单击接入中心

  2. 服务端应用区域单击OpenTelemetry卡片。

  3. 在弹出的OpenTelemetry面板中选择数据需要上报的地域。

    说明

    初次接入的地域将会自动进行资源初始化。

  4. 选择连接方式上报方式,然后复制接入点信息。

    • 连接方式:若您的服务部署在阿里云上,且所属地域与选择的接入地域一致,推荐使用阿里云内网方式,否则选择公网方式。

    • 上报方式:根据客户端支持的协议类型选择gRPC协议上报数据。

    87.jpg

接入步骤

一、下载 Nginx OTel 模块

  • Alibaba Cloud Linux、RedHat、RHEL 及其衍生版本

    sudo yum install nginx-module-otel
  • Debian、Ubuntu 及其衍生版本

    sudo apt install nginx-module-otel

二、启用 Nginx OTel 模块

若要为 Nginx 启用链路追踪,您需要在 Nginx 主配置文件/etc/nginx/nginx.conf中加载 OTel 模块并添加配置项。关于 OTel 模块的更多参数配置信息,请参见ngx_otel_module 模块文档

  • 全局配置:为所有 HTTP 请求开启链路追踪。

    说明

    请替换配置中的两个变量:

    • ${OTEL_COLLECTOR_GRPC_RECEIVER_ENDPOINT}:OpenTelemetry Collector gRPC 上报点。此上报点为您部署的 OpenTelemetry Collector 的 gRPC 上报点,例如localhost:4317,而不是前提条件中获取的可观测链路 OpenTelemetry 版的接入点。

    • ${SERVICE_NAME:应用名称。此名称将会作为 Nginx 的应用名称在可观测链路 OpenTelemetry 版控制台中展示。

    load_module modules/ngx_otel_module.so; # 加载 ngx_otel_module
    ...
    http {
        ...
    
        otel_exporter {
            endpoint ${OTEL_COLLECTOR_GRPC_RECEIVER_ENDPOINT}; # OpenTelemetry Collector 的上报地址,例如 localhost:4317
        }
    
        otel_trace on;                     # 开启链路追踪
        otel_service_name ${SERVICE_NAME}  # 应用名
        otel_trace_context inject;         # 向下游服务注入Trace上下文
        ...
    }
  • 单个 Location 配置:为单个 Location 开启链路追踪。

    说明

    请替换配置中的两个变量:

    • ${OTEL_COLLECTOR_GRPC_RECEIVER_ENDPOINT}:OpenTelemetry Collector gRPC 上报点。此上报点为您部署的 OpenTelemetry Collector 的 gRPC 上报点,例如localhost:4317,而不是前提条件中获取的可观测链路 OpenTelemetry 版的接入点。

    • ${SERVICE_NAME:应用名称。此名称将会作为 Nginx 的应用名称在可观测链路 OpenTelemetry 版控制台中展示。

    load_module modules/ngx_otel_module.so; # 加载 ngx_otel_module
    
    ...
    
    http {
    
        otel_exporter {
            endpoint ${OTEL_COLLECTOR_GRPC_RECEIVER_ENDPOINT}; # OpenTelemetry Collector 的上报地址,例如 localhost:4317
        }
    
        server {
            listen 127.0.0.1:80;
    
            location /hello {
                otel_trace         on;             # 只为 127.0.0.1:80/hello 开启链路追踪
                otel_service_name ${SERVICE_NAME}  # 应用名
                otel_trace_context inject;         # 向下游服务注入Trace上下文
    
                ...
            }
        }
    }

三、部署 OpenTelemetry Collector

下文以 Docker 方式为例,介绍如何部署 OpenTelemetry Collector。更多部署方式,请参见下载并部署OpenTelemetry Collector

  1. 创建opentelemetry-config.yaml文件,并将下面的内容拷贝至文件。

    该文件用于定义和配置OpenTelemetry Collector的行为和功能,包括如何接收、处理和导出数据。

    说明

    请将${GRPC_ENDPOINT}${GRPC_ENDPOINT_TOKEN}替换为前提条件中获取的 gRPC 接入点和鉴权 Token。

    receivers:
      otlp:
        protocols:
          grpc: 
            endpoint: 0.0.0.0:4317
    
    exporters:
      otlp:
        endpoint: ${GRPC_ENDPOINT}
        tls:
          insecure: true
        headers:
          "Authentication": "${GRPC_ENDPOINT_TOKEN}"
    
    processors:
      batch:
      
    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlp]
  2. 启动 OpenTelemetry Collector。

    docker run -v $(pwd)/opentelemetry-config.yaml:/etc/otelcol-contrib/config.yaml otel/opentelemetry-collector-contrib:0.105.0

四、查看 Nginx 调用链

完成以上配置并重启 Nginx 后,您可向 Nginx 发送请求,以生成调用链。然后登录可观测链路 OpenTelemetry 版控制台,查看由 OpenTelemetry 生成的 Nginx 调用链。

  • 应用列表页面查看 Nginx 应用。89.jpg

  • 调用链分析页面打开 Nginx 的调用链,查看请求耗时、客户端 IP、 HTTP 状态码等信息。如果您的后端服务也接入了可观测链路 OpenTelemetry 版,您将会看到串联在一起的 Nginx 与后端服务调用链。88.jpg

实践教程

接下来将通过案例演示如何采集 Nginx 及后端应用的调用链,并上报至可观测链路 OpenTelemetry 版

一、准备工作

二、项目结构

nginx-otel-demo
│
├── docker-compose.yml          # Docker Compose 配置文件
│
├── nginx_conf/                 # nginx 配置文件
│   ├── default.conf
│   └── nginx.conf              
│
├── otel_conf/                  # OpenTelemetry Collector 配置文件
│   └── config.yaml           
│
└── backend/                    # Node.js 后端服务
    ├── Dockerfile              
    ├── main.js
    ├── package.json
    └── package-lock.json

创建文件夹:

mkdir nginx-otel-demo && cd nginx-otel-demo

mkdir -p nginx_conf otel_conf backend

三、创建 Nginx 配置

  1. 创建 Nginx 主配置文件nginx.conf

    cat << 'EOF' > nginx_conf/nginx.conf
    load_module modules/ngx_otel_module.so;
    
    user  nginx;
    worker_processes  auto;
    
    error_log  /var/log/nginx/error.log notice;
    pid        /var/run/nginx.pid;
    
    
    events {
        worker_connections  1024;
    }
    
    
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
    
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        access_log  /var/log/nginx/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
    
        keepalive_timeout  65;
    
        #gzip  on;
    
        otel_exporter {
            endpoint otel-collector:4317;
        }
    
        otel_trace on;
    
        otel_trace_context inject;
    
        otel_service_name nginx;
    
        include /etc/nginx/conf.d/*.conf;
    
    }
    EOF
  2. 创建 Nginx 配置文件default.conf

    cat << 'EOF' > nginx_conf/default.conf
    server {
        listen       80;
        server_name  localhost;
    
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }
        
        # 在这里添加
        location /hello {
            proxy_pass http://backend-api:7001/hello;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    
        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
    EOF

四、创建 OpenTelemetry Collector 配置

创建 OpenTelemetry Collector 配置文件,该配置文件定义了数据如何被接收、处理和上报。

说明

请将${GRPC_ENDPOINT}${GRPC_ENDPOINT_TOKEN}替换为前提条件中获取的 gRPC 接入点和鉴权 Token。

cat << 'EOF' > otel_conf/config.yaml
receivers:
  otlp:
    protocols:
      grpc: 
        endpoint: 0.0.0.0:4317

exporters:
  otlp:
    endpoint: ${GRPC_ENDPOINT}
    tls:
      insecure: true
    headers:
      "Authentication": "${GRPC_ENDPOINT_TOKEN}"

processors:
  batch:
  
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]
EOF

五、创建后端服务(Node.js)

  1. 创建 package.json 文件。文件描述了项目的配置信息,包括项目名称、版本、依赖项等。

    cat << 'EOF' > backend/package.json
    {
      "name": "backend",
      "version": "1.0.0",
      "main": "index.js",
      "scripts": {},
      "keywords": [],
      "author": "",
      "license": "ISC",
      "description": "",
      "dependencies": {
        "@opentelemetry/api": "^1.9.0",
        "@opentelemetry/auto-instrumentations-node": "^0.52.0",
        "axios": "^1.7.7",
        "express": "^4.21.1"
      }
    }
    EOF
  2. 创建 main.js 文件,包含一个基本的 Express 框架 Web 应用程序。

    cat << 'EOF' > backend/main.js
    "use strict";
    const axios = require("axios").default;
    const express = require("express");
    const app = express();
    app.get("/", async (req, res) => {
      const result = await axios.get("http://localhost:7001/hello");
      return res.status(201).send(result.data);
    });
    app.get("/hello", async (req, res) => {
      console.log("hello world!")
      res.json({ code: 200, msg: "success" });
    });
    app.use(express.json());
    app.listen(7001, () => {
      console.log("Listening on http://localhost:7001");
    });
    EOF
  3. 创建 Dockerfile 文件。

    cat << 'EOF' > backend/Dockerfile
    FROM node:20.16.0
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    ENV OTEL_TRACES_EXPORTER="otlp"
    ENV OTEL_LOGS_EXPORTER=none
    ENV OTEL_METRICS_EXPORTER=none
    ENV OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc
    ENV OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://otel-collector:4317"
    ENV OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
    ENV OTEL_SERVICE_NAME="ot-nodejs-demo"
    ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
    EXPOSE 7001
    CMD ["node", "main.js"]
    EOF

六、创建 Docker Compose 文件

该文件定义了一个多容器应用程序的配置,包括 Nginx 反向代理、OpenTelemetry Collector 和 Node.js 后端服务,以及它们之间的网络连接和端口映射。

cat << 'EOF' > docker-compose.yml
version: "3"

services:
  nginx:
    image: nginx:1.27.2-alpine-otel # 默认包含 opentelemetry 模块的 nginx 镜像
    volumes:
      - ./nginx_conf/nginx.conf:/etc/nginx/nginx.conf:ro 
      - ./nginx_conf/default.conf:/etc/nginx/conf.d/default.conf:ro 
    ports:
      - "80:80"
    networks:
      - nginx-otel-demo

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    volumes:
      - ./otel_conf/config.yaml:/etc/otelcol-contrib/config.yaml # 挂载 OpenTelemetry Collector 配置文件
    ports:
      - "4317:4317" # OTLP gRPC receiver
    networks:
      - nginx-otel-demo

  backend-api:
    build:
      context: ./backend
      dockerfile: Dockerfile
    environment:
      - NODE_ENV=production
    ports:
      - "7001:7001"
    networks:
      - nginx-otel-demo

networks:
  nginx-otel-demo:
    driver: bridge
EOF

七、启动服务

  1. 在 nginx-otel-demo 目录中执行以下命令。

    docker compose up -d

    预期输出:image (14).png

  2. 访问应用。

    curl http://localhost:80/hello

    预期输出:

    {"code":200,"msg":"success"}
  3. 登录可观测链路 OpenTelemetry 版控制台查看 Nginx 及后端服务的调用链。

    在本例中,Nginx 在应用列表页面的名称为 nginx,后端应用名称为 ot-nodejs-demo。

常见问题

  1. 在 Nginx 配置文件中添加 Nginx OTel 模块配置后,Nginx 启动失败。

    请使用nginx -t命令检查 Nginx 配置是否正确,或查看 Nginx 日志中的错误信息。

    sudo tail -n 50 /var/log/nginx/error.log
  2. 启用 Nginx OTel 模块后,在可观测链路 OpenTelemetry 版控制台没有看到 Nginx 的调用链。

    请检查 Nginx 配置中 otel_exporter.endpoint 是否配置正确。该地址由 OpenTelemetry Collector 地址以及 gRPC 接收器端口组成,例如localhost:4317。可通过查看 Nginx 日志确认是否配置正确,当出现如下报错时,说明 otel_exporter.endpoint 未配置正确。

    image (15).png

  3. Nginx 的调用链无法与其他应用串联。

    请确保 Nginx 配置中添加了otel_trace_context inject;,并确认其他应用使用的链路透传协议与 Nginx 的透传协议是否相同。Nginx OTel 模块使用 OpenTelemetry W3C 作为透传协议,这需要其他应用也要使用 OpenTelemetry W3C 透传协议才能串联。

  4. Nginx OTel 插件模块是否会对 Nginx 的性能产生影响?

    Nginx OTel 插件模块为 Nginx 原生模块,官方提示该模块对 Nginx 性能的影响限制在10-15%。详细信息,请参见NGINX Native OpenTelemetry (OTel) Module

相关链接