Вирішуємо практичні завдання в Zabbix за допомогою JavaScript

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript
Тихін Усковінженер команди інтеграції Zabbix

Zabbix - кастомізована платформа, яка використовується для моніторингу будь-яких даних. З ранніх версій Zabbix у адміністраторів моніторингу була можливість запускати різні скрипти через Дії для перевірки на цільових вузлах мережі. При цьому запуск скриптів призводив до виникнення ряду складнощів, у тому числі таких як необхідність підтримки скриптів, їх доставки на вузли зв'язку та проксі, а також підтримки різних версій.

JavaScript для Zabbix

У квітні 2019 року був представлений Zabbix 4.2 з функцією попередньої обробки на JavaScript. Багато хто загорівся ідеєю відмовитися від написання скриптів, які десь забирають дані, перетравлюють їх і надають уже у зрозумілому для Zabbix форматі, а виконуватимуть прості перевірки, які отримуватимуть неготові для зберігання та обробки Zabbix дані, а потім оброблятимуть цей потік даних із використанням засобів Zabbix та JavaScript. У зв'язці з низькорівневим виявленням та залежними елементами даних, які з'явилися в Zabbix 3.4, вийшла досить гнучка концепція для сортування та керування отриманими даними.

У Zabbix 4.4, як логічне продовження передобробки на JavaScript, з'явився новий спосіб оповіщення Webhook, який можна використовувати для простої інтеграції оповіщень Zabbix зі сторонніми додатками.

JavaScript та Duktape

Чому були обрані саме JavaScript та Duktape? Розглядалися різні варіанти мов та движків:

  • Lua – Lua 5.1
  • Lua – LuaJIT
  • Javascript – Duktape
  • Javascript – JerryScript
  • Embedded Python
  • Вбудований Perl

Основними критеріями вибору були поширеність, простота інтеграції двигуна в продукт, низьке споживання ресурсів та загальна продуктивність двигуна, та безпека впровадження коду цією мовою в моніторинг. За сукупністю показників переміг JavaScript на двигуні Duktape.

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript

Критерії вибору та performance testing

Особливості Duktape:

- Стандарт ECMAScript E5/E5.1
- Модулі Zabbix для Duktape:

  • Zabbix.log() — дозволяє вписати безпосередньо в лог Zabbix Server повідомлення з різним рівнем деталізації, що забезпечує можливість зіставляти помилки, наприклад, в Webhook зі станом сервера.
  • CurlHttpRequest() — дозволяє робити HTTP-запити в мережу, на чому ґрунтується застосування Webhook.
  • atob() та btoa() — дозволяє кодувати та декодувати рядки у формат Base64.

ПРИМІТКА. Duktape відповідає стандартам ACME. У Zabbix використовується версія скрипта 2015 року. Наступні зміни незначні, тому їх можна ігнорувати.

Магія JavaScript

Вся магія JavaScript полягає в динамічній типізації та приведенні типів: рядкових, числових та логічних.

Це означає, що не потрібно заздалегідь оголошувати якогось типу змінна повинна повертати значення.

При математичних операціях значення, які повертаються операторами-функціями, перетворюються на числа. Виняток із таких операцій — додавання, оскільки, якщо хоча б один із доданків є рядком, до всіх доданків застосовується рядкове перетворення.

ПРИМІТКА. Методи, що відповідають за такі перетворення, як правило, реалізовані в батьківських прототипах об'єктів, значення и toString. значення викликається при чисельному перетворенні і завжди перед методом toString. Метод значення повинен повертати примітивні значення, інакше його результат ігнорується.

Для об'єкта викликається метод valueOF. Якщо він не знайдений або не повертає примітивне значення, викликається метод toString. Якщо метод toString не знайдено, проводиться пошук значення у прототипі об'єкта, і все повторюється до завершення обробки значення та приведення всіх значень у виразі до одного типу. Якщо для об'єкта реалізовано метод toString, який повертає примітивне значення, саме він використовується для рядкового перетворення. При цьому результатом цього методу не обов'язково є рядок.

Наприклад, якщо для об'єкта 'об'єкт' визначається метод toString,

`var obj = { toString() { return "200" }}` 

метод toString повертає саме рядок, і при складанні рядка з числом ми отримуємо склеєний рядок:

`obj + 1 // '2001'` 

`obj + 'a' // ‘200a'`

Але якщо переписати toStringЩоб метод повертав число, при складанні об'єкта буде виконуватися математична операція з числовим перетворенням і виходить результат математичної складання.

`var obj = { toString() { return 200 }}` 

`obj + 1 // '2001'`

При цьому, якщо ми виконуємо додавання з рядком, виконується рядкове перетворення, і ми отримуємо склеєний рядок.

`obj + 'a' // ‘200a'`

Саме в цьому криється причина великої кількості помилок користувачів JavaScript, що починають.

У метод toString можна вписати функцію, яка збільшуватиме поточне значення об'єкта на 1.

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript
Виконання скрипта за умови, що змінна дорівнює 3, і вона дорівнює 4.

При порівнянні з наведенням типів (==) щоразу виконується метод toString із функцією збільшення значення. Відповідно, при кожному наступному порівнянні значення збільшується. Цього можна уникнути шляхом порівняння без наведення типів (===).

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript
Порівняння без наведення типів

ПРИМІТКА. Не використовуйте порівняння з наведенням типів без необхідності.

Для складних скриптів, наприклад, Webhook зі складною логікою, у яких необхідне порівняння з наведенням типів, рекомендується попередньо написати перевірки для значень, які повертають змінні та обробити невідповідності та помилки.

Webhook Media

Наприкінці 2019 року та на початку 2020 року команда інтеграції Zabbix займалася активною розробкою Webhooks та інтеграцій «з коробки», які постачаються в дистрибутиві Zabbix.

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript
Посилання на документацію

Попередня обробка

  • Поява попередньої обробки на JavaScript дозволила відмовитися від більшості зовнішніх скриптів, і в даний час в Zabbix можна отримати будь-яке значення і перетворити його на зовсім інше будь-яке значення.
  • Передобробка в Zabbix реалізована кодом JavaScript, який при компіляції в байт-код перетворюється на функцію, що приймає єдине значення у вигляді параметра значення у вигляді рядка (у рядку може бути і цифра, і число).
  • Оскільки на виході виходить функція, в кінці скрипта обов'язковий повертати.
  • Можливе використання користувацьких макросів у коді.
  • Ресурси можна обмежити як на рівні операційної системи, а й програмно. Для кроку передобробки виділяється максимум 10 мегабайт оперативної пам'яті та ліміт часу виконання у 10 секунд.

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript

ПРИМІТКА. Значення тайм-ауту в 10 секунд досить багато, тому що збір умовних тисяч елементів даних за 1 секунду за досить важким сценарієм передобробки може уповільнити роботу Zabbix. Тому не рекомендується використовувати передобробку для виконання повноцінних скриптів на JavaScript через так звані тіньові елементи даних (dummy items), які запускаються лише для виконання передобробки.

Перевірити свій код можна через тест передобробки або за допомогою утиліти zabbix_js:

`zabbix_js -s *script-file -p *input-param* [-l log-level] [-t timeout]`

`zabbix_js -s script-file -i input-file [-l log-level] [-t timeout]`

`zabbix_js -h`

`zabbix_js -V`

Практичні завдання

Завдання 1

Замінити обчислюваний елемент даних перед обробкою.

Умова: отримуємо з датчика температуру в градусах Фаренгейту для зберігання в градусах Цельсія.

Раніше ми створили елемент даних, який збирає температуру в градусах за Фаренгейтом. Після цього — ще один елемент даних (який обчислюється), який за формулою перетворював би градуси за Фаренгейтом на градуси за Цельсієм.

Проблеми:

  • Необхідно дублювати елементи даних та зберігати всі значення в базі.
  • Необхідно узгодити інтервали для «батьківського» елемента даних, який обчислюється та використовується у формулі, і для обчислюваного елемента даних. В іншому випадку обчислюваний елемент даних може перейти в стан, що не підтримується, або порахувати попереднє значення, що позначиться на надійності результатів моніторингу.

Одним із рішень була відмова від гнучких інтервалів перевірок на користь фіксованих інтервалів, щоб гарантовано обчислювати обчислюваний елемент даних після елемента даних, що отримує дані (у нашому випадку температура в градусах за Фаренгейтом).

Але якщо, наприклад, шаблон ми використовуємо для перевірки великої кількості пристроїв, і перевірка виконується раз на 30 секунд, 29 секунд Zabbix «халтурить», а в останню секунду починає перевірки та обчислення. Це призводить до створення черги та впливає на продуктивність. Тому рекомендується використовувати фіксовані інтервали лише якщо це дійсно необхідно.

В даному завданні оптимальне рішення - передобробка з одного рядка на JavaScript, яка конвертує градуси за Фаренгейтом в градуси за Цельсієм:

`return (value - 32) * 5 / 9;`

Це швидко і просто, не потрібно створювати зайвих елементів даних і зберігати історію, а також можна використовувати для перевірок гнучкі інтервали.

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript

`return (parseInt(value) + parseInt("{$EXAMPLE.MACRO}"));`

Але, якщо в гіпотетичній ситуації необхідно отриманий елемент даних скласти, наприклад, з якоюсь константою, визначеною в макросі, необхідно враховувати, що параметр значення розкривається у рядок. При операції складання рядків два рядки просто об'єднуються в один.

Вирішуємо практичні завдання в Zabbix за допомогою JavaScript

`return (value + "{$EXAMPLE.MACRO}");`

Для отримання результату математичної дії необхідно привести типи отриманих значень у числовий формат. Для цього можна використати функцію parseInt (), яка видає ціле число, функцію parseFloat(), яка видає десятковий дріб, або функцію номеряка видає ціле число або десятковий дріб.

завдання 2

Отримати час у секундах до закінчення сертифіката.

Умова: якийсь сервіс видає дату закінчення сертифіката у форматі "Feb 12 12:33:56 2022 GMT".

В ECMAScript5 Date.parse() приймає дату у форматі ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ). Необхідно привести до нього рядок у форматі MMM DD YYYY HH:mm:ss ZZ

проблема: значення місяця виражено текстом, а чи не числом. Дані у такому форматі не приймаються Duktape.

Приклад рішення:

  • Насамперед оголошується змінна, яка набуває значення (весь скрипт — оголошення змінних, які перераховані через кому).

  • У першому рядку ми отримуємо дату у параметрі значення і поділяємо її пробілами методом розкол. Таким чином ми отримуємо масив, де кожному елементу масиву, починаючи з індексу 0, відповідає один елемент дати до і після пробілу. split(0) - Місяць, split(1) - Число, split(2) - Рядок з часом і т. д. Після цього до кожного елемента дати можна звертатися за індексом в масиві.

`var split = value.split(' '),`

  • Щомісяця (у хронологічному порядку) відповідає індекс його положення у масиві (з 0 до 11). Щоб конвертувати текстове значення в числове, до індексу місяця додається одиниця (бо нумерація місяців починається з 1). При цьому вираз із додаванням одиниці взято в дужки, тому що в іншому випадку буде отримано рядок, а не число. Наприкінці ми виконуємо фрагмент () — зріз масиву з кінця, щоб залишити лише два символи (що важливо для місяців із двоцифровим номером).

`MONTHS_LIST = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],`

`month_index = ('0' + (MONTHS_LIST.indexOf(split[0]) + 1)).slice(-2),`

  • Формуємо з отриманих значень рядок у форматі ISO звичайним додаванням рядків у відповідному порядку.

`ISOdate = split[3] + '-' + month_index + '-' + split[1] + 'T' + split[2],`

Дані в отриманому форматі – кількість секунд з 1970 року до якогось моменту у майбутньому. Використовувати дані в отриманому форматі в тригерах практично неможливо, тому що Zabbix дозволяє оперувати лише макросами {Дата} и {Time}, які повертають дату та час у зрозумілому для користувача форматі.

  • Після цього ми можемо отримати в JavaScript поточну дату у форматі Unix Timestamp і відняти її від отриманого значення дати закінчення сертифіката, щоб отримати кількість мілісекунд з поточного моменту до закінчення сертифіката.

`now = Date.now();`

  • Ділимо отримане значення на тисячу, щоб отримати секунди в Zabbix.

`return parseInt((Date.parse(ISOdate) - now) / 1000);`

У тригері можна вказати вираз 'last' та набір цифр, який відповідає кількості секунд у періоді, на який необхідно відреагувати, наприклад, у тижнях. Таким чином, тригер сповіщатиме про те, що термін дії сертифіката закінчується за тиждень.

ПРИМІТКА. Зверніть увагу на використання parseInt () у функції повертати, щоб сконвертувати дробове число, отримане в результаті поділу мілісекунд, ціле число. Також можна використовувати parseFloat() та зберігати дробові дані.

Дивитись доповідь

Джерело: habr.com

Додати коментар або відгук