Непрерывный мониторинг JVM с помощью Zabbix

    Предположим, у вас есть большое приложение написанное на Java. Это может быть web-сервлет размещенный в контейнере или standalone-сервис. В процессе разработки (да и во время эксплуатации) возникает необходимость отслеживать процессы, протекающие в JVM: работу garbage collector, использование памяти, жизненный цикл потоков, а так же иные специфичные для вашего проекта показатели посредством MBean. Самый простой вариант — использовать профилировщик. Но увы, проблемы не случаются по расписанию, и невозможно заранее знать, когда нужно подключить профилировщик, а держать его постоянно включенным тоже не вариант. В таких случаях идеальное решение — непрерывный мониторинг. О нем и пойдет речь. Но для начала пара слов о классической профилировке.

    Использование профилировщика


    Пожалуй, самый простой и доступный метод мониторинга приложения. Профилировщики позволяют в реальном времени наблюдать за состоянием JVM, детально анализировать статистики ее работы, вплоть до анализа деятельности отдельно взятых потоков. На рынке существуют как бесплатные, так и платные решения:
    • jconsole — утилита входящая в походный набор собирателей кофе “JDK Tools and Utils”. Дает доступ ко всем базовым статистикам JVM, включая прямую работу с MBeans.
    • VisualVM — продвинутый аналоги jconsole. С некоторых пор (когда кто-то кого-то купил) тоже поставляется вместе с jdk. Про VisualVM и основы профилировки можете прочитать в этой статье. От себя лишь добавлю, что если у вас возникает необходимость подключить профилировщик к JVM на удаленном сервере, а заморачиваться не хочется, то просто найдите бинарник visualvm на нем, а затем вызовите его по ssh с трансляцией X-сессии:
      ssh -X login@domain /usr/bin/jvisualvm
    • JProfiler — лидер среди коммерческих профилировщиков (если у кого-то есть богатый опыт его применения — буду рад его перенять).

    Непрерывный мониторинг


    У профайлеров есть один существенный недостаток — вы не можете держать его открытым 24 часа в сутки 7 дней в неделю. Он просто не предназначен для этого. Зачем нужен такой мониторинг? Затем, что вы никогда не знаете, когда у вашего приложения возникнут проблемы. Непрерывный мониторинг позволяет производить сводный анализ статистик за день, сутки, месяц, строить начальные предположения о причинах некорректной работы сервиса/приложения, базируясь на графиках использования памяти, потоков и прочих метриках. К примеру, на ночь вы запускаете QA-тесты, а утром анализируете поведение сервиса основываясь на непрерывном мониторинге.

    Готовых систем приемлемого сочетания цены и качества я не находил, поэтому, имея богатый опыт работы с Zabbix решил приспособить его под свои нужды (для тех, кто знаком с Zapcat — пара слов о нем будет в конце статьи). Вы сразу можете спросить: “почему Zabbix, а не Nagios?”. Я люблю готовые продукты, работающие из коробки и не требующие ручной доводки и стыковки модулей.

    Итак, приступим к серверной кулинарии ;-)! Для приготовления блюда “Непрерывный мониторинг JMX средствами Zabbix” нам понадобится:
    • Ubuntu Server 10.04 (в качестве заменителей можно использовать Debian и RedHat)
    • PostgreSQL либо MySQL по вкусу
    • Zabbix сервер
    • Zabbix клиент
    • Oracle Java (скрипты из данной статьи ориентированы на JVM HotSpot)
    • Jolokia (JMX-HTTP бридж) — помогает избежать проблемы с настройкой JMX RMI подключений

    Напомню, что все действия выполняются на свежеустановленной OS.

    Быстрая настройка Zabbix

    Если у вас уже имеется настроенный Zabbix-сервер с подключенными для мониторинга клиентскими машинами, то можете сразу перейти к следующему пункту.

    Zabbix сервер и агент можно поставить как из репозитория, так и собрать из исходников. Обычно в репозиториях находятся очень старые версии zabbix, для тестов сгодятся и они, но для повседневного использования я рекомендую что-нибудь поновее. Итак, ставим из репозитория:

    sudo apt-get install zabbix-agent zabbix-server-pgsql zabbix-frontend-php php5-pgsql tomcat6

    После установки зайдите на localhost/zabbix Входим под пользователем Admin с паролем zabbix. Идем во вкладку «Configuration -> Hosts» и кликаем по «Zabbix Server». Меняем статус с “Not monitored” на “Monitored”. Далее открываем в редакторе файл /etc/zabbix/zabbix_agentd.conf и заменяем его содержимое на предложенное:

    Server=127.0.0.1
    Hostname=redcraft
    StartAgents=16
    DisableActive=1
    EnableRemoteCommands=1
    DebugLevel=4
    Timeout=30
    PidFile=/var/run/zabbix-agent/zabbix_agentd.pid
    LogFile=/var/log/zabbix-agent/zabbix_agentd.log


    В Zabbix UI переходим в «Monitoring -> Last Data» и проверяем, что с локальной машины начали собираться данные. На этом базовая настройка Zabbix завершена, переходим к препарированию JMX.

    Подключение JVM-агентов Jolokia

    В качестве экспериментальной JVM будем использовать tomcat. Для того, чтобы не производить подключения к нему по RMI протоколу, а использовать HTTP, воспользуемся JMX-HTTP бриджом Jolokia. Для tomcat на официальном сайте доступена war-сборка. Переименовываем скаченный war-файл в jolokia.war и кладем в /var/lib/tomcat6/webapps. Перезапускаем tomcat. Открываем адрес localhost:8080/jolokia и, если все сделали правильно, видим следующую информацию:

    {"timestamp":1328444565,"status":200,"request":{"type":"version"},"value":{"protocol":"6.1","agent":"1.0.2","info":{"product":"tomcat","vendor":"Apache","version":"6.0.24"}}}

    Если у вас standalone-приложение то для подключения к нему jolokia следует добавить в строку запуска следующий параметр:

    -javaagent:$LIBDIR/jolokia-agent.jar=port=9090,host=localhost

    где $LIBDIR/jolokia-agent.jar — путь до Jolokia JVM-Agent

    Настройка сбора данных с Jolokia

    Сейчас у нас есть Zabbix и JVM с подключенным к ней JMX-HTTP бриджом. Нужно организовать сбор данных. Схема сбора данных будет выглядеть следующим образом:

    image

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

    Для сбора данных, мы будем использовать готовую библиотеку работы с JMX-HTTP бриджом. Библиотека написана на Perl и устанавливается средствами CPAN:

    sudo cpan -i JMX::Jmx4Perl

    После успешной установки попробуем получить первые метрики с tomcat при помощи jmx4perl:

    jmx4perl localhost:8080/jolokia read java.lang:type=Memory HeapMemoryUsage

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

    {
    committed => 65470464,
    init => 0,
    max => 132579328,
    used => 10264072
    }


    Как вы можете догадаться, мы только что получили информацию об использовании Heap-памяти JVM Tomcat.

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

    #!/usr/bin/perl
    use strict;
    use warnings;
    use Error qw( :try );
    use JMX::Jmx4Perl;
    use JMX::Jmx4Perl::Alias;
    use File::Path qw(make_path remove_tree);
    use Data::Dumper;
    
    my %source = (
            GC_COPY_COLLECTION_COUNT => ["java.lang:name=Copy,type=GarbageCollector", "CollectionCount"],
            GC_MARK_SWEEP_COLLECTION_COUNT => ["java.lang:name=ConcurrentMarkSweep,type=GarbageCollector", "CollectionCount"],
            GC_COPY_COLLECTION_TIME => ["java.lang:name=Copy,type=GarbageCollector", "CollectionTime"],
            GC_MARK_SWEEP_COLLECTION_TIME => ["java.lang:name=ConcurrentMarkSweep,type=GarbageCollector", "CollectionTime"],
            THREAD_COUNT => ["THREAD_COUNT"],
            MEMORY_HEAP_COMITTED => ["MEMORY_HEAP_COMITTED"],
            THREAD_COUNT_DAEMON => ["THREAD_COUNT_DAEMON"],
            THREAD_COUNT_STARTED => ["THREAD_COUNT_STARTED"],
            MEMORY_HEAP_INIT => ["MEMORY_HEAP_INIT"],
            RUNTIME_VM_VENDOR => ["RUNTIME_VM_VENDOR"],
            MEMORY_HEAP_MAX => ["MEMORY_HEAP_MAX"],
            RUNTIME_VM_NAME => ["RUNTIME_VM_NAME"],
            CL_TOTAL => ["CL_TOTAL"],
            CL_LOADED => ["CL_LOADED"],
            CL_UNLOADED => ["CL_UNLOADED"],
            THREAD_COUNT_PEAK => ["THREAD_COUNT_PEAK"],
            RUNTIME_UPTIME => ["RUNTIME_UPTIME"],
            MEMORY_HEAP_USED => ["MEMORY_HEAP_USED"],
            RUNTIME_VM_VERSION => ["RUNTIME_VM_VERSION"],
    );
    
    my $log_dir = "/var/jmx";
    
    my $result = 0;
    my $port = $ARGV[0];
    my $cmd = $ARGV[1];
    
    if(defined $cmd && defined $port) {
      try {
              my $jmx = JMX::Jmx4Perl->new(url => "http://localhost:$port/jolokia/");
              if($cmd eq "DUMP") {
                      make_path("$log_dir/$port");
                      while(my($key, $value) = each %source) {
                              open FILE, ">$log_dir/$port/$key" or
                                      throw Error::Simple("Could not open file");
                              print FILE $jmx->get_attribute(@$value) . "\n";
                              close FILE;
                  }
                      $result = 1;
              }
          else {
                      my $param = $source{$cmd};
                      $result = $jmx->get_attribute(@$param);
          }
      } catch Error with {
          $result = 0;
              # --- Uncomment for debug ---
              #my $ex = shift;
              #print $ex->{-text}."\n";
              #print $ex->{-line}."\n";
          # --- Debug block ended ---
      };
    }
    else {
      $result = 0;
    }
    print $result . "\n";
    


    Размещаем скрипт в /usr/local/sbin и называем jmx_grabber. Есть вероятность, что скрипт не заработает. Это связано с первыми четырьмя метриками: GC_COPY_COLLECTION_COUNT, GC_MARK_SWEEP_COLLECTION_COUNT, GC_COPY_COLLECTION_TIME, GC_MARK_SWEEP_COLLECTION_TIME. Метрики отображают ни что иное, как статистику «сборщика мусора» (GC). Названия сборщиков мусора, используемых в конкретной реализации JVM могут отличаться. Для JVM HotSpot я встречал две пары: PS Scavenge + PS MarkSweep и ConcurrentMarkSweep + Copy. Если у вас возникли сложности с определением имени вашего GC, то выполните команду

    jmx4perl localhost:8080/jolokia attributes | less

    а затем поищите по ключевому слову «GarbageCollector». Вы найдете что-то наподобие:

    java.lang:name=ConcurrentMarkSweep,type=GarbageCollector -- CollectionCount = 12
    java.lang:name=ConcurrentMarkSweep,type=GarbageCollector -- LastGcInfo =
    java.lang:name=ConcurrentMarkSweep,type=GarbageCollector -- CollectionTime = 0
    java.lang:name=ConcurrentMarkSweep,type=GarbageCollector -- Name = ConcurrentMarkSweep
    java.lang:name=ConcurrentMarkSweep,type=GarbageCollector -- Valid = [true]


    Значение «java.lang:name» и является названием одного из «сборщиков мусора».

    Теперь напишем сценарий на bash для получения данных из файлов:

    #!/bin/bash
    JMX_DIR="/var/jmx"
    if [ -r "$JMX_DIR/$1/$2" ]; then
        cat "$JMX_DIR/$1/$2"
    else
        echo 0;
    fi
    


    Для работы написанных скриптов нам понадобятся следующие Perl библиотеки: File::Path, Module::Find, JSON, Error. Устанавливаем их:

    sudo cpan -i File::Path
    sudo cpan -i Module::Find
    sudo cpan -i JSON
    sudo cpan -i Error


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

    /usr/local/sbin/jmx-grabber 8080 RUNTIME_VM_NAME

    Нам должна вернутся строка «Java HotSpot(TM) Client VM». Теперь создадим папку /var/jmx и вызовем скрипт с другими параметрами:

    /usr/local/sbin/jmx-grabber 8080 DUMP

    Проверим содержимое папки /var/jmx. В ней должен появится подкаталог 8080, содержащий файлы с метриками JVM, по одному файлу на каждую метрику. Не трудно догадаться, что 8080 — это локальный порт, который прослушивает либо jolokia-агент (standalone установка), либо tomcat с контейнером jolokia.

    Настроив сбор и считывание метрик перейдем к процессу выгрузки их в Zabbix.

    Выгрузка метрик JVM в Zabbix


    Добавим две строчки в файл /etc/zabbix/zabbix_agentd.conf:

    UserParameter=jmx_grabber[*],/usr/local/sbin/jmx-grabber $1 $2
    UserParameter=jmx_reader[*],/usr/local/sbin/jmx-stats-reader $1 $2


    После этого не забудьте перезапустить zabbix-агент. Добавленные строки позволяют обращаться к скриптам файловой системы как к обычным метрикам Zabbix. Существует и альтернативный путь: можно воспользоваться zabbix trapper, и отправлять статистику в активном режиме не использую zabbix-агент. Больших отличий нет. В случае использования Zabbix-agent периоды сбора данных выставляются в Zabbix через UI. В случае использования trapper вам придется раскидывать cron-сценарии отправки метрик через puppet (или его аналоги).

    Теперь пришло вермя создания шаблонов. Не буду глубоко вдаваться в этот процесс и приложу готовый xml-файл шаблона, который нужно импортировать в Zabbix. Для этого откройте вкладку «Configuration -> Export/Import» (в последних версиях Zabbix импорт находится в «Configuration ->Templates » далее кнопка «Import Template») и выберите в списке «Import»:

    image

    Теперь нужно назначить добавленный шаблон на наш сервер. Зайдите во вкладку «Configuration -> Hosts» и кликните на «Zabbix Server». В панели «Linked templates» добавьте наш шаблон «Template_Multitenant_Tomcat_JMX_Toolkit». Нажмите на «Save», после чего перейдите в «Monitoring -> Last Data». Через секунд 20 вы получите первые статистические данные с JVM Tomcat. Если данные не приходят, проверьте, запущен ли zabbix-agent и правильно ли он сконфигурирован (см. выше). Через пол часика можете взглянуть на графики («Monitoring -> Graphs»), там вы увидите примерно следующее для «Tomcat JVM Memory»:

    image

    Скорее всего, у вас возникнет вопрос, а что делать, если на одной физической/виртуальной машине находятся несколько JVM? Ведь в шаблоне явно указан порт 8080. Да, это неприятная особенность, но чтобы добавить несколько JVM одной машины на мониторинг, нужно создавать отдельный шаблон под каждый порт jolokia, с которого будет осуществляться сбор данных.

    Проведение тестов

    Хорошо, у нас есть система сбора метрик с JVM Tomcat. Было бы здорово опробовать ее в действии. Для этих целей я написал небольшой сервис, создающий потоки, которые, в свою очередь, непрерывно порождают объекты в JVM вплоть до своей остановки. Внешне доступный интерфейс управления выглядит так:

    image

    Кнопки «Increase» и «Decrease» увеличивают и уменьшают число потоков, а Multiplier дает контроль за множителем интервала времени задержки потока перед генерацией следующего объекта. Сервис можно скачать здесь.

    Разместим сервис в Tomcat, а затем произведем следующие манипуляции:
    • поднимем число потоков до 20 и подождем 5 минут
    • поднимем число потоков до 50 и вновь подождем 5 минут
    • уменьшим множитель задержки потока до 50 и ожидаем 5 минут
    • сбрасываем число потоков до нуля


    В результате мы получим следующие графики:

    image

    image

    image

    Zapcat

    Когда я говорил, что не нашел приемлемых готовых решений, я немного слукавил. Одно решение существует — это Zapcat. И, кстати сказать, при помощи него скрестить tomcat и Zabbix не займет более 5 минут. Зачем же я изобретал велосипед? Причины здесь две:
    • zapcat не обновляется с 2008 года. Не то что бы это совсе плохо — он до сих пор вполне сносно работал. Но использовать систему, которую никто не поддерживает, не очень хочется.
    • к standalone сервисам zapcat агент подключатся непосредственно в коде приложения. Для меня это выглядит как если бы детям при рождении пришивали тонометр и прочие датчики, которые снять в дальнейшем можно было лишь через хирургическое вмешательство. Мне крайне не хотелось делать zapcat (который более не поддерживается) частью приложения. Это и не безопасно, и с точки зрения архитектуры — неграмотно. В теории, zapcat можно оформить в качестве javaagent, но готовых реализаций, которые можно подключить при помощи одноименной директивы, я не находил.

    Для того, чтобы решить, нужен ли вам описанный мной метод мониторинга, или вам будет достаточно zapcat, как простой в развертывании альтернативы, задайтесь следующими вопросами:
    • хочу ли я интегрировать в свое приложение средства мониторинга? (актуально лишь для standalone-сервисов)
    • хочу ли я производить дополнительную обработку промежуточных данных, которые уже собраны в файловую систему (или в кеш). Возможно вы захотите вычислять средние суммарные показатели по всем копиям сервисов
    • хочу ли я обращаться к сервису\приложению черз jmx-бридж при помощи иных инструментов, отличных от zabbix?

    Напоследок


    Предложенный мною вариант не столько готовая система мониторинга, сколько каркас для построения системы контроля жизнедеятельности сервиса исходя из конкретных задач и потребностей. В совокупности с умением Zabbix'а обнаруживать свои агенты и автоматически добавлять сетевые узлы на мониториг, задача контроля работы java-сервисов упрощается в разы.
    Если у вас возникли трудности по настройке — пишите в личку или в комментариях, подскажу по мере возможности. Для тех, кто не хочет вручную настраивать тестовый стенд, предлагаю воспользоваться готовым образом для VirtualBox (логин red, пароль password).
    Приятных экспериментов и стабильной работы JVM! ;-)

    P.S.: отвечать на сообщения и комментарии смогу лишь в обед и по вечерам
    Поделиться публикацией
    Похожие публикации
    Комментарии 15
      0
      IBM ITCAM for AD в этом плане наверное самый продвинутый софт, но стоит как пара самолетов
        +1
        javamelody кстати тоже хорошая штука
          0
          да, я ее тоже встречал — интересный проект. Единственная причина, почему я его не использовал — удобнее консолидировать все системы мониторинга в одну. Zabbix это позволяет
          +3
          В новой версии zabbix ожидается нативная поддержка JMX. Эта фишка также доступна и в тестовых версиях 1.X.
            +1
            Вот это комментарий важнее всей статьи. В ветке 1.9 (1.9.9 на момент написания статьи) мониторинг JMX работает из коробки и без каких либо костылей.
              0
              Которая нифига не stable…
                0
                Которая просто работает, экономя время на сочинение костылей. Используем в продакшене с 1.9.7, полёт отличный. Например, кроме jmx, нам очень не хватало возможности снятия значения iowait с солярки в агенте версии 1.8.
                  0
                  Мы тоже используем ветку 1.9.x как раз из-за java gateway ootb.
                    0
                    Насколько легко перейти с 1.8 на 2-ю ветку, что бы не мучатся с костылями и заюзать уже готовый там Java bridge?
                  –1
                  1.9 не является стабильной и находится в разработке. Я не знаю, каковы ваши объемы ресурсов, подлежащих мониторингу, но работая с Zabbix’ом часто сталкивался с существенными проблемами нестабильных веток при большой нагрузке на систему сбора информации.
                  И да, печально, что вы из всей статье вы вынесли лишь идею мониторинга. Это рассмотрение каркаса и подходов к сбору информации.
                    0
                    Я не знаю, каковы ваши объемы ресурсов

                    Около 200 серверов и около 11000 объектов для сбора. Apache, MySQL, Tomcat. Много солярки, немного линуксов. Данные собираются через агенты и jmx, кое-где через SNMP.
                    сталкивался с существенными проблемами нестабильных веток при большой нагрузке на систему сбора информации

                    Проблема была только в недостаточной производительности базы. Около 400 запросов в секунду, большинство из которых INSERT. Решилось отдельным сервером для БД.
                    И да, печально, что вы из всей статье вы вынесли лишь идею мониторинга

                    Статья на 90% состоит из рассмотрения вопроса, как снимать данные с JMX и отдавать их в Zabbix. Что из себя представляет Zabbix интересующиеся уже давно усвоили, а начинающим это статья, как пример написания своих UserParameter, пригодится не очень.

                      0
                      База не на SSD стоит? потому что, например, у меня очень большой iowait и Load Average на серваке с заббиксом не ниже 6 всё время.

                      Имеем Intel® Xeon® CPU W3520 @ 2.67GHz (8 ядер) в Supermicro X8STi
                      24Gb RAM (DDR3 1333 MHz) и два винчестера ST32000641AS в зеркальном RAID
                      Ubuntu 10.04 LTS x86_64
                  +1
                    0
                    Насколько легко перейти с 1.8 на 2-ю ветку, что бы не мучатся с костылями и заюзать уже готовый там Java bridge?
                  0
                  ОЧЕНЬ БОЛЬШОЕ СПАСИБО!!!
                  Я тут в свою кассандру пытался впендюрить zapcat, как-то даже получилось :)
                  Но это реально геморрой постоянно её пересобирать и патчить :)

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

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