Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Бесспорно, интерфейс Gravitee представляет достаточно наглядные и удобные средства визуализации работы шлюзов Gravitee. Но в любом случае, возникает потребность предоставить доступ к этим инструментам службе мониторинга, владельцам или потребителям API и при этом они могут находится вне закрытого контура, в котором расположен менеджер API. Да и иметь всю доступную информацию по различным API на одном экране всегда удобнее.
Видеть происходящее на шлюзах, при этом не вдаваясь в особенности пользовательского интерфейса Gravitee, а администраторам - не тратить время на создание пользователей и разделение ролей и привилегий внутри Gravitee.
На Хабре уже была пара статей посвященных APIM Gravitee, тут и тут. По этому, в своей заметке, буду подразумевать, что читатель уже знаком с процессом установки/настройки APIM Gravitee и Grafana, рассмотрю только процесс настройки их интеграции.
Почему нельзя пойти простым путём?
По умолчанию, хранилищем для аналитики Gravitee является ElasticSearch. Информация накапливается в четырёх различных индексах, с посуточной разбивкой:
gravitee-request-YYYY.MM.DD - здесь хранится информация по каждому запросу (аналог access.log в nginx). Это наша основная цель;
gravitee-log-YYYY.MM.DD - здесь уже хранится более подробная информация о запросе (при условии, что включена отладка, см. рисунок ниже). А именно полные заголовки запросов и ответов, а также полезная нагрузка. В зависимости от настроек, логироваться может как обмен между потребителем и шлюзом, так и/или шлюзом и поставщиком API;
gravitee-monitor-YYYY.MM.DD - этот нас не интересует;
gravitee-health-YYYY.MM.DD - этот нас не интересует.
И казалось бы, что может быть проще: подключай ElasticSearch в качестве источника данных в Grafana и визуализируй, но не всё так просто.
Во первых, в индексе хранятся только идентификаторы объектов, т.е. человеко-читаемых имён поставщиков и потребителей, вы там не увидите. Во вторых, получить полную информацию соединив данные из двух источников непосредственно в интерфейсе Grafana, крайне проблематично. Gravitee хранит информацию о настройках и статистику своей работы в разных местах. Настройки, в MongoDB или PostgreSQL, по сути статическая информация. Таким образом в одном месте у нас (в терминах Grafana) - таблица, в другом - временной ряд.
B как же быть?
Большим преимуществом СУБД PostgreSQL является богатый набор расширений для работы с внешними источниками данных, в том числе и с ElasticSearch (тут). Благодаря этому интеграция сводится к тому, что Grafana общается с единственным источником данных - СУБД PostgreSQL, которая в свою очередь получает данные из ElasticSearch и обогащает их информацией и делает читаемой для администратора или любого другого бенефициара.
Схематически это будет выглядеть следующим образом (рисунок ниже).
Ну что же, за дело!
Все ниже описанные действия актуальны для следующей конфигурации: CentOS 7, APIM Gravitee 3.6, СУБД PostgreSQL 11, ElasticSearch 7.+
Начнём с интеграции PostgreSQL и ElasticSearch. Сам процесс интеграции достаточно прост и делится на следующие шаги:
Устанавливаем расширение multicorn11 и если не установлен pip, то ставим и его:
yum install multicorn11 python3-pip
Далее из pip-репозитория, устанавливаем библиотеку python3 для работы с ElasticSearch:
pip3 install pg_es_fdw
Далее, переходим к настройке PostgreSQL. Подключаемся целевой БД и добавляем расширение multicorn и подключаем необходимую библиотеку:
GRANT USAGE on FOREIGN DATA WRAPPER multicorn TO gatewaytest; GRANT USAGE ON FOREIGN SERVER multicorn_es TO gatewaytest;
CREATE EXTENSION multicorn; CREATE SERVER multicorn_es FOREIGN DATA WRAPPER multicorn OPTIONS (wrapper 'pg_es_fdw.ElasticsearchFDW');
Выдаём права, непривилегированному пользователю. В нашем случае это logreader:
GRANT USAGE on FOREIGN DATA WRAPPER multicorn TO logreader; GRANT USAGE ON FOREIGN SERVER multicorn_es TO logreader;
Для удобства, создадим отдельную схему logging, владельцем которой будет наш пользователь logreader:
CREATE SCHEMA logging AUTHORIZATION logreader;
Создадим родительскую таблицу, к которой мы будем подключать новые индексы и удалять не актуальные:
CREATE TABLE logging.requests ( id varchar(36), "@timestamp" timestamp with time zone, api varchar(36), "api-response-time" int, application varchar(36), custom json, endpoint text, gateway varchar(36), "local-address" varchar(16), method int, path text, plan varchar(36), "proxy-latency" int, "remote-address" varchar(16), "request-content-length" int, "response-content-length" int, "response-time" int, sort text, status int, subscription varchar(36), uri text, query TEXT, score NUMERIC) PARTITION BY RANGE("@timestamp");
Конечно же, индексы содержат больше полей, но для решения текущей задачи, приведенных выше - достаточно.
Для подключения и отключения индексов, создадим небольшой shell-скрипт и будем запускать его раз в сутки через cron:
#!/bin/sh NEWPART=${1:-$(date +'%Y.%m.%d')} OLDPART=$(date --date='14 days ago' +'%Y.%m.%d') curl http://gateway.corp/test psql gateway -U logreader -c "CREATE FOREIGN TABLE logging.\"requests_${NEWPART}\" PARTITION OF logging.requests FOR VALUES FROM ('${NEWPART} 00:00:00') TO ('${NEWPART} 23:59:59') SERVER multicorn_es OPTIONS ( host 'els-host', port '9200', index 'gravitee-request-${NEWPART}', rowid_column 'id', query_column 'query', query_dsl 'false', score_column 'score', sort_column 'sort', refresh 'false', complete_returning 'false', timeout '20', username 'elastic-ro', password 'Sup3rS3cr3tP@ssw0rd');" psql gateway -U gatewaydev -c "drop foreign table logging.\"requests_${OLDPART}\""
Немного пояснений:
NEWPART - текущая дата, для формирования имени партиции , при подключении нового индекса из ElasticSearch;
OLDPART - дата истекшего, неактуально индекса, здесь это 14 дней (определяется исходя из настроек ES Curator). Удалять партиции, ссылающиеся на несуществующие - обязательно. В противном случае запросы, к родительской таблице, будут прерываться с ошибками;
Вызов 'curl http://gateway.corp/test', необходим для того, что бы создавался индекс текущего дня, так как он создаётся в момент первого обращения к любому поставщику API. Если его не создать, то это будет приводить к ошибке, описанной выше. Такая проблема больше актуальна для тестовых стендов и стендов разработки;
Затем, создаём партицию на индекс текущего дня;
И на последнем шаге - удаляем неактуальный индекс.
Проверяем что всё работает
TABLE logging.requests LIMIT 1;
Если всё правильно, то должны получить похожий результат
-[ RECORD 1 ]-----------+------------------------------------- id | 55efea8a-9c91-4a61-afea-8a9c917a6133 @timestamp | 2021-05-16 00:00:02.025+03 api | 9db39338-1019-453c-b393-381019f53c72 api-response-time | 0 application | 1 custom | {} endpoint | gateway | 7804bc6c-2b72-497f-84bc-6c2b72897fa9 local-address | 10.15.79.29 method | 3 path | plan | proxy-latency | 2 remote-address | 10.15.79.27 request-content-length | 0 response-content-length | 49 response-time | 2 sort | status | 401 subscription | uri | /test query | score | 1.0
Рисуем графики
И вот мы подошли к тому, ради чего всё и делалось - визуализируем статистику Gravitee. Благодаря тому, что для доступа к аналитике используется единая точка входа, а именно СУБД PostgreSQL, это даёт дополнительные возможности. Например, выводить статическую информацию: количество поставщиков, количество потребителей и их статусы; количество и состояние подписок; параметры конфигурации для поставщика и многое другое, наряду с динамическими данными.
В том числе хотелось бы отметить, что у поставщиков и потребителей имеется раздел Metadata, которые можно заполнять кастомными данными и так же выводить в дашборды Grafana.
Вот тут:
А вот так это можно отобразить в Grafana:
SELECT
name "Наименование",
value "Значение"
FROM
metadata
WHERE
reference_id='${apis}'
Пример комплексного экрана
APIs (статика) - общее количество поставщиков и количество активных.
SELECT COUNT(*) AS "Всего" FROM apis;
SELECT COUNT(*) AS "Активных" FROM apis WHERE lifecycle_state='STARTED';
Для Applications, запросы составляются по аналогично, только из таблицы applications
API Hits - количество вызовов по каждому поставщику. Тут уже немного по сложнее
SELECT
date_trunc('minute',"@timestamp") AS time,
apis.name,ee с Grafana

COUNT(*)
FROM
logging.requests al
JOIN
apis ON al.api = apis.id
WHERE
query='@timestamp:[$__from TO $__to]'
GROUP BY 1,2
Average response time by API - среднее время ответа, по каждому поставщику считается аналогичным способом.
SELECT
date_trunc('minute',"@timestamp") AS time,
apis.name,
AVG(al."api-response-time")
FROM
logging.requests al
JOIN
apis ON al.api = apis.id
WHERE
query='@timestamp:[$__from TO $__to]'
GROUP BY 1,2
Еще один интересный показатель Hits, by gateways, это равномерность распределения запросов по шлюзам. Считается так:
SELECT
date_trunc('minute',"@timestamp") as time,
al."local-address",
COUNT(*)
FROM
logging.requests al
WHERE
query='@timestamp:[$__from TO $__to]'
GROUP BY 1,2
Заключение
Приведённое выше решение, по моему субъективному мнению, нисколько не уступает стандартным средствам визуализации APIM Gravitee, а ограничено лишь фантазией и потребностями.
Учитывая то, что Grafana, обычно является центральным объектом инфраструктуры мониторинга, то преимущества такого решения очевидны: более широкий охват, более высокая плотность информации и простая кастомизация визуальных представлений.
P.S.
В ближайшее время, планируется ещё статья по интеграции Gravitee с ActiveDirectory. Процесс достаточно прост, но как всегда, есть нюансы.
Конструктивная критика, пожелания и предложения приветствуются!