.Net Core 透過 Elastic APM 來即時監控服務

前言

紀錄 Elastic APM、opentelemetry-collector 環境建置、.Net Core 串接 OTLP 拋送資訊至 Elastic APM 來監控服務

機器環境&服務

環境
Linux Server: Ubuntu 18.04.6 LTS
服務: Kibana、ElasticSeatch、Elastic APM

Linux Server 端安裝服務

安裝Docker

參考 w4560000 - Linux 安裝 Docker、Docker-Compose

docker-compose 建立 kibana 、elasticsearch、apm、opentelemetry-collector 服務

官方文件請參考
OpenTelemetry Docs/Collector/Configuration
Elastic-7.8 open-telemetry-elastic-get-started

流程圖

需要注意的是 目前測試是用 APM 7.8版本,Elastic 有自行擴充 Elastic Exporter 在 otel/opentelemetry-collector-contrib-dev:40d055d5a23eb00e3732c23be56442c9cb1e1684
2023/04/15 測試時,發現最新版 otel/opentelemetry-collector-contrib-dev 的 otel-collector-config 格式 沒有支援 elastic
所以先延用舊版本 otel/opentelemetry-collector-contrib-dev:40d055d5a23eb00e3732c23be56442c9cb1e1684

todo 正式版的 otel/opentelemetry-collector-contrib 會在測試看看 是否支援 Elastic

vi apm.yaml
version: '3'
services:
  kibana:
    container_name: kibana
    image: kibana:7.8.0
    depends_on:
      - elasticsearch
    ports:  
      - 5601:5601
    environment:
      SERVER_NAME: kibana-server
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
  elasticsearch:
    container_name: elasticsearch
    image: elasticsearch:7.8.0
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    ports:
      - 9200:9200
    volumes:
      - /data01:/usr/share/elasticsearch/data
  apm:
    container_name: apm
    image: docker.elastic.co/apm/apm-server:7.8.0
    ports:
    - 8200:8200
    depends_on:
      - elasticsearch
      - kibana
  opentelemetry-collector:
    container_name: opentelemetry
    image: otel/opentelemetry-collector-contrib-dev:40d055d5a23eb00e3732c23be56442c9cb1e1684
    command: ["--config=/etc/otel-collector-config.yaml", ""]
    volumes:
      - /data02/otel-collector-config.yaml:/etc/otel-collector-config.yaml
    depends_on:
      - apm
    ports:
      - 4317:4317

建立 otel-collector-config.yaml

vi otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:
processors:
  batch:
exporters:
  elastic:
    apm_server_url: "http://apm:8200"
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [elastic]

將服務跑起來

docker-compose -f apm.yaml up -d

.Net Core 專案建置

範例專案可參考
Github-w4560000 DemoOpentelemetry

DemoOpentelemetry.UI = 入口站台
DemoOpentelemetry.API = API1
DemoOpentelemetry.API2 = API2

入口站台會呼叫 API1 的 WeatherForecast API
API1 的 WeatherForecast API 則會呼叫 DB、Redis、API2 的 WeatherForecast API
API2 的 WeatherForecast API 則非同步同時呼叫 一個政府公開資料的API

藉此模擬後端服務之間 Server to Server API 之間的串接,來觀察串接狀況

查看 APM

可清楚看到 API 串接呼叫流程,後面的非同步呼叫也可以看到同時間呼叫三次API

紀錄 API Request、Response

安裝 Nuget 套件 OpenTelemetry.Instrumentation.Http 1.0.0-rc9
程式碼參考 https://github.com/w4560000/DemoOpentelemetry/blob/master/API/Program.cs

    .AddHttpClientInstrumentation(options =>
    {
        options.Enrich = (activity, eventName, rawObject) =>
        {
            if (eventName.Equals("OnStartActivity"))
            {
                if (rawObject is HttpRequestMessage httpRequest)
                {
                    var request = "empty";
                    if (httpRequest.Content != null)
                        request = httpRequest.Content.ReadAsStringAsync().Result;
                    activity.SetTag("http.request_content", request);
                }
            }

            if (eventName.Equals("OnStopActivity"))
            {

                if (rawObject is HttpResponseMessage httpResponse)
                {
                    var response = "empty";
                    if (httpResponse.Content != null)
                        response = httpResponse.Content.ReadAsStringAsync().Result;
                    activity.SetTag("http.response_content", response);
                }
            }

        };
    })

紀錄呼叫 Sql Server QueryString、StoredProcedure、傳入參數

安裝 Nuget 套件 OpenTelemetry.Instrumentation.SqlClient 1.0.0-rc9

     .AddSqlClientInstrumentation(options =>
     {
         options.EnableConnectionLevelAttributes = true;
         options.SetDbStatementForStoredProcedure = true;
         options.SetDbStatementForText = true;
         options.RecordException = true;
         options.Enrich = (activity, connection, command) =>
         {
             if(command is DbCommand dbCommand)
             {
                 var parameters = dbCommand.Parameters;
                 foreach (SqlParameter parameter in parameters)
                     activity.SetTag($"sql.parameter.{parameter.ParameterName}", parameter.Value?.ToString());
             }
         };
     })

攔截 DB Query字串、參數

攔截 DB SP字串、參數

原本想嘗試抓 DB 回傳資料,但後來查原始碼發現 Enrich 是在呼叫 DB 之前執行的,所以無法攔截到 DB 回傳資料
DB回傳資料後的動作,該套件沒有提供自定義方法來供呼叫,所以抓不到 DB 回傳資料
https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs#L153

紀錄呼叫 Redis

安裝 Nuget 套件 OpenTelemetry.Instrumentation.StackExchangeRedis 1.0.0-rc9.7

     .AddRedisInstrumentation(connectionMultiplexer, o => o.SetVerboseDatabaseStatements = true)

結論

可以跨服務實現鏈路追蹤後,會讓開發或維運人員更清楚的了解當服務發生異常時,是哪一支 API 造成,或是延遲發生在哪一段流程
不然以往查看 Log,都要開發人員開專案起來比對 Log 邏輯,才能找出問題原因,甚至若 Log 沒印到的話,還需要重埋 Log 等下次才能重現

OpenTelemetry 目前已經有提供各語言的 SDK 供開發人員使用,且以 .Net Core 來說使用上是不用修改到專案程式碼
只需要在 Program.cs 註冊後即可使用
以往使用 Nuget 開源套件,假設有 Http 呼叫的動作,我們沒辦法得知當下他呼叫了多久,以及傳入回傳值,因為沒有 Log 可以追蹤
但有了鏈路追蹤之後,因為不用改到程式碼,所以這些套件的動作也都可以監控到了,這樣對專案服務的監控和掌控度就可以更多一些,也更能追蹤到異常點

參考連結

Opentelemetry Documentation
m@rcus 學習筆記 [APM] 提升應用程式效能的利器 Elastic APM : dotnet x Elastic
Nitesh Singhal OpenTelemetry with Jaeger in .NET Core


轉載請註明來源,若有任何錯誤或表達不清楚的地方,歡迎在下方評論區留言,也可以來信至 leozheng0621@gmail.com
如果文章對您有幫助,歡迎斗內(donate),請我喝杯咖啡

斗內💰

×

歡迎斗內

github