Хабрахабр

Логирование в распределенном php-приложении

Расскажу о логах по PSR. В статье пойдет речь о том, какую пользу оказывает логирование. Будет приведен пример, как можно организовать логирование и мониторинг с помощью ELK в приложении, написанном на Laravel и запущенном через Docker на нескольких инстансах. Добавлю немного личных рекомендаций по работе с уровнем, сообщением и контекстом логируемого события. Приведу пример скрипта, который поднимает одной командой весь стек мониторинга. Распишу важное правило системы оповещения.

Польза логирования

Хорошо организованное логирование позволяет, как минимум, следующее:

  • Знать о том, что что-то идёт не так, как задумано (есть ошибки)
  • Знать подробности ошибки, которые помогут сказать, с кем и где произошла ошибка, и не допустить повтора
  • Знать о том, что всё идёт как задумано (access.log, debug-, info-уровни)

Если сообщения в логах сопровождаются достаточным объемом контекста, то это значительно упрощает отладку, потому что вам будет доступно больше данных о ситуации, в которой произошло событие. Сама по себе запись в лог вам всего этого не скажет, но с помощью логов будет возможность самостоятельно узнать подробности событий, либо настроить систему мониторинга логов, которая будет иметь возможность оповещать о проблемах.

Чем писать и что писать

Одна из таких рекомендаций PSR-3 Logger Interface. Частью php-сообщества разработаны рекомендации по некоторым задачам написания кода. Для этого разработан интерфейс Psr\Log\LoggerInterface пакета "psr/log". Она как раз описывает то, чем нужно логировать. При его использовании нужно знать о трёх составляющих события:

  1. Уровень — важность события
  2. Сообщение — текст, описывающий событие
  3. Контекст — массив дополнительной информации о событии

Уровни события по PSR-3

Уровни заимствованы из RFC 5424 — The Syslog Protocol, примерное описание их следующее:

  • debug — Подробная информация для отладки
  • info — Интересные события
  • notice — Существенные события, но не ошибки
  • warning — Исключительные случаи, но не ошибки
  • error — Ошибки исполнения, не требующие сиюминутного вмешательства
  • critical — Критические состояния (компонент системы недоступен, неожиданное исключение)
  • alert — Действие требует безотлагательного вмешательства
  • emergency — Система не работает

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

  • emergency — это уровень для внешних систем, которые могут посмотреть на вашу систему и точно определить, что она полностью не работает, либо не работает её самодиагностика.
  • alert — система сама может продиагностировать своё состояние, например, задачей по расписанию, и в результате записать событие с этим уровнем. Это могут быть проверки подключаемых ресурсов или что-то конкретное, например, баланс на счету используемого внешнего ресурса.
  • critical — событие, когда сбой даёт компонент системы, который очень важен и всегда должен работать. Это уже сильно зависит от того, чем занимается система. Подходит для событий, о которых важно оперативно узнать, даже если оно произошло всего раз.
  • error — произошло событие о, котором при скором повторении нужно сообщить. Не удалось выполнить действие, которое обязательно должно быть выполнено, но при этом такое действие не попадает под описание critical. Например, не удалось сохранить аватарку пользователя по его запросу, но при этом система не является сервисом аватарок, а является чат-системой.
  • warning — события, для немедленного уведомления о которых нужно набрать значительное их количество за период времени. Не удалось выполнить действие, невыполнение которого ничего серьезного не ломает. Это всё ещё ошибки, но исправление которых может ждать рабочего расписания. Например, не удалось сохранить аватарку пользователя, а система — интернет-магазин. Уведомление о них нужно (при высокой частоте), чтобы узнать о внезапных аномалиях, потому что они могут быть симптомами более серьезных проблем.
  • notice — это события, которые сообщают о предусмотренных системой отклонениях, которые являются частью нормального функционирования системы. Например, пользователь указал неправильный пароль при входе, пользователь не заполнил отчество, но оно и не обязательно, пользователь купил заказ за 0 рублей, но у вас такое предусмотрено в редких случаях. Уведомление по ним при высокой частоте тоже нужно, так как резкий рост числа отклонений может быть результатом допущенной ошибки, которую срочно нужно исправить.
  • info — события, возникновение которых сообщает о нормальном функционировании системы. Например, пользователь зарегистрировался, пользователь приобрел товар, пользователь оставил отзыв. Уведомление по таким событиями нужно настраивать в обратном виде: если за период времени произошло недостаточное количество таких событий, то нужно уведомить, потому что их снижение могло быть вызвано в результате допущенной ошибки.
  • debug — события для отладки какого-либо процесса в системе. При добавлении достаточного количества данных в контекст события можно произвести диагностику проблемы, либо заключить об исправном функционировании процесса в системе. Например, пользователь открыл страницу с товаром и получил список рекомендаций. Значительно увеличивает количество отправляемых событий, поэтому допустимо убирать логирование таких событий через некоторое время. Как результат, количество таких событий в нормальном функционировании будет переменным, тогда и мониторинг для уведомления по ним можно не подключать.

Сообщение события

Кроме того, по PSR-3 строка сообщения может содержать плейсхолдеры вида ”User created”, которые могут быть заменены значениями из массива контекста. По PSR-3 сообщение должно быть либо строкой, либо объектом с методом __toString(). Кроме того, предлагаю обратить внимание на дополнительные требования к сообщению: При использовании Elasticsearch и Kibana для мониторинга я рекомендую не использовать плейсхолдеры, а писать фиксированные строки, потому что это упростит фильтрацию событий, а контекст всегда будет рядом.

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

Контекст события

Контекст можно оставить пустым, если по сообщению всё понятно о событии. Контекст события по PSR-3 это массив (можно вложенный) значений переменных, например, ID сущностей. При использовании Monolog через NormalizerFormatter из исключения будут автоматически извлечены полезные данные и добавлены в контекст события, в том числе и стектрейс. В случае логирования исключения следует передавать всё исключение, а не только getMessage(). То есть нужно вместо

[ 'exception' => $exception->getMessage(),
]

использовать

[ 'exception' => $exception,
]

Это можно сделать через Global Log Context (только для непойманных исключений или через report()), либо через LogFormatter (для всех событий). В Laravel можно для событий автоматически вписывать в контекст данные. Обычно, добавляется информация с id текущего пользователя, request URI, IP, request UUID и подобное.

То есть, если вы передавали в контексте customer_id числом, то при попытке сохранить событие с другим типом, например строкой (uuid), то такое сообщение не запишется. При использовании Elasticsearch в качестве хранилища логов следует помнить, что в нём используются фиксированные типы данных. Если индексы создаются каждый день, то новый тип запишется только на следующий день. Типы в индексе фиксируются при первом получении значения. Но даже это не избавит от всех проблем, потому что для Kibana типы будут смешанными и часть операций, привязанных к типу, будет недоступна пока будут смешанные индексы.

Для предотвращения этой проблемы рекомендую придерживаться правил:

  • Не использовать слишком общие имена ключей, которые могут быть разного типа
  • Делать явное приведение к типу значений, если нет уверенности в его типе

Пример: вместо

[ 'response' => $response->all(), 'customer_id' => $id, 'value' => $someValue,
]

использовать

[ 'smsc_response_data' => json_encode($response->all()), 'customer_id' => (string) $customer_id, 'smsc_request_some_value' => (string) $someValue, ]

Вызов логгера из кода

Рассмотрим некоторые из них. Для быстрой записи события в лог можно придумать несколько вариантов.

  1. Объявить глобальную функцию log() и вызывать её из разных частей программы. У такого подхода много недостатков. Например, в классах, где мы обращаемся к этой функции, образуется неявная зависимость. Этого следует избегать. Кроме этого такой логгер сложно настраивать, когда в системе нужно иметь несколько разных. Другой недостаток, если мы говорим о работе с Laravel, то, что мы не используем возможности, предоставляемые фреймворком для решения этой проблемы.
  2. Использовать Laravel фасад \Log. С таким подходом части системы, которые обращаются к этому фасаду, начинают зависеть от фреймворка. В частях системы, которые мы не собираемся выносить из-под фреймворка, такое решение вполне подойдет. Например, писать из некоторых экземпляров консольной команды, фоновой задачи, контроллера. Или когда уже есть запутанная структура сервисов, и прокинуть экземпляр логгера в них не так просто.
  3. Резолвить зависимость логгера через хелперы фреймворка app() и resolve(). Подход имеет те же недостатки, что и использование фасада, но при этом нужно чуть больше писать кода.
  4. Указывать зависимость от логгера в конструкторе класса, который этот логгер будет использовать. При этом в качестве типа следует указывать тот самый LoggerInterface, чтобы соблюсти DIP. Благодаря autowiring фреймворков, зависимости автоматически разрешатся в реализации по их объявленным абстракциям. В Laravel в некоторые классы зависимости можно указать в отдельном методе, вместо того, чтобы указывать в конструкторе всего класса.

Где в коде вызывать логгер

Должен ли это быть сервис? При организации кода в проекте может возникнуть вопрос, в каком классе мне следует писать в лог. Или каждое исключение само должно решать, что ему писать в лог с помощью его метода report (Laravel)? Или это нужно делать там, откуда сервис вызываем: контроллер, фоновая задача, консольная команда? Простого ответа сразу на все вопросы нет.

Исключение не может знать, насколько оно критично для системы, чтобы определить уровень события. Рассмотрим возможность, данную Laravel, делегировать классу исключения задачу логировать себя. Чтобы вызов метода render на исключении произошел, нужно либо не поймать исключение (будет использован глобальный ErrorHandler), либо поймать и использовать глобальный хелпер report(). Кроме того, у исключения нет доступа к контексту, если его специально не добавлять при вызове этого исключения. Но не думаю, что ради этого стоит давать исключению такую ответственность. Такой способ позволяет не вызывать PSR-3 логгер каждый раз, где мы это исключение можем поймать.

Действительно, в некоторых сервисах можно сделать логирование. Может показаться, что мы всегда можем логировать только в сервисах. Тогда этот сервис не знает о своей важности в проекте, а, значит, не сможет определить уровень логирования. Но рассмотрим сервис, который не зависит от проекта и, вообще, мы планируем вынести его в отдельный пакет. Если мы получили сетевую ошибку, то это ещё не значит, что она достаточно серьезная. Например, сервис интеграции с конкретным SMS-шлюзом. Только вот все эти интеграции должны быть вызваны из другого сервиса, который как раз и будет логировать. Возможно, в системе есть сервис интеграции с другим SMS-шлюзом, через который будет вторая попытка отправки, тогда ошибку от первого можно репортить как warning, а ошибку второго как error. Но иногда у нас нет сервиса-обёртки над другим сервисом — мы вызываем сразу из контроллера. Получается, что ошибка в одном сервисе, а логируем в другом. В этом случае я считаю допустимым писать в лог в контроллере вместо того, чтобы писать сервис-декоратор для логирования.

Пример, показывающий использование зависимости и передачу контекста:

<?php namespace App\Console\Commands; use App\Services\ExampleService;
use Illuminate\Console\Command;
use Psr\Log\LoggerInterface; class Example extends Command
{ protected $signature = 'example'; public function handle(ExampleService $service, LoggerInterface $logger) { try { $service->example(); } catch (\Exception $exception) { $logger->critical('Example error', [ 'exception' => $exception, ]); } }
}

Куда писать

Можно было бы указать в config логгера php://stdout, но из-за особенности php-fpm 7. Согласно 12 факторному приложению, писать нужно в stdout, stderr среды выполнения приложения. 2 в официальном образе docker получаем в docker logs

[21-Mar-2016 14:10:02] WARNING: [pool www] child 12 said into stdout: "2016-03-21 14:10:02 [x.x.x.x][-][-][info] Session started"

3, но это не значит, что все варианты ниже стали бесполезны. И длина такого сообщения ограничена (обрезается).
UPD: Этой проблемы нет в php-fpm 7.

Рассмотрим варианты решения проблемы.

  1. Игнорировать 12-factor, docker-way и писать в файлы. Laravel (Monolog) позволяют даже настроить ротацию логов. Далее сообщения из файлов можно собрать с помощью Filebeat и отправить в Logstash для разбора.
  2. Писать в fifo-файл перенаправляя его содержимое в stderr docker-контейнера. Подробнее об этом решении здесь.
  3. Мы можем совместить оба решения. Писать в файлы, которые с помощью Filebeat будут собраны и отправлены в Logstash. Писать в stderr контейнера, чтобы получить возможность использовать команды docker logs и быть готовым к удобному сбору логов из среды оркестрации контейнеров.
  4. Отправлять логи из приложения напрямую дальше, например, по UDP для увеличения производительности.

Варианты формата записи:

  • Человекочитаемый (переносы строк, отступы и прочее)
  • Машиночитаемый (обычно, json)
  • Оба формата одновременно: машиночитаемый в stdout для дальнейшей маршрутизации, человекочитаемый на случай внезапных проблем с маршрутизацией и быстрого дебага

Любой из вариантов предполагает, что логи подвергаются маршрутизации — как минимум, отправке в единую систему обработки (хранения) логов по следующим причинам:

  1. Долгосрочное хранение и архивация
  2. Построение крупномасштабных графиков трендов
  3. Гибкая система оповещения о событиях

По умолчанию — json-file, то есть, докер складывает вывод из контейнера в json-file на хосте. У докера есть возможность указать диспетчер логов. Если stdout/stderr контейнера был выбран единственным местом для записи логов приложения, то в случае сетевых проблем или проблем у единого хранилища, быстро извлечь записи для дебага может не получиться. Если мы выбираем диспетчер логов, который будет отправлять записи куда-то по сети, то мы больше не сможем использовать команду docker logs.

Получим и локальные логи и дальнейшую маршрутизацию. Мы можем использовать json-file докера и Filebeat. При записи события длиной больше 16KB докер разбивает запись символом \n, что путает многие сборщики логов. Стоит заметить здесь ещё одну особенность докера. Проблему со сторону докера решить не удалось, поэтому её решили со стороны сборщиков. Об этом есть issue. С некоторой версии Filebeat поддерживает такое поведение докера и корректно объединяет события.

Какую комбинацию всех возможностей мест назначений и форматов записи выбрать вы можете решить для вашего проекта самостоятельно.

Использование Filebeat + ELK + Elastalert

Кратко роль каждого сервиса можно описать так:

  • Filebeat — собирает события из файлов и отправляет
  • Logstash — парсит события и отправляет
  • Elasticsearch — хранит структурированные события
  • Kibana — выводит события (графики, агрегации и т.п.)
  • Elastalert — отправляет алерты на основе запросов

Дополнительно можно: zabbix, metricbeat, grafana и прочее.

Теперь подробнее о каждом.

Filebeat

Для работы с потоком событий из docker использует путь хоста /var/lib/docker/containers/*/*.log. Можно запускать как отдельной службой на хосте, можно отдельным докер-контейнером. Filebeat может сам парсить json внутри события, но в события может попасть и не json, что приведет к ошибке. Filebeat имеет широкий набор опций, которыми можно задать поведение в различных ситуациях (файл переименован, файл удалён и тому подобное). Всю обработку событий лучше произвести в одном месте.

Фрагмент конфигурация для Filebeat 6

filebeat.inputs: - type: docker containers: ids: - "*" processors: - add_docker_metadata: ~

Logstash

п.). Умеет принимать события от многих источников, но здесь мы рассматриваем Filebeat.
В каждом событии кроме самого события из stdout/stderr есть метаданные (хост, контейнер и т. п. Много встроенных фильтров обработки: парсить по регуляркам, разбирать json, изменять, добавлять, удалять поля и т. Умеет передавать данные в разные хранилища, но здесь рассматриваем Elasticsearch. Подходит для парсинга как логов приложения, так и nginx access.log в произвольном формате.

Фрагмент filter-конфигурации Logstash

if [status] { date { match => ["timestamp_nginx_access", "dd/MMM/yyyy:HH:mm:ss Z"] target => "timestamp_nginx" remove_field => ["timestamp_nginx_access"] } mutate { convert => { "bytes_sent" => "integer" "body_bytes_sent" => "integer" "request_length" => "integer" "request_time" => "float" "upstream_response_time" => "float" "upstream_connect_time" => "float" "upstream_header_time" => "float" "status" => "integer" "upstream_status" => "integer" } remove_field => [ "message" ] rename => { "@timestamp" => "event_timestamp" "timestamp_nginx" => "@timestamp" } }
}

Elasticsearch

Нельзя сохранить событие в индексе, если хотя бы у одного поля неподходящий тип.
Разные типы позволяют делать разные операции над группой документов (для чисел — sum, min, max, avg и т. Elasticsearch очень мощный инструмент для большого спектра задач, но с целью мониторинга логов его можно использовать зная лишь некоторый минимум.
Сохраненные события — это документ, документы хранятся в индексах.
Каждый индекс — это схема, в которой определен тип для каждого поля документа. п., для строк — нечеткий поиск и так далее).
Для логов руководства обычно рекомендуют использовать дневные индексы — каждый день новый индекс.

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

Фрагмент output-конфигурации Logstash

output { if [fields][log_type] == "app_log" { if [level] in ["DEBUG", "INFO", "NOTICE"] { elasticsearch { hosts => "${ES_HOST}" index => "logstash-app-log-debug-%{+YYYY.MM.dd}" } } else { elasticsearch { hosts => "${ES_HOST}" index => "logstash-app-log-error-%{+YYYY.MM.dd}" } } }
}

Запуск программы добавляется в расписание Cron, сама конфигурация может храниться в отдельном файле. Для автоматического удаления устаревших индексов предлагаю использовать программу от Elastic Curator.

Фрагмент конфигурации для удаления устаревших индексов

action: delete_indices
description: logstash-app-log-error
options: ignore_empty_list: True
filters: - filtertype: pattern kind: prefix value: logstash-app-log-error- - filtertype: age source: name direction: older timestring: '%Y.%m.%d' unit: months unit_count: 6

В недавних версиях Elasticsearch ввели разбиение на горячие-теплые-холодные индексы, возможно, их следует вам рассмотреть. Для долговечного хранения событий предлагаю использовать альтернативные системы хранения, которые могут быть подключены как сервисы приёма от Filebeat или Logstash, либо полностью отдельно.

Kibana

Это веб-приложение, которое выполняет запросы к Elasticsearch. Kibana здесь используется как инструмент для чтения журнала событий. Позволяет строить дашборды с разными визуализациями показателей.

Например, отдельная страница Discovery с отображением списка недавних событий из app с уровнем warning и выше, где выводятся столбцы time, message, exception class, host, client_id. Типичное использование Kibana — это ежедневный просмотр списка недавних событий в разделе Discovery с некоторыми фильтрами.

Например, выбрать все события по конкретному клиенту (делается в один клик по одному событию нужного клиента). Другой пример, страница Discovery с отображением списка недавних событий из nginx, у которых неуспешные статусы и не 404 с отображением столбцов time, message, request, status.
Кроме того Kibana используется для дебага, так как в ней можно быстро отфильтровать события: по сообщению, по уровню, по любому значению из контекста.

Пример изображения списка

image

Elastalert

Другими словами, алертинг. Elastalert используется для выполнения запросов к Elasticsearch и оповещению на основе их результатов. Есть широкий набор типов правил, с помощью которых можно построить эффективную систему оповещения об отклонениях.
Для каждого правила можно указать расписание запуска, интервал для повторной отправки по тому же условию (реалерт), список сервисов для оповещения и многое другое.

Некоторые примеры правил:

  • Не должно быть событий с уровнем ALERT, EMERGENCY. Реалерт — 10 минут
  • Не должно быть событий с уровнем CRITICAL. Реалерт — 30 минут
  • Не должно быть больше, чем N событий уровня X за M минут
  • Должны быть минимум 10 INFO за 3 часа
  • Процент ответов nginx с кодом 200, 201, 304 должен быть не ниже 75% за час, если было хотя бы 50 событий

Пример правила

name: Blacklist ALERT, EMERGENCY type: blacklist index: logstash-app-* compare_key: "level" blacklist: - "ALERT" - "EMERGENCY" realert: minutes: 5 alert: - "slack"

Я рекомендую настраивать автоматическое оповещение таким образом, чтобы уведомления шли только по событиям, требующим безотлагательного вмешательства. При подключении автоматических оповещений о событиях следует не допустить очень серьезную ошибку — слишком частое оповещение. Всё остальное нужно привыкать смотреть самостоятельно используя Kibana.

Если какое-то правило постоянно срабатывает, но ничего критичного не происходит, и уже нечего менять в правиле, то лучше его отключить совсем. Если вы настроили правило, например, что успешных http-кодов должно быть не менее 75% за час, затем получили оповещение об отклонении, проверили и обнаружили, что ничего страшного не произошло, то правило нужно менять.

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

Если их получается больше, значит, это уже не внезапные отклонения, а ожидаемые, а значит, уведомления по ним не нужны. Рекомендую границу в 5 уведомлений в неделю в среднем.

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

Это позволит точнее оценить ситуацию и корректно расставить приоритеты при решении проблемы. При получении оповещения хорошей привычкой может стать обзор ситуации через просмотр Kibana по всем недавним событиям.

Всё вместе

Причём всё конфигурируется таким образом, что всем стеком можно пользоваться как локально при разработке, так и в staging- и production-окружениях, где переменными остаются только переменные окружения. Всё описанное можно запускать в docker-контейнерах.

Проблему Elastalert удалось разрешить, используя команду вида
envsubst < /opt/elastalert/config.dist.yaml > /opt/elastalert/config.yaml в entrypoint-скрипте и доустановкой зависимостей, чтобы это работало. Все описанные сервисы, за исключением Elastalert, позволяют использовать переменные окружения в конфигурациях.

Конфигурации каких сервисов стека оставлять в репозитории проекта, а какие выносить в отдельный репозиторий мониторинга, зависит от наличия других проектов и того, как они будут переиспользовать стек.

Привожу пример разработанного мной Makefile для запуска стека к одному из проектов

build: docker build -t some-registry/elasticsearch elasticsearch docker build -t some-registry/logstash logstash docker build -t some-registry/kibana kibana docker build -t some-registry/nginx nginx docker build -t some-registry/curator curator docker build -t some-registry/elastalert elastalert push: docker push some-registry/elasticsearch docker push some-registry/logstash docker push some-registry/kibana docker push some-registry/nginx docker push some-registry/curator docker push some-registry/elastalert pull: docker pull some-registry/elasticsearch docker pull some-registry/logstash docker pull some-registry/kibana docker pull some-registry/nginx docker pull some-registry/curator docker pull some-registry/elastalert prepare: docker network create -d bridge elk-network || echo "ok" stop: docker rm -f kibana || true docker rm -f logstash || true docker rm -f elasticsearch || true docker rm -f nginx || true docker rm -f elastalert || true run-logstash: docker rm -f logstash || echo "ok" docker run -d --restart=always --network=elk-network --name=logstash -p 127.0.0.1:5001:5001 -e "LS_JAVA_OPTS=-Xms256m -Xmx256m" -e "ES_HOST=elasticsearch:9200" some-registry/logstash run-kibana: docker rm -f kibana || echo "ok" docker run -d --restart=always --network=elk-network --name=kibana -p 127.0.0.1:5601:5601 --mount source=elk-kibana,target=/usr/share/kibana/optimize some-registry/kibana run-elasticsearch: docker rm -f elasticsearch || echo "ok" docker run -d --restart=always --network=elk-network --name=elasticsearch -e "ES_JAVA_OPTS=-Xms1g -Xmx1g" --mount source=elk-esdata,target=/usr/share/elasticsearch/data some-registry/elasticsearch run-nginx: docker rm -f nginx || echo "ok" docker run -d --restart=always --network=elk-network --name=nginx -p 80:80 -v /root/elk/.htpasswd:/etc/nginx/.htpasswd some-registry/nginx run-elastalert: docker rm -f elastalert || echo "ok" docker run -d --restart=always --network=elk-network --name=elastalert --env-file=./elastalert/.env some-registry/elastalert run: prepare run-elasticsearch run-kibana run-logstash run-elastalert delete-old-indices: docker run --rm --network=elk-network -e "ES_HOST=elasticsearch:9200" some-registry/curator curator --config /curator/curator.yml /curator/actions.yml

Такой вариант стека имеет следующие преимущества:

  • Снаружи доступен только 80 порт nginx, который проксирует запросы по basic auth к Kibana
  • Порт Logstash тоже закрыт. Доступ к нему производится с помощью ssh-туннеля
  • Одной командой поднимается весь стек кроме nginx
  • Отдельными командами можно пересобирать, перезапускать docker-образы
  • Отдельными командами можно произвести переразвертывание новой версии одного из образов
  • Сервисы автоматически запускаются при перезагрузке сервера или внутреннем сбое
  • Разворачивается одинаково в локальном и боевом окружении, отличаясь .env-файлом алертинга и файлом nginx-паролем
  • Параметры *_JAVA_OPTS подобраны таким образом, чтобы весь стек стабильно работал на одном инстансе с 4GB RAM (зависит от потока событий и длительности хранения в ES).

Стоит заметить, что в данном подходе не используется ни один из xpack-плагинов.

Главное, что основная конфигурация, состоящая из Dockerfile-ов, конфига Filebeat, конфигов Logstash, правил оповещения, правил автоудаления индексов, находится под системой контроля версий, получая возможность быстрого переразвертывания, хранения истории изменений и всех других преимуществ VCS. Допустим вариант использования docker-compose.

Я предлагаю организовать проверку следующим образом. Важно уделять внимание автоматической проверки работоспособности стека. Сама задача вызывает логгер и записывает сообщение с уровнем ALERT. В самом приложении создается задача, исполняемая по расписанию (в Laravel для этого есть scheduler), скажем, раз в неделю за 5 минут до дейлимитинга. Если такого оповещения нет, а вы к нему привыкли, то это станет сигналом для разбирательств. Если весь стек функционирует нормально, то вы получите оповещение.

Заключение

Привёл пример стека с описанием каждого компонента. Я описал, какую пользу можно извлечь из логирования, рассказал о некоторых вариантах, которые можно выбирать на каждом шаге построения стека логирования и мониторинга. Данные мной рекомендации были опробованы на нескольких проектах и хорошо себя показали в бою. Надеюсь, вы нашли что-то полезное для себя в этом материале. Но следует помнить, что всегда есть, что улучшить.

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть