Проверка log-файлов в Zenoss
Одним из возможных способов мониторинга является проверка логов. К сожалению, в Zenoss отсутствуют более или менее вменяемые механизмы именно для этой задачи, поэтому приходится ее решать, используя доступные методы. В этой статье я расскажу о том, как эту задачу решал я.
Итак, имеется сервер MySQL, в котором включен лог долгих запросов. Одним из способов включения этого лога является добавление 2 строк в файл /etc/my.cnf:
set-variable=long_query_time=30 log-slow-queries=/var/log/mysql/log-slow-queries.log
первая строка указывает, что долгими будут считаться запросы, выполняющиеся более 30 секунд, вторая строка определяет путь к файлу, куда будет записываться информация о таких запросах.
Вот отрывок файла, который дает представление о его структуре:
[collapse collapsed]/usr/libexec/mysqld, Version: 5.0.77-log (Source distribution). started with: Tcp port: 0 Unix socket: /var/lib/mysql/mysql.sock Time Id Command Argument /usr/libexec/mysqld, Version: 5.0.77-log (Source distribution). started with: Tcp port: 0 Unix socket: /var/lib/mysql/mysql.sock Time Id Command Argument # Time: 101125 13:07:05 # User@Host: sugar[sugar] @ [192.168.42.97] # Query_time: 158 Lock_time: 0 Rows_sent: 1 Rows_examined: 200 SELECT SQL_CALC_FOUND_ROWS * FROM `email_addr_bean_rel` , `emails` , `email_addresses` LIMIT 1; /usr/libexec/mysqld, Version: 5.0.77-log (Source distribution). started with: Tcp port: 0 Unix socket: /var/lib/mysql/mysql.sock Time Id Command Argument[/collapse]
Теперь поговорим о том, каким образом мы будем решать поставленную задачу:
- для передачи лога (а точнее его фрагмента) в Zenoss будем использовать datasource, получающий информацию через SSH
- обработку поступившей информации проведем с помощью собственного парсера, благо в Zenoss имеется стандартный механизм назначения парсера для обработки вывода SSH команд в datasource c последующим формированием не только datapoints, но и евентов.
- созданным евентам с помощью маппинга будем определять их класс и назначать их на нужные устройства (логичнее, чтобы евент о долгом запросе пришел не с сервера MySQL, а с того хоста, который этот запрос отправил)
- и, наконец, чтобы каждый раз не забирать и не обрабатывать весь лог, создадим Configuration Property, в котором будем хранить информацию, однозначно определяющую последний полученный евент о долгом запросе, с тем, чтобы включить эту информацию в SSH команду.
Начнем с последнего пункта , поскольку он отражается практически на всех остальных:
Есть 2 способа создать новый configuration property: модифицируя файлы Zenoss и с помощью зенпака. Второй вариант более правильный (имхо), поскольку в зенпак мы сможем включить также и другие результаты нашей работы с тем, чтобы в дальнейшем добавлять всю функциональность максимально просто и быстро. Ну а тем, кто интересуется первым способом, рекомендую обратиться к developers guide.
Для добавления configuration property из зенпака нужно в файл __init__.py из каталога зенпака (здесь и далее под каталогом зенпака будет иметься в виду каталог "ZenPacks.community.<zenpack_name>/ZenPacks/community/<zenpack_name>/") добавить несколько строк. Конечный его вид будет следующим:
[collapse collapsed]import Globals import os.path skinsDir = os.path.join(os.path.dirname(__file__), 'skins') from Products.CMFCore.DirectoryView import registerDirectory if os.path.isdir(skinsDir): registerDirectory(skinsDir, globals()) from Products.ZenModel.ZenPack import ZenPackBase class ZenPack(ZenPackBase): """ ZenPacks.community.MySqlSlowQueries ZenPack loader. """ packZProperties = [ ('zMySqlLastSlowQuery', '', 'string'), ][/collapse]
Как видно из кода, для добавления configuration property достаточно в список packZProperties добавить новый кортеж вида ("имя", "значение по умолчанию", "тип").
Теперь создадим новый Monitoring Template (его потом можно будет добавить в наш зенпак), в котором создадим datasource типа COMMAND и включим опцию "Use SSH". В нем определим команду, которая будет выполняться на хосте и забирать последние изменения в логе:
$$(which awk) '/Time: ${here/zMySqlLastSlowQuery}/,EOF' /var/log/mysql/log-slow-queries.log |grep -v -e 'mysqld, Version:' -e 'Tcp port:' -e 'Id Command'
Здесь ${here/zMySqlLastSlowQuery} - это ссылка на значение configuration property для текущего устройства, в котором хранится время последнего медленного запроса. Запись этого значения будет производиться в маппинге.
Результат выполнения команды передается парсеру, указанному в свойствах datasource. Zenoss ищет парсеры в каталоге $ZENHOME/Products/ZenRRD/parsers/, а также в каталогах parsers/ для каждого из зенпаков. Поскольку мы создаем новый зенпак, то наш парсер лучше поместить в каталог parsers нашего зенпака.
Вот код этого парсера:
[collapse collapsed]__doc__ = """Parser for log-slow-queries.log on MySQL Server (logging of slow queries must be enabled in my.cnf) """ import re import Globals from Products.ZenRRD.CommandParser import CommandParser class mysql_slow_queries(CommandParser): def sendEvent(self,results,dic): results.events.append(dict( summary='Slow query (%(query_time)s secs) at %(date)s by %(user)s@%(host)s: %(query)s' % dic, component='mysqld', eventClassKey='mysqld', severity=3)) def parse(self,query): query=query.split("n") dat=query[0] regexp=re.search("# User@Host: (.*)[.*] @ (.*) [(.*)]",query[1]) usr=regexp.group(1) hst=regexp.group(2) if not len(hst): hst=regexp.group(3) qtim=re.search("# Query_time: (.*?) .*",query[2]).group(1) quers=' '.join(query[3:]) return {'date':dat,'user':usr,'host':hst,'query_time':qtim,'query':quers} def processResults(self, cmd, results): firstRun=re.search("$(which awk) '/Time: (.*)/,EOF'(.*)",cmd.command).group(1)=='' res=cmd.result.output if len(res): for query in res.split("# Time: ")[1 if firstRun else 2:]: self.sendEvent(results,self.parse(query))[/collapse]
На последнем этапе необходимо создать маппинг получаемых евентов.
В качестве класса евентов я выбрал /App/MySQL, в котором добавил новый mapping. Для определения нужного евента в маппинге был задан следующий Regex:
Slow query (d+ secs) at (?P<date>d{6} d{2}:d{2}:d{2}) by (?P<user>S+)@(?P<host>S+): (?P<query>.*)
А в трансформе указаны следующие действия:
[collapse collapsed]from transaction import commit device.zMySqlLastSlowQuery=evt.date commit() if not evt.host=='localhost': dvc=dmd.Devices.findDevice(evt.host) if dvc: evt.device=evt.host evt.prodState=dvc.productionState evt.Location=dvc.getLocationName() evt.DeviceClass=dvc.getDeviceClassName()[/collapse]
Первые 3 строки необходимы для записи времени последнего долгого запроса в configuration property, а остальная часть переопределяет в евенте устройство с сервера MySQL на то, с которого пришел долгий запрос.
В результате теперь каждые 5 минут Zenoss будет проверять лог на сервере MySQL и, в случае появления в нем записей о новых долгих запросах, будут созданы новые евенты для тех устройств, с которых эти запросы были отправлены.
Комментарии
06/12/2010 - 13:05
Спасибо за статью. Есть несколько только одно уточнение: добавление нового command datasource. Этот момент вроде описан в документации, но ввиду довольно широкого спектра использования command datasource можно скинуть ссылку.
26/05/2011 - 16:14
Добрый день! Очень вас прошу описать поэтапно создание своего парсера.
Например я создала свой парсер в папке $ZENHOME/Products/ZenRRD/parsers/, а также сделала его компиляцию. Видим его в списке при создании Datasource.
Но он не работает..... Что еще нужно сделать. Я уже пробовала просто сделать копию имеющегося рабочего парсера, просто имя изменила, и это не работает. :( Почему? Еще нужно что-то скомпилить или наставить или рестартовать! Пожалуйста, уже несколько дней потратила на это....
11/07/2011 - 16:28
Добрый день! Я использую подобный datasource типа COMMAND (у меня такая команда: "awk '/${here/zLastTime}/,EOF' /root/Oxana/zenoss.log") с использованием SSH. Но по прошествии некоторого времени возникает такая ошибка и zencommand вообще перестает работать.
2011-06-29 08:47:09,804 ERROR ZEO.zrpc: (2762) CW: error in notifyConnected (('localhost', 8100))
Traceback (most recent call last):
File "/usr/local/zenoss/zenoss/lib/python/ZEO/zrpc/client.py", line 477, in notify_client
File "/usr/local/zenoss/zenoss/lib/python/ZEO/ClientStorage.py", line 521, in notifyConnected
File "/usr/local/zenoss/zenoss/lib/python/ZEO/ClientStorage.py", line 1199, in verify_cache
File "/usr/local/zenoss/python/lib/python2.4/tempfile.py", line 466, in TemporaryFile
File "/usr/local/zenoss/python/lib/python2.4/tempfile.py", line 236, in _mkstemp_inner
OSError: [Errno 24] Too many open files: '/tmp/tmp8z50I1.inv'
как только число открытых файлов "/root/Oxana/zenoss.log" достигает 1024, выдается такая ошибка. Как закрывать эти файлы, чтоб их число не достигало такого размера? или может еще какие-то решения есть?
12/07/2011 - 13:23
Можно попробовать для начала изменить максимальное число открытых файлов ( echo "32768" > /proc/sys/fs/file-max)..
12/07/2011 - 14:13
Спасибо за ответ.
А когда число открытых файлов достигнет и этого числа, снова возникнет ошибка? или все таки эти файлы когда-то закрываются?
14/07/2011 - 09:07
Ровно с вашей проблемой не сталкивался. Но вообще при ситуации вида "Too many open files" есть два варианта развития событий - или вам повезет, или нет. ) Если повезет - приложению (в данном случае Zenoss) нужно больше чем 1024 открытых дескрипторов, но это число какое-то разумное и конечное. В этом случае после увеличения лимита все будет работать хорошо. Такая ситуация встречается на самом деле часто. Если не повезет - в приложении (вряд ли Zenoss вообще, скорее отдельный сторонний Zenpack) действительно есть баг из-за которого файлы открываются, но потом не закрываются. В этом случае увеличение лимита поможет ненадолго, а для решения проблемы придется найти баг и думать что с ним делать.
14/07/2011 - 10:10
Спасибо большущее за разъяснения.
С первым вариантом решения я пока маюсь, я изменила конфиг /etc/security/limits.conf:
* hard nofile 65000
zenoss soft nofile 60000
zenoss hard nofile 60000
Добавила строчку session required pam_limits.so в несколько файлов в каталоге /etc/pam.d/
А при входе как суперпользователь zenoss все равно имею лимит 1024.
Может что-то надо перерестартовать?
14/07/2011 - 19:52
Ничего не надо делать. Только перелогиниться. Максимум в конфиги pam'а один раз вписать то самое session required pam_limits.so, хотя оно и так должно быть по умолчанию (по крайней мере это так в Fedora/RHEL и их клонах).
Немного смущает "при входе как суперпользователь zenoss" - все-таки как root или как zenoss?
P.S. Первую строчку (* hard nofile 65000) лучше бы убрать - нет смысла ломать умолчания для всей системы.
21/07/2011 - 18:11
Первую строчку убрала. Хоть как рут захожу, хоть как zenoss, все равно 1024.
Может вы знаете ответ еще на такой вопрос.
В шаблоне прописана например такая команда:
head /root/Oxana/zenosstime.txt, и каждые 5 минут (как прописано в cycletime) вижу по ulimit -n, как число открытых файлов увеличивается сразу на 3. Почему на 3? Я думала, хотя бы на 1.