Слышали ли вы когда-нибудь о такой практике, как "Monitoring Events Enrichment"? Если вкратце, то это практика наполнения дополнительной информацией сообщений от мониторинга, так что на выяснение причины проблемы уходит меньше времени и телодвижений.

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

Сама эта идея мне очень нравится, и вот после очередного неинформативного сообщения от мониторинга, которое пришло тогда, когда я был далеко от компьютера, и гласило CheckDockerStats CRITICAL: 91% CPU Used!, я решил добавить таким сообщениям полезного контекста.

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

Для обычных проверок check_load и check_memory из пакета nagios-plugins я добавил топ-5 потребителей ресурсов достаточно легко - обернул стандратные плагины в простенький wrapper на bash, получилось примерно так:

#!/bin/bash
# check_memory wrapper
output=$(/usr/lib/nagios/plugins/check_memory "$@")
status=$?
message=$(echo "$output" | cut -d\| -f 1)
perfdata=$(echo "$output" | cut -d\| -f 2)
echo -n "$message"
if [ 0 -ne "$status" ]; then
  echo
  ps aux --sort -pmem | head -n 5
fi
echo " | $perfdata"
exit $status

При превышении заданных лимитов вывод этого wrapperа получается такой:

root@host:~# /usr/lib/nagios/plugins/check_memory_wrapper.sh -f -C -w 15 -c 10
CRITICAL - 4.9% (1198364 kB) free!
USER       PID %CPU %MEM    VSZ   RSS      TTY  STAT START   TIME COMMAND
traffic+ 28086 11.3 91.7 25970900 22651436 ?    Sl   Mar18 5553:57 /usr/bin/traffic_server -M --httpport 8080:fd=9
haproxy  26404  7.0  0.2 85832    59364    ?    Ss   Mar19 3332:04 /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -D -sf 25137
syslog   25077  0.5  0.1 332568   29764    ?    Ssl  Apr06  109:24 rsyslogd
root     15342  0.1  0.0 84124    15212    ?    S    Mar08   95:21 /usr/bin/perl -w /usr/sbin/ldirectord /etc/ldirectord.cf start
 | TOTAL=24678520KB;;;; USED=23480156KB;20976742;22210668;; FREE=1198364KB;;;; CACHES=980600KB;;;;

и сразу из сообщения о проблеме видно, кто сожрал всю память на сервере.

Для контейнера получить такую информацию сложнее, поскольку:

  • в контейнере может не быть ни NRPE-сервера, ни sshd;
  • утилиты top, ps и прочие, получающие данные из общесистемных счетчиков в /proc, показывают в контейнерах неверные данные;
  • cAdvisor не предоставляет информации о процессах внутри контейнера.

Поскольку информацию об использовании памяти/CPU я беру по HTTP из cAdvisor, я решил следовать этому же сценарию и для обогащения сообщений о проблемах, и написать свой мини-сервис для получения информации о том, какие процессы запущены в контейнере.

Поскольку сервис получался комплиментарным к cAdvisor, я назвал его cAdvisor-companion.

В общем, алгоритм решения задачи оказался не сложным - достаточно всего лишь пройтись по файловой системе /proc на родительском хосте, собрать данные по всем процессам, и фильтровать их по cgroup.

Поскольку мой сервис должен был собирать в отдельном потоке данные, и при этом отвечать на HTTP-запросы к API, да еще делать все это быстро, я выбрал для реализации не привычный Python, а "стильный, модный, молодежный" Golang.

Сразу скажу, что это решение оказалось удачным, и позволило мне достаточно быстро и просто написать этот небольшой сервис, уложившись меньше чем в 500 строк кода вместе с тестами.

Итогом нескольких дней разработки в свободное от работы время стал сервис, пригодный к разворачиванию в контейнере, которому для работы требуется только read-only доступ к /proc, и который по HTTP API отдает в виде JSON историю запущенных процессов для указанного контейнера.

Подробнее о том, как этот сервис запускать, что из себя представляет API, почему я не стал использовать Docker API, и прочие полезные вещи можно почитать на github-страничке cAdvisor-companion, ну а я хочу рассказать как я использую этот самописный сервис, и какие у него есть недостатки.

Использую я его как и планировал - получаю список процессов в контейнере в случае сообщений от мониторинга, и вывожу вместе с алертом. Теперь сообщение из начала статьи, которое и побудило меня к написанию своего сервиса, выглядит примерно так:

CheckDockerStats WARNING: 83.32% CPU used!
 USER   PID  %CPU  %MEM      VSZ      RSS   STAT COMMAND
65534  1285  18.3   0.0    18396     1632      S /usr/sbin/nutcracker -v 11 -c /etc/twemproxy/config.yml
 1000 24891   7.5   1.4   299524    99200      S python /path/to/program.py
 1000  4474   7.4   1.4   303356   103416      S python /path/to/program.py
 1000  4468   7.4   1.4   301416   101228      S python /path/to/program.py
 1000  4475   7.4   2.5   382636   181792      S python /path/to/program.py

Проверки использования ресурсов в контейнерах, пример вывода которых я привел выше, я делаю все тем же скриптом check_cadvisor.py, только слегка доработанным для получения данных о процессах от cAdvisor-companion.

Ну а теперь о недостатках:

  • мы видим не PID процесса в контейнере, а PID процесса в родительской системе
  • %CPU, который мы получаем от cAdvisor-companion, это относительный процент использования CPU в контейнере. Чтобы было понятнее - мы вычисляем то, какой процент из всего процессорного времени, использованного за некий interval контейнером, использовал конкретный процесс, в то время как ps и top показывает то, какой процент доступного процессорного времени использовал процесс за тот же interval.

Несмотря на перечисленные недостатки, сервис со своей задачей справляется, так что если хотите обогатить сообщения от мониторинга полезным контекстом - попробуйте cAdvisor-companion в паре с check_cadvisor.py, это очень легко и абсолютно безопасно - при запуске в виде контейнера он получает только read-only доступ к /proc.


Comments

comments powered by Disqus