- PVSM.RU - https://www.pvsm.ru -
@jcutrer [1]
Сегодня мы будем говорить про журналы событий, количественные метрики и наблюдение за всем этим с целью увеличения скорости реакции команды на инциденты и уменьшения времени простоя целевой системы.
Erlang/OTP как фреймворк и идеология построения распределенных систем дает нам регламентированные подходы к разработке, инструменты и реализацию стандартных компонентов. Допустим мы применили потенциал OTP и прошли весь путь от прототипа до продакшена. Наш Erlang проект прекрасно себя чувствует на боевых серверах, кодовая база постоянно развивается, появляются новые требования и функционал, в команду приходят новые люди, и все вроде бы хорошо. Но иногда что-то идет не так и технические проблемы, помноженные на человеческий фактор, могут привести к аварии.
Поскольку подстелить соломку абсолютно для всех возможных случаев отказов и проблем невозможно или же экономически нецелесообразно, то необходимо управленческими и программными решениями сократить время простоя системы в случае сбоев.
В информационных системах всегда будет существовать вероятность возникновения сбоев различной природы:
Сразу разграничим ответственность: за работу вычислительного оборудования и сетей передачи данных будет отвечать мониторинг инфраструктуры, например, организованный средствами zabbix. Про установку и настройку такого мониторинга написано немало, повторяться не будем.
C точки зрения разработчика, проблема доступности и качества лежит в плоскости раннего обнаружения ошибок и проблем с производительностью и скорейшего реагирования на них. Для этого нужны подходы и средства оценки. Итак, попробуем вывести количественные метрики, анализируя которые на разных этапах разработки и эксплуатации проекта мы можем значительно улучшить качество.
Еще раз напомню про важность инженерного подхода и тестирования при разработке программного обеспечения. Erlang/OTP предлагает сразу два фреймворка для тестирования: eunit и common test.
В качестве метрик для первичной оценки состояния кодовой базы и ее динамики можно использовать количество успешных и проблемных тестов, время их выполнения и процент покрытия кода тестами. Оба фреймворка позволяют сохранять результаты тестирования в формате Junit.
Например для rebar3 и ct необходимо добавить следующие строки в rebar.config:
{cover_enabled, true}.
{cover_export_enabled, true}.
{ct_opts,[
{ct_hooks, [{cth_surefire, [{path, "report.xml"}]}]}
]}.
Количество успешных и неудачных тестов позволит построить график тренда:
взглянув на который, можно оценить динамику работы команды и регрессию тестов. Для примера, в Jenkins данный график можно получить, используя Test Results Analyzer Plugin.
Если тесты покраснели или стали выполняться долго, метрики позволят еще на этапе сборки и автоматического тестирования отправить релиз на доработку.
Кроме метрик операционной системы в мониторинг следует включить метрики приложений, например количество просмотров в секунду, количество платежей, и другие критически важные индикаторы.
В своих проектах я использую шаблон вида ${application}.${metrics_type}.${name}
для именования метрик. Данное именование позволяет получать списки метрик вида
messaging.systime_subs.messages.delivered = 1654
messaging.systime_subs.messages.proxied = 0
messaging.systime_subs.messages.published = 1655
messaging.systime_subs.messages.skipped = 3
Пожалуй, чем больше метрик, тем проще понять, что происходит в сложной системе.
Отдельное внимание следует уделить мониторингу Erlang VM. Идеология let it crash прекрасна, а правильное использование OTP конечно же поможет поднять упавшие части приложения внутри Erlang VM. Но не стоит забывать и о самой Erlang VM, ведь уронить ее сложно, но можно. Все варианты основаны на исчерпании ресурсов. Перечислим основные:
Переполнение таблицы атомов.
Атомы – идентификаторы, основная задача которых это улучшение читабельности кода. Атомы, единожды созданные, остаются навсегда в памяти экземпляра Erlang VM, так как не очищаются сборщиком мусора. Почему это происходит? Сборщик мусора работает отдельно в каждом процессе c данными этого процесса, в то время как атомы могут быть распределены по структурам данных множества процессов.
По умолчанию можно создать 1,048,576 атомов. В статьях про то как убить Erlang VM обычно можно найти что-то подобное
[list_to_atom(integer_to_list(I)) || I <- lists:seq(erlang:system_info(atom_count), erlang:system_info(atom_limit))]
в качестве иллюстрации данного эффекта. Казалось бы, искусственная проблема и в реальных системах недостижима, но бывают случаи… Например в обработчике внешнего API при разборе запросов используется binary_to_atom/2
вместо binary_to_existing_atom/2
или list_to_atom/1
вместо list_to_existing_atom/1
.
Для мониторинга состояния атомов стоит использовать следующие параметры:
erlang:memory(atom_used)
– количество используемой памяти под атомыerlang:system_info(atom_count)
– число созданных атомов в системе. Совместно с erlang:system_info(atom_limit)
можно вычислить утилизацию атомов.
Утечки процессов.
Сразу хочется сказать, что при достижении process_limit (+P аргумент erl) erlang vm не падает, но переходит в аварийное состояние, например даже подключиться к ней будет, скорее всего, невозможно. В конечном итоге исчерпание доступной памяти при выделении на утекшие процессы приведет к падению erlang vm.
erlang:system_info(process_count)
– количество активных процессов в данный момент. Совместно с erlang:system_info(process_limit)
можно вычислить утилизацию процессов.erlang:memory(processes)
– выделенная память под процессыerlang:memory(processes_used)
– используемая память под процессы.
Переполнение mailbox процесса.
Типичный пример подобной проблемы – процесс отправитель шлет сообщения процессу получателя без ожидания подтверждения, при этом receive
в процессе получателя игнорирует все эти сообщения из-за отсутствующего или неверного паттерна. В итоге сообщения копятся в mailbox. Хотя в erlang и существует механизм замедления отправителя в случае если обработчик не справляется с обработкой, все равно после исчерпания доступной памяти vm падает.
Понять есть ли проблемы с переполнением mailbox поможет etop.
$ erl -name etop@host -hidden -s etop -s erlang halt -output text -node dest@host -setcookie some_cookie -tracing off -sort msg_q -interval 1 -lines 25
В качестве метрики для постоянного мониторинга можно взять количество проблемных процессов. Для их выявления можно использовать следующую функцию:
top_msq_q()->
[{P, RN, L, IC, ST} || P <- processes(), { _, L } <- [ process_info(P, message_queue_len) ], L >= 1000, [{_, RN}, {_, IC}, {_, ST}] <- [process_info(P, [registered_name, initial_call, current_stacktrace]) ] ].
Также этот список можно логировать, тогда при получении нотификации от мониторинга, упрощается анализ проблемы.
Переполнение mailbox процесса.
Память под большие (больше 64 байт) binaries выделяется в общей куче. Выделенный блок имеет счетчик ссылок, показывающий количество процессов имеющих доступ к нему. После обнуления счетчика, происходит очистка. Простейшая система, но, как говорится, есть нюансы. В принципе, существует вероятность возникновения процесса генерирующего столько мусора в куче, что системе не хватит памяти для проведения очистки.
В качестве метрики для мониторинга выступает erlang:memory(binary)
, показывающий выделенную под binaries память.
Итак, случаи, приводящие к падению vm, разобраны, однако кроме них неплохо вести мониторинг не менее важных параметров, прямо или косвенно влияющих на корректное функционирование ваших приложений:
erlang:memory(ets)
.erlang:memory(code)
.erlang:memory(system)
. Показывает потребление памяти рантаймом erlang. erlang:memory(total)
. Это сумма памяти, потребляемой процессами и рантаймом.erlang:statistics(reductions)
.erlang:statistics(run_queue)
.erlang:statistics(runtime)
– позволяет без анализа логов понять, был ли перезапуск.erlang:statistics(io)
.Сформируем файл, содержащий метрики приложения и метрики erlang vm, который будем обновлять раз в N секунд. Для каждого erlang-узла файл метрик должен содержать метрики работающих на нем приложений и метрики экземпляра erlang vm. В итоге должно получиться что-то подобное:
messaging.systime_subs.messages.delivered = 1654
messaging.systime_subs.messages.proxied = 0
messaging.systime_subs.messages.published = 1655
messaging.systime_subs.messages.skipped = 3
….
erlang.io.input = 2205723664
erlang.io.output = 1665529234
erlang.memory.binary = 1911136
erlang.memory.ets = 1642416
erlang.memory.processes = 23596432
erlang.memory.processes_used = 23598864
erlang.memory.system = 50883752
erlang.memory.total = 74446048
erlang.processes.count = 402
erlang.processes.run_queue = 0
erlang.reductions = 148412771
....
С помощью zabbix_sender
будем отправлять данный файл в zabbix, где уже будет доступно графическое представление и возможность создания триггеров автоматизации и нотификации.
Теперь, имея в системе мониторинга метрики и созданные на их основе триггеры автоматизации и события нотификации, у нас есть шанс избежать аварий, заранее реагируя на все опасные отклонения от полнофункционального состояния.
Когда в проекте 1-2 сервера, наверное еще можно жить без центрального сбора логов, но как только появляется распределенная система с множеством серверов, кластеров, окружений, появляется необходимость решения задачи сбора и удобного просмотра логов.
Для записи логов в своих проектах я применяю lager. Часто по пути от прототипа до продакшена проекты проходят следующие стадии сбора логов:
О минусах, плюсах и количественных метриках, которые можно применять, используя последний, и поговорим в призме конкретной реализации – lager_clickhouse
, который я использую в большинстве разрабатываемых проектов. Пару слов о lager_clickhouse
. Это бэкенд lager для сохранения событий в clickhouse. На данный момент, это внутренний проект, но планы сделать его открытым есть. При разработке lager_clickhouse пришлось обходить некоторые особенности clickhouse, например использовать буферизацию событий, чтобы не делать частых запросов в clickhouse. Затраченные усилия окупились стабильной работой и хорошей производительностью.
Основной минус подхода c сохранением в хранилище – дополнительная сущность – clickhouse и необходимость разработки кода сохраняющего события в него и пользовательского интерфейса для анализа и поиска событий. Также для некоторых проектов может быть критичным использование tcp для отправки логов.
Но плюсы, как мне кажется, перевешивают все возможные минусы.
Легкий и быстрый поиск событий:
Примерный вид интерфейса просмотра логов представлен на скриншоте:
Возможность автоматизации.
С введением хранилища логов появилась возможность в реальном времени получать сведения о количестве ошибок, возникновении критических неисправностей, активности системы. Вводя определенные лимиты мы можем генерировать экстренные события выхода системы из функционального состояния, обработчики которых будут выполнять действия автоматизации для устранения данного состояния и отправлять уведомления ответственным за функционал членам команды:
Дальнейшим развитием темы автоматизации обработки экстренных событий стало применение lua скриптов. Любой разработчик или администратор может написать сценарий обработки логов и метрик. Скрипты приносят гибкость и позволяют создавать персональные сценарии автоматизации и уведомлений.
Для понимания процессов, происходящих в системе, и расследования инцидентов жизненно необходимо иметь количественные показатели и журналы событий, а также удобные инструменты их анализа. Чем больше собираемой о системе информации нам доступно, тем проще анализировать ее поведение и исправлять проблемы еще на этапе их возникновения. В случае когда наши меры не сработали в нашем распоряжении всегда есть графики и подробные логи инцидента.
А как вы эксплуатируете решения на Erlang/Elixir и с какими интересными случаями сталкивались в продакшене?
Автор: mr_elzor
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/306942
Ссылки в тексте:
[1] @jcutrer: https://unsplash.com/@jcutrer
[2] lager_syslog: https://github.com/basho/lager_syslog
[3] Источник: https://habr.com/ru/post/437720/?utm_campaign=437720
Нажмите здесь для печати.