使用 OpenTelemetry Collector
1. 概述
OpenTelemetry Collector收集、處理和發送有關我們軟體運作情況的遙測資料。它與各種系統和工具集成,使我們能夠將其合併到我們現有的設定中。
在本教程中,我們將研究 OpenTelemetry Collector 的核心功能:接收遙測資料、處理遙測資料以及將遙測資料匯出到資料儲存。我們也將簡要探討擴充的使用。
2. 設定
為了示範這些功能,我們將使用DictionaryService ,它將充當託管在我們內部電腦上的第三方庫 JAR。此DictionaryService針對每個請求以隨機暫停間隔產生一個英文單字及其相關定義。然後,我們將使用一個單獨的TriviaService來啟動對DictionaryService HTTP 呼叫。
首先,讓我們安裝收集器。
OpenTelemetry Collector 提供了多種跨各種環境的安裝選項。對於我們的本地設置,讓我們在 Docker 容器中運行收集器:
docker pull otel/opentelemetry-collector-contrib:latestdocker run -p 4317:4317 -p 4318:4318 --name telemetry-collector -d otel/opentelemetry-collector-contrib:latest連接埠 4317 透過 gRPC 協定接收遙測數據,而連接埠 4318 透過 HTTP 接收遙測數據。讓我們驗證一下我們的收集器是否已正確啟動:
docker logs -f telemetry-collector預設安裝以 10 秒的間隔收集自己的指標,如上一個指令所示。現在我們已經運行了一個收集器,讓我們向它發送遙測資料。
3.自動儀表(零代碼)
自動檢測使我們能夠添加可觀察性,而無需編輯庫的源代碼。讓我們下載一個代理 JAR 文件,用於偵測第三方DictionaryService :
curl -L -O https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar接下來,讓我們啟動第三方獨立應用程序,該應用程式打包為名為dictionary-service.jar的JAR檔案:
java -javaagent:/path/to/file/opentelemetry-javaagent.jar \
 -Dotel.service.name=dictionary-service-application \
 -jar /path/to/library/dictionary-service-0.0.1.jar使用此命令,OpenTelemetry Java 代理程式將向收集器發送預設指標,例如與作業系統相關的指標、JVM 指標等。
我們可以在連接埠 8081 上存取託管字典服務。
curl -i http://127.0.0.1:8081/api/words/random我們的收集器日誌應顯示類似以下的輸出:
telemetry-collector-1 | ScopeLogs SchemaURL:
 telemetry-collector-1 | InstrumentationScope com.baeldung.DictionaryController
 telemetry-collector-1 | LogRecord #0
 telemetry-collector-1 | ObservedTimestamp: 2024-10-21 12:43:03.079415993 +0000 UTC
 telemetry-collector-1 | Timestamp: 2024-10-21 12:43:03.079342085 +0000 UTC
 telemetry-collector-1 | SeverityText: INFO
 telemetry-collector-1 | SeverityNumber: Info(9)
 telemetry-collector-1 | Body: Str(Processing received request for a random word)
 telemetry-collector-1 | Trace ID: d6f2fabcd2d28ebc405c0ee6965d612c
 telemetry-collector-1 | Span ID: a8cc3a77f30e98a3telemetry-collector-1 | 2024-10-21T12:43:07.480Z info ResourceLog #0
 telemetry-collector-1 | Resource SchemaURL: https://opentelemetry.io/schemas/1.24.0
 telemetry-collector-1 | Resource attributes:
 telemetry-collector-1 | -> container.id: Str(cf34dafd2008f7106c0be4effc94da302ee69b1f493acd88948e8375351028ca)
 telemetry-collector-1 | -> host.arch: Str(amd64)
 telemetry-collector-1 | -> host.name: Str(codespaces-70d510)
 telemetry-collector-1 | -> os.description: Str(Linux 6.5.0-1025-azure)
 telemetry-collector-1 | -> os.type: Str(linux)
 telemetry-collector-1 | -> process.command_args: Slice(["/usr/local/sdkman/candidates/java/17.0.13.fx-librca/bin/java","-javaagent:./opentelemetry-javaagent.jar","-Dotel.service.name=dictionary-service-application","-jar","./target/dictionary-service-0.0.1.jar"])
 telemetry-collector-1 | -> process.executable.path: Str(/usr/local/sdkman/candidates/java/17.0.13.fx-librca/bin/java)
 telemetry-collector-1 | -> process.pid: Int(19539)接下來,讓我們手動檢測我們的消費者服務。
4. 手動儀表
透過手動檢測,我們可以自訂我們的服務產生的一些遙測訊號。我們首先新增所需的依賴項:
<dependencyManagement>
 <dependency>
 <groupId>io.opentelemetry</groupId>
 <artifactId>opentelemetry-bom</artifactId>
 <version>1.41.0</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 </dependencyManagement>
Maven BOM 確保所有 OpenTelemetry 依賴項版本的一致對齊。
<dependencies>
 <dependency>
 <groupId>io.opentelemetry</groupId>
 <artifactId>opentelemetry-api</artifactId>
 </dependency>
 <dependency>
 <groupId>io.opentelemetry</groupId>
 <artifactId>opentelemetry-sdk</artifactId>
 </dependency>
 <dependency>
 <groupId>io.opentelemetry</groupId>
 <artifactId>opentelemetry-exporter-otlp</artifactId>
 </dependency>
 </dependencies>API 提供了一組用於收集遙測資料的接口,而 SDK 提供了實際的實作。
4.1.設定提供者
接下來,我們將實例化一些提供程序,我們的消費者服務將使用它們將遙測資料傳送到 OpenTelemetry Collector。讓我們從SdkTracerProvider:
public class TelemetryConfig {
 private static TelemetryConfig telemetryConfig;
 private static final String OTLP_TRACES_ENDPOINT
 = "http://telemetry-collector:4318/v1/traces";
 //...
 private TelemetryConfig() {
 Resource resource = Resource.getDefault()
 .toBuilder()
 .put(AttributeKey.stringKey("service.name"), "trivia-service")
 .put(AttributeKey.stringKey("service.version"), "1.0-SNAPSHOT")
 .build();
 SpanExporter spanExporter = OtlpHttpSpanExporter.builder()
 .setEndpoint(OTLP_TRACES_ENDPOINT).build();
 SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
 .setResource(resource)
 .addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
 .build();
 //...
 }
 }在TelemetryConfig中,我們定義了一個Resource來描述服務。 SpanExporter將追蹤資料傳送到遠端系統,而SdkTracerProvider建立實際的跨度(追蹤事件)。接下來我們將實例化SdkMeterProvider:
public class TelemetryConfig {
 private static final String OTLP_METRICS_ENDPOINT
 = "http://telemetry-collector:4318/v1/metrics";
 //...
 private TelemetryConfig() {
 //...
 MetricExporter metricExporter = OtlpHttpMetricExporter.builder()
 .setEndpoint(OTLP_METRICS_ENDPOINT).build();
 MetricReader metricReader = PeriodicMetricReader.builder(metricExporter)
 .setInterval(30, TimeUnit.SECONDS)
 .build();
 SdkMeterProvider meterProvider = SdkMeterProvider.builder()
 .setResource(resource)
 .registerMetricReader(metricReader)
 .build();
 //...
 }
 }MetricExporter將指標資料傳送到端點, SdkMeterProvider負責我們如何定期收集和讀取這些指標。最後,我們將介紹SdkLoggerProvider:
public class TelemetryConfig {
 private static final String OTLP_LOGS_ENDPOINT
 = "http://telemetry-collector:4317";
 //...
 private TelemetryConfig() {
 //...
 LogRecordExporter logRecordExporter = OtlpGrpcLogRecordExporter.builder()
 .setEndpoint(OTLP_LOGS_ENDPOINT).build();
 LogRecordProcessor logRecordProcessor
 = BatchLogRecordProcessor.builder(logRecordExporter).build();
 SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder()
 .setResource(resource)
 .addLogRecordProcessor(logRecordProcessor).build();
 //...
 }
 }SdkLoggerProvider設定產生日誌的記錄器,處理完日誌後, LogRecordExporter將它們傳送到端點。然後我們把它們放在一起:
public class TelemetryConfig {
 //...
 private final OpenTelemetry openTelemetry;
 private TelemetryConfig() {
 //...
 openTelemetry = OpenTelemetrySdk.builder()
 .setMeterProvider(meterProvider)
 .setTracerProvider(tracerProvider)
 .setLoggerProvider(sdkLoggerProvider)
 .setPropagators(
 ContextPropagators.create(
 TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(),
 W3CBaggagePropagator.getInstance())
 )
 )
 .buildAndRegisterGlobal();
 //...
 }
 }ContextPropagators使我們能夠在服務之間共享上下文數據,並且我們的程式碼使用W3CTraceContextPropagator和W3CBaggagePropagator來遵守行業標準。在這裡,我們使用所有元件配置openTelemetry對象,並全域註冊它,以便在整個系統中輕鬆存取。
4.2.使用提供者
接下來,讓我們呼叫託管的DictionaryService並捕獲一些指標:
@Path("/trivia")
 public class TriviaResource {
 private final Tracer tracer;
 private final Meter meter;
 private final LongCounter httpRequestCounter;
 private TriviaService triviaService;
 static final String OTEL_SERVICE_NAME = "trivia-service";
 static final String WORD_SERVICE_URL
 = "http://localhost:8081/api/words/random";
 public TriviaResource() {
 this.triviaService = new TriviaService(new OkHttpClient());
 TelemetryConfig telemetryConfig = TelemetryConfig.getInstance();
 this.tracer = telemetryConfig.getOpenTelemetry()
 .getTracer(OTEL_SERVICE_NAME, "0.0.1-SNAPSHOT");
 this.meter = telemetryConfig.getOpenTelemetry()
 .getMeter(OTEL_SERVICE_NAME);
 this.httpRequestCounter = meter.counterBuilder("http.request.count")
 .setDescription("Counts the number of HTTP requests")
 .setUnit("1")
 .build();
 }
 @GET
 @Produces(MediaType.TEXT_PLAIN)
 public Response retreiveCard() {
 httpRequestCounter.add(1, Attributes.builder().put("endpoint", "/trivia")
 .build());
 Span span = tracer.spanBuilder("retreive_card")
 .setAttribute("http.method", "GET")
 .setAttribute("http.url", WORD_SERVICE_URL)
 .setSpanKind(SpanKind.CLIENT).startSpan();
 try (Scope scope = span.makeCurrent()) {
 WordResponse wordResponse
 = triviaService.requestWordFromSource(WORD_SERVICE_URL);
 span.setAttribute("http.status_code", wordResponse.httpResponseCode());
 return Response.ok(wordResponse.wordWithDefinition()).build();
 } catch(IOException exception) {
 span.setStatus(
 StatusCode.ERROR, "Error retreiving info from dictionary service"
 );
 span.recordException(exception);
 return Response.noContent().build();
 } finally {
 span.end();
 }
 }
 }在我們的retrieveCard方法中,我們使用tracer.spanBuilder來建立並啟動一個Span ,它追蹤操作的整個生命週期,捕捉其持續時間和過程中的任何錯誤。此外,我們將外部服務回應代碼新增為Span上的屬性以取得更多資訊。
我們的TriviaResource將在每次收到請求時記錄遙測資料。讓我們在 Docker 容器中運行它:
docker build -t trivia-webservice .docker run -p 8080:8080 --name trivia-webservice -d -t trivia-webservice:latest
然後,當我們向服務端點發出請求時,它將傳回一個隨機單字及其定義:
curl http://127.0.0.1:8080/trivia-webservice/api/trivia現在我們已經完成了設置,讓我們將注意力轉回收集器。
5. 收集器管路
之前,當我們使用 Docker 啟動 OpenTelemetry Collector 時,我們沒有指定任何配置,因此收集器使用預設設定。我們可以透過指定 YAML 設定檔來更改此行為。讓我們建立一個collector-config.yaml文件,在下一小節中,我們將指定接收器。
5.1.接收器
顧名思義,接收器負責接受來自各種來源的遙測資料。讓我們編輯collector-config.yaml檔案來反映這一點:
receivers:
 otlp:
 protocols:
 grpc:
 endpoint: 0.0.0.0:4317
 http:
 endpoint: 0.0.0.0:4318
 hostmetrics:
 collection_interval: 60s
 scrapers:
 cpu:
 memory:
 load:
 # ...
 service:
 pipelines:
 traces:
 metrics:
 receivers: [hostmetrics, otlp]
 logs:此組態指定接收器偵聽傳入 OTLP 資料的位址和連接埠。此外,當我們將 OpenTelemetry Collector 部署為主機上的代理程式時, 主機指標接收器會收集並記錄有關主機系統的指標。
接下來,我們繼續修改處理器功能。
5.2.處理器
OpenTelemetry Collector 處理器功能可讓我們在將遙測資料匯出到儲存或分析工具之前對其進行清理。讓我們調整collector-config.yaml檔案以合併此功能:
processors:
 batch:
 attributes/remove_client_address:
 actions:
 - key: client.address
 action: delete
 # ...
 service:
 pipelines:
 traces:
 receivers:
 processors: [batch, attributes/remove_client_address]
 exporters:透過此配置,我們將從收到的遙測有效負載中刪除client.address span 屬性。
此外,讓我們配置一個記憶體限制器處理器來幫助我們的收集器避免記憶體不足的情況:
processors:
 #...
 memory_limiter:
 check_interval: 1s
 limit_mib: 6000
 spike_limit_mib: 1200
 #...
 service:
 pipelines:
 traces:
 processors: [memory_limiter, batch, attributes/client_address]
 metrics:
 processors: [memory_limiter]
 logs:
 processors: [memory_limiter, batch]透過此配置,我們的收集器將每秒測量記憶體使用情況,並在本實例中記憶體使用率達到 4.8GB 時套用軟限制。我們將memory_limiter處理器首先放置在管道中,以確保向接收器施加背壓並降低資料遺失的風險。
接下來,讓我們使用導出器卸載遙測資料。
5.3.出口商
OpenTelemetry Collector 匯出器有助於將收集的資料傳輸到各種後端以進行分析和視覺化。讓我們更新collector-config.yaml文件,其中包含有關將遙測資料傳送到何處的說明:
exporters:
 prometheus:
 endpoint: "0.0.0.0:8889"
 otlp:
 endpoint: "jaeger:4317"
 tls:
 insecure: true
 # ...
 service:
 pipelines:
 traces:
 exporters: [otlp]
 metrics:
 exporters: [prometheus]透過此配置,我們的 Collector 直接將追蹤發送到Jaeger後端,為我們提供了一個用戶友好的介面來查看和分析這些追蹤。此外,Prometheus 將透過主動抓取我們的指標來追蹤我們系統隨時間的健康狀況和性能。
進行這些更新後,我們需要重新啟動 Collector Docker 容器並指定 YAML 設定檔:
docker run -p 4317:4317 -p 4318:4318 -p 8889:8889 -v /path/to/file/collector-config.yaml:/etc/collector-config.yaml --name telemetry-collector -d otel/opentelemetry-collector-contrib:latest或者,我們可以定義一個docker-compose.yaml文件,以便我們可以使用 Docker Compose 啟動我們的服務:
---
 services:
 jaeger-all-in-one:
 image: jaegertracing/all-in-one:latest
 container_name: jaeger
 ports:
 - 16686:16686
 networks:
 - otel-network
 telemetry-collector:
 image: otel/opentelemetry-collector:latest
 volumes:
 - ./collector-config.yaml:/etc/collector-config.yaml
 command:
 - --config=/etc/collector-config.yaml
 ports:
 - 4317:4317
 - 4318:4318
 - 8889:8889
 - 55679:55679
 networks:
 - otel-network
 prometheus:
 image: prom/prometheus
 container_name: prometheus
 volumes:
 - "./prometheus.yml:/etc/prometheus/prometheus.yml"
 ports:
 - 9090:9090
 networks:
 - otel-network
 web:
 image: trivia-webservice:latest
 ports:
 - 8080:8080
 networks:
 - otel-network
 networks:
 otel-network:
 driver: bridge準備好docker-compose.yaml檔案後,我們可以使用 Docker Compose 指令啟動服務:
docker compose up -d一旦我們呼叫服務幾次,我們就可以檢查 Prometheus 以查看它收到了多少請求:
curl 'http://127.0.0.1:9090/api/v1/query?query=http_request_count_total{endpoint="/trivia"}'該查詢應該給我們一個如下所示的結果:
{
 "status": "success",
 "data": {
 "resultType": "vector",
 "result": [
 {
 "metric": {
 "__name__": "http_request_count_total",
 "endpoint": "/trivia",
 "exported_job": "trivia-service",
 "instance": "telemetry-collector:8889",
 "job": "otel-collector"
 },
 "value": [
 1729545261.56,
 "8"
 ]
 }
 ]
 }
 }/ trivia端點的http_request_count_total指標顯示值為 8。
我們也可以透過使用服務名稱來檢查 Jaeger,以仔細查看請求:
curl -G 'http://127.0.0.1:16686/api/traces' --data-urlencode 'service=trivia-service'該調用應該給我們一個如下結果:
{
 "data": [
 {
 "traceID": "fa2aa25585dc6c217ddba732455d7583",
 "spans": [
 {
 "traceID": "fa2aa25585dc6c217ddba732455d7583",
 "spanID": "92628f88adb16011",
 "operationName": "retreive_card",
 "references": [],
 "startTime": 1729594621616443,
 "duration": 1565913,
 "tags": [
 {
 "key": "http.method",
 "type": "string",
 "value": "GET"
 },
 {
 "key": "http.status_code",
 "type": "int64",
 "value": 200
 },
 {
 "key": "http.url",
 "type": "string",
 "value": "http://127.0.0.1:8081/api/words/random"
 },
 {
 "key": "internal.span.format",
 "type": "string",
 "value": "otlp"
 },
 {
 "key": "span.kind",
 "type": "string",
 "value": "client"
 }
 ],
 "logs": [
 {
 "timestamp": 1729594621617640,
 "fields": [
 {
 "key": "event",
 "type": "string",
 "value": "http.request.word-api"
 }
 ]
 }
 ],
 "processID": "p1",
 "warnings": null
 }
 ],
 "warnings": null
 }
 ],
 "errors": null
 }例如,從這個輸出中,我們可以看到請求執行了哪個操作以及花費了多長時間。
6. 營運增強
正如我們到目前為止所看到的,OpenTelemetry Collector 是高度可配置的。擴充功能——我們可以附加到收集器的專門附加元件——提供額外的功能和可自訂的行為。讓我們配置一個 zPages 擴充功能來看看它在實踐中是如何運作的。
6.1.擴大
zPages 幫助我們監控 OpenTelemetry Collector 的效能並在其運行時診斷問題。要將 zPages 擴充附加到我們的 Collector,讓我們使用extensions配置更新collector-config.yaml檔案:
#...
 extensions:
 zpages:
 endpoint: 0.0.0.0:55679
 service:
 pipelines:
 #...
 extensions: [zpages]透過此設置,我們指定一個為 zPage 提供服務的 HTTP 端點,並且收集器從指定連接埠公開 zPage 路由。重新啟動收集器後,我們可以在瀏覽器中查看 zPage 路由,或者對於基於文字的瀏覽器使用以下命令:
curl http://localhost:55679/debug/tracez | lynx --stdin我們可以導航到 TraceZ 路線,它可以讓我們根據延遲檢查跨度並對其進行分類。該擴充的自述文件列出了公開的路由。
七、結論
在本文中,我們研究了 OpenTelemetry Collector 的核心功能。我們了解如何利用自動和手動儀器將遙測資料傳送到我們的收集器。我們也考慮瞭如何自訂收集器接收、處理和傳輸收集的資料的方式。最後,我們研究如何使用擴充功能為收集器附加附加功能。
與往常一樣,所有範例的原始程式碼都可以 在 GitHub 上找到。