Opentelemetry 有很多种开源组合方案,我们通过三种平台/架构来分别介绍并演示 Opentelemetry 在不同技术架构下部署

1、OpenTelemetry to Jeager 、Grafana、ELK 2、OpenTelemetry to Grafana 3、OpenTelemetry to 观测云

OpenTelemetry

OTEL 是 OpenTelemetry 的简称, 是 CNCF 的一个可观测性项目,旨在提供可观测性领域的标准化方案,解决观测数据的数据模型、采集、处理、导出等的标准化问题,提供与三方 vendor 无关的服务。
OpenTelemetry 是一组标准和工具的集合,旨在管理观测类数据,如 Traces、Metrics、Logs 等 (未来可能有新的观测类数据类型出现)。目前已经是业内的标准。

OTLP

OTLP(全称 OpenTelemetry Protocol )是 OpenTelemetry 原生的遥测信号传递协议,虽然在 OpenTelemetry 的项目中组件支持了Zipkin v2或Jaeger Thrift的协议格式的实现,但是都是以第三方贡献库的形式提供的。只有 OTLP 是 OpenTelemetry 官方原生支持的格式。OTLP 的数据模型定义是基于 ProtoBuf 完成的,如果你需要实现一套可以收集 OTLP 遥测数据的后端服务,那就需要了解里面的内容,对应可以参考代码仓库:opentelemetry-proto(https://github.com/open-telemetry/opentelemetry-proto

OpenTelemetry-Collector

OpenTelemetry Collector (以下简称“otel-collector”)针对如何接收、处理和导出遥测数据提供了与供应商无关的实现。它消除了运行、操作和维护多个代理/收集器的需要,以支持将开源可观察性数据格式(例如 Jaeger、Prometheus 等)发送到一个或多个开源或商业后端。此外,收集器让最终用户可以控制他们的数据。收集器是默认位置检测库导出其遥测数据。

OpenTelemetry-Java

Opentelemetry基于java语言开发的sdk,支持将数据通过各种exporter 推送到不同的观测平台。

OpenTelemetry-JS

OpenTelemetry 推出的基于前端 js 的链路追踪。

架构

image.png

架构说明

1、应用 server 和 client 将 metric 、trace 数据通过 otlp-exporter push 到 otel-collector
2、front-app 为前端链路,将链路信息 push 到 otel-collector,并访问应用服务 API
3、otel-collector 对数据进行收集、转换后,将数据 push 到 Jaeger、Zipkin
4、同时 Prometheus 从 otel-collector pull 数据。
日志两种推送方式:
方式一:通过 OTLP 上报日志
应用 server 和 client 将 log 通过 otlp-exporter push 到 otel-collector,再通过 otel-collector exporter 到 Elasticsearch。由于 Opentelemetry log 方面还不稳定,所以推荐log单独处理,不走 otel-collector,在测试过程中也发下了 同时配置 log 和 metric 存在冲突问题,主要表现在 otel-collector 上,等待官方修复吧。
方式二:通过 Logback-logstash 上报日志
应用 server 和client将 log 通过 Logback-logstash 推送到 logstash。
otel-collector 配置了四个 exporter.

  1. prometheus:
  2. endpoint: "0.0.0.0:8889"
  3. const_labels:
  4. label1: value1
  5. zipkin:
  6. endpoint: "http://otel_collector_zipkin:9411/api/v2/spans"
  7. format: proto
  8. jaeger:
  9. endpoint: otel_collector_jaeger:14250
  10. tls:
  11. insecure: true
  12. elasticsearch:
  13. endpoints: "http://192.168.0.17:9200"

注意,所有的应用都部署在同一个机器上,机器 ip 为 192.168.0.17。如果应用和一些中间件单独分开部署,则注意修改对应的 IP。如果是云服务器,则注意开放相关端口,以免访问失败。

安装部署

安装 OpenTelemetry-Collector

源码地址

https://github.com/lrwh/observable-demo/tree/main/opentelemetry-collector-to-all

配置 otel-collector-config.yaml

新增 collecter 配置,配置1个recevier(otlp)、4个exporter(prometheus、zipkin、jaeger 和 elasticsearch。

  1. receivers:
  2. otlp:
  3. protocols:
  4. grpc:
  5. http:
  6. cors:
  7. allowed_origins:
  8. - http://*
  9. - https://*
  10. exporters:
  11. prometheus:
  12. endpoint: "0.0.0.0:8889"
  13. const_labels:
  14. label1: value1
  15. zipkin:
  16. endpoint: "http://otel_collector_zipkin:9411/api/v2/spans"
  17. format: proto
  18. jaeger:
  19. endpoint: otel_collector_jaeger:14250
  20. tls:
  21. insecure: true
  22. elasticsearch:
  23. endpoints: "http://192.168.0.17:9200"
  24. processors:
  25. batch:
  26. extensions:
  27. health_check:
  28. pprof:
  29. endpoint: :1888
  30. zpages:
  31. endpoint: :55679
  32. service:
  33. extensions: [pprof, zpages, health_check]
  34. pipelines:
  35. traces:
  36. receivers: [otlp]
  37. processors: [batch]
  38. exporters: [zipkin, jaeger]
  39. metrics:
  40. receivers: [otlp]
  41. processors: [batch]
  42. exporters: [prometheus]
  43. logs:
  44. receivers: [otlp]
  45. processors: [batch]
  46. exporters: [elasticsearch]

通过 docker-compose 安装 otel-collector

  1. version: '3.3'
  2. services:
  3. jaeger:
  4. image: jaegertracing/all-in-one:1.29
  5. container_name: otel_collector_jaeger
  6. ports:
  7. - 16686:16686
  8. - 14250
  9. - 14268
  10. zipkin:
  11. image: openzipkin/zipkin:latest
  12. container_name: otel_collector_zipkin
  13. ports:
  14. - 9411:9411
  15. # Collector
  16. otel-collector:
  17. image: otel/opentelemetry-collector:0.50.0
  18. command: ["--config=/etc/otel-collector-config.yaml"]
  19. volumes:
  20. - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
  21. ports:
  22. - "1888:1888" # pprof extension
  23. - "8888:8888" # Prometheus metrics exposed by the collector
  24. - "8889:8889" # Prometheus exporter metrics
  25. - "13133:13133" # health_check extension
  26. - "4317:4317" # OTLP gRPC receiver
  27. - "4318:4318" # OTLP http receiver
  28. - "55670:55679" # zpages extension
  29. depends_on:
  30. - jaeger
  31. - zipkin
  32. prometheus:
  33. container_name: prometheus
  34. image: prom/prometheus:latest
  35. volumes:
  36. - ./prometheus.yaml:/etc/prometheus/prometheus.yml
  37. ports:
  38. - "9090:9090"
  39. grafana:
  40. container_name: grafana
  41. image: grafana/grafana
  42. ports:
  43. - "3000:3000"

配置 Prometheus

  1. scrape_configs:
  2. - job_name: 'otel-collector'
  3. scrape_interval: 10s
  4. static_configs:
  5. - targets: ['otel-collector:8889']
  6. - targets: ['otel-collector:8888']

启动容器

  1. docker-compose up -d

查看启动情况

  1. docker-compose ps

image.png

Docker 安装 ELK

采用 Docker 安装 ELK ,简单又方便,相关组件版本为7.16.2

拉取镜像

  1. docker pull docker.elastic.co/elasticsearch/elasticsearch:7.16.2
  2. docker pull docker.elastic.co/logstash/logstash:7.16.2
  3. docker pull docker.elastic.co/kibana/kibana:7.16.2

配置目录

  1. # Linux 特有配置
  2. sysctl -w vm.max_map_count=262144
  3. sysctl -p
  4. # Linux 配置结束
  5. mkdir -p ~/elk/elasticsearch/plugins
  6. mkdir -p ~/elk/elasticsearch/data
  7. mkdir -p ~/elk/logstash
  8. chmod 777 ~/elk/elasticsearch/data

Logstash配置

  1. input {
  2. tcp {
  3. mode => "server"
  4. host => "0.0.0.0"
  5. port => 4560
  6. codec => json_lines
  7. }
  8. }
  9. output {
  10. elasticsearch {
  11. hosts => "es:9200"
  12. index => "springboot-logstash-demo-%{+YYYY.MM.dd}"
  13. }
  14. }

input 参数说明:

tcp : 为tcp协议。 port: tcp 端口 codec:json行解析

Docker-compose 配置

  1. version: '3'
  2. services:
  3. elasticsearch:
  4. image: docker.elastic.co/elasticsearch/elasticsearch:7.16.2
  5. container_name: elasticsearch
  6. volumes:
  7. - ~/elk/elasticsearch/plugins:/usr/share/elasticsearch/plugins #插件文件挂载
  8. - ~/elk/elasticsearch/data:/usr/share/elasticsearch/data #数据文件挂载
  9. environment:
  10. - "cluster.name=elasticsearch" #设置集群名称为elasticsearch
  11. - "discovery.type=single-node" #以单一节点模式启动
  12. - "ES_JAVA_OPTS=-Xms512m -Xmx512m" #设置使用jvm内存大小
  13. - "ingest.geoip.downloader.enabled=false" # (Dynamic, Boolean) If true, Elasticsearch automatically downloads and manages updates for GeoIP2 databases from the ingest.geoip.downloader.endpoint. If false, Elasticsearch does not download updates and deletes all downloaded databases. Defaults to true.
  14. ports:
  15. - 9200:9200
  16. logstash:
  17. image: docker.elastic.co/logstash/logstash:7.16.2
  18. container_name: logstash
  19. volumes:
  20. - ~/elk/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf #挂载logstash的配置文件
  21. depends_on:
  22. - elasticsearch #kibana在elasticsearch启动之后再启动
  23. links:
  24. - elasticsearch:es #可以用es这个域名访问elasticsearch服务
  25. ports:
  26. - 4560:4560
  27. kibana:
  28. image: docker.elastic.co/kibana/kibana:7.16.2
  29. container_name: kibana
  30. depends_on:
  31. - elasticsearch #kibana在elasticsearch启动之后再启动
  32. links:
  33. - elasticsearch:es #可以用es这个域名访问elasticsearch服务
  34. environment:
  35. - "elasticsearch.hosts=http://es:9200" #设置访问elasticsearch的地址
  36. ports:
  37. - 5601:5601

启动容器

  1. docker-compose up -d

查看启动情况

  1. docker-compose ps

image.png

Springboot 应用接入(APM)

源码地址

https://github.com/lrwh/observable-demo/tree/main/springboot-server

启动 server

  1. java -javaagent:opentelemetry-javaagent-1.13.1.jar \
  2. -Dotel.traces.exporter=otlp \
  3. -Dotel.exporter.otlp.endpoint=http://localhost:4350 \
  4. -Dotel.resource.attributes=service.name=server,username=liu \
  5. -Dotel.metrics.exporter=otlp \
  6. -Dotel.logs.exporter=otlp \
  7. -Dotel.propagators=b3 \
  8. -jar springboot-server.jar --client=true

启动 client

  1. java -javaagent:opentelemetry-javaagent-1.13.1.jar \
  2. -Dotel.traces.exporter=otlp \
  3. -Dotel.exporter.otlp.endpoint=http://localhost:4350 \
  4. -Dotel.resource.attributes=service.name=client,username=liu \
  5. -Dotel.metrics.exporter=otlp \
  6. -Dotel.logs.exporter=otlp \
  7. -Dotel.propagators=b3 \
  8. -jar springboot-client.jar

参数说明

otel.traces.exporter:otlp # 配置exporter类型为 otlp,默认otlp。
otel.exporter.otlp.endpoint: otlp exporter endpoint (grpc)
otel.resource.attributes: 配置tag。
otel.metrics.exporter:otlp # 配置 metrics exporter类型,默认none。
otel.logs.exporter:otlp # 配置 logs exporter 类型,默认none。
otel.propagators: 配置trace的传播器。

由于 OpenTelemetry log 方面并不成熟稳定,所以不推荐生产使用,测试过程中也出现一些 Bug,仅作为学习。

Springboot 应用接入(Log)

方式一:通过 OTLP 上报日志

应用 server 和 client 将 log 通过 otlp-exporter push 到 otel-collector,再通过 otel-collector exporter 到 Elasticsearch。

修改启动参数

需要在应用启动时添加参数-Dotel.logs.exporter=otlp

  1. java -javaagent:opentelemetry-javaagent-1.13.1.jar \
  2. -Dotel.traces.exporter=otlp \
  3. -Dotel.exporter.otlp.endpoint=http://localhost:4350 \
  4. -Dotel.resource.attributes=service.name=server,username=liu \
  5. -Dotel.metrics.exporter=otlp \
  6. -Dotel.logs.exporter=otlp \
  7. -Dotel.propagators=b3 \
  8. -jar springboot-server.jar --client=true
  1. java -javaagent:opentelemetry-javaagent-1.13.1.jar \
  2. -Dotel.traces.exporter=otlp \
  3. -Dotel.exporter.otlp.endpoint=http://localhost:4350 \
  4. -Dotel.resource.attributes=service.name=client,username=liu \
  5. -Dotel.metrics.exporter=otlp \
  6. -Dotel.logs.exporter=otlp \
  7. -Dotel.propagators=b3 \
  8. -jar springboot-client.jar

启动后,日志会通过 otlp 协议传输到 otel-collector,并由 otel-collector exporter 到 Elasticsearch。

方式二:通过 Logstash-logback 上报日志

主要是通过 Logstash-logback 提供的 socket 方式将日志上传到 Logstash 上,需要对代码做部分调整。

1、项目Maven引用 Logstash-logback

  1. <dependency>
  2. <groupId>net.logstash.logback</groupId>
  3. <artifactId>logstash-logback-encoder</artifactId>
  4. <version>7.0.1</version>
  5. </dependency>

2、新增 logback-logstash.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <!-- 部分参数需要来源于properties文件 -->
    <springProperty scope="context" name="logName" source="spring.application.name" defaultValue="localhost.log"/>
    <!-- 配置后可以动态修改日志级别-->
    <jmxConfigurator />
    <property name="log.pattern" value="%d{HH:mm:ss} [%thread] %-5level %logger{10} [traceId=%X{trace_id} spanId=%X{span_id} userId=%X{user-id}] %msg%n" />

    <springProperty scope="context" name="logstashHost" source="logstash.host" defaultValue="logstash"/>
    <springProperty scope="context" name="logstashPort" source="logstash.port" defaultValue="4560"/>
    <!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,,,, -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/${logName}/${logName}.log</file>    <!-- 使用方法 -->
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/${logName}/${logName}-%d{yyyy-MM-dd}.log.%i</fileNamePattern>
            <maxFileSize>64MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- LOGSTASH输出设置 -->
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <!-- 配置logStash 服务地址 -->
        <destination>${logstashHost}:${logstashPort}</destination>
        <!-- 日志输出编码 -->
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC+8</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "podName":"${podName:-}",
                        "namespace":"${k8sNamespace:-}",
                        "severity": "%level",
                        "serverName": "${logName:-}",
                        "traceId": "%X{trace_id:-}",
                        "spanId": "%X{span_id:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "message": "%message\n%exception"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
        <!-- 保活 -->
        <keepAliveDuration>5 minutes</keepAliveDuration>
    </appender>

    <!-- 只打印error级别的内容 -->
    <logger name="net.sf.json" level="ERROR" />
    <logger name="org.springframework" level="ERROR" />

    <root level="info">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="LOGSTASH"/>
    </root>
</configuration>

3、新增 application-logstash.yml

application-logstash.yml

logstash:
  host: localhost
  port: 4560
logging:
  config: classpath:logback-logstash.xml

4、重新打包

mvn clean package -DskipTests

5、启动服务

java -javaagent:opentelemetry-javaagent-1.13.1.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://localhost:4350 \
-Dotel.resource.attributes=service.name=server,username=liu \
-Dotel.metrics.exporter=otlp \
-Dotel.propagators=b3 \
-jar springboot-server.jar --client=true \
--spring.profiles.active=logstash \
--logstash.host=localhost \
--logstash.port=4560
java -javaagent:opentelemetry-javaagent-1.13.1.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://localhost:4350 \
-Dotel.resource.attributes=service.name=client,username=liu \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=otlp \
-Dotel.propagators=b3 \
-jar springboot-client.jar \
--spring.profiles.active=logstash \
--logstash.host=localhost \
--logstash.port=4560

JS 接入(RUM)

源码地址

https://github.com/lrwh/observable-demo/tree/main/opentelemetry-js

配置 OTLPTraceExporter

const otelExporter = new OTLPTraceExporter({
  // optional - url default value is http://localhost:55681/v1/traces
  url: 'http://192.168.91.11:4318/v1/traces',
  headers: {},
});

此处 url 为 otel-collector 的 otlp 接收地址( http 协议)。

配置server_name

const providerWithZone = new WebTracerProvider({
      resource: new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: 'front-app',
      }),
    }
);

安装

npm install

启动

npm start

默认端口_8090_

APM 与 RUM 关联

APM 与 RUM 主要通过 header 参数进行关联,为了保持一直,需要配置统一的传播器(Propagator),这里RUM 采用的是 B3,所以 APM 也需要配置B3,只需要在 APM 启动参数加上-Dotel.propagators=b3即可。

APM 与 Log 关联

APM 与 Log 主要是通过在日志埋点 traceId 和 spanId。不同的日志接入方式,埋点有差异。

UI展示

通过访问前端url产生 trace 信息。
image.png

ELK 日志展示

ELK 为 ElasticSearch 、Logstash 、Kibana 简称。

通过 OTLP 上报日志

otel-log-es.gif
通过 otlp 方式输出日志,会产生很多 tag,同时也会产生 traceId 和 spanId 。
image.png
展开后日志 source 部分

"_source": {
  "@timestamp": "2022-05-18T08:39:20.661000000Z",
  "Body": "this is method3,null",
  "Resource.container.id": "7478",
  "Resource.host.arch": "amd64",
  "Resource.host.name": "cluster-ecs07",
  "Resource.os.description": "Linux 3.10.0-1160.15.2.el7.x86_64",
  "Resource.os.type": "linux",
  "Resource.process.command_line": "/usr/java/jdk1.8.0_111/jre:bin:java -javaagent:opentelemetry-javaagent-1.13.1.jar -Dotel.traces.exporter=otlp -Dotel.exporter.otlp.endpoint=http://localhost:4350 -Dotel.resource.attributes=service.name=server,username=liu -Dotel.metrics.exporter=otlp -Dotel.logs.exporter=otlp -Dotel.propagators=b3",
  "Resource.process.executable.path": "/usr/java/jdk1.8.0_111/jre:bin:java",
  "Resource.process.pid": 5728,
  "Resource.process.runtime.description": "Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 25.111-b14",
  "Resource.process.runtime.name": "Java(TM) SE Runtime Environment",
  "Resource.process.runtime.version": "1.8.0_111-b14",
  "Resource.service.name": "server",
  "Resource.telemetry.auto.version": "1.13.1",
  "Resource.telemetry.sdk.language": "java",
  "Resource.telemetry.sdk.name": "opentelemetry",
  "Resource.telemetry.sdk.version": "1.13.0",
  "Resource.username": "liu",
  "SeverityNumber": 9,
  "SeverityText": "INFO",
  "SpanId": "bb890485f7b6ba05",
  "TraceFlags": 1,
  "TraceId": "b4841a6b3ec9aa93d7f002393a156ff5"
  },

通过 otlp 协议,实现了traceId 和 spanId 在 log 上的自动埋点。

通过 Logstash-logback 上报日志

logtash-kibana.gif
展开后日志 source 部分

  "_source": {
    "@timestamp": "2022-05-18T13:43:34.790Z",
    "port": 55630,
    "serverName": "otlp-server",
    "namespace": "k8sNamespace_IS_UNDEFINED",
    "message": "this is tag\n",
    "@version": "1",
    "severity": "INFO",
    "thread": "http-nio-8080-exec-1",
    "pid": "3975",
    "host": "gateway",
    "class": "c.z.o.server.controller.ServerController",
    "traceId": "a7360264491f074a1b852cfcabb10fdb",
    "spanId": "e4a8f1c4606ca598",
    "podName": "podName_IS_UNDEFINED"
  },

通过 Logstash-logback 方式需要将 traceId 和 spanId 手动埋点。

Prometheus & Grafana 指标展示

image.png

image.png

Jeager、Zipkin 链路展示

jaeger-ui.gif

zipkin-ui.gif

下一篇 我们来介绍 Opentelemetry 是如何基于 grafana 相关组件进行可观测。