Knock

Самый простой способ мониторинга вашего сайта без лишних затрат

Начать бесплатно

Установка influxdb, telegraf и grafana с помощью ansible

Для настройки собственного мониторинга на базе influxdb необходимо установить целый набор компонентов: сам influxdb, telegraf и grafana. Все это можно установить одной командой с помощью одного единственного плейбука для ansible.

Ansible

Почему ansible? Этот инструмент конфигурации хорош по многим причинам. Для ansible не нужен агент на сервере, вообще ничего не нужно устанавливать, кроме питона. Ansible использует SSH протокол для выполнения команд на сервере. Все это дает вам универсальный инструмент для конфигурирования сервера с помощью task’ов и playbook’ов. Для написания плейбуков не нужно учить новый язык программирования, все описывается с помощью YAML формата.

Influxdb

Influxdb - это очень удобная, производительная и функциональная база данных для хранения временных рядов. Вам не нужны дополнительные зависимости для использования этой базы данных. Кроме того, у influxdb есть поддержка Erlang, Go, Haskell, Python, Java, PHP и многих других языков программирования.

Готовим playbook

Обратите внимание, что все что о чем написано в этой статье справедливо для ubuntu 16.04 и ansible >= 2.0.0.2.

Playbook(сценарий) состоит из задач описанных в разделе “tasks”. Для каждого сценария нужно указывать группу хостов, на котором будут выполнятся описанные команды. Название группы указывается в параметре “hosts”. Список серверов описан в отдельном файле. Пример файла hosts:

[webservers]
example.ru

[monitor]
94.103.89.245

Задать этот файл с хостами можно либо при запуске ansible-playbook с помощью параметра i(inventory), либо указав в конфиге ansible.cfg где его нужно брать:

[defaults]
inventory = hosts
host_key_checking = False

Сам ansible.cfg должен лежать в папке для конфигов по умолчанию(на моей ubuntu это /etc/ansble/) или в той папке, где вы выполняете команду ansible-playbook.

Любой сценарий должен начинаться с ---:

---
- hosts: webservers

В плейбуках можно использовать комментарии

# Устнаовка influxdb, telegraf, grafana
---
- hosts: webservers

Кроме параметров hosts и tasks может быть указан еще целый ряд параметров. Рассмотрим некоторые из них.

---
- hosts: webservers
  user: yourname
  sudo: yes

В примере выше в параметре user указан пользователь под которым будет выполняться соединение. При этом, все команды будут выполняться через sudo(конечно, если у пользователя yourname достаточно прав).

Можно использовать раздел vars для указания дополнительных параметров:

- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200

Определимся со структурой нашего сценария. Для этого нам нужно понять, что мы хотим сделать. Весь процесс установки можно описать как набор шагов:

  • Добавление репозитория для influxdb и telegraf
  • Установка influxdb и telegraf
  • Конфигурация
  • Запуск influxdb и запуск telegraf
  • Добавление репозитория для grafana
  • Установка grafana
  • Запуск grafana

Теперь можно перейти к самому интересному. Начнем указывать список задач, которые нужно выполнить. Для каждой задачи можно задать имя(но не обязательно) и нужно указать модуль, который будет использоваться при выполнении.

tasks:
- name: Update apt cache
  apt: update_cache=yes
  sudo: yes

Вот пример простой задачи. Я назвал ее Update apt сache. Если вы пользовались дебианами или убунтами, то вы вероятно догадались, что мы используем модуль apt для обновления кеша.

Теперь добавляем ключ для репозитория influxdb:

- name: Import InfluxDB GPG signing key
  apt_key: url=https://repos.influxdata.com/influxdb.key state=present

В этом месте мы используем встроенные ansible модуль apt_key, который позволяет добавить GPG ключи для настройки менеджера макета apt в нашей ubuntu. Этот модуль использует два параметра: url - указывает на расположение ключа, state - определяет статус, который должен быть у задачи после ее выполнения(в нашем случае present).

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

- name: Add InfluxDB repository
  apt_repository: repo='deb https://repos.influxdata.com/ubuntu xenial stable' state=present

Тут мы используем встроенный модуль apt_repository. Значение параметра repo взято из документации по установке influxdb. Не забудьте заменить xenial на вашу версию ubuntu, если это необходимо. Значение stable определяет какую версию influxdb вы собираетесь установить.

Займемся непосредственно установкой необходимых пакетов.

- name: Install InfluxDB packages
  apt: name=influxdb state=present

- name: Install Telegraf packages
  apt: name=telegraf state=present

Используем встроенный модуль apt с новым параметром name в котором указываем название необходимо пакета. Как видите, мы устанавливает подряд два пакета: influxdb и telegraf. Значение present параметра state означает что пакеты не нужно переустанавливать если они уже были установленны. После выполнения этих задач, на вашем сервере будут установленны influxdb и telegraf.

Нам нужно сконфигурировать наш свежеустановленный influxdb.

- name: Modify InfluxDB hostname
  replace:
    dest=/etc/influxdb/influxdb.conf
    regexp='hostname = "localhost"'
    replace='hostname = "{{ ansible_hostname }}"'
    backup=yes

Для этого нам нужен встроенный модуль replace с помощью которого мы в конфиге influxdb на лету меняем необходимые строки. В параметре regexp указываем паттерн, по которому мы будем искать текст для замены. В replace указываем строку, которую впишем вместо найденного по паттерну. Обратите внимание на двойные фигурные скобки - это пример использования шаблонизатора Jinja2. Строка {{ ansible_hostname }} автоматически заменится на реальный хост на котором будет выполнен наш playbook. Параметр dest указывает на файл в котором мы будет проводить поиск и замену. При использовании параметра backup=yes ваш старый конфигурационный файл будет сохранен с другим названием.

Теперь о конфигурировании telegraf. Тут все немного по другому. Вместо использования замены на лету, просто зальем на сервер подготовленный конфигурационный файл. Для этого скачаем дефолтный конфиг и изменим в нем кое-что.

Так как мы хотим собирать метрики с нашего сайта с помощью периодического опроса специальной страницы этого сайта(например http://example.ru/_stats), то нам нужно настроить специальный модуль telegraf, который сможет это делать. Найдите в конфиге часть, которая отвечает за метод получения данных httpjson и приведите его к вот такому виду.

# Read flattened metrics from one or more JSON HTTP endpoints
[[inputs.httpjson]]
   ## NOTE This plugin only reads numerical measurements, strings and booleans
   ## will be ignored.

   ## a name for the service being polled
   name = "knock_metrics"

   ## URL of each server in the service's cluster
   servers = [
     "http://example.ru/_stats",
   ]
   ## Set response_timeout (default 5 seconds)
   response_timeout = "5s"

   ## HTTP method to use: GET or POST (case-sensitive)
   method = "GET"

   ## List of tag names to extract from top-level of JSON server response
   # tag_keys = [
   #   "my_tag_1",
   #   "my_tag_2"
   # ]

   ## HTTP parameters (all values must be strings)
   # [inputs.httpjson.parameters]
   #  event_type = "cpu_spike"
   #  threshold = "0.75"

   ## HTTP Header parameters (all values must be strings)
   # [inputs.httpjson.headers]
   #   X-Auth-Token = "my-xauth-token"
   #   apiVersion = "v1"

   ## Optional SSL Config
   # ssl_ca = "/etc/telegraf/ca.pem"
   # ssl_cert = "/etc/telegraf/cert.pem"
   # ssl_key = "/etc/telegraf/key.pem"
   ## Use SSL but skip chain & host verification
   # insecure_skip_verify = false

Два самых важных параметра, которые необходимо указать: name и servers. Более подробно о этих параметрах и зачем они нужны можно почитать в прошлых статьях.

Наш подготовленный конфиг нужно скопировать на сервер.

- name: Configure Telegraf
  copy: src=files/telegraf/telegraf.conf dest=/etc/telegraf/telegraf.conf

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

Осталось запустить influxdb и telegraf с новыми конфигами. И для этого тоже есть встроенные модуль, который называется service.

- name: Start the InfluxDB service
  service: name=influxdb state=restarted

- name: Start the Telegraf service
  service: name=telegraf state=restarted

Параметр name указывает какой сервис нам нужно перезапустить.

Теперь установим и запустить grafana. Тут уже все просто и понятно.

- name: Import Grafana GPG signing key
  apt_key: url=https://packagecloud.io/gpg.key state=present

- name: Add Grafana repository
  apt_repository: repo='deb https://packagecloud.io/grafana/stable/debian/ jessie main' state=present

- name: Install Grafana packages
  apt: name=grafana state=present

- name: Start the Grafana service
  service: name=grafana-server state=restarted 

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

На этом все. После запуска сценария командой ansible-playbook monitor.yaml у вас будет настроенный и сконфигурированный сервер для мониторинга ваших сайтов и приложений.

Сам сценарий со всеми дополнительными файлами можно найти на github.com/horechek/monitor.

Дополнительно почитать:

PHP модуль на C++

Продолжаем развивать тему про сбор метрик из PHP приложения. В прошлой статье мы рассмотрели как использовать apc кеш для хранения и агрегации простых метрик. Теперь начнем работать над нативным PHP модулем, который даст нам еще больше возможностей.

Нативные расширения в PHP это достаточно магическая штука, как и большинство написанного на C кода. Но мы немного схитрим и воспользуемся www.php-cpp.com. С помощью этой штуки, мы будем писать расширение на C++, а не на чистом C. И это должно неплохо ускорить разработку нашего расширения.

Установка PHP-CPP очень проста. Клонируем репозиторий с github и запускаем сборку:

git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git
cd PHP-CPP
make
sudo make install

PHP расширения компилируются в разделяемые библиотеки(.so файлы). И для загрузки этой библиотеки вам нужно указать ее в конфигурационном файле.

Перед тем как создавать расширение, давайте разберемся как вообще PHP их загружает. Когда PHP запускается, он загружает конфигурационные *.ini файлы. Затем получает названия модулей из этих файлов, пытается их загрузить и вызвать метод get_module() для каждого. А это значит, кто каждый модуль должен обязательно определить функцию get_module(). Этот метод вызывается в момент старта PHP, до начала обработки запросов. Он должен возвращать указатель на участок памяти в котором хранится информация о модуле, всех его функциях, переменных и т.д.

Структура, содержащая информацию о модуле, определена в заголовочном файле Zend engine. Это довольно сложная структура и, конечно же, нет ни какой вменяемой документации по ее использованию. К счастью, библиотека PHP-CPP избавляет нас от лишних забот, и мы можем использовать понятные классы для реализации нашего расширения.

Для начала сделаем простую заготовку для расширения:

#include <phpcpp.h>

extern "C" {
        PHPCPP_EXPORT void *get_module()
        {
                // static(!) объект Php::Extension должен остаться в памяти
                // на время работы всего процесса
                static Php::Extension simple("simple", "1.0");

                // @todo: тут мы будем добавлять наш функционал

                // возвращаем наше расширение
                return simple;
        }
}

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

#include <phpcpp.h>
#include <iostream>

void example() {
        Php::out << "I'm example" << std::endl;
}

extern "C" {
        PHPCPP_EXPORT void *get_module()  {
                static Php::Extension simple("simple", "1.0");

                // делаем нашу функцию доступной из PHP скрипта 
                simple.add<example>("example");
                
                return simple;
        }
}

И теперь, собственно, то зачем мы все это устроили. Наша задача - это наладить сбор метрик из PHP приложения, а для этого нам нужно уметь сохранять данные между запросами. В прошлый раз мы использовали apc. Теперь можем воспользоваться чуть более гибким методом - будем хранить наши счетчики как обычные сишные переменные.

#include <phpcpp.h>
#include <iostream>

int counter = 0;

void example() {
        Php::out << "I'm example" << std::endl;
}

Php::Value simple_counter() {
        counter++;

        return counter;
}

extern "C" {
        PHPCPP_EXPORT void *get_module()  {
                static Php::Extension simple("simple", "1.0");

                simple.add<example>("example");
                simple.add<simple_counter>("simple_counter");

                return simple;
        }
}

Мы добавили переменную counter и еще одну функцию simple_counter. Наша новая функция увеличивает значение переменной counter и возвращает его. Осталось все это скомпилировать и посмотреть что получилось.

Для сборки расширения нам нужен Makefile:

NAME = simple

# У вас этот будет другая папка. Узнать, где находятся
# актуальные ini файлы можно с помощью команды `php --ini`
INI_DIR	= /home/artem/.phpbrew/php/php-7.0.8/var/db

EXTENSION_DIR =	$(shell php-config --extension-dir)

EXTENSION =	${NAME}.so
INI 	  =	${NAME}.ini

COMPILER =	g++
LINKER	 =	g++

COMPILER_FLAGS		=	-Wall -c -O2 -std=c++11 -fpic -o
LINKER_FLAGS		=	-shared
LINKER_DEPENDENCIES	=	-lphpcpp

RM	=	rm -f
CP	=	cp -f
MKDIR	=	mkdir -p

SOURCES	=	$(wildcard src/*.cpp src/*/*.cpp)
OBJECTS	=	$(SOURCES:%.cpp=%.o)


all: ${OBJECTS} ${EXTENSION}


${EXTENSION}: ${OBJECTS}
			${LINKER} ${LINKER_FLAGS} -o $@ ${OBJECTS} ${LINKER_DEPENDENCIES}

${OBJECTS}:
			${COMPILER} ${COMPILER_FLAGS} $@ ${@:%.o=%.cpp}

install:
			${CP} ${EXTENSION} ${EXTENSION_DIR}
			${CP} ${INI} ${INI_DIR}

clean:
			${RM} ${EXTENSION} ${OBJECTS}

format:
			clang-format -i src/*.cpp
			clang-format -i src/*/*.cpp
			clang-format -i src/*/*.h
			clang-format -i tests/*.cpp

И кроме этого Makefile нам нужен очень простой файл конфигурации нашего расширения simple.ini:

extension=simple.so

При выполнении скрипта make install файл нашего расширения simple.so копируется в папку EXTENSION_DIR, а конфиг нашего модуля копируется в папку INI_DIR.

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

<?php

echo simple_counter();

В результате, мы увидим, что счетчик увеличивается с каждым обновлением страницы.

Написав совсем немного кода, мы получили рабочую заготовку, которую сможем использовать в дальнейших экспериментах с метриками

Метрики для сайта. Реализация на PHP

Сбор метрик в вашем веб-приложении или сайте - очень важное и увлекательное занятие. Это способ заглянуть внутрь вашего приложения и убедиться, что все работает как надо. Метрики позволяют крепко спать по ночам и вовремя реагировать, когда что-то пошло не так.

Инструменты

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

Сколько пользователей пошли по тому или иному пути? Сколько запросов к стороннему ресурсу мы выполняем? Какое время работы того или иного куска кода? Чтобы ответить на подобные вопросы вам придется встраивать метрики непосредственно в свое приложение, в свой сайт.

Для начала, определимся с выбором хранилища для наших метрик. Нам нужна база данных для временных рядов. Их существует великое множество. Есть честные базы, такие как influxdb, есть новы и молодежные проекты, такие как prometheus, а есть древние артефакты, такие как graphite.

Не хочется вдаваться в споры и холивары какая из указанных выше систем луч Для нашего проекта мы возьмем influx. Это довольно функциональная система с кучей плагинов и дополнений которые нам пригодятся. Influx очень похожа на обычную sqlную базу данных, вроде MySQL, поэтому у вас навряд ли возникнут трудности при ее установке и настройке.

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

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

Прежде всего, устанавливаем influx и telegraf. Telegraf будет ходить за метриками на наш сайт и складывать их в базу. Для этого его нужно правильно настроить.

[[inputs.httpjson]]
   ## NOTE This plugin only reads numerical measurements, strings and booleans
   ## will be ignored.

   ## a name for the service being polled
   name = "webserver_stats"
 
   ## URL of each server in the service's cluster
   servers = [
     "http://localhost:9999/stats/",
     "http://localhost:9998/stats/",
   ]
   ## Set response_timeout (default 5 seconds)
   response_timeout = "5s"

   ## HTTP method to use: GET or POST (case-sensitive)
   method = "GET"

   ## List of tag names to extract from top-level of JSON server response
   # tag_keys = [
   #   "my_tag_1",
   #   "my_tag_2"
   # ]

   ## HTTP parameters (all values must be strings)
   [inputs.httpjson.parameters]
     event_type = "cpu_spike"
     threshold = "0.75"

   ## HTTP Header parameters (all values must be strings)
   # [inputs.httpjson.headers]
   #   X-Auth-Token = "my-xauth-token"
   #   apiVersion = "v1"

   ## Optional SSL Config
   # ssl_ca = "/etc/telegraf/ca.pem"
   # ssl_cert = "/etc/telegraf/cert.pem"
   # ssl_key = "/etc/telegraf/key.pem"
   ## Use SSL but skip chain & host verification
   # insecure_skip_verify = false

В этом конфиге http://localhost:9999/stats/ это адрес сайта, с которого мы будем снимать метрики. Можно указать что-то вроде http://localhost:9999/metrics.php. Чтобы telegraf мог правильно распарсить метрики, они должны быть в правильном формате. Для забора этих метрик будет использоваться плагин httpjson. JSON на странице с метриками будет выглядеть примерно так:

{
    "counter1": 1,
    "counter2": 3
}

Как видите, ничего сложного. Для начала будем работать с простыми счетчиками. И прям тут нас уже ждут проблемы. Как в php сделать счетчики, чтобы они сохраняли свое состояние между запросами? Конечно, можно писать в базу или файл, но как-то это слишком много для инкремента числа. Если бы мы писали, например, на C, то могли бы не парится по этому поводу и просто держать счетчики на куче. Но тут нам придется хитрить.

Если немного подумать, то можно вспомнить про кеш. Например, можем использовать memcached. Но мы сделаем все еще проще - воспользуемся apc(или apcu). Apc позволить хранить счетчики в памяти и иметь к ним доступ из любого процесса в fpm пуле.

Для начала реализуем базовый класс для наших метрик

<?php

namespace Ruler;


abstract class Metrics
{
    public $name = "";

    public function __construct($name)
    {
        $this->name = $name;
    }

    abstract public function update($val = null);
    
    abstract public function get(array $params = []);
}

В конструкторе мы указываем название метрики, которое в будущем будет использоваться для выборки значений из influxdb.

Теперь реализуем класс счетчика, который хранит свое значение в apc:

<?php

namespace Ruler\Metrics;

use Ruler\Metrics;


class Counter extends Metrics
{

    public function update($val = null)
    {
        if (!apcu_add($this->name, 1)) {
            apcu_inc($this->name);
        }

        $val = apcu_fetch($this->name);

        if ($val >= PHP_INT_MAX) {
            $val = 1;
            apcu_store($this->name, $val);
        }

        return $val;
    }

    public function get(array $params = [])
    {
        $result = false;
        $value =  apcu_fetch($this->name, $result);

        return $result?$value:0;
    }

    public function set($val)
    {
        apcu_store($this->name, $val);
    }
}

Все готово, мы можем использовать наш счетчик. Предположим, что мы хотим посчитать количество запросов в секунду к определенной странице нашего сайта. На этой странице нужно разместить код, вида:

use Ruler\Metrics\Counter;

$counter = new Counter("counter1");
$counter->update();

И теперь на странице metrics.php (которую мы указали в настройках telegraf) вывдоим значение нашего счетчика:

use Ruler\Metrics\Counter;

$counter = new Counter("counter1");
echo json_encode([
	"counter1" => $counter->get()
]);

Все наши данные попадут в influx в базу данных с названием telegraf в measurement(табличка, если так удобней) с названием вида httpjson_webserver_stats. В названии таблички httpjson это префикс по умолчанию, а webserver_stats это настройка name которую вы указали в конфиге telegraf.

Для визуализации данных можно использовать grafana, которая прекрасно умеет работать с influx.

Пример запроса в influx, который выведет количество запросов в секуду

SELECT non_negative_derivative("counter1", 1s) 
FROM "httpjson_webserver_stats" WHERE $timeFilter GROUP BY "server"

Использованием non_negative_derivative позволяет определить насколько быстро растет наш счетчик, что нам и нужно.