Следует признать, что история с обновлениями этого канала несколько затянулась - а все потому, что в продолжение этого времени я была занята собственной практикой в отношении elasticsearch - как оно водится, через "голый" API безо всякого графического интерфейса, и успела набить немало шишек на этом пути.
Здесь, конечно, можно задаться вопросом, а к чему вообще все эти трудности, коль скоро в нашем распоряжении имеется прекрасная графическая оболочка под названием kibana? Во-первых, может так случиться, что однажды GUI попросту "не завезут", да и устанавливать его будет некуда. Во-вторых, коль скоро перед вами встанет задача разработки собственного ПО, совместимого с elasticsearch, вам так или иначе придется разбираться в том, каким образом взаимодействуют между собой различные программные компоненты - ну и, наконец, в общей совокупности все это дает гораздо более глубокое понимание системы, что, на мой взгляд, является крайне ценным профессиональным качеством, отличающим подлинного специалиста от выпускника трехмесячного интенсива курсов по программированию на Python.
Как бы то ни было, а синтаксис взаимодействия с elasticsearch посредством командной строки остается довольно мудреным, поэтому я решила собрать воедино все изученные мною к текущему моменту нюансы, тем самым избавив читателя от необходимости собственноручно выискивать их на разнородных англоязычных форумах.
1. Как зайти в elasticsearch без GUI?
Да практически так же, как обычно: для того, что бы ответить на этот вопрос, давайте вспомним, как выполняется вход в "обычный" ELK, который с kibana - для этого, мы просто вбиваем в поисковую строку браузера URL нужного нам сервера и обращаемся по нему по 5601-му порту. Ну, или по 9200-му, коль скоро нам нужен именно elasticsearch в обход kibana.
Но вот незадача: поскольку у нас нет графической оболочки, то нет и браузера. Как тут быть? Специально для таких случаев предусмотрена специальная утилита - curl, позволяющая обращаться к целевому ресурсу посредством различных HTTP (и не только) запросов, получая выходные данные в виде "сырой" разметки - да, не так красиво, но с точки зрения своего содержательного наполнения в большинстве случаев вполне преемлемо.
Как я уже писала ранее, залогиниться в elasticsearch из терминала можно при помощи команды:
curl -k --user логин : 'пароль' http(s)://адрес-хоста::9200
где curl - обращение к используемой утилите, флаг -k указывает на игнорирование возможных косяков с сертификатами безопасности, параметр --user указывает на то, что далее последуют аутентификационные данные, после чего следуют сами логин с паролем (пароль - строго в одинарных кавычках, логин - без них) и целевой url. Пока что ничего особо мудреного.
В случае успеха вы увидите примерно следующую картину:
2. Запрос к базе данных
Ну хорошо, залогиниться-то мы залогинились, теперь давайте попробуем обратиться к базе данных с логами. Для этого снова используем curl с теми же учетными данными:
Далее, следует флаг -X, определяющий используемый метод HTTP (в данном случае - GET), и, собственно, url. Здесь предлагаю остановиться поподробнее, чтобы не копировать кучу мудреных слов вслепую, а понимать смысл каждого из них, тем самым приобретая ценную возможность адаптировать их под свои задачи. Так, в строке
"localhost:9200/[индекс]/_search/?pretty"
- localhost:9200 - базовый адрес elasticsearch,
- [индекс] - аналог имени конкретной БД для elasticsearch. В моем случае, он назывался logs* - тут уж, каюсь, без привлечения стороннего OSInt'a не обошлось,
- _search - совершаемое действие, т.е. поиск;
- ?pretty - параметр, позволяющий отображать json в читаемом человеком виде - с разбивом строк, табуляцией и прочими украшениями.
Далее следует флаг -H, задающий заголовок (header) уже для самого HTTP-запроса, и его значение - 'Content-Type: application/json', недвусмысленно намекающий на то, что впоследствии будет использоваться формат .json, о котором мы еще поговорим ниже. Наконец, последним следует флаг -d и его полезная нагрузка, т.е. сам запрос - обязательно в одинарных кавычках.
Также, в конце я указала то, что выходные данные по итогам этого запроса будут перенаправлены команде less, позволяющей прокручивать окно терминала клавишами вверх-вниз - удобно, если объем возвращаемых данных не умещается на экране.
3. Все есть .json
Первое, что следует понимать, что в контексте elasticsearch все на свете сводится к формату .json: именно посредством него мы будем составлять запросы к базе данных, и в нем же elasticsearch хранит сами данные. Нельзя сказать, что это сильно облегчает восприятие информации, но все же лучше, чем ничего.
Кстати говоря, примерно так выглядят логи в .json-представлении:
Много - много строковых пар вида "ключ":"значение", взятых в фигурные скобки, в том числе и вложенных - включая сильно многоэтажные конструкции.
{'ключ 1': {'ключ 2': 'значение 2'} }
Возможны перечисления, они берутся в квадратные скобки:
{'ключ 1': [{'ключ 2': 'значение 2'}, {'ключ 3': 'значение 3'}] }
Кстати говоря, тут есть один важный нюанс: если я хочу обратиться к вложенному поля по его имени (ключу), то мне потребуется прописать его полное имя, начиная от самой внешней скобки, разделяя их точками. Условно говоря, в примере ниже для того, чтобы обратиться к полю со значением "Linux", мне придется указать ключ "user_agent.os.name".
При этом, набор полей не только может быть различен, но и в значительной степени варьироваться внутри одного индекса в зависимости от того, откуда была получена данная запись и какой агент при был использован. Так что коль скоро к моменту начала работы вы знаете, какие поля присутствуют в интересующем вас логе, это способно значительно облегчить дальнейшие поиске.
4. Структура запроса
В начале запроса указывается ключевое слово "query", содержащее в качестве своего значения тело всего остального запроса. Что же, логично.
Простейший запрос, позволяющий отобразить все записи (аналог select * в классических БД), будет выглядеть вот так:
Штука, кстати, не такая уж и бессмысленная, так как позволяет несколько сориентироваться на месте, а, в особенности в том, какие там вообще встречаются поля, коль скоро этого не было известно заранее. Критерий "match_all" ("подойдут любые") не принимает каких-то дополнительных опций, однако, вторая фигурная скобка все равно нужна, т.к. json - это всегда пара [ключ : значение].
4.1. Простой поиск по значению
Теперь немного усложним задачу: предположим, я хочу найти все записи, где какое-то конкретное поле принимает заданное значение - например, просмотреть информацию, относящуюся к конкретному пользователю по его имени или id, отсортировать данные по дате или выбрать посреди них только те, которые относились бы к определенному приложению, etc. Здесь, вложенный параметр "match" задает отношение, которым должны удовлетворять заданное поле и его значение, в то время как следующая фигурная скобка определяет имена их самих.
4.2. Поиск по нескольким значениям. Логические связки
Тем не менее, в практических задачах выборка производится сразу по нескольким критериям. Для того, чтобы иметь возможность работать сразу с несколькими условиями, используется оператор bool, позволяющий связывать атомарные условия логическими связками.
Помимо самого оператора bool, у нас появляется вложенный в него оператор must (аналог логического "И"), говорящий о том, что все содержащиеся внутри условия должны быть выполнены. Обратите внимание: после логического оператора следует квадратная скобка, т.е. начинается перечисление некого множества условий - даже, если оно состоит из одного-единственного элемента. Условия, заключенные в фигурные скобки, разделяются запятыми.
Аналогичным образом определяются логические связки should (логическое "ИЛИ", "выполняется хотя бы один") и must_not (оператор "НЕ", "ни одно из условий не должно быть истинно").
Отдельным образом определяется оператор filter, фактически сводящийся к тому же логическому "И". Разница между ним и упомянутым уже ранее must состоит в том, что filter предназначен для фильтрации результатов уже сгенерированной выборки - грубо говоря, взяли все в большие круглые скобки и добавили еще одно "AND".
Можно использовать сразу несколько - просто ставим после первой квадратной скобки запятую и пишем следующий логический блок, прямо внутри того же bool:
4.3. Другие критерии поиска (Term-Level Queries)
Помимо разнообразных логических операторов, существуют различные виды условий, которым "может", "должна" или "не должна" соответствовать запись. Так, за вычетом упомянутого уже match ("заданное значение заданного поля"), существуют:
- Term - более мягкий аналог match, допускающий расхождения в регистре в имени поля или его значении.
- Fuzzy - еще более вольное правило, допускающее возможность опечатки (неверный символ, пропуск или добавление лишних символов, перестановка двух соседних символов. Дополнительно можно задать ряд дополнительных параметров, позволяющих более гибко настроить эти правила:
- Prefix - поиск по указанному началу строки:
- Exists - в логе должно присутствовать поле с заданным именем вне зависимости от его значения (обратите внимание, что в данном случае слово field является одним из служебных!)
- Wildcard - поиск неполного совпадения среди значений указанного поля, когда нам известна лишь часть строки: довольно удобно, если я знаю, что речь идет, скажем, об исполняемом файле (.exe), но не имею никакого представления о его имени. В таком случае, я задаю маску "*.exe", где звёздочка заменяет один или несколько символов. Аналогичным образом, знак вопроса позволяет заменить один пропущенный символ. Имеются определённые ограничения: так, например, я не могу использовать несколько "звездочек" внутри одной маски, зато могу использовать несколько масок одновременно, соединив их оператором AND.
Пример с использованием нескольких масок:
- Regexp - старые-добрые регулярные выражения. Штука довольно громоздкая и неудобная, поэтому на практике используется редко.
- Range - позволяет задать одно- или двусторонний интервал для заданной величины. Доступны следующие опции: gte ("больше или равно"), gt ("больше"), lte ("меньше или равно"), lt ("меньше"). Применимо к числам и датам.
... ах, да, вишенка на торте: в нашем любимом черно-белом терминале, без разбива строк и табуляции, все это будет выглядеть вот так... Да, вполне реальный сценарий, когда вам нужно отфильтровать большое количество параметров!
5. Практические примеры
В качестве чуть более наглядной иллюстрации приведу несколько примеров из моих собственных лабораторных работ:
Здесь я ищу логи, в которых:
- присутствует поле "user_agent.os.full",
- @timestamp равен 2023-12-12,
- код http-ответа (http.response.status_code) выдавал бы ошибку 404.
В этом примере мне были нужны записи, соответствующие:
- процессу, запущенному от имени root-пользователя ("winlog.event_data.ParentUser":"root (uid=0)"),
- относящемуся к файлу rs.php - при этом, полный путь к вышеобозначенному файлу неизвестен.
Здесь:
- Дата (@timestamp) равна 2024-02-04,
- Код события - единица, т.е. создание нового процесса,
- Исполняемый файл имеет расширение .exe,
- Рабочая директория процесса - какая угодно, кроме system32.
Резюме
В данной статье был рассмотрен базовый набор функций, предоставляемый search API elasticsearch. Разумеется, данное руководство не является исчерпывающим (хотя бы потому, что помимо поисковых запросов, API-шное взаимодействие с elasticsearch включает в себя уйму других операций), однако, дает определенное представление об инструментах, которые позволили бы вам осуществлять более или менее комфортную работу с логами в обход GUI.
Повторюсь, синтаксис запросов не столь очевиден, и мне потребовалось порядочно времени на то, чтобы найти и досконально разобраться во всей нужной информации, и потому я надеюсь, что данная статья окажется способной сократить чье-то время на аналогичный research.
Ну а если у вас сложилось впечатление, что то, о чем я вам рассказала, слишком просто, и вам нужны более продвинутые фишки QueryDSL elasticsearch, добро пожаловать на страницы официальной документации - не в пример более тяжеловесной и объемной, однако, по-прежнему исключительно содержательной и полезной к изучению.
Оставайтесь на связи.