ELK. Создание и настройка индексов c помощью Elasticsearch

В прошлом уроке мы рассказали, как реализовать первую часть концепции наблюдаемости — сбор и визуализацию метрик. Теперь ваши сервисы обвешаны индикаторами, которые следят за их самочувствием. Но что делать, если показатели говорят о проблемах? Если метрики показывают ошибки и тайм-ауты, вам понадобится больше информации, чтобы найти источник проблем. Представьте, что вы работаете с онлайн-магазином. В сезон осенних распродаж нагрузка сильно возрастает, и из-за этого случаются отказы и ошибки. Бизнесу жизненно необходимо быстро найти «больной» компонент, чтобы не потерять продажи. Чаще всего с этим могут помочь логи.
В инфраструктуре может быть тысячи серверов. Если будете подключаться к каждому серверу и изучать там логи, процесс займёт много времени. В этом уроке мы расскажем про стек ELK. Он объединяет инструменты, которые помогают ускорить процесс и быстрее найти проблему.

ELK

✏️ ELK — это аббревиатура от ElasticSearch, Logstash и Kibana. Вместе с Beats они составляют набор инструментов для извлечения, поиска, анализа и визуализации данных. Их ещё называют «стек ELK» или «стек Elastic».
Как и в любом другом стеке, инструменты ELK дополняют друг друга. У каждого из них своя зона ответственности и свой порядок применения:
Вместо Beats на схеме изображён Filebeat. Это агент на серверах, который отправляет разные типы оперативных данных. Он входит в семью утилит для доставки данных Beats и специализируется на логах. Вы можете подключить его к элементам вашей инфраструктуры.
Итак, Filebeat агрегирует данные из разных источников — например, Kubernetes, Jenkins, Redis. Его задача — следить за логами и отправлять их дальше:
  • Filebeat передаёт данные в Logstach — конвейер данных.
  • Logstach передаёт данные в Elasticsearch — базу данных.
  • Elasticsearch обменивается данными с Kibana, инструментом для визуализации.
Теперь посмотрим на задачи остальных инструментов.

Logstash

Logstash — это серверный конвейер обработки данных. Он собирает входные данные и отправляет их в Elasticsearch. Программа собирает данные из разных источников. Чаще всего используют Beats.

Elasticsearch

Elasticsearch — это документоориентированная распределённая база данных NoSQL. Она основана поисковой системе Lucene и предоставляет удобный RESTful API. Об особенностях этой базы данных мы ещё поговорим. Пока запомните, что Elasticsearch хранит сложные структуры данных, которые сериализованы в виде документов JSON.
💡 В 2021 году компания Amazon сделала копию сервиса Elasticsearch — OpenSearch. В последние годы OpenSearch стал популярным инструментом в России. В том числе этому способствовало то, что у OpenSearch лицензия Apache 2.0, а вот у оригинального инструмента — лицензия Elastic. По функциональности они почти идентичны.

Kibana

Kibana — это инструмент, который используют для визуализации документов Elasticsearch. Он помогает разработчикам сразу получить представление о них. 
Панель мониторинга Kibana предоставляет разные интерактивные диаграммы, геопространственные данные, временные рамки и графики для визуализации сложных запросов, которые выполняются с помощью Elasticsearch.
Пример дашборда в Kibana
Используя Kibana, вы можете создавать и сохранять пользовательские графики для ваших задач. Например, сделать отчёт по бизнес-показателям заказов магазина.
⚠️ У ELK широкий функционал. Из-за этого некоторые инженеры используют его в качестве базы данных. Но это слишком дорого — всё равно, что забивать гвозди микроскопом.
Ещё не стоит использовать ELK для хранения бинарных данных. Стек не предназначен для работы в режиме файлового хранилища.
Теперь, когда вы познакомились с инструментами стека, мы можем углубиться в детали. Посмотрим, как работает центральное звено ELK — Elasticsearch.

Как работает Elasticsearch

Elasticsearch — это нереляционная база данных. Такие базы объединяют под общим названием NoSQL, потому что они используют иные модели предоставления информации. Кроме Elasticsearch, к таким базам данных относятся MongoDB и Redis.
Если сопоставить компоненты Elasticsearch с компонентами реляционной БД, то получится:
  • индекс вместо базы данных,
  • тип вместо таблицы,
  • поле в документе вместо колонки таблицы,
  • документ вместо записи в таблице.
Если сравнить индекс с другими базами данных, вы увидите такую картину:
ElasticsearchPostgreSQLMongoDB
ИндексБаза данныхБаза данных
Маппинг(mapping)/типТаблицаКоллекция
ПолеКолонкаПоле
Объект (JSON)JSONBОбъект (BSON)
🔍 Индекс — это сердце стека Elastic. Он реализован с помощью библиотеки полнотекстового поиска Lucene.
Индекс Elasticsearch можно рассматривать как оптимизированную коллекцию документов. Каждый документ представляет собой набор полей, которые, в свою очередь, являются парами ключ-значение. Они и содержат ваши данные.
Вот пример такого документа:

JSX

{
  "_index": "index-2024.06.06", 
  "_type": "_doc",
  "_id": "yvNZcWwBygdfsdsdss",
  "_version": 1,
  "_score": null,
  "_source": {
        "user_uuids": [
          "dae7f01c-4c98-db35-a643-bfbb8fcf40f0",
          "db6e3718-cf2f-4ae0-8681-529cb75be9a6"
        ]
    }
} 
Системе удобнее работать с небольшими индексами, чем с одной большой базой данных. Например, можно создать отдельный индекс для логов за каждый день или индекс для категории товаров из каталога.
Индексация происходит после добавления документа через API. Мы рассмотрим основные методы дальше.
По умолчанию Elasticsearch индексирует все данные в каждом поле. Каждое индексируемое поле имеет специальную оптимизированную структуру данных. Например, текстовые поля хранятся в инвертированных индексах, а числовые и географические поля — в деревьях. Основная причина высокой скорости поиска Elasticsearch — это как раз возможность использовать разные структуры данных для каждого поля, чтобы собирать и возвращать результаты поиска.

Типы полей Elasticsearch

В Elasticsearch четыре типа полей:
  • Текстовое. В текстовом поле есть две настройки:
    • «полный текст» — подойдёт для аналитических задач. Например, для поиска в корпоративной системе знаний.
    • «по ключевым словам» — подойдёт, если вам нужно найти совпадение по задачам в конкретных очередях (например, в трекере задач). Или вы хотите найти конкретную метрику.
  • Числовое. Есть несколько типов числовых полей: integer, long, float и double. Выбор между ними зависит от того, нужно вам точное число или диапазон значений.
  • Дата. Определите, нужна ли вам поддержка запросов диапазона дат, арифметики дат (сложение, вычитание и т. д.) или агрегации на основе дат. При выборе учитывайте формат даты и её тип.
  • Геопозиция. Используйте этот тип, если вы работаете с геопространственными данными — точками, фигурами, границами. Учитывайте необходимость таких запросов и требования индексации.

Шардирование

Индекс можно шардировать.
По умолчанию количество шардов для индекса будет равным единице:
index.number_of_shards: 1 
У каждого индекса может быть максимум 1024 сегмента. Каждый шард способен хранить 2^32 записей, а это достаточно много.
💡 Рекомендуем держать размер сегментов в пределах 10–50 ГБ. Это избавит вас от ряда проблем:
  • Большие сегменты могут затруднить восстановление Elasticsearch после сбоя.
  • Слишком большое количество маленьких сегментов может вызвать проблемы с производительностью и ошибки нехватки памяти. Помните, что каждый сегмент использует определённый объём процессора и памяти.
Текущий размер шардов:

JSX

GET _cat/shards?v=true&h=index,prirep,shard,store&s=prirep,store&bytes=gb 
В конце урока вы поработаете с ELK — настроите индексацию, подключите Logstash и Kibana. Но перед этим дадим несколько рекомендаций, которые упростят работу с индексами.

Рекомендации по работе с индексами

  • Удаляйте индексы, а не отдельные документы. Когда вы удаляете документ, Elasticsearch помечает его как удалённый на каждом связанном шарде, но физически он остаётся в системе. Документ останется до тех пор, пока его не удалит Elasticsearch во время периодического слияния сегментов. Поэтому лучше удалять целые индексы, если это возможно. Так вы сразу освободите ресурсы.
  • Распределяйте шарды по нодам. Если вы будете работать с кластером Elasticsearch, не допускайте скопления шардов на одной ноде. Распределяйте их по разным нодам. Подробнее об этой рекомендации можно почитать на официальном сайте Elastic.
  • Явно настраивайте маппинг. Elasticsearch автоматически может создавать сопоставление для каждого поля в каждом документе, который он индексирует. Используйте статическое сопоставление вместо динамического, чтобы избежать создания ненужных полей.
  • Используйте политику управления индексами — ILM. Про неё сейчас расскажем!

ILΜ и этапы жизненного цикла индекса

Работать с данными в индексе помогает политика управления индексами — Index Lifecycle Management, или ILM. Она позволяет настроить автоматическое удаление и объединение индексов, а ещё совершать другие операции.
Подробнее о возможностях ILM можно почитать в документации.
Жизненный цикл индекса состоит из чётырёх основных этапов:
  1. Создание индекса. Чтобы создать новый индекс, нужно установить начальный набор параметров конфигурации: определить количество сегментов, выставить настройки реплик и сопоставления. Обычно для этого используют шаблон.
  2. Ролловер. Когда в индекс добавляются данные, его размер увеличивается. Если размер индекса достиг максимального значения, ILM запускает процесс ролловера. На основе предопределённого шаблона создаётся новый индекс и начинается индексация новых данных. В политике можно настроить и другие условия для ролловера: max_primary_shard_size, max_age, max_docs, max_size.
  3. Хранение. ILM позволяет определить срок хранения индекса. Когда срок истекает, индекс можно пометить для удаления или переместить на более дешёвый уровень хранения. Например, вместо SSD использовать HDD.
  4. Удаление. ILM позволяет автоматически удалять индексы, у которых истёк установленный срок хранения или которые перестали соответствовать другим критериям. Удаление помогает управлять ресурсами хранилища и гарантирует, что данные не хранятся дольше необходимого. Это помогает экономить место хранилища, а ещё соблюдать законы о хранении персональных данных.

Рекомендации по работе с данными в Elasticsearch

  • Выберите анализаторы и токенизаторы. Elasticsearch использует анализаторы и токенизаторы для предварительной обработки текстовых полей во время индексации и поиска. Выбирайте анализаторы и токенизаторы в зависимости от вашего языка, требований к анализу текста и вариантов использования поиска. Учитывайте такие факторы, как стоп-слова, нормализация нижнего и верхнего регистров, n-граммы или пользовательские правила токенизации.
  • Отрегулируйте интервал обновления. Он определяет, как часто Elasticsearch обновляет индекс, чтобы сделать новые данные доступными для поиска. По умолчанию Elasticsearch обновляется каждую секунду. Но вы можете увеличить интервал обновления, если индексируете большой объём данных, чтобы снизить частоту операций обновления и повысить пропускную способность индексации.
💡 При массовой загрузке информации лучше отключить обновление на время, а потом включить. Так вы сможете избежать просадки производительности для систем-потребителей.
Ответьте на несколько вопросов, чтобы закрепить новый материал, а потом перейдём к практике.

Задание 1

Какую функцию выполняет индекс в Elasticsearch?
Индекс в Elasticsearch — это ключевой элемент. С его помощью оптимизируют поиск по документам.

Задание 2

Перед вами несколько утверждений об этапах жизненного цикла индекса. Выберите все верные.
Это так. Поисковые данные можно перенести, например, с дисков HDD на более экономичные диски SSD.
Это правильный ответ. В политике можно настроить и другие условия для ролловера. Например, максимальный размер шардов или максимальное количество документов в индексе.
А теперь — перейдём к практике.

Практика: настройте Elasticsearch, Logstash и Kibana

Для логирования важны:
  • скорость поиска,
  • скорость индексации логов,
  • стабильность поискового движка.
Вы создадите Dockerfile с помощью Docker Compose, настроите индексацию и подключите Logstach и Kibana.
Вот целевая схема архитектуры:
Дальше — инструкция, как её реализовать. Выполняйте её шаг за шагом.
  1. Разверните ELK через Docker Compose. Для этого создайте файл docker-compose.yaml:

    YAML

     version: "3"
     
     services:
       elasticsearch:
         image: elasticsearch:8.14.3
         volumes:
           - ./conf/elasticsearch/conf.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro
           - ./elasticsearch/data:/usr/share/elasticsearch/data
         environment:
           ES_JAVA_OPTS: "-Xmx1G"
           ELASTIC_USERNAME: "elastic"
           ELASTIC_PASSWORD: "sampleELK"
           discovery.type: single-node
         networks:
           - elknet
         ports:
           - "9200:9200"
           - "9300:9300"
     
       logstash:
         image: logstash:8.14.3
         volumes:
           - ./conf/logstash/conf.yml:/usr/share/logstash/config/logstash.yml:ro
         environment:
           LS_JAVA_OPTS: "-Xmx512m -Xms512m"
         ports:
           - "5044:5044"
           - "5010:5000"
           - "9600:9600"
         networks:
           - elknet
         depends_on:
           - elasticsearch
     
       kibana:
         image: kibana:8.14.3
         depends_on:
           - elasticsearch
         volumes:
           - ./conf/kibana/conf.yml:/usr/share/kibana/config/kibana.yml:ro
         networks:
           - elknet
         ports:
           - "5601:5601"
     
     networks:
       elknet:
         driver: bridge
      
  2. Создайте дополнительно файлы конфигурации — conf/elasticsearch/conf.yml:

    YAML

     cluster.name: "sample-elk"
     network.host: 0.0.0.0
     xpack.security.enabled: false # Отключить аутентификацию
     xpack.license.self_generated.type: basic # Тип лицензии "basic"
      
    Сейчас безопасность включать не будем. В Elastic много нюансов, которые касаются настройки xpack.security. Не всегда они пригодятся, особенно если у вашей организации есть собственная система аутентификации.
  3. Скопируйте файл конфигурации Kibana — conf/kibana/conf.yml:

    YAML

     server.name: kibana
     server.host: 0.0.0.0
     server.publicBaseUrl: "http://localhost:5601"
     monitoring.ui.container.elasticsearch.enabled: true
     
     elasticsearch.hosts: ["http://elasticsearch:9200"]
      
  4. Скопируйте файл конфигурации Logstash — conf/logstash/conf.yml:

    YAML

     http.host: "0.0.0.0"
      
  5. Выполните команду, которая запустит контейнеры:

    BASH

     docker-compose up -d
      
    Подождите от одной до трёх минут. Затем проверьте результат — обратитесь к сервисам.
    Для Elasticsearch — http://localhost:9200/.
    Для Kibana — http://localhost:5601/app/home#/.
    Как только убедились, что всё хорошо, — закройте страницы, на которые перешли, и приступайте к настройке Logstash и индекса. Если возникли сложности, сбросьте всё с помощью команды docker-compose down и начните заново.
  6. Добавьте nginx — он будет источником логов. В docker-compose.yaml добавьте:

    YAML

       nginx: 
         image: nginx:latest
         volumes:
           - ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf
           - ./var/log/nginx:/var/log/nginx
         networks:
           - elknet
         ports:
           - 80:80
      
  7. Создайте конфигурации nginx — /conf/nginx/nginx.conf:

JSON

user  nginx; # Установить системного пользователя для сервиса nginx
worker_processes  1; 

error_log  /var/log/nginx/error.log warn; # Логи ошибок nginx
pid        /var/run/nginx.pid; # PID nginx для запуска

events {
    worker_connections  1024; # Максимальное количество разрешённых соединений в процессе
}

http { # Конфигурация HTTP-запросов, НТТP может содержать несколько серверов

    # Определение Content-Type
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format json '{"@timestamp":"$time_iso8601",'
           '"host":"$server_addr",'
           '"ipaddr":"$remote_addr",'
           '"login_user":"$remote_user",'
           '"size":$body_bytes_sent,'
           '"responsetime":$request_time,'
           '"upstreamtime":"$upstream_response_time",'
           '"upstreamhost":"$upstream_addr",'
           '"http_host":"$host",'
           '"url":"$uri",'
           '"domain":"$host",'
           '"xff":"$http_x_forwarded_for",'
           '"referer":"$http_referer",'
           '"status":"$status"}';

    access_log /var/log/nginx/access.json json;

    sendfile        on;
    keepalive_timeout  65;

    gzip  on;
    server { # Сервер может иметь несколько настроенных локаций
        listen       80;
        server_name  localhost; # Имя хоста, доменное имя
        
        location / {
            root   /usr/share/nginx/html; 
            index  index.html index.htm; # Страница по умолчанию
        }
  
        # Перенаправлять ошибки 500, 502, 503, 504 на страницу /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html { # Установить путь к странице, которая определена в error_page
            root   /usr/share/nginx/html; 
        }
    }
    include /etc/nginx/conf.d/*.conf;
} 
  1. Для Logstash добавьте конфигурацию пайплайна логов — /conf/logstash/pipelines.yml:

    JSON

     - pipeline.id: nginx_logs
       pipeline.workers: 1
       pipeline.batch.size: 1
       path.config: "/usr/share/logstash/config/pipelines/nginx.conf"
      
  2. Добавьте конфигурацию логов nginx — /conf/logstash/pipelines/nginx.conf:

    JSON

     
     input {
       beats {
         port => 5044
       }
     }
     
     filter {
             json { 
               source => "message"
             }
             mutate {
               add_field => { "sample" => "Hello world, from %{host}" }
             }
     }
     
     output {
         stdout { codec => json }
         elasticsearch {
           hosts => ["http://elasticsearch:9200"]
           index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}"
         }
       
     }
      
  3. Настройте Filebeat для сбора логов с nginx — /conf/filebeat/conf.yml:

    YAML

    filebeat.inputs:
      - type: log
        enabled: true
        paths:
          - /var/log/nginx/*.json
        json.keys_under_root: true
        json.overwrite_keys: false
        json.add_error_key: true
    
    output.logstash:
      enabled: true
      hosts: ["logstash:5044"]
    
    setup.kibana:
      host: "http://kibana:5601" 
  4. В конце добавьте в docker-compose.yaml конфигурации для Logstash:

    YAML

      logstash:
        image: logstash:8.14.3
        volumes:
          - ./conf/logstash/conf.yml:/usr/share/logstash/config/logstash.yml:ro
          - ./conf/logstash/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
          - ./conf/logstash/pipelines:/usr/share/logstash/config/pipelines:ro
        environment:
          LS_JAVA_OPTS: "-Xmx512m -Xms512m"
        ports:
          - "5044:5044"
          - "5010:5000"
          - "9600:9600"
        networks:
          - elknet
        depends_on:
          - elasticsearch 
  5. Запустите Filebeat:

    YAML

      beats:
        image: elastic/filebeat:8.14.3
        volumes:
          - ./conf/filebeat/conf.yml:/usr/share/filebeat/filebeat.yml:ro
          - ./var/log/nginx:/var/log/nginx
        networks:
          - elknet
        depends_on:
          - nginx
          - elasticsearch 
  6. Запустите команды:

    BASH

    docker-compose up elasticsearch -d
    docker-compose up nginx -d
    docker-compose up kibana -d 
  7. Настройте шаблон индекса для логов. Для этого откройте http://localhost:5601/app/management/data/index_management/indices.
  8. Перейдите в Index Templates и нажмите Create template:
  1. Заполните Name и Index patterns. Заполните их так, как указано на скрине выше. То есть так же, как в шаблоне пайплайна для Logstash. В поле Index patterns должно быть указано filebeat-*.
  2. Выключите режим Create data stream и включите Allow auto create.
  3. Остальные настройки на страницах Logistics, Component templates и Index settings можно оставить дефолтными. Нажмите Next → Next → Next и переходите на вкладку Mapping:
  1. Отключите динамический маппинг. Откройте вкладку Advanced options, там выберите Enable dynamic mapping.
  2. На вкладке Mapped fields добавьте поля из лога nginx:

    JSON

    log_format json '{"@timestamp":"$time_iso8601",'
               '"host":"$server_addr",'
               '"ipaddr":"$remote_addr",'
               '"login_user":"$remote_user",'
               '"size":$body_bytes_sent,'
               '"responsetime":$request_time,'
               '"upstreamtime":"$upstream_response_time",'
               '"upstreamhost":"$upstream_addr",'
               '"http_host":"$host",'
               '"url":"$uri",'
               '"domain":"$host",'
               '"xff":"$http_x_forwarded_for",'
               '"referer":"$http_referer",'
               '"status":"$status"}' 
  1. Эти поля будут добавляться в индекс, и по ним можно будет искать.
  2. Нажмите Next и создайте шаблон.
  3. Включите пайплайн:

    BASH

    docker-compose up logstash -d
    docker-compose up beats -d 
  4. Проверьте результат командой docker ps -a. При необходимости посмотрите логи docker logs -f id_контейнера.
  5. Посмотрите, получился ли индекс. Для этого откройте http://localhost:5601/app/management/data/index_management/indices.
  1. Индекс появился. У него правильный маппинг. Дальше этот шаблон будет применяться на все новые индексы в соответствии с паттерном, который вы указываете при создании:
  1. Нажмите Discover index:
  1. Выберите Create data view:
  1. Заполните Name и Index pattern. Нажмите Create:
  1. Откроются логи. Там можно найти по индексированным полям запросы, кастомную метрику, IP-адреса:
  1. Выведите информацию на дашборд. Для этого выберите в меню Dashboards и нажмите Create a dashboard:
  1. Создайте визуализацию:
  1. Добавьте status на график и поменяйте тип с Bard vertical stacked на Pie:
  1. Выберите метрику Count of status — это покажет, например, долю ошибочных запросов. В результате вы получите долю запросов:
  1. Сохраните дашборд:
На этом настройка в этом уроке закончена. Мы разобрали основы ELK, индексации Elasticsearch и настроили кастомный пайплайн логов nginx.

Итоги

  • Стек ELK объединяет четыре инструмента: агент доставки данных Beats, конвейер данных Logstach, базу данных Elasticsearch и инструмент визуализации Kibana.
  • Индекс Elasticsearch — это сердце стека Elastic. Он представляет собой оптимизированную коллекцию документов. Каждый документ — это набор полей, которые, в свою очередь, являются парами ключ-значение. В Elasticsearch можно использовать разные структуры данных для каждого поля, это ускоряет поиск. Всего есть четыре типа полей: текстовое, числовое, дата и геопозиция. Индексы можно шардировать.
  • ILM — это политика управления индексами (Index Lifecycle Management). Она позволяет настроить автоматическое удаление и объединение индексов, а ещё совершать другие операции. В жизненном цикле индекса четыре основных этапа: создание, ролловер, хранение и удаление.
В этом уроке вы потренировались работать с ELK: создали Dockerfile с помощью Docker Compose, настроили индексацию логов nginx и подключили Logstach и Kibana. В следующем уроке расскажем про трейсинг.

Что почитать про ELK?

  • Статья на Хабре про настройку Logstash.
  • Статья на Хабре с примером настройки дашборда в Kibana.
  • Файлы конфигурации ELK на GitHub.
  • Статья в блоге Elastic о том, как настроить Hot-Warm-Cold архитектуру для индекса с помощью ILM.
  • Обзор OpenSearch от Yandex Cloud.
  • Статья на Хабре про то, как установить и настроить стек OpenSearch.
  • Статья про географические типы данных Elasticsearch.
  • Статья о том, как настроить отказоустойчивый ELK.

Курсы

Подробнее о курсе

Входной тест

Как всё устроено

Микрофронтенды и разбивка монолитной системы на микросервисы

Шардирование и репликация

Создание микросервисов, построение пайплайна CI/CD

Кеширование, построение мониторинга и адаптация к высокой нагрузке

Переход на Hybrid cloud и Multi-cloud, создание AI/ML чат-бота

Создание highload в realtime-среде

Как пройти аудит безопасности и соответствовать его требованиям

Объединение сервисов через SSO и работа с данными для аналитики

Работа с требованиями и стейкхолдерами

Соответствие продукта требованиям data privacy, планирование сложной миграции

Построение архитектуры данных и технологические тренды

Завершающий модуль