Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров2.9K

Вообще, эта статья — по сути краткий обзор современных инструментов логирования, которые часто используются в.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, который существенно упрощает жизнь.

Теги:
Хабы:
+8
Комментарии5

Публикации

Ближайшие события