学习如何使用 OpenTelemetry instrument NGINX

博客文章在发布后不会更新。这篇文章已经发布一年多了,其内容可能已过时,部分链接可能无效。在依赖任何信息之前,请务必核实。

Apache HTTP Server 和 NGINX 是最流行的 Web 服务器。您的应用程序很可能正在使用其中一个。在之前的博客文章中,您学习了如何使用 OpenTelemetry Module for Apache HTTP Server 为 Apache HTTP Server 添加可观察性。在这篇博客文章中,您将学习如何为 NGINX 实现可观察性!

安装 NGINX 模块

在接下来的内容中,您将使用 docker 运行一个启用了 ngx_http_opentelemetry_module.so 并已配置的 NGINX 服务器。当然,您也可以使用 Dockerfile 中使用的相同命令集来配置裸金属机器上的 NGINX 服务器。

从一个空目录开始。创建一个名为 Dockerfile 的文件,并将以下内容复制到其中

FROM nginx:1.23.1
RUN apt-get update ; apt-get install unzip
ADD https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/webserver%2Fv1.0.3/opentelemetry-webserver-sdk-x64-linux.tgz /opt
RUN cd /opt ; unzip opentelemetry-webserver-sdk-x64-linux.tgz.zip; tar xvfz opentelemetry-webserver-sdk-x64-linux.tgz
RUN cd /opt/opentelemetry-webserver-sdk; ./install.sh
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib
RUN echo "load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.23.1/ngx_http_opentelemetry_module.so;\n$(cat /etc/nginx/nginx.conf)" > /etc/nginx/nginx.conf
COPY opentelemetry_module.conf /etc/nginx/conf.d

这个 Dockerfile 的作用

  • 拉取预装了 NGINX 1.23.1 的基础镜像
  • 安装 unzip
  • 下载 opentelemetry-webserver-sdk-x64-linux
  • 解压包,将其放入 /opt 并运行 ./install.sh
  • /opt/opentelemetry-webserver-sdk/sdk_lib/lib 的依赖项添加到库路径 (LD_LIBRARY_PATH)
  • 告诉 NGINX 加载 ngx_http_opentelemetry_module.so
  • 将模块的配置添加到 NGINX。

接下来,创建另一个名为 opentelemetry_module.conf 的文件,并将以下内容复制到其中

NginxModuleEnabled ON;
NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName DemoService;
NginxModuleServiceNamespace DemoServiceNamespace;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;

这将启用 OpenTelemetry 并应用以下配置

  • 通过 OTLP 将 span 发送到 localhost:4317
  • 将属性 service.name 设置为 DemoServiceservice.namespace 设置为 DemoServiceNamespaceservice.instance_id 设置为 DemoInstanceId
  • 将跟踪报告为错误,以便您可以在 NGINX 日志中看到它们

要了解所有可用设置,请参阅 指令的完整列表

Dockerfile 和 NGINX 配置就位后,构建您的 docker 镜像并运行容器

docker build -t nginx-otel --platform linux/amd64 .
docker run --platform linux/amd64 --rm -p 8080:80 nginx-otel
...
2022/08/12 09:26:42 [error] 69#69: mod_opentelemetry: ngx_http_opentelemetry_init_worker: Initializing Nginx Worker for process with PID: 69

容器启动并运行后,例如使用 curl localhost:8080 向 NGINX 发送请求。

由于上面的配置将 NginxModuleTraceAsError 设置为 ON,您将在 NGINX 的错误日志中看到您的跟踪转储

2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: startMonitoringRequest: Starting Request Monitoring for: / HTTP/1.1
Host, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: startMonitoringRequest: WebServer Context: DemoServiceNamespaceDemoServiceDemoInstanceId, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: startMonitoringRequest: Request Monitoring begins successfully , client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_startInteraction: Starting a new module interaction for: ngx_http_realip_module, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_payload_decorator: Key : tracestate, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_payload_decorator: Value : , client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_payload_decorator: Key : baggage, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_payload_decorator: Value : , client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_payload_decorator: Key : traceparent, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_payload_decorator: Value : 00-987932d28550c0a1c0a82db380a075a8-fc0bf2248e93dc42-01, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_startInteraction: Interaction begin successful, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"
2022/08/12 09:31:12 [error] 70#70: *3 mod_opentelemetry: otel_stopInteraction: Stopping the Interaction for: ngx_http_realip_module, client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"

在 Jaeger 中查看 span

此时,NGINX 生成的遥测数据并未发送到 OpenTelemetry Collector 或任何其他可观察性后端。您可以通过创建一个 docker-compose 文件轻松实现这一点,该文件会启动 NGINX 服务器、collector 和 Jaeger

创建一个名为 docker-compose.yml 的文件,并添加以下内容

version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - '16686:16686'
  collector:
    image: otel/opentelemetry-collector:latest
    command: ['--config=/etc/otel-collector-config.yaml']
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
  nginx:
    image: nginx-otel
    volumes:
      - ./opentelemetry_module.conf:/etc/nginx/conf.d/opentelemetry_module.conf
    ports:
      - 8080:80

创建一个名为 otel-collector-config.yaml 的文件,其中包含以下内容

receivers:
  otlp:
    protocols:
      grpc:
      http:
exporters:
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [jaeger]

在启动容器之前,请更新 opentelemetry_module.conf 的第 3 行,以具有正确的导出器端点

NginxModuleEnabled ON;
NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint collector:4317;

您无需重新构建 docker 镜像,因为上面的 docker-compose.yaml 在容器启动时会将 opentelemetry_module.conf 加载为文件卷。

启动所有组件1

docker compose up

在另一个 shell 中,生成一些流量

curl localhost:8080

在浏览器中打开 localhost:16686,搜索来自 DemoService 的跟踪,然后深入查看其中一个。

A screenshot of the Jaeger trace view, showing a waterfall of spans representing the time consumed by different NGINX modules.

您将看到请求期间执行的每个 NGINX 模块都有一个 span。这样,您就可以轻松地发现特定模块的问题,例如,重写规则出现问题。

将 NGINX 放在两个服务之间

当然,NGINX 很少作为独立解决方案使用!大多数情况下,它被用作另一个服务前面的反向代理或负载均衡器。而且,可能有一个服务调用 NGINX 来访问该下游服务。

在运行的示例中添加另外两个服务

  • 一个名为 frontend 的 Node.js 服务,它位于最前面并调用 NGINX
  • 一个名为 backend 的 Java 服务,它位于 NGINX 后面

更新 docker-compose 文件,使其包含这两个服务并覆盖 NGINX 的 default.conf

version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - '16686:16686'
  collector:
    image: otel/opentelemetry-collector:latest
    command: ['--config=/etc/otel-collector-config.yaml']
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
  nginx:
    image: nginx-otel
    volumes:
      - ./opentelemetry_module.conf:/etc/nginx/conf.d/opentelemetry_module.conf
      - ./default.conf:/etc/nginx/conf.d/default.conf
  backend:
    build: ./backend
    image: backend-with-otel
    environment:
      - OTEL_TRACES_EXPORTER=otlp
      - OTEL_METRICS_EXPORTER=none
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318/
      - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
      - OTEL_SERVICE_NAME=backend
  frontend:
    build: ./frontend
    image: frontend-with-otel
    ports:
      - '8000:8000'
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318/
      - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
      - OTEL_SERVICE_NAME=frontend

创建 default.conf 文件,该文件会将 NGINX 的请求转发到后端服务

server {
    listen       80;
    location / {
        proxy_pass http://backend:8080;
    }
}

创建两个空文件夹 backendfrontend

在 frontend 文件夹中,创建一个简单的 Node.js 应用

const opentelemetry = require('@opentelemetry/sdk-node');
const {
  getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');
const {
  OTLPTraceExporter,
} = require('@opentelemetry/exporter-trace-otlp-http');

const initAndStartSDK = async () => {
  const sdk = new opentelemetry.NodeSDK({
    traceExporter: new OTLPTraceExporter(),
    instrumentations: [getNodeAutoInstrumentations()],
  });

  await sdk.start();
  return sdk;
};

const main = async () => {
  try {
    const sdk = await initAndStartSDK();
    const express = require('express');
    const http = require('http');
    const app = express();
    app.get('/', (_, response) => {
      const options = {
        hostname: 'nginx',
        port: 80,
        path: '/',
        method: 'GET',
      };
      const req = http.request(options, (res) => {
        console.log(`statusCode: ${res.statusCode}`);
        res.on('data', (d) => {
          response.send('Hello World');
        });
      });
      req.end();
    });
    app.listen(8000, () => {
      console.log('Listening for requests');
    });
  } catch (error) {
    console.error('Error occurred:', error);
  }
};

main();

为了完成 frontend 服务,请创建一个空的 Dockerfile 并包含以下内容

FROM node:16
WORKDIR /app
RUN npm install @opentelemetry/api @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-node express
COPY app.js .
EXPOSE 8000
CMD [ "node", "app.js" ]

对于 backend 服务,您将使用带有 OpenTelemetry Java 代理的 Tomcat。为此,在 backend 文件夹中创建类似以下的 Dockerfile

FROM tomcat
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar javaagent.jar
ENV JAVA_OPTS="-javaagent:javaagent.jar"
CMD ["catalina.sh", "run"]

如您所见,Dockerfile 会自动下载并添加 OpenTelemetry Java 代理。

现在,您的顶层目录中应该有以下文件

  • ./default.conf
  • ./docker-compose.yml
  • ./Dockerfile
  • ./opentelemetry_module.conf
  • ./otel-collector-config.yaml
  • ./backend/Dockerfile
  • ./frontend/Dockerfile
  • ./frontend/app.js

一切就绪后,您现在可以启动演示环境1

docker compose up

几分钟后,您应该有五个 docker 容器正在运行

  • Jaeger
  • OpenTelemetry Collector
  • NGINX
  • 前端
  • 后端

使用 curl localhost:8000 向 frontend 发送一些请求,然后打开浏览器中的 Jaeger UI localhost:16686。您应该会看到从 frontend 到 NGINX 再到 backend 的跟踪。

frontend 跟踪应显示错误,因为 NGINX 正在转发 Tomcat 的 Page Not Found

A screenshot of the jaeger trace view, showing a waterfall of spans going from the frontend to NGINX down to the backend.

下一步?

现在,您应该可以将本博客文章中学到的知识应用于您自己的 NGINX 安装。我们很乐意听取您的经验!如果您遇到任何问题,请创建 issue


  1. docker-compose 已弃用。有关详细信息,请参阅 迁移到 Compose V2。 ↩︎ ↩︎