Opentelemetry(4): Nginx 添加 Opentelemetry 支持

如果在 公众号 文章发现状态为 已更新, 建议点击 查看原文 查看最新内容。

状态: 未更新

原文链接: https://typonotes.com/posts/2023/05/09/nginx-add-opentelemetry-support/

关于 Nginx 添加 OpenTelemetry 支持, 官方有两种推荐:

  1. Nginx Ingress Controller 第三方插件 OpenTelemetry 推荐 otel_ngx_module.so
  2. OpenTelemetry 官网 中, 推荐 opentelemetry-webserver-sdk-x64-linux, 这是一种扩展性更强和跟踪功能更多的插件。

经过测试, 我最终选择了 otel_ngx_module.so, 包含以下原因。

  1. 要保证 自建 NginxNginx-Ingress-Controller 行为保持一致。
  2. 没有找到使用 opentelemetry-webserver 后, 在日志中打印 trace_idspan_id 的方法。
  3. opentelemetry-webserver 目前只支持 x64 平台, arm64 还不支持。

Nginx 模块 otel_ngx_module.so 的编译和安装

提供了两种编译方式

  1. 与官方保持一直
  2. 提取 Dockerfile: 这种方式可能过期。

1. 与官方保持一直的 Github Action 编译

官方在 Github Action 中提供了多平台的模块文件下载。 但是

  1. 官方编译的的 so 文件已经过期, 无法下载。
  2. 该版本的 so 文件 只支持 debian 和 ubuntu

关于过期的解决方法也很简单: Fork 项目到自己的仓库, 在 .github/workflows/nginx.yml 中添加任意空白字符, 触发 CI, 编译完成后即可下载。

需要注意的是 nginx.yml 中定义的版本是 mainlinestable。 因此编译时对应的实际版本要到 https://nginx.org/en/download.html 查证。

否则, 在执行过程中如果出现类似以下错误, 则为编译时版本与运行时版本不一致

2023/05/11 14:36:35 [emerg] 1#1: module "/opt/modules/otel_ngx_module.so" version 1024000 instead of 1023004 in /etc/nginx/nginx.conf:1

注意: 数值版本号中分隔符 . 将以 0 代替。 则 1024000 -> 1.24.00, 1023004 -> 1.23.04。 因此上述错误表示, so 文件对应的 nginx 版本为 1.24.00, 而运行环境为 1.23.04, 因此版本不匹配。

2. 从官方中提取的 Dockerfile 编译

使用这种方法更简单, 可以更精确的指定 Nginx 的版本

访问 https://github.com/tangx/Nginx-With-OpenTelemetry , 克隆项目到本地。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## 编译 debian 系统的依赖
docker build \
    -t example.com/library/nginx-otel:1.24.0-debian \
    -f Dockerfile.nginx-debian \
    --build-arg=IMAGE=nginx:1.24.0-debian .

## 编译 alpine 系统的依赖
docker build \
    -t example.com/library/nginx-otel:1.21.6-alpine \
    -f Dockerfile.nginx-alpine \
    --build-arg=IMAGE=nginx:1.21.6-alpine .

使用 otel_ngx_module.so 的配置管理

官方给出了 详细的 Nginx 配置案例 , 可以直接参考。

这里列出部分比较常用和重要的配置, 注意变更内容。

1. Nginx_Otel 变量管理

我觉得一个最重要的事情, 就是要把 trace_id 打印出来。 这个最直观。

# 加载 so 文件
load_module /etc/nginx/modules/otel_ngx_module.so;

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      # 可以全局使用 opentelemetry 的变量
                      '"$opentelemetry_trace_id" '
                      '"$opentelemetry_context_traceparent" ' ;
    access_log  /var/log/nginx/access.log  main;
}
  1. 首先 在配置文件最外层加载 load_module otel_ngx_module.so 文件。
  2. 其次 在 http, server, location 中使用相关 Nginx 变量管理 , 具体参考文档。

这里我将 opentelemetry_trace_idopentelemetry_context_traceparent 两个变量作为日志字段放在了模板中。

输入日志如下

192.168.144.1 - - [17/May/2023:08:24:22 +0000] "GET / HTTP/1.1" 200 122 "-" "vscode-restclient" "-" "bb98708725f37382f6b80b212f560e21" "00-bb98708725f37382f6b80b212f560e21-c517c60d4b7aee3e-01" 

2. TraceParent 的传递

在 OpenTelemetry 标准中, 需要在请求中添加 特定的 traceparent Header 才能将 trace_id 不断向下传递

在使用了 TraceParent 之后, 在 HTTP 请求将有以下两种情况。

  1. 不带 traceparent header: Nginx 生成一个并往后传递
  2. 带 tracepraent header: Nginx 使用病往后传递。

以下 Demo 是 Nginx 接受时候的案例

1
2
3
4
5
6
### 不带 traceparent, nginx 会自动生成一个 
GET http://127.0.0.1:18080/

### 带 traceparent, nginx 会保持 trace_id, 并向下传递
GET http://127.0.0.1:18080/
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

无论 Nginx 自建还是接受 trace_id, 都需要向下传递, 这里就需要两个重要的参数了

  1. opentelemetry_propagate: 表示需要传递 trace_id。 支持多种模式。
  2. opentelemetry_operation_name my_example_backend: 指定当前操作名称。 my_example_backend 就是名字。
## 加载 so 文件
load_module /etc/nginx/modules/otel_ngx_module.so;

http {
  ## 指定跟踪配置文件
  opentelemetry_config /conf/otel-nginx.toml;

  server {
    listen 80;
    server_name otel_example;

    root /var/www/html;

    location = / {
      # 指定跟踪服务名称
      opentelemetry_operation_name my_example_backend; 
      # 添加 header, 向后传递 traceparent
      opentelemetry_propagate;
      proxy_pass http://localhost:3500/;
    }

    location = /b3 {
      opentelemetry_operation_name my_other_backend;
      ## b3 模式
      opentelemetry_propagate b3;
      ## 添加用户自定义属性
      # Adds a custom attribute to the span
      opentelemetry_attribute "req.time" "$msec";
      proxy_pass http://localhost:3501/;
    }

    location ~ \.php$ {
      root /var/www/html/php;
      opentelemetry_operation_name php_fpm_backend;
      opentelemetry_propagate;
      fastcgi_pass localhost:9000;
      include fastcgi.conf;
    }
  }
}

在 Jaeger 中, 可以看到

蓝色(2) 就是我们上面指定的跟踪服务名称。

3. Jaeger 收集跟踪信息

如果收集跟踪信息, 需要提供相应配置。

以下是 nginx.conf 的配置

## 加载 so 文件
load_module /etc/nginx/modules/otel_ngx_module.so;

http {
  ## 指定跟踪配置文件
  opentelemetry_config /conf/otel-nginx.toml;

  server {
    # ... 省略和上面一样
  }
}

关于 otel-nginx.toml 的完整配置 , 可以参考官网。

主要修改内容就是:

  1. exporter.host 地址, 也就是收集器地址。
  2. service.name 服务名称。 图一中 红色(1)
1
2
3
4
5
6
7
8
9
[exporters.otlp]
# Alternatively the OTEL_EXPORTER_OTLP_ENDPOINT environment variable can also be used.
### 收集器地址
host = "collector"
port = 4317

[service]
# Can also be set by the OTEL_SERVICE_NAME environment variable.
name = "nginx-proxy" # Opentelemetry resource name

我这里的收集是通过 OpenTelemetry Collector 进行中转的。

my-app -> collector -> jaeger

collector 可以根据需求配置不同的后端(jaeger角色)。 这样就实现了 解耦

实现效果如下

通过环境变量提供收集器地址

注意: 官方文档中说可以通过环境变量 OTEL_EXPORTER_OTLP_ENDPOINT="localhost:4317" 来管理收集器地址, 以达到简化配置的效果。

经测试, 这种方法是不可用的。

资源地址

  1. 在项目 tangx/nginx-otel-demo 中, 有全套的测试, 开箱即用。
  2. 在项目 tangx/Nginx-With-OpenTelemetry 提供最新的编译安装方式以及编译结果下载。

其他

1. alpine 下 otel_ngx_module 的编译

官方针对的是 debian 下的 nginx 进行的 so 文件编译。 如果需要使用 alpine 环境, 可以参考博客 alpine nginx 安装编译 otel_ngx_module.so 。 文章是针对 openrestry 实现的, 并且实现较早, 直接替换使用 nginx:1.24-alpine 是不行的。

2. otel_ngx_module 在 debian 和 alpine 下不通用

同样的, 也不能 直接使用 debian 下编译的 so 文件到 alpine 下使用。

2023/05/12 09:02:18 [emerg] 1#1: dlopen() "/opt/modules/otel_ngx_module.so" failed (Error loading shared library libstdc++.so.6: No such file or directory (needed by /opt/modules/otel_ngx_module.so)) in /etc/nginx/nginx.conf:2
nginx: [emerg] dlopen() "/opt/modules/otel_ngx_module.so" failed (Error loading shared library libstdc++.so.6: No such file or directory (needed by /opt/modules/otel_ngx_module.so)) in /etc/nginx/nginx.conf:2