Я разобрался с ELK в .NET, чтобы вам не пришлось

от автора

Вообще, эта статья — по сути краткий обзор современных инструментов логирования, которые часто используются в.NET. Но из‑за того, что настройка ELK — относительно трудоемкий процесс, весь фокус по итогу сместился в сторону использования сего стека. Я просто смирился с этим. Надеюсь, мой опыт сэкономит время и силы читателю, желающему ознакомиться с ELK.

Что такое и зачем нужно?

ELK — это довольно популярный стек для сбора логов, основанный на базе данных Elasticsearch. Приблизительная схема работы ELK такова: есть три сервиса (сам Elasticsearch, Logstash и Kibana) и есть ваше приложение.

  • Ваше приложение отправляет свои логи в Logstash

  • Logstash их сохраняет в Elasticsearch

  • Через Kibana пользователь может посмотреть сохраненные логи

Почему так сложно, и не проще ли grep’ать данные из файликов? Да, проще. Но не когда у тебя куча сервисов, ещё и от разных команд. Тут понадобится мощный инструмент для хранения и поиска (он же — Elasticsearch) и желательно удобный интерфейс (привет, Kibana).

Окно просмотра логов в Kibana. Основная особенность - через поиск можно найти и отфильтровать любую интересующую информацию

Окно просмотра логов в Kibana. Основная особенность — через поиск можно найти и отфильтровать любую интересующую информацию

Мониторинг в ELK

На самом деле Elastic предоставляет множество различных решений для сбора данных, которые так или иначе могут помочь в troubleshooting’е. Одно из них — сервис Application Performance Monitoring (APM). Он осуществляет сбор телеметрии — метрик, логов и трассировок. Это позволяет разработчикам не только отловить нежелательное поведение, но и полностью проследить «путь» поломавшегося запроса, даже если он проходит через брокер сообщений типа RabbitMQ. В общем, APM — инструмент впечатляющий и безумно полезный в SOA‑подходах.

APM использует протокол OpenTelemetry для получения данных из приложения. Это значит, что ваш код, написанный для отправки телеметрии в APM, также сгодится и для Prometheus или.NET Aspire. По крайней мере в теории, ибо на практике приходется скачивать дополнительные Nuget‑пакеты для совместимости.

APM собирает телеметрию и отправляет в Elasticsearch. После чего её можно просмотреть через Kibana (В каком-то смысле, это EAK-стек?)

APM собирает телеметрию и отправляет в Elasticsearch. После чего её можно просмотреть через Kibana (В каком-то смысле, это EAK-стек?)

Немного об аналогах

Loki-стек

Раз речь зашла не только о ELK, хотелось бы кратенько затронуть тему его конкурентов. Наверное, один из самых известных аналогов — Loki‑стек. Он представляет собой связку Promtail + Loki + Grafana. К сожалению, у меня нет опыта его использования. Но мне доводилось пользоваться связкой Grafana + Prometheus. Это стек для сбора телеметрии. Из плюсов могу отметить, что он потребляет значительно меньше ресурсов, чем решение от Elastic.

.NET Aspire

Говоря об инструментах логирования и мониторинга в.NET, нельзя не упомянуть.NET Aspire (а вернее отдельный его компонент — Dashboard). Он является dev‑time решением, которое осуществляет сбор логов и телеметрии. Его очень легко развернуть (достаточно запустить докер‑образ) и он не поедает много ресурсов. В общем, очень рекомендую к ознакомлению.

Трассировка запроса в .NET Aspire

Трассировка запроса в .NET Aspire

Написание кода

Подключить отправку логов в ELK в C# очень легко. Достаточно добавить nuget-пакет Elastic.Extensions.Logging, после чего будет доступен специальный метод расширения для отправки данных в Logstash:

// По-умолчанию присоединится к http://localhost:5044 builder.Logging.AddElasticsearch();

Для мониторинга нужно установить пакеты Elastic.OpenTelemetry и OpenTelemetry.Instrumentation.AspNetCore

builder.Services     .AddOpenTelemetry()     .WithTracing(opt =>         opt             .AddOtlpExporter(x => x.Endpoint = new Uri("http://localhost:8200"))             .WithElasticDefaults()             .AddSource("MassTransit") // если используете MassTransit             .AddAspNetCoreInstrumentation()             .AddHttpClientInstrumentation()             .ConfigureResource(resource => resource.AddService("MicroFun.A"))     ).WithLogging(opt =>         opt             .AddOtlpExporter(x => x.Endpoint = new Uri("http://localhost:8200"))             .ConfigureResource(resource => resource.AddService("MicroFun.A"))             .WithElasticDefaults()     ).WithMetrics(opt => opt         .AddOtlpExporter(x => x.Endpoint = new Uri("http://localhost:8200"))         .ConfigureResource(resource => resource.AddService("MicroFun.A"))         .WithElasticDefaults()     );

Этого в принципе достаточно, чтобы собирать телеметрию. Если нужно собрать метрики по специфичному участку кода, то можно добавить в трейсинг новый источник и обернуть этот участок в Activity

builder.Services.AddOpenTelemetry().WithTracing(opt => opt.AddSource("MicroFun.B")   // ... );  // ... в некотором участке кода: using var source = new System.Diagnostics.ActivitySource("MicroFun.B"); using var activity = source.StartActivity("CreatingProduct"); // ...

Установка ELK

Установка ELK может занять некоторые время, т.к. потребуется сконфигурировать каждый сервис. В примере я буду использовать docker-compose

Elasticsearch

В первую очередь запускаем сам Elasticsearch

services:   my-elasticsearch:     image: elasticsearch:9.0.2     environment:       ES_JAVA_OPTS: -Xms2g -Xmx2g # Elasticsearch очень любит поедать оперативку        cluster.name: "docker-cluster"       network.host: 0.0.0.0       discovery.type: single-node       node.name: my-elasticsearch       xpack.security.enabled: false       xpack.security.audit.enabled: false     ports:       - 9200:9200

и генерим токен для Кибаны

docker exec -it dd6add1b6b82 sh ./bin/elasticsearch-service-tokens create elastic/kibana my-token # SERVICE_TOKEN elastic/kibana/my-token = AAEAAWVsYXN0aWMva2liE... <--- это наш токен

Такая сложность только с Elasticsearch. Остальные сервисы можно будет спокойно запустить с уже готовым docker-compose

Logstash

Теперь можно сконфигурировать остальные сервисы. Для Logtash пишем простой конфиг, который собирает логи по TCP и передает их в Elasticsearch

input { tcp { port => 5044 type => "logs" } }  output { elasticsearch { hosts => ["my-elasticsearch:9200"] index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}" } }
services:   my-elasticsearch:     // ...   my-logstash:     image: logstash:9.0.2     volumes:       - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf     ports:       - 5044:5044     depends_on:       - my-elasticsearch

Kibana

Для Kibana не забываем добавить сгенерированный токен в конфиг (ей богу, не знаю, как это автоматизировать)

server.host: "0.0.0.0" server.shutdownTimeout: "5s" elasticsearch.hosts: [ "http://my-elasticsearch:9200" ] elasticsearch.serviceAccountToken: AAEAAWVsYXN0aWMva2liYW5h...
services:   my-elasticsearch:     // ...   my-logstash:     // ...   my-kibana:     image: kibana:9.0.2     volumes:       - ./kibana.yml:/usr/share/kibana/config/kibana.yml     ports:       - 5601:5601     depends_on:       - my-elasticsearch

APM

Для APM никаких хитростей благо нет. Так же пишем конфиг и добавляем в docker-compose секцию с сервисом

apm-server:   host: "0.0.0.0:8200"   shutdown_timeout: 15s output.elasticsearch:   hosts: ['my-elasticsearch:9200'] setup.dashboards.enabled: true setup.kibana:   host: "my-kibana:5601"
services:   my-logstash:     // ...   my-elasticsearch:     // ...   my-kibana:     // ...   my-apm:     image: elastic/apm-server:9.0.2     volumes:       - ./apm-server.yml:/usr/share/apm-server/apm-server.yml     ports:       - "8200:8200"     depends_on:       - my-elasticsearch

Теперь можем запускать наше приложение и смотреть в Kibana, как Elasticsearch наполняется драгоценными логами. Для этого переходим в Observability > Logs > Discover и наблюдаем записи

По-умолчанию ничего полезного и понятного, но если перенести из Available fields столбцы service.name и message, то ситуация становится лучше

По-умолчанию ничего полезного и понятного, но если перенести из Available fields столбцы service.name и message, то ситуация становится лучше

Пара слов о best-practises

Во‑первых, хотел бы упомянуть, что вообще в ELK‑стек чуть ли не по‑умолчанию входит Filebeat. Это сервис, который собирает логи из файлов и отправляет их в Logstash. Мне он показался не настолько интересным, чтобы перегружать им статью и без того полную всяких конфигов. Тем не менее это не отменяет его полезности и даже необходимости, поскольку как‑минимум third‑party приложения скорее всего не смогут самостоятельно слать логи в Logstash.

Во‑вторых, хорошей практикой в C# является использование Serilog. Если кто‑то вдруг с ним не знаком, то в двух словах Serilog — это такой провайдер логов «на стероидах» (хотя в основном все его используют просто для сохранения логов в файлы, т.к. в.NET нет такого провайдера по‑умолчанию). И скорее всего в реальном приложении вам потребуется пакет Elastic.Serilog.Sinks https://www.elastic.co/docs/reference/ecs/logging/dotnet/serilog‑data‑shipper

Вместо заключения

Несмотря на то, что современные инструменты логирования обладают высоким порогом вхождения (и я имею в виду в первую очередь ресурсоемкость сбора и обработки логов), они созданы для того, чтобы наоборот упростить нам жизнь. И в целом с этой задачей справляются они неплохо, хоть и сисадмины скорее всего облегчения особо не чувствуют.

Для интеграции ELK в.NET приложение разработчик Elastic предоставляет очень удобный Nuget‑пакет, с помощью которого сия задача решается в одну строчку кода. Примерно так же обстоят дела с мониторингом, что не может не радовать.

Из минусов можно отметить то, что ELK довольно громоздкий. По этой причине в будущем хочу копнуть в сторону Loki‑стека. Впрочем, для облачных приложений это не проблема. Популярные облачные платформы поддерживают интеграцию с ELK, а в development среде всегда можно развернуть Prometheus + Grafana или воспользоваться.NET Aspire, который существенно упрощает жизнь.


ссылка на оригинал статьи https://habr.com/ru/articles/922942/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *