Logic - логические индикаторы и контекст

Логический метод проверки, позволяющий реализовать логические правила. Очень мощный инструмент для тонкой настройки мониторинга.

Что можно сделать через логические индикаторы

Условно индикаторы делятся на "нижние" (или "сырые") отражающие техническую информацию, и "верхние" индикаторы (логического типа), которые анализируют данные нижних индикаторов. При этом часто нижним индикаторам устанавливается флаг silent ("тихий"), чтобы они не генерировали алерты.

Борьба с "морганием" (flapping)

Иногда бывает так, что индикатор переходит в состояние ERR, но сам из него выходит через короткое время. Например, загрузка сервера (Load Average) может иногда быть высокой, и мы не хотим получать сообщения каждый раз об этом, но тем не менее, нам важно знать, если загрузка будет высокой в течение длительного времени (скажем, более 45 минут).

С правильной настройкой логических индикаторов, мы не будем получать лишние сообщения. Алерт придет только если высокая нагрузка будет длится достаточно долго.

Работа с резервированием (N из M)

Мы можем допускать ситуацию, когда у нас 5 серверов какого-то типа, и мы разрешаем, чтобы 2 из них были отключены (не менее 3 включено).

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

Разные правила для разного времени

Логический индикатор может учитывать время и дату. Например, мы можем разрешить отключение серверов в интервале от 4 до 6 утра. В это время, если какой-то из наблюдаемых серверов не будет доступен, алерта не будет. Но в 6 утра, если сервер все еще не будет введен в строй, проверка это обнаружит и вышлет оповещение.

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

Аргументы

  • expr логическое выражение в синтаксисе языка Python.
  • dump перечисленные через запятую имена переменных из контекста, которые мы ходим видеть в деталях индикатора (не влияет на статус индикатора). Обычно удобно перечислять те переменные, которые у нас используются в expr (не обязательно).
  • init перечисленные через запятую имена переменных из контекста для инициализации (в 0). Для случаев, когда переменная есть не всегда.

Статус проблемы (problem)

Если логическое выражение невозможно выполнить (например, оно синтаксически неверно или использует переменную, значение которой не определено), индикатору выставляется флаг 'problem' и далее индикатор (с этим флагом) никогда не будет обрабатываться. Чтобы исправить проблему, нужно исправить ошибочное логическое выражение и вручную снять флаг problem, затем применить изменения.

Контекст

Для логического индикатора доступны все данные из контекста проекта. Контекст проекта в виде JSON структуры можно открыть со страницы индикаторов проекта в строчке заголовка, под иконкой листа с текстом.

Есть контекст проекта и контекст индикатора. При обработке логического индикатора используется именно контекст этого индикатора. Он строится на основе контекста проекта, но в него добавляются данные о индикаторе нижнего уровня (если есть) и инициализированные значения переменных из аргумента init.

IMGUR

Время и дата:

  • day: день месяца (например, 12)
  • hhmm: текущее серверное время в формате HHMM, например, 1911 (19:11)
  • weekday: день недели, с нуля. Понедельник - 0, воскресенье - 6.
  • year: год. (2017)

Статус индикаторов (s)

s[name] - Если индикатор name в состоянии ОК, то s[name] - True. Иначе False.

"s": {
        "alpha:apache2": true,
        "alpha:df-/": true,
        "alpha:df-/boot": true,
....

Подробные данные об индикаторах (i)

    "i": {
        "alpha:apache2": {
            "age": 3,
            "errage": 0,
            "id": 557313,
            "maintenance": false,
            "mtime": 1505243736,
            "name": "alpha:apache2",
            "patience": 1200,
            "status": "OK",
            "statusage": 1829617,
            "uerrage": 0
        },
....
  • agesec: возраст индикатора (количество секунд с последнего обновления)
  • status: статус индикатора в виде строки ("OK" либо "ERR")
  • statusage: возраст стастуса (количество секунд с последнего изменения статуса)
  • errage: возраст ошибки. Если индикатор в состоянии OK или имеет флаг silent, то errage=0. Иначе он равен statusage.
  • uerrage: возраст необработанной ошибки (unhandled error age) . В отличие от errage, если индикатор в статусе maintenance, то uerrage = 0.
  • id: id индикатора. Техническая информация, не должна использоваться в пользовательских выражениях.
  • maintenance: булевое выражение, True если индикатор в режиме настройки (maintenance), иначе False.
  • name - имя индикатора
  • mtime - время модификации (unixtime) индикатора.
  • patience - допустимая задержка индикатора в секундах. (из политики или из аргумента patience)

Продолжительность ошибок индикаторов (age).

    "age": {
        "ATTENTION:errage": 442179,
        "ATTENTION:uerrage": 436446,
        "ERR:errage": 442179,
        "ERR:uerrage": 436446,
        "OK:errage": 0,
        "OK:uerrage": 0,
        "active:errage": 419290,
        "active:uerrage": 0,
....

По каждому тегу в проекте - максимальные значения errage и uerrage.

Для индикатора в состоянии OK или с флагом silent (тихий), оба значения errage и uerrage равны 0.

OK:errage и OK:uerrage будут всегда равны 0. Чтобы знать максимальные errage и uerrage по всем индикаторам проекта, надо смотреть на значения ERR:errage и ERR:uerrage, так как все индикаторы в статусе ошибки имеют тэг "ERR".

Обратите внимание, некоторые значения могут быть не всегда, например, age['TAG:errage'] (и age['TAG:uerrage']) будут только если у нас в проекте есть индикаторы с меткой TAG. Это касается и метки ERR. Если все индикаторы в статусе OK, то age['ERR:errage'], например, не будет существовать (и выражение, которое будет его использовать не выполнится, и поставить статус 'problem' у индикатора). На случай, если ваше выражение использует эту переменную, укажите его в init.

okerrm-состояние

Чтобы понять структуры ниже, нужно ввести понятие okerrm-состояние индикатора. В упрощенном виде, индикатор может иметь статус либо OK, либо ERR. Но при этом каждый из индикаторов может быть так же в состоянии настройки (maintenance), а так же быть "тихим" (silent) и это имеет значение. Например, наличие индикатора в статусе ERR, не всегда означает, что существует необработанная проблема. Вполне может быть, что сервер корректно выведен из работы, индикатор переведен в режим maintenance.

Каждый индикатор имеет строго одно okerrm-состояние. Возможные состояния (по приоритетам):

  • DISABLED
  • MAINTENANCE
  • SILENT
  • статус (OK или ERR)

При этом индикаторы в статусе DISABLED игнорируются при построении контекста.

Префиксы (prefix)

    "prefix": {
        "ERR:alpha": 0,
        "ERR:alpha:dnsbl": 0,
        "ERR:braconnier": 0,
        "ERR:charlie": 0,
        "ERR:charlie:dnsbl": 0,
        "ERR:cz1": 0,
        "ERR:dns": 0,
....
        "OK:alpha": 17,
        "OK:alpha:dnsbl": 1,
        "OK:braconnier": 0,
        "OK:charlie": 16,
        "OK:charlie:dnsbl": 1,

По каждому из okerrm-состояний (OK, ERR, MAINTENANCE, SILENT) и по каждому префиксу индикаторов (часть имени индикатора до последнего двоеточия, то есть, для индикатора 'servers:datacenter1:srv1' префиксами будут 'servers' и 'servers:datacenter1') - количество серверов с этим состоянием.

Например, если индикатор servers:datacenter1:srv1 был в состоянии "OK" и перешел в "ERR", то значения "OK:servers" и "OK:servers:datacenter1" уменьшится на единицу, а "ERR:servers" и "ERR:servers:datacenter1" увеличивается на единицу. Если после этого мы перевели индикатор в состояние maintenance, то "ERR:servers" и "ERR:servers:datacenter1" уменьшатся на единицу, а "MAINTENANCE:servers" и "MAINTENANCE:servers:datacenter1" - увеличится.

Метки (tags)

Аналогично префиксам, количество индикаторов по каждому из состояний (OK, ERR, MAINTENANCE, SILENT) с каждой из меток.

    "tags": {
        "ERR:ATTENTION": 0,
        "ERR:ERR": 0,
....
        "OK:OK": 78,
        "OK:active": 25,
        "OK:df": 14,
...

Тут мы видим, например, что у нас в проекте нет индикаторов в состоянии ERR. В состоянии OK всего 78 индикаторов, 14 индикаторов с меткой df, 25 индикаторов с меткой active, и так далее.

Локальная работа с контекстом

Ниже приведен скрипт testlogic.py, который удобен для быстрой локальной проверки и отладки выражений, для логических индикаторов

#!/usr/bin/python

import requests
import json
import argparse
import sys

#
# support functions
#

class AuthException(Exception):
    pass

def get_server(textid):
    r = requests.get('https://cp.okerr.com/api/director/'+textid)
    if r.status_code == 200:
        return r.text.rstrip()    

def get_context(user, password, textid, iname=None):
    srv = get_server(textid)
    if iname:
        url = srv + '/pdsjson/'+textid+'/'+iname
    else:
        # just textid
        url = srv + '/pjson/' + textid
    r = requests.get(url, auth=(user,password))
    if r.status_code == 401:
        raise AuthException 
    return json.loads(r.text)


parser = argparse.ArgumentParser(description='okerr logic indicator tester')
parser.add_argument('--dump', nargs='+',
                    help='dump project variable')
parser.add_argument('--user', required=True, help='okerr username (email)')
parser.add_argument('--pass', required=True, dest='password', help='okerr password')
parser.add_argument('--id', required=True, help='project textid or indicator iname@textid')
parser.add_argument('--expr', required=True, help='logical expression to test')
args = parser.parse_args()


if '@' in args.id:
    iname, textid = args.id.split('@')
else:
    textid=args.id
    iname=None

#
# get project context
#
try: 
    ctx = get_context(args.user, args.password, textid, iname)
except AuthException:
    print "Wrong login/password"
    sys.exit(1)

#
# override context if you want
# examples:
#
# ctx['hhmm'] = 600 
# ctx['s']['test:la'] = False
# ctx['i']['test:la']['errage'] = 110


if args.dump:
    for var in args.dump:
        print "{} = {}".format(var, json.dumps(eval(var, ctx), indent=4))

print eval(args.expr, ctx)
  • --user и --pass - логин и пароль пользователя в okerr
  • --id - textid проекта или (рекомендуется) полное имя индикатора в формате: my_logic_indicator_name@mytextid
  • --dump - имена переменных, которые мы хотим увидеть перед выполнением логического выражения (опционально)
  • --expr - логическое выражение

Скрипт запрашивает контекст из проекта, показывает некоторые переменные, и затем выполняет логическое выражение и пишет его результат. Если результат True, логический индикатор выставится в статус "OK", если False - то в "ERR"

Если указано полное имя индикатора (с textid), то запросится контекст "с точки зрения" этого индикатора (более полный, тот самый, который будет использоваться okerr при обработки значения индикатора.

Помимо того, что можно просто посмотреть значения контекста (через --dump), можно их и переопределить (в целях отладки), по примеру, как указано в исходном коде скрипта, в секции "OVERRIDE context if you want"

Например. Здесь логическое выражение будет устанавливать ошибку только если наблюдаемый индикатор будет в состоянии ошибки более 30 секунд:

# set status OK for first test
$ okerrclient --name test:la -s OK
okerr updated test:la = OK

# logic check would set status OK
$ ./testlogic.py --user test@gmail.com --pass "mypass" --id mytextid  --dump "s['test:la']" "i['test:la']['errage']" --expr "s['test:la'] or i['test:la']['errage']<30"
s['test:la'] = true
i['test:la']['errage'] = 0
True

# simulate switch to status ERR
$ okerrclient --name test:la -s ERR
okerr updated test:la = ERR

# logic check would still set status OK, because very short time passed
$ ./testlogic.py --user test@gmail.com --pass "mypass" --id okrrdm  --dump "s['test:la']" "i['test:la']['errage']" --expr "s['test:la'] or i['test:la']['errage']<30"
s['test:la'] = false
i['test:la']['errage'] = 4
True

# Wait...
$ sleep 30

# test same expression again
$ ./testlogic.py --user test@gmail.com --pass "mypass" --id okrrdm  --dump "s['test:la']" "i['test:la']['errage']" --expr "s1['test:la'] or i['test:la']['errage']<30"
s['test:la'] = false
i['test:la']['errage'] = 42
False

Примеры

Antiflapping

Допустим, у нас есть индикатор alpha:la, замеряющий нагрузку load average на сервере alpha. Иногда нагрузка достаточно высока и индикатор переходит в состояние ошибки, и мы часто получаем алерты об этом. Мы не хотим получать алерты только если высокая нагрузка длится более часа.

Создадим логический индикатор "верхнего уровня" alpha:la-up и установим выражение (expr):

s['test:la'] or i['test:la']['errage']<1800

Индикатор перейдет в состоянии ошибки, только после того, как контролируемый индикатор test:la будет в ошибке более получаса.

N из M

Создадим индикаторы test:srv1, test:srv2, test:srv3. Каждому из них установим метку "webservers".

Проверим выражение

$ ./testlogic.py --user test@gmail.com --pass "mypass" --textid mytextid  --dump "tags['OK:webservers']" --expr "tags['OK:webservers']>=2"
tags['OK:webservers'] = 3
True

Если мы установим один из наших индикаторов в ERR (когда сервер сообщил об ошибке или не сообщил ничего), то tags['OK:webservers'] будет навно 2, но выражение все еще будет давать успешный результат (нас устраивает, когда 2 сервера из 3 в стрю). Но если же еще один сервер упадет (в бою останется только 1 из 3), то tags['OK:webservers'] уже будет равно 1 и наше выражение даст False, логический индикатор перейдет в статус ERR.

Ленивые админы

Допустим, отдельная команда системных администраторов обслуживает некоторую группу серверов. Поставим всем им одну мету (скажем, test), в контексте мы увидим что-то вроде:

    "age": {
....
        "test:errage": 10553,
        "test:uerrage": 0,

В нашем случае такая картинка потому, что индикатор более 2 часов находится в состоянии maintenance и имеет статус ERR. Контролируя значение uerrage, мы следим за unhandled (необработанными) ERR-индикаторами. Контролируя значение errage, мы следим за долгим исправлением ошибки.

Допустим, мы приняли административные правила:

  1. Приступать к исправлению ошибки нужно за 30 минут
  2. Исправить ошибку необходимо за 3 часа.

Для реализации первого правила мы сделаем логический индикатор с выражением: age["test:uerrage"] <= 1800 Если в проекте появится индикатор с меткой 'test', у которого статус ERR будет установлен более 1800 секунд назад, и он не будет переведен в состояние настройки (maintenance) - индикатор зарегистрирует ошибку.

Для реализации второго правила, выражение будет: age["test:errage"] <= 10800 Индикатор "сработает" (перейдет в ошибку) если любой из индикаторов с меткой 'test' будет в состоянии ошибки более 3 часов, вне зависимости от того, началась ли работа по исправлению ошибки или нет.

Если мы хотим использовать этот механизм для всех индикаторов проекта разом (не выставляя им отдельных меток), просто будем проверять age["ERR:errage"] и age["ERR:uerrage"]. Чтобы избежать проблемы, когда у нас все индикаторы в статусе OK (и тогда ERR:errage не определено), в init укажем: age["ERR:errage"], age["ERR:uerrage"]. После этого, эти переменные будут равны нулю даже если у нас нет индикаторов в статусе ERR.

Ночной режим

Мы можем игнорировать какие-то проблемы в зависимости от текущего времени. Например, мы ходим получать алерты по ситуации с индикатором test:test только в дневное время.

~~~~

test:test is ERR, but it's night, so OK.

$ ./testlogic.py --user test@gmail.com --pass "mypass" --textid mytextid --dump "s['test:test']" hhmm --expr "s['test:test'] or hhmm>2200 or hhmm<800" s['test:test'] = false hhmm = 2242 True

test:test is ERR, and it's not too late, so set ERR

$ ./testlogic.py --user test@gmail.com --pass "mypass" --textid mytextid --dump "s['test:test']" hhmm --expr "s['test:test'] or hhmm>2300 or hhmm<800" s['test:test'] = false hhmm = 2242