OpenTelemetry 入门和部署

在维护现代分布式系统时,了解服务的运行状况和进行错误分析往往充满挑战。当客户请求横跨多个微服务、函数和基础设施时,如何能够清晰地洞察整个请求链路的健康状况与性能表现?这个问题的答案,就在于完善的服务可观测性。
可观测性不仅仅是传统的监控(记录已知的故障模式),它赋予我们通过系统外部输出来探索、分析和理解系统内部状态的能力。这通常建立在三大支柱之上:指标(Metrics)、链路(Traces) 和日志(Logs)。
OpenTelemetry简介
在传统的可观测性实践中,应用程序需要分别使用不同的SDK对接各种监控平台,如Prometheus用于指标监控、Jaeger用于链路跟踪、Elasticsearch用于日志处理。在异构技术栈并存的微服务环境中,如何以一种统一、标准化的方式采集和关联这些运行数据,是一个复杂的难题。正是在这样的背景下,OpenTelemetry(简称OTel) 应运而生。它是一个由云原生计算基金会(CNCF)托管的开源项目,旨在提供一套与供应商无关的、统一的API、SDK和工具集,用于采集和导出遥测数据。
使用OpenTelemetry,实现可观测性目标的过程被大大简化。应用程序只需要对接OpenTelemetry,SDK将指标发送给OpenTelemetry,然后OpenTelemetry会负责进行数据处理,并转发给Prometheus、Jaeger和Elasticsearch等服务。OpenTelemetry的主要目标之一就是让任何编程语言、基础设施和运行环境中的应用程序和系统都易于进行观测。
具体来说,OpenTelemetry解决了3个核心问题:
定义一套标准——OTLP协议:OpenTelemetry Protocol (OTLP) 是OpenTelemetry项目定义的一种通用遥测数据传递协议。OTLP旨在为遥测数据(包括Traces、Metrics和Logs)的编码、传输和传递提供一个标准化的机制。它定义了数据从遥测源(如应用程序的SDK)到中间节点(如OpenTelemetry Collector收集器),再到最终遥测后端(如存储和分析系统)的整个过程,实现了标准化、高效性和通用性的metrics、traces、logs数据传递。OTLP推荐并主要使用Protocol Buffers (Protobuf)进行数据的序列化和gRPC传输,也支持其他序列化和HTTP传输方式。OTLP是OpenTelemetry生态系统的"通用语言",它通过定义标准的数据格式和传输机制,极大地简化了可观测性数据的采集和互操作性。
适配不同编程语言和框架的SDK:OpenTelemetry为各种主流编程语言(如Java、Go、Python、JavaScript等)提供了官方实现的SDK。开发者只需使用对应语言的SDK进行简单集成,就能自动或手动地从应用程序中采集遥测数据,并将其转换成符合OTLP标准的格式。这极大地简化了开发者的接入成本。
指标采集和处理服务——OpenTelemetry Collector:这是一个独立运行的代理服务,是OpenTelemetry架构中的"智能枢纽"。应用程序将OTLP格式的数据发送到Collector,由它来统一负责后续繁重的工作,例如:
- 接收与转发:接收来自多个应用的数据,然后批量转发给一个或多个后端平台。
- 处理与加工:对数据进行清洗、过滤、采样或富化(添加额外的标签信息)。
- 格式转换:将OTLP数据转换成其他后端系统支持的格式(如Jaeger的格式、Prometheus的格式等)。
基于以上三点,OpenTelemetry构建了一个标准化的、端到端的、高性能的遥测数据采集与处理管道。
下图展示了OpenTelemetry的可观测性架构:

可以看到,OpenTelemetry提供了各类语言的SDK用于对接OpenTelemetry,也提供了中间用于数据处理和转发的OpenTelemetry Collector工具。而数据最终的归属,依旧是大家常用的那些Elasticsearch和Prometheus等平台。OpenTelemetry就像一座桥梁,左边连接应用软件,右边连接各个可观测平台,它提供的统一SDK和数据处理工具大大降低了对接的复杂性。
接下来我们将搭建一套极简版本的OpenTelemetry架构,基于以下的基础环境:
- OS:Debian 13
- Docker:docker-ce 29.1.2
OpenTelemetry部署
接下来使用Docker Compose部署一套基本可用的OpenTelemetry服务,包括以下4个服务:
- OpenTelemetry Collector:接收应用程序发送过来的链路跟踪、指标和日志数据等,并处理和转发到各个后端服务。
- Jaeger:2.12.0版本,接收OpenTelemetry Collector发送过来的OTLP标准的跟踪数据,提供链路跟踪和可视化查询能力。
- Prometheus:接收OpenTelemetry Collector发送过来的metrics,提供指标存储和查询的能力。
- Elasticsearch:8.19.7版本,作为存储后端,本案例中有两个作用,一方面用于保存OpenTelemetry Collector发送过来的日志,另一方面作为Jaeger的后端存储服务。
本文使用了比较常见的技术栈,实际上,在OpenTelemetry的生态系统中,有大量支持OTLP协议的服务可以替换上述各组件。此处不再一一赘述,详细清单可参见官方文档。
Docker Compose配置
为了简化部署,本文使用Docker Compose的方式部署4个服务。compose.yml的内容如下:
services:
jaeger:
image: jaegertracing/jaeger:2.12.0
container_name: jaeger
command:
- "--config=file:/etc/jaeger/jaeger.yml"
volumes:
- ./conf/jaeger.yml:/etc/jaeger/jaeger.yml
ports:
- 16686:16686 # HTTP UI
- 14317:4317 # GRPC
- 14318:4318 # HTTP
depends_on:
elasticsearch:
condition: service_healthy
prometheus:
container_name: prometheus
image: prom/prometheus
user: 1000:1000
command:
- --web.enable-otlp-receiver
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus/data/
volumes:
- ./conf/prometheus.yml:/etc/prometheus/prometheus.yml
- ./prom_data/:/prometheus/data/
ports:
- 9090:9090
elasticsearch:
image: elasticsearch:8.19.7
container_name: elasticsearch
restart: always
environment:
- cluster.name=elasticsearch
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.enrollment.enabled=false
ports:
- "9200:9200"
volumes:
- ./es_data:/usr/share/elasticsearch/data
healthcheck:
interval: 5s
retries: 60
test: curl --fail -s -o /dev/null http://127.0.0.1:9200/
otel-collector:
image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.142.0
container_name: otel-collector
command:
- --config=/etc/otelcol-config.yml
volumes:
- ./conf/otelcol-config.yml:/etc/otelcol-config.yml
ports:
- 4317:4317 # GRPC
- 4318:4318 # HTTP
depends_on:
elasticsearch:
condition: service_healthy
jaeger:
condition: service_started
上面的compose.yml文件看起来较长,但仔细观察会发现每个容器的配置都十分精简且易于理解,建议仔细阅读。
Jaeger配置解释
在上面定义的compose.yml中,Jaeger项目作为一个trace后端,接收trace类型的遥测数据,并提供对这些数据的处理、聚合、数据挖掘和可视化功能。
容器功能:
- 容器内监听
4317和4318端口,映射到宿主机的14317和14318,避免与otel-collector监听的端口产生冲突。 16686端口提供Jaeger的Web UI服务,用于查看应用的trace链路。- Jaeger会将trace数据保存到Elasticsearch服务中,所以需要依赖Elasticsearch先启动。
- 本地的
conf/jaeger.yml文件保存Jaeger的配置文件,里面定义了数据的接收和发送方式。
conf/jaeger.yml配置文件的内容如下,基于Jaeger v2版本:
extensions:
jaeger_query:
storage:
traces: myes_storage
metrics: myes_storage
base_path: /jaeger/ui
jaeger_storage:
backends:
myes_storage: &elasticsearch_config
elasticsearch:
server_urls:
- http://elasticsearch:9200
indices:
index_prefix: "jaeger-main"
metric_backends:
myes_storage: *elasticsearch_config
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
jaeger_storage_exporter:
trace_storage: myes_storage
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger_storage_exporter]
配置说明:
extensions:定义了jaeger_query和jaeger_storage两个扩展,前者配置了Jaeger的Web UI功能,后者是Jaeger的存储功能。存储功能中又配置了trace存储和metric存储,都使用Elasticsearch服务保存。在myes_storage中配置了Elasticsearch服务的API地址和使用的indices名字前缀。receivers:指定Jaeger作为接收者,监听gRPC和HTTP端口。otel-collector和应用可以把trace数据发往这两个端口。processors:Jaeger具有处理指标的能力,这里仅配置了常用的batch处理器。exporters:定义了将数据发送到Elasticsearch存储服务中。service:引用了extensions、receivers、processors、exporters的配置。重要的是pipelines中的管道,配置了从OTLP协议接收traces数据,发送到exporter配置中定义的Elasticsearch存储服务。
上面配置的基本含义是:通过OTLP协议在4317 (gRPC) 和4318 (HTTP) 端口接收trace数据,使用批处理器对数据进行批量处理,通过exporter将数据存储到Elasticsearch,Jaeger Query扩展从相同的Elasticsearch后端读取数据并在Web UI展示。
Jaeger的OTLP端口和OpenTelemetry Collector一样,在同一个主机上监听相同的端口号会有冲突。如果修改Jaeger的端口号,会导致Jaeger的自身指标采集报错,虽然可以用环境变量
OTEL_TRACES_SAMPLER=always_off禁用Jaeger的自监控,但是不建议这么做。
Jaeger的官方文档内容相对较少:https://www.jaegertracing.io/docs/2.12/ Jaeger仓库中的参考配置案例:https://github.com/jaegertracing/jaeger/tree/v2.12.0/cmd/jaeger
Prometheus配置解释
Prometheus一般使用Pull模式拉取指标,但它也支持作为OTLP接收器。打开CLI标志--web.enable-otlp-receiver后,就可以在Prometheus的/api/v1/otlp/v1/metrics路径上提供OTLP指标接收服务。
在Prometheus的Docker定义中做了如下配置:
- 启用
--web.enable-otlp-receiverCLI flag,开启Prometheus的OTLP指标接收功能。OpenTelemetry Collector会往这个服务发送metrics数据。 - 另外将本地的
conf/prometheus.yml配置文件和prom_data目录映射到容器中,映射prom_data目录用于提供数据持久化的能力。 - Prometheus的9090端口提供接收指标和UI查询功能,暴露到主机上。
Prometheus配置文件conf/prometheus.yml:
global:
scrape_interval: 15s
otlp:
keep_identifying_resource_attributes: true
promote_resource_attributes:
- service.instance.id
- service.name
- service.namespace
- service.version
- deployment.environment.name
Prometheus的配置相对简单:
- 配置OTLP接收器处理来自应用程序的遥测数据
- 将
service.name、service.instance.id等资源级别的属性提升为指标标签,便于查询和筛选。
Prometheus一般作为指标存储服务,常与Grafana可视化平台搭配使用,简单起见,本文不涉及Grafana的内容。
Elasticsearch配置解释
Elasticsearch提供日志存储和查询功能,可视化的查询服务需要部署Kibana。关于Kibana服务的内容,在之前的文章EFK日志体系快速入门中已经有所提及,为了简化演示,本次就不部署Kibana了。如果部署Kibana,要保持Kibana和Elasticsearch的版本一致。
由于Jaeger官方文档指定支持Elasticsearch的7.x和8.x版本,所以没有使用最新的Elasticsearch 9.x版本。
在compose.yml文件中,对Elasticsearch做了如下的简单配置:
- 关闭了Elasticsearch安全功能,免去配置用户名和密码的麻烦。这种操作仅限于测试环境。
- Elasticsearch的数据映射到
es_data目录下,实现存储数据持久化。
OpenTelemetry Collector配置详解
OpenTelemetry Collector的Docker相对简单,主要是将接收数据的端口4317(gRPC)和4318(HTTP)暴露出来用于应用上报数据。
conf/otelcol-config.yml配置文件的内容如下,定义了数据接收、处理和转发的逻辑:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 2s
send_batch_size: 1000
transform:
log_statements:
- statements:
- set(log.attributes["elasticsearch.index"], Concat(["logs", resource.attributes["service.name"]], "-"))
exporters:
otlp:
endpoint: "jaeger:4317"
tls:
insecure: true
otlphttp/prometheus:
endpoint: "http://prometheus:9090/api/v1/otlp"
tls:
insecure: true
elasticsearch:
endpoint: "http://elasticsearch:9200"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: []
exporters: [otlp]
metrics:
receivers: [otlp]
processors: []
exporters: [otlphttp/prometheus]
logs:
receivers: [otlp]
processors: [batch, transform]
exporters: [elasticsearch]
otel-collector的配置文件和Jaeger有相似的结构,这套配置文件中定义了如下3个部分:
- receivers:otel-collector监听的gRPC和HTTP服务端口,应用程序可以往这些端口上发送数据。
- processors:创建了batch和transform 2个processor:
- batch:批量处理数据,提升处理效率。
- transform:为日志数据新增了
elasticsearch.index这个属性,格式是logs-${service.name},service.name是客户端上报的属性名字。Elasticsearch会根据elasticsearch.index的值自动创建索引。如此,就实现了不同应用使用不同索引的需求。transform proccessor 十分有用,可以很方便对日志元数据和内容进行编辑。
- exporters:otel-collector将把数据发往这些后端服务,分别是Jaeger、Prometheus和Elasticsearch服务。
- service:定义了数据处理的pipeline,trace类型的指标发往Jaeger、metric的指标发往Prometheus、日志类型的数据发往Elasticsearch。
OpenTelemetry Processor 文档 中可以查看所有的各类processor。有需要的请前往了解。
Note:Elasticsearch Exporter默认使用 Data Stream 将数据发送到Elasticsearch,Data Stream 类型的索引的前缀一般是
.ds-,十分合适存储只会append数据的存储场景。使用Data Stream类型时,发送给Elasticsearch的索引(即elasticsearch.index属性)必须是logs-、metrics-或者traces-为prefix的,否则 Elasticsearch会返回报错index_not_found_exception。如果要自定义非Data Stream 格式的索引名字,必须显式的关闭Data Stream,方法为elasticsearch.mapping::mode = none。这个地方踩了很久的坑,有太多隐藏的约定,值得注意。
数据的处理和存储形态可能是多种多样的,比如可能有Kafka消息队列应对大规模并发;也可能仅使用Elasticsearch技术栈,毕竟Elasticsearch+Kibana几乎可以承担所有的Trace、Metric和Log的存储、处理和展示。这里展现的只是一种常见的组合方式,而OpenTelemetry的强大之处也在于对各类平台的广泛支持。
另外,为了叙述方便,文中的Docker和各平台的配置都做了极简化的处理,能运行起来。但省略了安全、性能等方面的优化,在正式使用时,还需要再读读文档,对配置进行与环境适配性的改造。
启动服务
确保上面的compose.yml和conf/jaeger.yml、conf/prometheus.yml、conf/otelcol-config.yml文件已经都创建好了。
启动前先创建Elasticsearch和Prometheus的数据持久化目录:
$ mkdir es_data
$ mkdir prom_data
$ sudo chown -R 1000:1000 es_data prom_data
$ docker compose up -d
测试验证
虽然现在还没有开发上报数据的应用,但是Jaeger自身的指标也会被采集和发送到OpenTelemetry Collector,打开Jaeger的Web UI界面,多刷新几次就能看到Jaeger自身的trace数据了。
接下来使用curl命令向OpenTelemetry Collector的HTTP端口发送各类简单的测试数据。
测试Traces功能
向otel-collector上报一条trace数据,构造一条模拟耗时10s的trace数据(startTimeUnixNano和endTimeUnixNano相差10s):
$ curl -X POST http://localhost:4318/v1/traces -H "Content-Type: application/json" -d '{
"resourceSpans": [{
"resource": {
"attributes": [{
"key": "service.name",
"value": {"stringValue": "test-service"}
}]
},
"scopeSpans": [{
"spans": [{
"traceId": "'$(openssl rand -hex 16)'",
"spanId": "'$(openssl rand -hex 8)'",
"name": "curl-test-span",
"kind": 1,
"startTimeUnixNano": '"$(date --date='10 seconds ago' +%s%N)"',
"endTimeUnixNano": '"$(date +%s%N)"',
"attributes": [{
"key": "test.attribute",
"value": {"stringValue": "42"}
}]
}]
}]
}]
}'
打开Jaeger的Web UI地址为http://127.0.0.1:16686,在打开页面的Service下拉框中应该能选中test-service,然后点击Find Traces按钮,右侧就能看到刚刚上报的trace的详情了。其中的duration显示为10s。
测试Metrics功能
向otel-collector上报一条测试的metric数据:
$ curl -X POST http://localhost:4318/v1/metrics -H "Content-Type: application/json" -d '{
"resourceMetrics": [{
"resource": {
"attributes": [{
"key": "service.name",
"value": {"stringValue": "test-service"}
}]
},
"scopeMetrics": [{
"metrics": [{
"name": "curl_test_gauge",
"gauge": {
"dataPoints": [{
"timeUnixNano": '"$(date +%s%N)"',
"asInt": "42"
}]
}
}]
}]
}]
}'
打开Prometheus的Web UI,本机地址为http://127.0.0.1:9090,在输入框中填入指标名curl_test_gauge并搜索,就能看到指标了。提交数据中的属性service.name在Prometheus中对应了service__name="test-service" label。
测试Logs功能
向otel-collector上报一条日志数据:
$ curl -X POST http://localhost:4318/v1/logs -H "Content-Type: application/json" -d '{
"resourceLogs": [{
"resource": {
"attributes": [{
"key": "service.name",
"value": { "stringValue": "test-service" }
}]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s%N)'",
"severityText": "ERROR",
"body": { "stringValue": "error occurred" }
}]
}]
}]
}'
查看Elasticsearch的indices和数据,省略了其中uuid的信息:
$ curl 127.0.0.1:9200/_cat/indices?v # 查看索引
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open jaeger-main-jaeger-service-2025-12-14 ... 5 1 13 0 41.9kb 41.9kb 41.9kb
yellow open .ds-logs-test-service-2025.12.14-000001 ... 1 1 1 0 5.8kb 5.8kb 5.8kb
yellow open jaeger-main-jaeger-span-2025-12-14 ... 5 1 335 0 208.4kb 208.4kb 208.4kb
$ curl http://127.0.0.1:9200/logs-test-service/_search | jq . # 查看index中的日志
...
查看indices时,jaeger-main-*格式的index是Jaeger发送过来的trace数据。
而.ds-logs-test-service.*格式的是应用通过 otel-collector发送的日志数据,其中的doc数量是1,就是我们刚刚上报的一条日志。
在proccessor.transform中设置的elasticsearch.index 明明是 logs-${service.name},现在为什么变成了 .ds-logs-test-service.* ?这个index名字可能有点奇怪,原因在于 logs-${service.name}的index格式匹配了Elasticsearch的内置模板,名字为 logs,会导致所有 logs-*-*的索引都保存为 Data Stream格式,并且添加日期后缀。可以通过Elasticsearch的 /_index_template/logs接口查看这个模板的信息。
具体可以参考 Elasticsearch Exporter。
到此为止,OpenTelemetry的各个组件部署和测试就完成了。
Elasticsearch的Data Stream
在测试OpenTelemetry对接Elasticsearch是才第一次遇到Data Stream,解决了许多问题,这里对Data Stream做个介绍。
Data Stream在Elasticsearch 7.9版本中引入,用于优化时序数据(如Logs、Traces和Metrics)的存储,这类数据的特点是经常append,但是不会update、insert。Data Stream因此能更高效的处理时间范围过滤。当我们创建 logs-test-service这个Data Stream时,Elasticsearch会自动创建索引.ds-logs-test-service-2025.12.14-000001。这个索引的各字段含义为:
.ds-:固定前缀,表示这是 Data Stream 的后端索引,一个Data Stream索引可能由多个这样的后端索引构成,分散的存储数据。.开头的索引被视为隐藏索引(Hidden Indices)。- 不要手动向
.ds-索引进行数据增删改操作。不要手动删除.ds-索引。 .ds-索引应该由ILM(索引生命周期管理)自动化管理,ILM可以通过/_ilm/policy/logs查看。- 用户应该始终使用
logs-test-service,而不是使用.ds-logs-test-service-*。
logs-test-service:定义的 Data Stream 名字。Data Stream 不是独立存在的,它必须匹配一个“开启了 Data Stream 功能”的索引模板。比如logs-test-service就符合logs索引模板的 pattern (logs--)。2025.12.14:该索引创建时的日期。000001:代际号(Generation)。这是一个 6 位填充的数字,从 000001 开始,每当发生一次 Rollover(滚动),这个数字就会加 1。rollover的时机可能是index的大小或者文档数量达到设定的阈值了。
以下是和 Data Stream相关的一些操作:
- GET /_data_stream:查看所有数据流
- GET /_data_stream/logs-test-service:查看特定数据流的详细信息
- GET /_data_stream/_stats:查看数据流统计信息
- POST /logs-test-service/_rollover:手动rollver,产生新的后端索引
此外,每次往data stream中写入数据时,必须指定为create index操作。比如
$ curl -X POST 'http://127.0.0.1:9200/_bulk?pretty' -H "Content-Type: application/json" \
-d '{"create":{"_index":"logs-test-service"}}
{"body":{"text":"error occurred"}}
'
Data Stream还有一个好处,在Kibana上创建data view时,可以直接指定 Data Stream的名字logs-test-service,而不需要使用 logs-test-service-*这种传统的索引匹配方法。
小结
本文中,我们首先介绍了OpenTelemetry通过制定标准、统一接入的方式,优化了可观测性方面的难题。然后,使用Docker部署了一套常见的OpenTelemetry架构。最后,我们使用curl工具向OpenTelemetry Collector的HTTP端口分别上报Traces、Metrics和日志数据,并进行逐一验证。
为了方便展示,文中的配置都是极简的,仅限于测试,在真正用于生产环境之时,以上所有的配置文件,甚至整体架构方面都有大量可以优化的地方。
在下一篇文章中,将使用Python的Flask框架,分享如何在应用程序中集成OpenTelemetry的SDK,并向OpenTelemetry Collector上报数据,最终展示到各个可观测平台。
Last updates at Dec 18,2025
Views (77)
total 0 comments