company_banner

Как мы Elasticsearch в порядок приводили: разделение данных, очистка, бэкапы

    Эта статья — практическая история о том, как мы столкнулись с проблемой разделения логов, хранимых в Elasticsearch, из-за которой пришлось поменять подход к бэкапам и управлению индексами.



    Всё началось вскоре после того, как было поднято production-окружение. У нас был «боевой» кластер Kubernetes, все логи из которого собирал fluentd и направлял их напрямую в индексы logstash-yyy.mm.dd



    Однако появился запрос хранить некоторые логи приложений для поиска до 90 дней. На тот момент мы не могли себе этого позволить: хранение текущих индексов за такой период превысило бы все разумные меры. Поэтому было принято решение создать отдельный индекс-паттерн для таких приложений и настроить на него отдельный retention.

    Разделение логов


    Чтобы выполнить такую задачу и отделить «нужные» логи от «ненужных», мы воспользовались параметром match у fluentd и создали еще одну секцию в output.conf для поиска совпадений по нужным сервисам в пространстве имён production согласно тегированию, указанному для input-плагина (а также изменив @id и logstash_prefix, чтобы направить запись в разные места).

    Фрагменты получившегося output.conf:

    <match kubernetes.var.log.containers.**_production_**>
          @id elasticsearch1
          @type elasticsearch
          logstash_format true
          logstash_prefix log-prod
         ...
    </match>
    <match kubernetes.**>
          @id elasticsearch
          @type elasticsearch
          logstash_format true
          logstash_prefix logstash
         ...
    </match>

    Итог — у нас два вида индексов (log-prod-yyyy.mm.dd и logstash-yyyy.mm.dd):



    Очистка индексов


    На тот момент в кластере уже был настроен curator, который очищал индексы старше 14 дней. Он описывался как объект CronJob для разворачивания прямо в кластер Kubernetes.

    Иллюстрация в action_file.yml:

    -
        actions:
          1:
            action: delete_indices
            description: >-
              Delete indices older than 14 days (based on index name), for logstash-
              prefixed indices. Ignore the error if the filter does not result in an
              actionable list of indices (ignore_empty_list) and exit cleanly.
            options:
              ignore_empty_list: True
              timeout_override:
              continue_if_exception: False
              disable_action: False
              allow_ilm_indices: True
            filters:
            - filtertype: pattern
              kind: prefix
              value: logstash
            - filtertype: age
              source: name
              direction: older
              timestring: '%Y.%m.%d'
              unit: days
              unit_count: 14

    Однако мы решили, что вариант с куратором избыточен (запускается как отдельное ПО, требует отдельной настройки и запуска по крону) — ведь можно переделать очистку с помощью index lifecycle policy.

    В Elasticsearch с версии 6.6 (вышла в январе 2019 года) появилась возможность прикрепления к index template политики, которая будет отслеживать время хранения для индекса. Политики можно использовать не только для управления очисткой, но и других задач, которые позволят упростить взаимодействие с индексами (например, выравнивание индексов по размеру, а не по дням).

    Для этого требовалось лишь создать две политики такого вида:

    PUT _ilm/policy/prod_retention
    {
        "policy" : {
          "phases" : {
            "delete" : {
              "min_age" : "90d",
              "actions" : {
                "delete" : { }
              }
            }
          }
        }
      }
    
    PUT _ilm/policy/default_retention
    {
        "policy" : {
          "phases" : {
            "delete" : {
              "min_age" : "14d",
              "actions" : {
                "delete" : { }
              }
            }
          }
        }
      }

    … и прикрепить их к требуемым index templates:

    PUT _template/log_prod_template
    {
      "index_patterns": ["log-prod-*"],                 
      "settings": {
        "index.lifecycle.name": "prod_retention",        
      }
    }
    PUT _template/default_template
    {
      "index_patterns": ["logstash-*"],                 
      "settings": {
        "index.lifecycle.name": "default_retention",        
      }
    }

    Теперь кластер Elasticsearch будет самостоятельно управлять хранением данных. Это означает, что все индексы, которые попадут под шаблон в index_patterns, указанный выше, будут очищаться после заданного в политике количества дней.

    Но тут мы сталкиваемся с другой проблемой…

    Реиндексирование внутри кластера и из удаленных кластеров


    Созданные нами политики, шаблоны и те изменения, что были применены во fluentd, будут иметь эффект только для новых создаваемых индексов. Чтобы привести в порядок то, что уже имеем, придется запустить процесс переиндексации, обратившись к Reindex API.

    Требуется выделить «нужные» логи из уже закрытых индексов и реиндексировать их самих, чтобы были применены политики.

    Для этого достаточно двух простых запросов:

    POST _reindex
    {
      "source": {
        "index": "logstash-2019.10.24",
        "query": {
          "match": {
            "kubernetes.namespace_name": "production"
          }
        }
      },
      "dest": {
        "index": "log-prod-2019.10.24"
      }
    }
    
    POST _reindex
    {
      "source": {
        "index": "logstash-2019.10.24"
        "query": {
          "bool": { 
            "must_not": {
              "match": {
                "kubernetes.namespace_name": "production"
               }
            }
          }
        }
      },
      "dest": {
        "index": "logstash-2019.10.24-ri"
      }
    }

    Теперь старые индексы можно удалить!

    Однако есть и другие трудности. Как уже известно, ранее был настроен curator, который чистил кластер с retention в 14 дней. Таким образом, для актуальных для нас сервисов потребуется восстановить данные и из того, что уже было когда-то удалено. Здесь помогут бэкапы.

    В простейшем случае, что применимо и к нашему, бэкапы делаются посредством вызова elasticdump для всех индексов разом:

    /usr/bin/elasticdump --all $OPTIONS --input=http://localhost:9200 --output=$

    Развернуть все, что было от запуска production, не представлялось возможным, т.к. в кластере банально не было столько места. К тому же, доступ к логам из бэкапа требовался уже сейчас.

    Решение — развернуть временный кластер, в который восстанавливается бэкап, откуда уже и достаются нужные нам логи (способом, аналогичным уже описанному). А параллельно мы начали думать, как сделать снятие бэкапов более удобным способом — подробнее об этом см. ниже.

    Итак, следующие шаги (по временному кластеру):

    1. Установить еще один Elasticsearch на отдельный сервер с достаточно большим диском.
    2. Развернуть бэкап из имеющегося у нас dump-файла:

      /usr/bin/elasticdump --bulk --input=dump.json --output=http://127.0.0.1:9200/
    3. Обратите внимание, что индексы в кластере не приобретут состояние green, т.к. мы переносим дамп в 1-узловую конфигурацию. Но переживать из-за этого не стоит: главное — вытащить только primal-шарды.
    4. Не нужно забывать, что для разрешения реиндексирования из удаленных кластеров нужно добавить их в whitelist в elasticsearch.yaml:

      reindex.remote.whitelist: 1.2.3.4:9200
    5. Далее произведем запрос на реиндексацию из удаленного кластера в текущем production-кластере:

      POST _reindex
      {
        "source": {
          "remote": {
            "host": "http://1.2.3.4:9200"
          },
          "size": 10000,
          "index": "logstash-2019.10.24",
          "query": {
            "match": {
              "kubernetes.namespace_name": "production"
            }
          }
        },
        "dest": {
          "index": "log-prod-2019.10.24"
        }
      }

    Этим запросом мы получаем из индексов в удаленном кластере документы, которые будут передаваться в production-кластер по сети. На стороне временного кластера все документы, не подходящие под запрос, будут отфильтрованы.

    Параметры size и slice используются для ускорения процесса реиндексации:

    • size — для увеличения количества документов для индексации, передаваемых в пакете;
    • slice — для деления этой задачи на 6 подзадач, которые будут параллельно заниматься реиндексированием. (Параметр не работает в реиндексации из удаленных кластеров).

    На принимающем Elasticsearch мы настроили шаблоны индексов на максимальную производительность.

    Когда все нужные логи оказались в одном месте полностью разделенными так, как требовалось, временный кластер можно удалять:



    Бэкапы Elasticsearch


    Самое время вернуться к бэкапам: запуск elasticdump на все индексы — далеко не самое оптимальное решение. Хотя бы по той причине, что восстановление может занимать неприлично много времени, а бывают случаи, когда важен каждый час.

    Такую задачу можно «отдать» самому Elasticsearch — Snapshot Repository на базе S3. И вот основные причины, по которым мы сами выбрали такой подход:

    • Удобство создания и восстановления из таких бэкапов, поскольку используется родной синтаксис запросов к Elasticsearch.
    • Снапшоты накатываются инкрементально, т.е. добавляя новые данные к уже имеющимся.
    • Возможность восстановить из снапшота любой индекс и даже восстановить глобальное состояние кластера.
    • S3 кажется более надежным местом для хранения бэкапов, чем просто файл (в том числе и по той причине, что обычно мы используем S3 в режимах HA).
    • Переносимость S3: мы не привязаны к конкретным провайдерам и можем развернуть S3 на новом месте самостоятельно.

    Настройка бэкапа в S3 требует дополнительной установки плагина в Elasticsearch, чтобы Snapshot Repository мог общаться c S3. Вся конфигурация сводится к следующим шагам:

    1. Устанавливаем плагин для S3 на все узлы:

      bin/elasticsearch-plugin install repository-s3

      … и параллельно добавляем S3 в whitelist в elasticsearch.yml:

          Repositories.url.allowed_urls:
      - "https://example.com/*"
    2. Добавляем ключи SECRET и ACCESS в Elasticsearch keystore. По умолчанию для подключения к S3 используется пользователь default:

      bin/elasticsearch-keystore add s3.client.default.access_key
      bin/elasticsearch-keystore add s3.client.default.secret_key

      … и после этого поочередно перезапускаем сервис Elasticsearch на всех узлах.
    3. Создаем репозиторий для снапшотов:

      PUT /_snapshot/es_s3_repository
      {
        "type": "s3",
        "settings": {
          "bucket": "es-snapshot",
          "region": "us-east-1",
          "endpoint": "https://example.com"
        }
      }

      В данном случае для создания снапшота хватит примеров из документации, где имя снапшота будет в формате: snapshot-2018.05.11,— т.е.:

      PUT /_snapshot/my_backup/%3Csnapshot-%7Bnow%2Fd%7D%3E
    4. Осталось лишь протестировать восстановление индекса:

      POST /_snapshot/es_s3_repository/snapshot-2019.12.30/_restore
      {
        "indices": "logstash-2019.12.29",
        "rename_pattern": "logstash-(.+)",
        "rename_replacement": "restored_index_$1"
      }

      Так мы восстанавливаем индекс «рядом», просто переименовав его. Однако можно восстановить его и в тот же индекс — только для этого индекс в кластере должен быть закрыт и иметь такое же количество шардов, что и индекс в снапшоте.

    Статус снятия снапшота можно проверять по имени через API, вызвав информацию о снапшоте и посмотрев в поле state:

    GET /_snapshot/es_s3_repository/snapshot_2019.12.30

    Статус восстановления же можно проверять из самого кластера: в начале восстановления кластер перейдет в статус red, т.к. будут восстанавливаться primal-шарды ваших индексов. Как только процесс будет завершен, индексы и кластер перейдут в статус yellow — до того момента, как будет создано указанное количество реплик.

    Также отслеживать статус можно с помощью wait_for_completion=true, указанном прямо в строке запроса.

    Результат — получаем точную копию индекса из сделанного снапшота:

    green open restored_index_2019.12.29    eJ4wXqg9RAebo1Su7PftIg 1 1 1836257 0   1.9gb 1000.1mb
    green open logstash-2019.12.29          vE8Fyb40QiedcW0k7vNQZQ 1 1 1836257 0   1.9gb 1000.1mb

    Итоги и недостатки


    Оптимальными для нас решениям в кластере Elasticsearch оказались:

    • Настройка output-плагина в fluentd, с которой можно разделять логи прямо на выходе из кластера.
    • Политика index lifecycle, позволяющая не заботиться о проблемах с местом, занятым индексами.
    • Новый вид бэкапов через snapshot repository (вместо бэкапа целого кластера), с которым появилась возможность восстанавливать отдельные индексы из S3, что стало гораздо удобнее.

    Впрочем, полезно будет предостеречь, что изменения в политиках и шаблонах из-за изменяющихся потребностей в дальнейшем приведут к необходимости реиндексировать все индексы в кластере. А с бэкапами картина ещё дальше от идеала…

    Несмотря на то, что мы «передали» выполнение бэкапов самому Elasticsearch, запуск снятия снапшота и наблюдение за его выполнением всё еще остается нашей задачей: обращение к API, отслеживание статусов (через wait_for_completion) и по статусу мы будем производить сами с помощью скриптов. При всей удобности backup repository есть у него и другая проблема: слишком мало плагинов. Например, нет возможности работать через WebDAV, если вместо S3 понадобится что-то совсем простое, но такое же мобильное.

    В целом, такая обособленность системы плохо ложится на использование централизованного подхода к бэкапам и мы пока не нашли (среди Open Source-инструментов) универсальное средство, которое бы это позволило.

    P.S.


    Читайте также в нашем блоге:

    Флант
    Специалисты по DevOps и Kubernetes

    Похожие публикации

    Комментарии 18

      0
      Материал интересный, но может кто нибудь пожалуйста объяснить зачем хранить логи в базах типо эластика?
        0
        Парсинг на лету, индексация, быстрые отчеты. У нас еще оттуда заббикс дергает данные постфактум и смотрит на предмет косяков.
          0
          Kibana — удобный инструмент для их последующего анализа, построения графиков по определённым поискам и т.п. Опять же можно экспортировать отдельные выборки в метрики для прометеуса / графаны.

            0
            Логи из эластика теперь можно и в самой графане смотреть. И сразу же графики по запросам строить.
            0
            Если поподробней, то у нас уже была некоторая обзорная статья с тем что мы используем для сбора логов и почему habr.com/ru/company/flant/blog/480946
            Если вкратце от себя:
            Эластик может переваривать большие объемы данных на запись, индексировать их как удобно и быстро искать по ним в довольно больших выборках. Сам по себе он требует много ресурсов, потому для проектов где нет такого потока данных и с логами ничего не нужно делать он будет избыточен. Но в комбинации с кибаной и её плагинами отлично используется для создания дашбордов, для бизнес метрик, алертинга и дальнейшей обработки статистики.
              0
              Сам по себе он требует много ресурсов

              Какой у вас размер индекса и какая конфигурация кластера (ноды/память/cpu)?
                0
                Не могу сказать в общем по компании, т.к. Эластиков много, но конкретно в этом случае размеры индексов есть на скриншотах в статье.
                За основу статьи брался 3 нодовый Эластик, каждая нода 16CPU 32RAM
            0

            Спасибо за статью. Расскажите, пожалуйста, поподробней про "S3 в режимах HA". Это AWS S3? Проприетарное хранилище?

              0
              В зависимости от платформы где развёрнут проект. Пользуемся и S3 в облаках (в том числе и AWS), и Сeph S3 или Minio для bare-metal.
              0

              Чтобы назначить ilm на существующие индексы, достаточно сделать


              curl -X PUT "localhost:9200/mylogs-pre-ilm*/_settings?pretty" -H 'Content-Type: application/json' -d'
              {
              "index": {
              "lifecycle": {
              "name": "mylogs_policy_existing"
              }
              }
              }
              '
              reindex, мне кажется, overkill. Или я неправильно понял идею.

                0
                Всё верно, но в нашем случае нам нужно было не только применить политику, но и разделить те индексы что уже были в кластере, потому политики мы прикрепляли сразу к шаблонам. Проблема была именно в этом. Но за пример спасибо, не подумал о том чтобы добавить уточнение по этому поводу.
                0
                Используйете ли вы elastic в kubernetes? Если да, то смогли ли вы обойти ulimit, я про github.com/kubernetes/kubernetes/issues/3595
                0

                Спасибо, интересное описание.
                Так же недавно отказался от куратора в пользу ilm.


                Вопрос шифрования бэкапов в S3 остался за кадром, т.е. при доступе к бакету индексы становятся условно доступны/восстановимы в любом другом кластере.
                Рассматривали какое-то решение, кроме шифрования силами S3?

                  0
                  В данном случае мы обходимся разными access/secret key для кластеров и бакетов и политикой доступа. Само S3 же просто закрыто по сети для всего кроме Эластика (с некоторыми исключениями в разных случаях).
                  +1

                  Небольшая придирка на всякий случай:


                  Version 2.0.0 of Elasticdump removes the bulk options. These options were buggy, and differ between versions of Elasticsearch. If you need to export multiple indexes, look for the multielasticdump section of the tool.

                    0
                    Развернуть все, что было от запуска production, не представлялось возможным, т.к. в кластере банально не было столько места. К тому же, доступ к логам из бэкапа требовался уже сейчас. Решение — развернуть временный кластер, в который восстанавливается бэкап, откуда уже и достаются нужные нам логи

                    Допускаете ли Вы возможность, когда во временный кластер тоже не поместится такое количество снапшотов, которое Вам нужно? Вот в этом комментарии было описано решение от человека, который собирает 5Тб в день: иметь параллельно снапшоты и текстовый архив логов. Если надо восстанавливать точечно — восстанавливаешь снапшоты. Если надо искать вширь — ищешь по текстовым логам. Мы попробовали, — это работает. Только у нас уже было много старых снапшотов, поэтому пришлось использовать отдельный конвертер снапшотов в текст. 60Тб снапшотов конвертировали в 3Тб зазипованных JSON, и в них вполне можно искать определенные нужные вещи за пару лет назад. Не так замечательно как в Эластике, но в [наш] временный кластер по любому 2 года снапшотов не влезает.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое