Система мониторинга curry

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

Задача

У меня есть небольшая домашняя инфраструктура для моих маленьких экспериментальных проектов — у меня куплено несколько доменов и https-сертификатов. Мне хочется иметь одну точку, из которой я могу узнать что все хорошо (все домены и сертификаты оплачены больше чем на 30 дней), или узнать что есть проблемы и узнать список этих проблем.

Кроме доменов и сертификатов есть куча вещей, которые я хотел запихнуть в эту единую точку — проверка что у меня zero inbox, проверка что на серверах работают нужные процессы, проверка что специальная страница отвечает http статусом 200, достаточно ли у меня денег на мобильном телефоне — в общем, есть большой список вещей про которые я хочу знать все ли с ними в порядке.

Раньше у меня был набор тестов, которые я мог выполнить и получить текущее состояние всей моей инфраструктуры. Но такое решение плохое — во первых, хочется получать информацию мгновенно, а запуск и прогон всех тестов шел несколько минут; во вторых — у меня не было системы по которой я запускал эти тесты — я мог очень долгое время их не запускать и быть не в курсе что что-то уже сломалось; и в третьих (наименее важно, но все же) — такая система не сохраняет историю.

Суммирую. Мне хотелось получить систему, которая хранит информацию об объектах. Чтобы можно было рассказать системе — вот этот объект 'ok', а вот этот — 'fail'. И еще, очень важный момент — мне хотелось, чтобы в системе был срок жизни информации об объекте — чтобы системе можно было указать "если в течении часа не придут данные об этом объекте, то нужно отметить что с ним есть проблема".

Конечно же хочется чтобы эта система жила в виде веб сервера и все взаимодействие с системой проходило в виде http-запросов. И я считаю что для такой системы веб-интерфейс избыточен — вполне достаточно получать данные только в виде JSON.

Попытка найти готовое решение

Я был бы очень рад, если бы существовала система, которую я описал. Я знаю про Nagios и Zabbix, но это огромные монстры, которые слишком сложны для моей задачки. Что еще есть? Munin, Monit. Тоже не то. У меня было несколько подходов к поиску готовый системы, которая бы мне подошла, но я ничего не смог найти и поэтому был вынужден написать свое решение.

Мое решение

Итак, я написал маленький проект curry. Исходный код доступен на GitHub:

https://github.com/bessarabov/curry

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

Во первых, нужно поставить docker. Docker можно поставить как на mac и linux, так и на windows.

После того как docker установлен, для того чтобы развернуть работающую систему нужно выполнить одну (!!!) команду:

docker run --publish 15000:3000 bessarabov/curry:1.0.0

Эта команда скачает версию 1.0.0 проекта (для версионирования я использую SemVer) из Docker Hub и запустит ее. После выполнения этой команды на порту 15000 будет работать сервис.

Запуск проекта в продакшене будет чуть-чуть сложнее (нужно подключить персистент сторадж, включить авторизацию и, возможно, поставить перед curry nginx, для того чтобы включить https), но для того чтобы попробовать как все это работает, достаточно одной единственной команды (и, вообще, docker — это отличная вещь).

Итак, на порту 15000 работает curry. Сейчас покажу несколько примеров как с помощью команды curl работать с системой. Вместо хоста я буду использовать слово curry, вам нужно его заменить на ip адрес вашего докера (в случае linux это 127.0.0.1, а для mac и windows ip адрес можно выяснить с помощью команды boot2docker ip).

Давайте отправим в curry информацию об объекте:

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/set?path=zero_inbox&status=ok&expire=1d"

Мы обратились к ручке '/api/1/set' (цифра 1 — это версия API) и передали туда следующее:

В запросе еще необходимо передавать заголовок "X-Requested-With", это сделано для безопасности, чтобы предотвратить возможность проведения CSRF атаки (подробности в документации).

Давайте отправим информацию еще об одном объекте, но на этот раз со статусом fail:

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/set?path=jenkins&status=fail&expire=1d"

Сейчас в curry у нас теперь есть информация о двух объектах. Кроме ручки set для отправки данных у curry есть несколько ручек, с помощью которых можно узнать текущее состояние системы.

С помощью ручки get можно узнать список объектов с которыми есть проблемы:

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/get"

Ручка отдает JSON (как и все ручки в curry), этот JSON содержит следующие данные:

{
   "success" : true,
   "result" : {
      "status" : "fail",
      "objects" : [
         {
            "path" : "jenkins",
            "status" : "fail"
         }
      ]
   }
}

success — это информация что ручка успешно отработала, а в result находится сам ответ ручки. result.status — это общее состояние системы, может быть 'ok' или 'fail' — в данном случае у нас есть проблемные объекты, поэтому 'fail'. Массив result.objects — это список элементов с которыми есть проблемы. path — это имя объекта, а в качестве status может быть 'ok', 'fail' или 'unknown'.

Кроме ручки get, которая возвращает данные только о проблемных объектах, еще есть ручка get_all — она возвращает данные по всем объектам:

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/get_all"

Эта ручка возвращает точно такую же структуру, что и get, но и с объектами в статусе ok:

{
   "success" : true,
   "result" : {
      "status" : "fail",
      "objects" : [
         {
            "path" : "jenkins",
            "status" : "fail"
         },
         {
            "path" : "zero_inbox",
            "status" : "ok"
         }
      ]
   }
}

Давайте отметить что объект jenkins починился (обратите внимание, expire является обязательным только для первого обращения, для следующих его уже можно не указывать):

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/set?path=jenkins&status=ok"

И после этого ручка get скажет что проблемных объектов нет:

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/get"

{
   "success" : true,
   "result" : {
      "status" : "ok",
      "objects" : []
   }
}

Еще есть ручка get_object , с помощью которой можно посмотреть все данные об объекте, включая всю историю:

curl -H "X-Requested-With: XMLHttpRequest" "http://curry:15000/api/1/get_object?path=jenkins"

{
   "success" : true,
   "result" : {
      "path" : "jenkins",
      "status" : "ok",
      "expire" : "1d",
      "history" : [
         {
            "status" : "fail",
            "dt" : "2015-02-09 14:23:23"
         },
         {
            "dt" : "2015-02-09 14:33:41",
            "status" : "ok"
         }
      ]
   }
}

Вот. =) Собственно говоря, это все что умеет curry. Можно записывать в curry информацию о статусе объектов и узнавать что работает, а что нет.

Использование

Когда-то давно в детстве в каком-то журнале (кажется, это был Юный Техник), я прочитал фразу, которая мне очень запомнилась — эта фраза "информация на кончиках пальцев". Я очень стараюсь чтобы все нужная информация была доступна мгновенно. И благодаря созданию и внедрению curry я еще немного продвинулся в этом направлении.

У меня есть работающий сервер curry. И у меня есть набор тестов, которые запускаются по крону и отправляют результаты проверок в curry.

В качестве примера: у меня ежедневно запускаются проверка что все мои домены оплачены больше чем на 2 месяца. Написание такого теста — это совершенно тривиальная задача, благодаря тому что существует отличная библиотека Net::Domain::ExpireDate).

У меня есть некий набор тестов, которые запускаются автоматически, но в curry я так же отправляют данные и руками.

Например, я использую замечательную программу YNAB для учета денег. Там есть процесс который называется "reconciliation" — сверка что записанное в YNAB количество денег совпадает с фактическим. Как только я это делаю, я отправляю в curry данные что объект с именем "money.reconcile_ynab" — находится в статусе "ok". В curry у меня записано что данные по этому объекту истекают через 6 дней, так что когда я вижу что curry говорит что есть проблема с этим объектом, это означает что я давно не делал reconciliation — и я сажусь за YNAB. Такой процесс я называю Fail Driven Working (по аналогии с Test Driven Development) — появилась информация что-то сломалось — нужно идти чинить, пока такой информации нет — чинить не нужно. Мне очень нравится использовать curry для записи регулярных рутинных действий — чтобы следить что эти действия действительно регулярно выполняются.

Резюме

Проект curry — это эксперимент. Для меня эту штука феерично удобна и мне кажется, что я сделал хорошую вещь. Но показатель успеха — будет ли кто-нибудь кроме меня использовать эту систему.

Я буду очень благодарен за любой feedback про этот проект — пишите либо мне на email ivan@bessarabov.ru, либо в GitHub Issues проекта.

Иван Бессарабов
ivan@bessarabov.ru

16 февраля 2015