Как Grafana, Prometheus и Oracle ORDS помогли нам быстрее выявлять инциденты в биллинге

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Привет! Я работаю в компании Bercut, которая входит в группу компаний Ростелеком и более 20 лет занимается разработкой и поддержкой ПО для операторов сотовой и фиксированной связи. Прошел путь от инженера в отделе сопровождения до менеджера продукта. В последние годы работаю ведущим специалистом в отделе администрирования (Senior DBA) и знаю все про работу высоконагруженных биллинговых базах данных, обслуживающих от сотен тысяч до десятков миллионов абонентов. Сегодня я хочу рассказать про решение по комплексному мониторингу биллинговой системы у оператора связи на основе Grafana, Prometheus и Oracle ORDS.

Почему нам понадобилось новое решение

Сейчас биллинговая система на стороне оператора связи ― это мегасложный комплекс с огромным количеством разных модулей и подсистем. Появляются новые модули, добавляются взаимосвязи между ними. Еще быстрее растет число бизнес-критичных функций. Все это нужно контролировать и своевременно выявлять и устранять проблемы. Используются жесткие SLA, временные рамки на ликвидацию аварий очень узкие. Поэтому чем раньше сбой будет обнаружен, проведен первичный анализ и сама проблема локализована, тем быстрее удастся исправить ситуацию.

Для настройки, управления и мониторинга всех продуктов в Bercut используется модуль Bercut ATOMS, с агентами на хостах и графической консолью MIB Explorer для администраторов ПО Bercut. В консоли специалисты операторов и Bercut выполняют все действия по настройке компонентов систем, трассировку, мониторинг производительности и т.д. Консоль имеет и встроенные средства для отображения графиков. С появлением Prometheus и Grafana, ставшими де-факто стандартами в ИТ, и возможности объединения мониторинга всех систем, системного и прикладного ПО, серверов, СХД, SAN, сетевой инфраструктуры в одном приложении мы также стали активно использовать Grafana. Ребята сделали MIB Exporter для экспорта метрик модулей систем в Prometheus. Коллега разработал Solaris Exporter для мониторинга серверов Oracle SPARC. Сейчас Prometheus и Grafana установлены у большинства наших заказчиков, и все перечисленное доступно через несколько десятков дашбордов. 

Мониторинг метрик бизнес-логики биллинговой системы в БД Oracle, который я когда-то написал на PL/SQL, исторически был реализован обособленно. Он обладал необходимой функциональностью для формирования алертов и анализа инцидентов, но графического дашборда не имел. Конечно, мы делали попытки использовать Oracle Enterprise Manager, Zabbix и др., но это были скорее кастомные решения. Настройка отдельной метрики через User defined metrics/Metric extensions в OEM довольно трудоемка и посильна больше DBA, нежели администраторам комплекса.

При этом вопрос функционирования и нагрузки на БД Oracle периодически возникал при проблемах и отбоях: "Сережа, что с базой?" Инженеры в большинстве случаев не имеют доступа в Oracle Enterprise Manager и самостоятельно оценить нагрузку, факт конкуренции или блокировок в БД не могут. Т.е. требовался еще и простой "монитор", посмотрев на который коллеги могли бы самостоятельно проверить, не отличается ли текущая активность БД и нагрузка от той, что была час, день или неделю назад.

В общем, требовалось объединить моторинг работы БД и мониторинг бизнес-процессов внутри в БД в единое решение.

Почему мы выбрали Grafana, Prometheus и Oracle ORDS

Глядя на то, как Grafana отлично справляется с визуализацией метрик с оборудования, инфраструктуры и серверов приложений, я решил, что было бы неплохо сделать дашборд и для мониторинга компекса на уровне БД. И, по моим ощущениям и отзывам коллег, получилось неплохо. Grafana ― это opensource решение, позволяющее легко создать дашборд с отображением метрики из разных источников. В Grafana можно объединить информацию по активности БД, нагрузке на сервере БД с ключевыми показателями работы биллинговой системы на одном экране, при этом интервал окна или временной период меняется всего несколькими кликами. Но не буду забегать вперед, начнем по порядку.

Grafana не умеет напрямую использовать БД Oracle как источник метрик, поэтому требовалась дополнительная прослойка. С учетом уже имеющегося у брата опыта, для сбора биллинговых метрик и хранилища истории был выбран Prometheus. В качестве экспортера из БД Oracle я исследовал возможности OracleDB Exporter. Oracledb_exporter представляет собой небольшой сервис, написанный на Ruby и имеющий конфигурационный файл на простейшем TOML языке. В конфигурационном файле содержатся SQL запросы, которые сервис будет запускать при поступлении запроса со стороны Prometheus. Однако я в нем быстро разочаровался по нескольким причинам:

  • Необходимость настройки отдельного экземпляра сервиса oracledb_exporter для каждой БД, если их несколько.

  • Необходимость настройки отдельного экземпляра сервиса oracledb_exporter для сбора метрик с разным интервалом опроса. Некоторые метрики нужно собирать очень часто, и они являются легкими с точки зрения выполнения запроса в БД; другие отрабатывают довольно долго, но нет необходимости в их частом запуске ― порой достаточно и раза в сутки.

  • Появление новой технологии (TOML) и точки настройки мониторинга для наших заказчиков и инженеров Bercut. Для кастомизации и добавления новых метрик коллегам придется изучать TOML.

  • И самое важное ― сложность в поддержке и обновлении метрик. C развитием продукта или по результатам каких-то инцидентов мы добавляем новые метрики; заказчики часто добавляют свои. Возникает проблема с применением изменений к конфигурационному TOML-файлу. 

В то же время полностью от oracledb_exporter я не отказываюсь и продолжаю его использовать для получения некоторых метрик с экземпляра БД и Oracle ASM ― иметь статистику о топе ожиданий БД и % свободного пространства в ASM дисковых группах на дашборде биллинговой системы довольно важно.

В результате поиска и исследований выбор пал на решение на базе Oracle Rest Data Services и собственную разработку на PL/SQL. ORDS позволяет легко и непринужденно реализовать API в БД с взаимодействием по протоколу HTTP(s). Архитектура решения для мониторинга биллинговой системы представлена на рис. 1

Рис.1 Архитектура решения для мониторинга биллинговой системы
Рис.1 Архитектура решения для мониторинга биллинговой системы

Prometheus, согласно настройкам, каждую минуту опрашивает ORDS сервис, который на каждый запрос запускает в БД PL/SQL процедуру, а она в свою очередь в HTML формате отдает текущие значения заранее рассчитанных метрик. Учитывая то, что часть метрик по большим или высоконагруженным таблицам могут рассчитываться минуты, а некоторые метрики не требует частого обновления, вариант заранее собрать и подготовить к публикации метрики оказался лучшим решением. Также это избавляет от необходимости иметь конфигурацию с несколькими экспортерами с различными интервалами опроса и таймаутами, что упрощает общую конфигурацию.

Полученные значения метрик Prometheus сохраняет в свою БД и хранит данные 2 недели. Также значения метрик сохраняются в БД Victoria Metrics на значительно более длительный срок.

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

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

Вычисление метрик на стороне БД

Вычисление и хранение метрик реализовано в виде набора таблиц и пакета на PL/SQL. 

Рис. 2 Схема данных
Рис. 2 Схема данных

Основной таблицей схемы является METRIC_DICT, в которой хранятся список метрик, параметры расписания запуска и SQL запрос для сбора метрики. Ряд метрик могут иметь несколько значений, например, метрика для контроля свободного места в табличном пространстве, ГБ" будет иметь несколько значений ― название табличного пространства в качестве метки и размер в качестве значения. Подобная реализация позволит в дальнейшем отображать все значения в Grafana на одном графике как одну метрику.
Справочник метрик выглядит так:

Рис.3 Справочник метрик
Рис.3 Справочник метрик

В PL/SQL пакете реализован ряд процедур. Две процедуры периодически запускаются при помощи Oracle Scheduler Jobs. Процедура вычисления метрик выбирает те метрики, у которых наступило время следующего запуска NEXT_RUN, и выполняет SQL оператор. Чтобы исключить или минимизировать влияние мониторинга на работу БД, запросы по большим и/или высоконагруженным таблицам выполняются с использованием dblink на резервном сервере БД ― Oracle Active Standby DB, открытой для чтения в режиме Real Time Apply. При этом отслеживается факт отставания Apply Lag, и, если он более заданной величины, запросы выполняются на Primary DB. Значение метрик сохраняется в таблицы *CURRENT*, а перед этим предыдущие значения переносятся в  таблицы *HIST*, для истории.  Метрики, по которым значение превышает установленный порог, также сохраняются в таблицу METRIC_ALERTS.  Второй worker предназначен для формирования email-уведомлений администраторам. При появлении алерта администраторы получат сообщение со списком проблемных метрик и значениями остальных метрик (справочно). Механизм оставлен для совместимости с предыдущим решением и, возможно, в будущем будет заменен на Prometheus Alertmanager.

Публикация метрик на стороне приложений и сервисов

На серверах приложений установлен Linux или Solaris node exporter, который собирает метрики ОС (cpu, memory, disk usage & utilization), а также MIB Exporter, собирающий и публикующий параметры работы сервисов и модулей биллинговой системы.

Установка Oracle Rest Data Services

Oracle Rest Data Services ― это Java-EE приложение как развитие Oracle HTTP сервера и mod_plsql. Оно предоставляет широкие возможности по настройке, безопасности и кешированию для RESTful сервисов. Может работать в составе серверов приложений, например, Oracle Weblogic и Apache Tomcat, либо в standalone режиме.

ORDS скачиваем тут https://www.oracle.com/database/technologies/appdev/rest-data-services-downloads.html Описанные ниже настройки выполнялись для версии 20.4.3

root@prometheus# mkdir /opt/oracle
root@prometheus# chown oracle:oinstall /opt/oracle
root@prometheus# su - oracle
oracle@prometheus$ mkdir /opt/oracle/ords
oracle@prometheus$ mkdir /opt/oracle/ords/params
oracle@prometheus$ unzip ords-20.4.3.050.1904.zip -d /opt/oracle/ords
oracle@prometheus$ cd /opt/oracle/ords

Для установки и настройки ORDS нужен JDK. Если его нет, его нужно установить.

oracle@prometheus$ which java
/usr/bin/which: no java in (/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin)
oracle@prometheus$
 
root@prometheus# yum install java
...
Installed:
  java-1.8.0-openjdk.x86_64 1:1.8.0.292.b10-1.el7_9
root@prometheus# exit
 
oracle@prometheus$ java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)

Установка выполняется в интерактивном режиме, либо в silent, с использованием parameter file  /opt/ords-20.4.3/params/ords_params.properties

Я планирую сделать Ansible playbook для автоматического развертывания, поэтому использовал второй вариант. Файл параметров для установки в минимальном варианте:

oracle@prometheus$ cat /opt/oracle/ords/params/ords_params.properties
 
db.connectionType=customurl
db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)HOST=192.168.17.172)PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
standalone.mode=true
standalone.http.port=7778
standalone.use.https=false
standalone.context.path=/ords
standalone.scheme.do.not.prompt=true
rest.services.ords.add=false
plsql.gateway.add=true
migrate.apex.rest=false
rest.services.apex.add=false
db.password=prometheus
db.username=prometheus
resource.templates.enabled=false

Все параметры с комментариями (справочно):

#### Connection type: basic,tns or customurl
db.connectionType=customurl
 
### Parameters for "basic" connection type
 
#db.hostname=localhost
#db.port=1521
#db.servicename=myservice
#db.sid=
 
### Parameters for "tns" connection type
 
#db.tnsDirectory=/path/to/tnsfolder
#db.tnsAliasName=
 
### Parameters for "customurl" connection type
 
db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
 
### Parameters for Installing Oracle REST Data Services in Standalone Mode
 
standalone.mode=true
standalone.http.port=7778
## Specifies the location of Application Express images.
##  If you are using Application Express, specify the location of Application Express images.
#standalone.static.images=/path/to/images
standalone.use.https=false
#standalone.https.port=8443
## Specifies the Secure Socket Layer (SSL) certificate hostname.
#standalone.ssl.host=mysecurehost
#standalone.use.ssl.cert=true
#standalone.ssl.cert.path=
#standalone.ssl.key.path=
standalone.context.path=/ords
standalone.scheme.do.not.prompt=true
 
 
### Parameters for Installing Oracle REST Data Services
 
## Specifies whether to install the Oracle REST Data Services schema.
rest.services.ords.add=false
## Specifies the ORDS_METADATA default tablespace
#schema.tablespace.default=USERS
## Specifies the ORDS_METADATA temporary tablespace
#schema.tablespace.temp=TEMP
## Specifies the password for ORDS_PUBLIC_USER.
#user.public.password=
## Specifies the ORDS_PUBLIC_USER default tablespace
#user.tablespace.default=USERS
## Specifies the ORDS_PUBLIC_USER temporary tablespace.
#user.tablespace.temp=TEMP
 
### Parameters for Configuring Application Express
## Specifies whether to configure Oracle REST Data Services for Application Express.
plsql.gateway.add=false
## Specifies the PL/SQL gateway username. For Application Express, you must specify APEX_PUBLIC_USER
#db.username=APEX_PUBLIC_USER
#db.password=
migrate.apex.rest=false
## Specifies whether to configure Oracle REST Data Services for Application Express RESTful Services.
## Set this value to true if you want to use APEX RESTful Services. Default - false
rest.services.apex.add=false
## Specifies the password for APEX_LISTENER.
##  If rest.services.apex.add is set to true, you must provide a password for APEX_LISTENER
#user.apex.listener.password=
## Specifies the password for APEX_REST_PUBLIC_USER.
##  If rest.services.apex.add is set to true, you must provide a password for APEX_REST_PUBLIC_USER.
#user.apex.restpublic.password=

Установка в silent режиме:

oracle@prometheus$ java -jar ords.war configdir /opt/oracle/ords
2021-06-03T09:07:01.282Z INFO        Set config.dir to /opt/oracle/ords in: /opt/oracle/ords/ords.war
 
oracle@prometheus$ java -jar ords.war install --silent --parameterFile /opt/oracle/ords/params/ords_params.properties
2021-06-03T10:11:16.537Z INFO        HTTP and HTTP/2 cleartext listening on host: localhost port: 7778
2021-06-03T10:11:16.568Z INFO        Disabling document root because the specified folder does not exist: /opt/oracle/ords/ords/standalone/doc_root
2021-06-24T14:21:33.956Z INFO        Configuration properties for: |apex||
cache.caching=false
cache.directory=/tmp/apex/cache
cache.duration=days
cache.expiration=7
cache.maxEntries=500
cache.monitorInterval=60
cache.procedureNameList=
cache.type=lru
db.connectionType=customurl
db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
debug.debugger=false
debug.printDebugToScreen=false
error.keepErrorMessages=true
error.maxEntries=50
jdbc.DriverType=thin
jdbc.InactivityTimeout=1800
jdbc.InitialLimit=3
jdbc.MaxConnectionReuseCount=1000
jdbc.MaxLimit=10
jdbc.MaxStatementsLimit=10
jdbc.MinLimit=1
jdbc.statementTimeout=900
log.logging=false
log.maxEntries=50
misc.compress=
misc.defaultPage=apex
security.disableDefaultExclusionList=false
security.maxEntries=2000
db.password=******
db.username=prometheus
resource.templates.enabled=true
2021-06-24T14:21:33.958Z WARNING     *** jdbc.MaxLimit in configuration |apex|| is using a value of 10, this setting may not be sized adequately for a production environment ***
2021-06-24T14:21:33.959Z WARNING     *** jdbc.InitialLimit in configuration |apex|| is using a value of 3, this setting may not be sized adequately for a production environment ***
2021-06-03T10:11:18.909Z INFO        Oracle REST Data Services initialized
Oracle REST Data Services version : 20.4.3.r0501904
Oracle REST Data Services server info: jetty/9.4.35.v20201120
 
^C
oracle@prometheus$

Сервис стартует в режиме standalone, для его остановки и дальнейшей настройки используется Ctrl-C.

В БД создается пользователь с минимальными привилегиями:

SQL> create user prometheus identified by prometheus default tablespace users temporary tablespace temp;
 
SQL> grant connect to prometheus;

Настройка соединения с БД. Используется файл параметров /opt/oracle/ords/params/db_params.properties

db.connectionType=customurl
db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
error.keepErrorMessages=true
error.maxEntries=50
jdbc.DriverType=thin
# Session timeout
jdbc.InactivityTimeout=1800
# Number of connections to create
jdbc.InitialLimit=3
# Max number of times to reuse a connection before create new
jdbc.MaxConnectionReuseCount=1000
# Max number of connections
jdbc.MaxLimit=10
# Max statement to cache in connection
jdbc.MaxStatementsLimit=10
# Min number of connections
jdbc.MinLimit=1
jdbc.statementTimeout=900
db.password=prometheus
db.username=prometheus
resource.templates.enabled=false
rest.services.ords.add=false
plsql.gateway.add=true

Создание подключения к БД:

oracle@prometheus$ java -jar ords.war setup --database db2104 --silent -parameterFile /opt/oracle/ords/params/db_params.properties
2021-06-03T11:16:32.532Z INFO        reloaded pools: [|apex||, |db2104||]

Создание маппинга URL к созданной конфигурации БД:

oracle@prometheus$ java -jar ords.war map-url --type base-path /db2104 db2104
2021-06-03T11:16:50.151Z INFO        Creating new mapping from: [base-path,/db2104] to map to: [db2104, null, null]

Запуск сервиса:

oracle@prometheus$ java -jar ords.war standalone
2021-06-03T11:17:18.238Z INFO        HTTP and HTTP/2 cleartext listening on host: localhost port: 7778
2021-06-03T11:17:18.274Z INFO        Disabling document root because the specified folder does not exist: /opt/oracle/ords/ords/standalone/doc_root
2021-06-03T11:17:20.875Z INFO        Configuration properties for: |db2104||
db.connectionType=customurl
db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
db.password=******
db.username=prometheus
resource.templates.enabled=true
 
2021-06-03T11:17:20.877Z WARNING     *** jdbc.MaxLimit in configuration |db2104|| is using a value of 10, this setting may not be sized adequately for a production environment ***
2021-06-03T11:17:20.877Z WARNING     *** jdbc.InitialLimit in configuration |db2104|| is using a value of 3, this setting may not be sized adequately for a production environment ***
2021-06-03T11:17:23.884Z INFO        Oracle REST Data Services initialized
Oracle REST Data Services version : 20.4.3.r0501904
Oracle REST Data Services server info: jetty/9.4.35.v20201120

Для удобства в ОС создан сервис, который будет автоматом подниматься при старте сервера. Создан конфигурационный файл:

root@prometheus# cd /etc/systemd/system
root@prometheus# vi billing_monitoring.service
 
[Unit]
Description=Bercut Billing Monitoring (ORDS)
Wants=network-online.target
After=network-online.target
 
[Service]
User=oracle
Group=bercut
Type=simple
ExecStart=/bin/java -jar /opt/oracle/ords/ords.war standalone
 
#systemd-escape
 
KillMode=process
RemainAfterExit=no
Restart=on-failure
RestartSec=5s
 
[Install]
WantedBy=multi-user.target
root@prometheus# systemctl daemon-reload
root@prometheus#
root@prometheus#
root@prometheus# systemctl start billing_monitoring.service
root@prometheus#
root@prometheus#
root@prometheus# systemctl -l status billing_monitoring.service
● billing_monitoring.service - Bercut Billing Monitoring (ORDS)
   Loaded: loaded (/etc/systemd/system/billing_monitoring.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2021-06-03 14:46:08 MSK; 18s ago
 Main PID: 26605 (java)
   CGroup: /system.slice/billing_monitoring.service
           └─26605 /bin/java -jar /opt/oracle/ords/ords.war standalone
 
Jun 03 14:46:13 prometheus java[26605]: db.connectionType=customurl
Jun 03 14:46:13 prometheus java[26605]: db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
Jun 03 14:46:13 prometheus java[26605]: db.password=******
Jun 03 14:46:13 prometheus java[26605]: db.username=prometheus
Jun 03 14:46:13 prometheus java[26605]: resource.templates.enabled=true
Jun 03 14:46:13 prometheus java[26605]: 2021-06-03T11:46:13.419Z WARNING     *** jdbc.MaxLimit in configuration |db2104|| is using a value of 10, this setting may not be sized adequately for a production environment ***
Jun 03 14:46:13 prometheus java[26605]: 2021-06-03T11:46:13.420Z WARNING     *** jdbc.InitialLimit in configuration |db2104|| is using a value of 3, this setting may not be sized adequately for a production environment ***
Jun 03 14:46:16 prometheus java[26605]: 2021-06-03T11:46:16.164Z INFO        Oracle REST Data Services initialized
Jun 03 14:46:16 prometheus java[26605]: Oracle REST Data Services version : 20.4.3.r0501904
Jun 03 14:46:16 prometheus java[26605]: Oracle REST Data Services server info: jetty/9.4.35.v20201120
root@prometheus# ^C

Публикация метрик через HTTP средствами ORDS

Для публикации метрик и их значений по HTTP на стороне БД реализована PL/SQL процедура. Она публикует метрики с одним и несколькими значениями (метками), публикует список текущих алертов ― метрик, у которых значение превышает установленный порог; и значения установленных порогов в виде метрик с тем же названием и префиксом 'th_', для возможности настройки Alertmanager в последующем. 

CREATE OR REPLACE PROCEDURE SMASTER.get_prometheus_metrics (i_debug NUMBER DEFAULT 0)
IS
    /******************************************************************************
       NAME:       get_prometheus_metrics
       PURPOSE:    Generating Prometheus formatted metrics from the Invoice DB

       REVISIONS:
       Ver        Date        Author           Description
       ---------  ----------  ---------------  ------------------------------------
       1.7        20/07/2021  SGolikov         1. Добавлены метрики со значениями порогов
       1.6        15/07/2021  SGolikov         1. Исправена ошибка - в списке открытых алертов отсуствовали labeled алерты
       1.5        09/07/2021  SGolikov         1. Добавлен параметр i_debug, с которым формируется только 1, 2, 3 или 4 части или все (0)
                                               2. Добавлено удаление " из имени метки
       1.4        30/06/2021  SGolikov         1. Метка host заменена на hostname
       1.3        29/06/2021  SGolikov         1. Вызов вокрера для сбора метрик realtime=1
       1.2        25/06/2021  SGolikov         1. Добавлена метрика с числом метрик с ошибками расчета
       1.1        25/06/2021  SGolikov         1. Добавлен вывод текущих активных метрик
       1.0        28/04/2021  SGolikov         1. Created this procedure.
    ******************************************************************************/
    v_db_name        VARCHAR2 (100);
    v_db_host        VARCHAR2 (100);
    v_failed_count   NUMBER;

    CURSOR c_single_metrics IS
        SELECT LOWER (c.metric_name)     metric_name,
               c.VALUE,
               metric_type,
               d.metric_desc
          FROM metric_current c, metric_dict d
         WHERE c.metric_name = d.metric_name AND enabled = 1 AND VALUE != -1;

    --
    CURSOR c_multi_metrics IS
        SELECT LOWER (c.metric_name)                                                                     metric_name,
               LOWER (c.label_name)                                                                      label_name,
               TRIM (REGEXP_REPLACE (REGEXP_REPLACE (LOWER (c.label_value), '(\")'), '\s{2,}', ' '))     label_value,
               c.VALUE,
               metric_type,
               d.metric_desc
          FROM metric_current_labeled c, metric_dict d
         WHERE c.metric_name = d.metric_name AND enabled = 1;

    CURSOR c_open_alerts IS
        SELECT LOWER (c.metric_name)     metric_name,
               c.VALUE,
               c.threshold,
               metric_type,
               d.metric_desc
          FROM metric_alerts c, metric_dict d
         WHERE REGEXP_REPLACE (c.metric_name, '#.*') = d.metric_name AND enabled = 1 AND VALUE != -1;

    CURSOR c_metric_thresholds IS
        SELECT LOWER (c.metric_name)     metric_name,
               c.threshold,
               metric_type,
               d.metric_desc
          FROM metric_current c, metric_dict d
         WHERE c.metric_name = d.metric_name AND enabled = 1 AND metric_type = 'gauge' AND c.threshold IS NOT NULL;
--

--
BEGIN
    -- Сбор метрик с realtime=1
    inv_mon.worker (i_realtime => 1);
    --
    OWA.num_cgi_vars := 0;
    HTP.init ();
    OWA_UTIL.mime_header ('text/plain', TRUE, 'utf-8');

    SELECT SYS_CONTEXT ('USERENV', 'DB_NAME') db_name, SYS_CONTEXT ('USERENV', 'SERVER_HOST') HOST
      INTO v_db_name, v_db_host
      FROM DUAL;

    IF i_debug IN (0, 1)
    THEN
        --
        -- Invoice single value metrics
        --
        FOR rec_single_metrics IN c_single_metrics
        LOOP
            HTP.PRINT ('# HELP ' || rec_single_metrics.metric_name || ' ' || rec_single_metrics.metric_desc);
            HTP.PRINT ('# HELP ' || rec_single_metrics.metric_name || ' ' || rec_single_metrics.metric_type);
            HTP.PRINT (rec_single_metrics.metric_name || '{db_name="' || v_db_name || '",hostname="' || v_db_host || '"} ' || rec_single_metrics.VALUE);
        END LOOP;
    END IF;

    IF i_debug IN (0, 2)
    THEN
        --
        --
        -- Invoice multiple value metrics
        --
        FOR rec_multi_metrics IN c_multi_metrics
        LOOP
            HTP.PRINT ('# HELP ' || rec_multi_metrics.metric_name || ' ' || rec_multi_metrics.metric_desc);
            HTP.PRINT ('# HELP ' || rec_multi_metrics.metric_name || ' ' || rec_multi_metrics.metric_type);
            HTP.PRINT (
                   rec_multi_metrics.metric_name
                || '{db_name="'
                || v_db_name
                || '",hostname="'
                || v_db_host
                || '",'
                || rec_multi_metrics.label_name
                || '="'
                || rec_multi_metrics.label_value
                || '"} '
                || rec_multi_metrics.VALUE);
        END LOOP;
    END IF;

    IF i_debug IN (0, 3)
    THEN
        --
        --
        -- Invoice open alerts
        --
        FOR rec_open_alerts IN c_open_alerts
        LOOP
            HTP.PRINT ('# HELP open_alert Metrics with a value greater than the threshold');
            HTP.PRINT ('# HELP open_alert gauge');
            HTP.PRINT (
                   'open_alert{db_name="'
                || v_db_name
                || '",hostname="'
                || v_db_host
                || '",metric_name="'
                || rec_open_alerts.metric_name
                || '",threshold="'
                || rec_open_alerts.threshold
                || '"} '
                || rec_open_alerts.VALUE);
        END LOOP;
    END IF;

    IF i_debug IN (0, 4)
    THEN
        --
        -- Invoice metric thresholds
        --
        FOR rec_threshold IN c_metric_thresholds
        LOOP
            HTP.PRINT ('# HELP threshold for ' || rec_threshold.metric_name || ' ' || rec_threshold.metric_desc);
            HTP.PRINT ('# HELP ' || rec_threshold.metric_name || ' ' || rec_threshold.metric_type);
            HTP.PRINT ('th_' || rec_threshold.metric_name || '{db_name="' || v_db_name || '",hostname="' || v_db_host || '"} ' || rec_threshold.threshold);
        END LOOP;
    END IF;


    --
    --
    -- Invoice open alerts
    --
    SELECT COUNT (*)
      INTO v_failed_count
      FROM metric_current
     WHERE err_msg IS NOT NULL;

    HTP.PRINT ('# HELP failed_metrics_count Number of metrics with error');
    HTP.PRINT ('# HELP failed_metrics_count counter');
    HTP.PRINT ('failed_metrics_count{db_name="' || v_db_name || '",hostname="' || v_db_host || '"} ' || v_failed_count);
END get_prometheus_metrics;
/

Созданному пользователю выдаются права на вызов процедуры и создается синоним

SQL> grant execute on smaster.GET_PROMETHEUS_METRICS to prometheus;
 
SQL> create synonym prometheus.GET_PROMETHEUS_METRICS for smaster.GET_PROMETHEUS_METRICS;

В результате сервис работает, процедура создана. Открываем в браузере URL в формате:

https://<hostname>:<port>/ords/<db name>/get_prometheus_metrics
Рис.4 Пример работы ORDS сервиса
Рис.4 Пример работы ORDS сервиса

Сбор и накопление метрик в Prometheus

В конфигурационный файл Prometheus добавлен следующий блок:

- job_name: billing_monitor
  metrics_path: /ords/db2104/get_prometheus_metrics
  params:
    i_debug: ['0']
  scrape_interval: 60s
  scrape_timeout: 50s
  static_configs:
    - targets:
      - 192.168.хх.хх:7778
      labels:
        module: "billingdb"

Параметр i_debug я добавил для разделения списка всех метрик на 4 группы. Бывает, при наличии синтаксической ошибки, Prometheus не может распарсить вывод сервиса ORDS, и определить конкретную строку с ошибкой трудно. Мне не удалось найти средство, которое позволяет распарсить вывод и указать, на какой конкретно строке Prometheus спотыкается. Если вам известно решение, напишите в комментариях.

Проверяем статус джоба в прометее:

Рис.5 Статус джоба сбора метрик с биллинга в Prometheus
Рис.5 Статус джоба сбора метрик с биллинга в Prometheus

И доступность метрик:

Рис. 6 Просмотр метрики в Prometheus
Рис. 6 Просмотр метрики в Prometheus

Визуализация метрик в Grafana

Для качественноого выполнения задачи нужны реальные данные. С одним из наших заказчиков была согласована разработка пилотной версии мониторинга, и уже на реальных данных с небольшого, но боевого комплекса я создал дашборд в Grafana. Источником выступают данные Prometheus. 

В верхней левой части скриншота присутствует информация о текущей и исторической утилизации CPU, количество выполняющихся сессий в БД и график событий ожидания активных сессий БД. Зеленый означает выполнение на CPU, синий ― ожидание чтения с диска. В итоге ― полет нормальный. Правее ― таблица текущих открытых алертов. Метрик гораздо больше, чем графиков, и не всегда по каждой метрике требуется график. Метрика попадает на экран, если ее текущее значение превысило установленный порог. Рядом правее ― два счетчика, количество метрик с ошибками расчета и время отставания репликации данных на резервную БД, с которой снимается большинство метрик. Ниже ― метрики самой биллинговой системы.

Рис.7 Дашборд, часть 1
Рис.7 Дашборд, часть 1
Рис. 8 Дашборд, часть 2
Рис. 8 Дашборд, часть 2

Для удобства фильтрации по выбранной БД и хосту я создал переменные hostname и db_name:

Рис. 9 Переменные для фильтров
Рис. 9 Переменные для фильтров

Для большинства метрик используется просто публикация метрики, с фильтром по имени хоста:

Рис. 10  График активности сессий в БД
Рис. 10 График активности сессий в БД

На указанном выше графике отображается статистика ASH по активным сессиям в Oracle с группировкой по классам ожидания ― требуется лицензия Oracle Diagnostic and Tuning Pack.

В ходе разработки дашборда с метриками типа Counter я стокнулся с проблемой. Т.к. метрики собирались асинхронно с опросом источника со стороны Prometheus, т.е. метрика вычислялась джобом и сохранялась в таблицу, а Prometheus с равным интервалом считывал значение метрик через сервис ORDS, то периодически возникала ситуация, когда Prometheus считывал то же значение, что было в предыдущий раз. В какой-то момент дельта изменений счетчика была равна 0, а при следующем опросе получался двойной прирост счетчика. В итоге на графике была гребенка, не соответствующая действительности:

Рис.11 Некорректный график по метрике "Скорость тарификации"
Рис.11 Некорректный график по метрике "Скорость тарификации"

Я сделал доработку и добавил признак realtime для метрик. Метрики с установленным флагом realtime теперь вычисляются не джобом, а в режиме реального времени, в момент запроса со стороны Prometheus. Все стало правдиво и красиво:

Рис.12 Правильный график по метрике "Скорость тарификации"
Рис.12 Правильный график по метрике "Скорость тарификации"

Еще одна отличная функция, которую позволяет реализовать Prometheus, ― выявление аномалий. На основе статьи и ее перевода я написал формулы и сделал график, которые позволяют отслеживать факт нахождения текущих показателей метрики в границах, полученных на основе наблюдений за предыдущие 3 недели, с учетом отбрасывания нетипичных (праздничных) дней. И хотя визуальное отслеживание графика в масштабе нескольких часов или даже дней и так позволяет понять, находится ли показатель в норме, функция будет очень полезна именно для формирования алертов в автоматическом режиме. Конкретно эта метрика, по числу тарифицируемых звонков, показала себя не очень хорошо. Случаются "ложные" срабатывания в сторону увеличения числа тарифицируемых событий в момент массовых рассылок. Но такова действительность: запуск рассылки от МЧС или маркетологов трудно предсказать и учесть. В перспективе планирую допилить формулу так, чтобы она не реагировала на резкое увеличение значения метрики.

Рис.13 Выявление аномалий в скорости тарификации
Рис.13 Выявление аномалий в скорости тарификации

Формулы графика:

Рис.14 Формула текущего значения скорости тарификации
Рис.14 Формула текущего значения скорости тарификации
Рис.15 Формула верхней границы типичной скорости тарификации
Рис.15 Формула верхней границы типичной скорости тарификации
Рис.16 Формула нижней границы типичной скорости тарификации
Рис.16 Формула нижней границы типичной скорости тарификации

Дополнительно привожу настройки графика Series overrides, т.к. из оригинальной статьи не совсем очевидно следовало, как отображать именно границу допустимых значений. Я взял коэффициент 1,5:

Рис. 17 Настройки графика
Рис. 17 Настройки графика

Формула алерта:

root# vi alerts.billing.yml
groups:
 
- name: 'Invoice Alerts'
  rules:
  - alert: 'calls_ratings_speed_outside_range'
    expr: >
      abs(
        (
         job:inv_rating_calls_speed:rate5m -job:inv_rating_calls_speed:rate5m_prediction
        ) / job:inv_rating_calls_speed:rate5m:stddev_over_time_1w
      ) > 1.5
    for: 15m
    labels:
      severity: 'warning'
    annotations:
      title: "Deviation for inv_rating_calls_speed > 1.5x"
      description: 'Invoice calls rating speed {{ $labels.db_name }} is outside of expected operating parameters based observations over the past 3 weeks'

Скриншот сформированного алерта:

Рис.18 Пример алерта
Рис.18 Пример алерта

Чтобы визуализация отслеживания аномалий заработала, пришлось подождать несколько недель, пока накопилась история наблюдений и формулы стали выдавать результат. 

Рассылка уведомлений

Помимо отправки уведомлений, реализованной в PL/SQL пакете, имеется возможность настроить Prometheus Alertmanager, который может отправлять алерты Slack, Teams, Telegram, Teams через специальные адаптеры, либо настроить Karma dashboard для удобного отображения алертов из Prometheus Alertmanager. 

Пример сформированного алерта в Karma dashboard:

Рис.19 Пример алерта в Karma Alerts dashboard
Рис.19 Пример алерта в Karma Alerts dashboard

На данный момент alertmanager планируется использовать как дополнительную опцию, только в случае явной потребности заказчика в уведомлениях по каналам, отличным от email.

Выводы

Решение с использованием Oracle REST Data Services, Prometheus, Grafana и с небольшой разработкой на PL/SQL позволило мне быстро и без закупки дополнительных лицензий реализовать мониторинг комплекса биллинговой системы. В едином графическом интерфейсе Grafana я объединил метрики приложения из БД, некоторые статистики работы самой БД Oracle и статистики сервера БД. Теперь в Grafana инженерам Bercut и заказчика доступен мониторинг всего работающего оборудования и ПО с использованием прочих экспортеров для OS Solaris, СХД, SAN и пр. С помощью единой платформы мониторинга время на выявление и локализацию проблемы, по моей оценке, сокращается на 15-60 минут. Используемое ПО позволяет легко добавить в комплекс мониторинга допы в виде отправки уведомлений по различным каналам; быстро адаптировать визуальное отображение данных и комбинировать разные источники метрик в одном дашборде. Grafana + Prometheus + ORDS ― это круто и современно!

Источник: https://habr.com/ru/company/rostelecom/blog/590791/


Интересные статьи

Интересные статьи

В прошлой части мы поговорили о советах директору по разработке бизнес-процесса в Битрикс24, сейчас же я постараюсь дать советы руководителям отделов, поскольку по моему опыту почти всегд...
Oracle cloud предлагает некоторые ресурсы совершенно бесплатно. В частности на халяву можно получить 2 виртуальные машины. При создании машины у вас есть выбор из centos, Ubuntu или oracl...
Вам приходилось сталкиваться с ситуацией, когда сайт или портал Битрикс24 недоступен, потому что на диске неожиданно закончилось место? Да, последний бэкап съел все место на диске в самый неподходящий...
Этот пост будет из серии, об инструментах безопасности, которые доступны в Битриксе сразу «из коробки». Перечислю их все, скажу какой инструмент в какой редакции Битрикса доступен, кратко и не очень р...
Один из самых острых вопросов при разработке на Битрикс - это миграции базы данных. Какие же способы облегчить эту задачу есть на данный момент?