воскресенье, 20 сентября 2020 г.

Исправление доступа к периодам обслуживания в Zabbix 3.4

В статье Исправление ручного закрытия проблем в Zabbix 3.4 я уже описывал, как дать права на ручное закрытие проблем пользователям, не имеющим прав редактирования узла. Похожая ситуация возникла и с редактированием периодов обслуживания. Для того, чтобы предоставить пользователю права редактировать периоды обслуживания, нужно дать ему права на редактирование самого узла. Пользователь, имеющий доступ к редактированию узла в Zabbix, может злоупотребить своими правами и просто снять с контроля узел на время его обслуживания. Ещё хуже, если этот пользователь по окончании обслуживания забудет вернуть узел на контроль.

Чтобы избежать подобных злоупотреблений, может быть лучше выдать права создавать и редактировать периоды обслуживания всем пользователям, имеющим права на просмотр соответствующей группы узлов. Именно такое изменение внёс в исходный код веб-интерфейса Zabbix мой коллега Кирилл. Изменил он два файла: frontends/php/include/classes/api/services/CMaintenance.php и frontends/php/maintenance.php. Первый файл исправляет права доступа в методах API maintenance, причём права удалять периоды обслуживания при помощи метода delete пользователю, не имеющему доступа к группе узлов, не предоставляется. Второй файл исправляет права доступа к периодам обслуживания непосредственно через сам веб-интерфейс Zabbix. Получившаяся заплатка выглядит следующим образом:
Index: zabbix-3.4.12-1+buster/frontends/php/include/classes/api/services/CMaintenance.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/include/classes/api/services/CMaintenance.php
+++ zabbix-3.4.12-1+buster/frontends/php/include/classes/api/services/CMaintenance.php
@@ -276,7 +276,6 @@ class CMaintenance extends CApiService {
                // hosts permissions
                $options = [
                        'hostids' => $hostids,
-                       'editable' => true,
                        'output' => ['hostid'],
                        'preservekeys' => true
                ];
@@ -289,7 +288,6 @@ class CMaintenance extends CApiService {
                // groups permissions
                $options = [
                        'groupids' => $groupids,
-                       'editable' => true,
                        'output' => ['groupid'],
                        'preservekeys' => true
                ];
@@ -458,7 +456,6 @@ class CMaintenance extends CApiService {
                        'selectGroups' => ['groupid'],
                        'selectHosts' => ['hostid'],
                        'selectTimeperiods' => API_OUTPUT_EXTEND,
-                       'editable' => true,
                        'preservekeys' => true
                ]);
 
@@ -580,7 +577,6 @@ class CMaintenance extends CApiService {
                        $db_hosts = API::Host()->get([
                                'output' => [],
                                'hostids' => $hostids,
-                               'editable' => true,
                                'preservekeys' => true
                        ]);
 
@@ -598,7 +594,6 @@ class CMaintenance extends CApiService {
                        $db_groups = API::HostGroup()->get([
                                'output' => [],
                                'groupids' => $groupids,
-                               'editable' => true,
                                'preservekeys' => true
                        ]);
 
Index: zabbix-3.4.12-1+buster/frontends/php/maintenance.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/maintenance.php
+++ zabbix-3.4.12-1+buster/frontends/php/maintenance.php
@@ -105,7 +105,6 @@ if (isset($_REQUEST['maintenanceid'])) {
        $dbMaintenance = API::Maintenance()->get([
                'output' => API_OUTPUT_EXTEND,
                'selectTimeperiods' => API_OUTPUT_EXTEND,
-               'editable' => true,
                'maintenanceids' => getRequest('maintenanceid'),
        ]);
        if (empty($dbMaintenance)) {
@@ -412,7 +411,6 @@ if (!empty($data['form'])) {
                        'maintenanceids' => $data['maintenanceid'],
                        'real_hosts' => true,
                        'output' => ['hostid'],
-                       'editable' => true
                ]);
                $data['hostids'] = zbx_objectValues($data['hostids'], 'hostid');
 
@@ -458,7 +456,6 @@ if (!empty($data['form'])) {
 
        // get groups
        $data['all_groups'] = API::HostGroup()->get([
-               'editable' => true,
                'output' => ['groupid', 'name'],
                'real_hosts' => true,
                'preservekeys' => true
@@ -475,7 +472,6 @@ if (!empty($data['form'])) {
        $data['hosts'] = API::Host()->get([
                'output' => ['hostid', 'name'],
                'real_hosts' => true,
-               'editable' => true,
                'groupids' => $data['twb_groupid']
        ]);
 
@@ -483,7 +479,6 @@ if (!empty($data['form'])) {
        $hostsSelected = API::Host()->get([
                'output' => ['hostid', 'name'],
                'real_hosts' => true,
-               'editable' => true,
                'hostids' => $data['hostids']
        ]);
        $data['hosts'] = array_merge($data['hosts'], $hostsSelected);
@@ -532,7 +527,6 @@ else {
                'search' => [
                        'name' => ($filter['name'] === '') ? null : $filter['name']
                ],
-               'editable' => true,
                'sortfield' => $sortField,
                'sortorder' => $sortOrder,
                'limit' => $config['search_limit'] + 1
Эту заплатку можно взять по ссылке zabbix3_4_12_permit_edit_maintenances.patch.

воскресенье, 13 сентября 2020 г.

Местонахождение устройства в панели проблем Zabbix 3.4

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

В Zabbix'е к каждому устройству можно прикрепить так называемые «инвентарные данные», среди которых есть поле адреса. Было бы неплохо показывать это поле в списке проблем, чтобы можно было без лишних телодвижений определить адрес устройства. К сожалению, Zabbix не предоставляет для этого штатных средств. Но к счастью, это можно сделать, внеся в исходный текст веб-интерфейса Zabbix небольшую правку.

Интересующий нас виджет находится в файле frontends/php/app/views/monitoring.widget.problems.view.php

Этот виджет фигурирует в списке маршрутов в файле frontends/php/include/classes/mvc/CRouter.php:
'widget.problems.view'  => ['CControllerWidgetProblemsView',    'layout.widget',                'monitoring.widget.problems.view'],
Класс CControllerWidgetProblemsView описан в файле frontends/php/app/controllers/CControllerWidgetProblemsView.php. Именно в этом классе готовятся данные, которые потом будут использованы в виджете для отображения. Данные об узлах, связанных с триггерами, в этом классе формируется при помощи функции getTriggersHostsList.

Определение функции getTriggersHostsList находится в файле frontends/php/include/triggers.inc.php, для получения списка узлов с триггерами используется метод API host.get:
$db_hosts = $hostids
    ? API::Host()->get([
        'output' => ['hostid', 'name', 'status', 'maintenanceid', 'maintenance_status', 'maintenance_type'],
        'selectGraphs' => API_OUTPUT_COUNT,
        'selectScreens' => API_OUTPUT_COUNT,
        'hostids' => array_keys($hostids),
        'preservekeys' => true
    ])
    : [];
Внесём правку, которая добавит в этот список строку местоположения устройства из его инвентарных данных:
Index: zabbix-3.4.12-1+buster/frontends/php/include/triggers.inc.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/include/triggers.inc.php
+++ zabbix-3.4.12-1+buster/frontends/php/include/triggers.inc.php
@@ -2170,6 +2170,7 @@ function getTriggersHostsList(array $tri
                        'output' => ['hostid', 'name', 'status', 'maintenanceid', 'maintenance_status', 'maintenance_type'],
                        'selectGraphs' => API_OUTPUT_COUNT,
                        'selectScreens' => API_OUTPUT_COUNT,
+                       'selectInventory' => ['location'],
                        'hostids' => array_keys($hostids),
                        'preservekeys' => true
                ])
Теперь эти данные нужно отобразить в виджете. Внесём соответствующую правку в файл frontends/php/app/views/monitoring.widget.problems.view.php:
Index: zabbix-3.4.12-1+buster/frontends/php/app/views/monitoring.widget.problems.view.php
===================================================================
--- zabbix-3.4.12-1+buster.orig/frontends/php/app/views/monitoring.widget.problems.view.php
+++ zabbix-3.4.12-1+buster/frontends/php/app/views/monitoring.widget.problems.view.php
@@ -54,6 +54,7 @@ $table = (new CTableInfo())
                $show_recovery_data ? _('Status') : null,
                _('Info'),
                ($data['sortfield'] === 'host') ? [_('Host'), $sort_div] : _('Host'),
+               ($data['sortfield'] === 'location') ? [_('Location'), $sort_div] : _('Location'),
                [
                        ($data['sortfield'] === 'problem') ? [_('Problem'), $sort_div] : _('Problem'),
                        ' • ',
@@ -198,11 +199,19 @@ foreach ($data['data']['problems'] as $e
                ];
        }
 
+       $trigger_hosts = array_values($data['data']['triggers_hosts'][$trigger['triggerid']]);
+       $locations = array();
+       foreach($trigger_hosts as $host)
+       {
+               $locations[] = $host['inventory']['location'];
+       }
+
        $table->addRow(array_merge($row, [
                $show_recovery_data ? $cell_r_clock : null,
                $show_recovery_data ? $cell_status : null,
                makeInformationList($info_icons),
                $triggers_hosts[$trigger['triggerid']],
+               join(', ', $locations),
                $description,
                (new CCol(
                        ($problem['r_eventid'] != 0)
Как видно, в правке:
  1. в таблицу был добавлен заголовок новой колонки Location,
  2. по каждому из триггеров формируется строка со списком адресов узлов, на значения элементов данных из которых опирается этот триггер,
  3. строки с адресами через запятую с пробелом склеиваются в одну строку,
  4. полученная строка добавляется в строку таблицы, в колонку Location.
Готовую заплатку можно взять по ссылке zabbix3_4_12_frontend_location.patch.

воскресенье, 6 сентября 2020 г.

Оптимизация Linux при использовании SSD

В прошлых статьях были описаны процедура обновления прошивки твердотельного накопителя Micron модели SSD 5200 MAX и шаблон Zabbix для контроля основных показателей его состояния. В этой статье пойдёт речь о том, какие дополнительные настройки Linux можно сделать для того, чтобы увеличить производительность системы при работе с твердотельными накопителями и увеличить срок службы самих накопителей.

Изменение планировщика ввода-вывода

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

Изменить планировщик диска sda можно при помощи следующей команды:
# echo "deadline" > /sys/block/sda/queue/scheduler
Для того, чтобы выбранный планировщик диска применялся при загрузке системы, можно поставить пакет sysfsutils:
Оптимизация Linux при использовании SSD
# apt-get install sysfsutils
И прописать планировщик в файл /etc/sysfs.conf:
block/sda/queue/scheduler = deadline
Другой способ сделать изменения постоянными - создать файл /etc/udev/rules.d/60-ssd.rules со следующими правилами udev:
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="deadline"
Это правило для всех не вращающихся дисков с именем sd* будет устанавливать планировщик deadline.

Размер страницы

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

Например, по данным SMART размер логического сектора диска равен 512 байтам, а размер страницы равен 4096 байт:
# smartctl -i /dev/sda | grep Sector
Sector Sizes:     512 bytes logical, 4096 bytes physical
Убедиться в том, что ядро операционной системы Linux знает о размере физического сектора, можно следующим образом:
# cat /sys/block/sda/queue/physical_block_size 
4096

Увеличение резерва страниц

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

Часть общего объёма страниц диска закладывается в резерв. Напрмер, SSD объёмом 480 Гигабайт может иметь реальный объём 512 Гигабайт, а разница используется как раз для равномерного использования ресурса всех страниц.

Кроме того, в файловой системе может иметься свободное место, не занятое никакими данными. Это свободное место на SSD можно приобщить к резерву. Для этого операционная система может сообщать диску о неиспользуемых ею страницах при помощи ATA-команды TRIM. Для этого SSD должен поддерживать операцию TRIM, а файловая система должна поддерживать опцию монитрования discard.

Проверить наличие поддержки TRIM в SSD можно при помощи утилиты hdparm:
# hdparm -I /dev/sda | grep TRIM
           * Data Set Management TRIM supported (limit 8 blocks)
           * Deterministic read ZEROs after TRIM
Вторая строчка означает, что секторы, над которыми произведена команда TRIM, при попытке чтения будут возвращать нули. Другим возможным режимом может быть «Deterministic read after TRIM», когда при чтении возвращаются не нули, а какая-то другая всегда одинаковая последовательность данных.

Если на странице руководства man mount среди опций интересующей файловой системы имеется опция discard, то файловую систему можно перемонтировать с поддержкой этой опции.

Сначала посмотрим, с какими опциями смонтирована файловая система:
# findmnt /
TARGET SOURCE   FSTYPE OPTIONS
/      /dev/md0 ext4   rw,relatime,errors=remount-ro,data=ordered
Перемонтируем файловую систему, добавив к списку опций remount и discard:
# mount -o remount,rw,relatime,errors=remount-ro,data=ordered,discard /
Убеждаемся, что новая опция добавилась к текущему списку:
# findmnt /
TARGET SOURCE   FSTYPE OPTIONS
/      /dev/md0 ext4   rw,relatime,discard,errors=remount-ro,data=ordered
Чтобы отключить опцию discard, можно повторить процедуру перемонтирования, указав вместо опции discard опцию nodiscard.

Чтобы при перезагрузке операционная система монтировала файловую систему с опцией discard, нужно добавить её к списку опций монитрования в файле /etc/fstab. Например, строчка монтирования может выглядеть следующим образом:
UUID=324f1a70-5229-4376-afbb-eb274c8e60aa /               ext4    errors=remount-ro,discard 0       1
Чтобы сообщить диску о неиспользуемых секторах, которые были освобождены до включения опции discard, или при отключенной опции discard, можно воспользоваться командой fstrim:
# fstrim -v /
/: 146,6 MiB (153755648 bytes) trimmed
Кроме увеличения ресурса диска, использование TRIM и discard может приводить к увеличению скорости операций записи и чтения. Т.к. у контроллера есть в распоряжении много очищенных блоков, ему не придётся тратить время на их очистку для записи новых данных. При этом операция очистки блока может выполняться в фоновом режиме, когда SSD не занят выполнением операций чтения или записи.

Если на пути между файловой системой и диском имеются менеджер томов LVM или RAID-массив, то информация о неиспользуемых секторах может застревать в этих подсистемах и не доходить до SSD. Чтобы LVM сообщал о неиспользуемых секторах на нижележащий уровень, нужно в секции devices из файла конфигурации /etc/lvm/lvm.conf выставить следующую опцию:
issue_discards = 1
Убедиться в том, что TRIM корректно передаётся нижележащему хранилищу, можно при помощи команды:
# lsblk -D
Если TRIM поддерживается на всех уровнях, то в столбцах DISC-GRAN и DISC-MAX будут ненулевые значения:
NAME    DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
sda            0        4K       2G         0
└─sda1         0        4K       2G         0
  └─md0        0        4K       2G         0
sdb            0        4K       2G         0
└─sdb1         0        4K       2G         0
  └─md0        0        4K       2G         0
Если же TRIM не используется, то можно увидеть такую картину:
NAME                     DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
sda                             0        0B       0B         0
├─sda1                          0        0B       0B         0
│ └─md0                         0        0B       0B         0
└─sda2                          0        0B       0B         0
  └─md1                         0        0B       0B         0
    ├─vg0-mon--disk             0        0B       0B         0
    └─vg0-mon--swap             0        0B       0B         0
sdb                             0        0B       0B         0
├─sdb1                          0        0B       0B         0
│ └─md0                         0        0B       0B         0
└─sdb2                          0        0B       0B         0
  └─md1                         0        0B       0B         0
    ├─vg0-mon--disk             0        0B       0B         0
    └─vg0-mon--swap             0        0B       0B         0
Возможны промежуточные варианты, когда файловая система отправляет операции TRIM на нижележащий уровень, но дальше эти операции не проходят.

Файловые системы в оперативной памяти

Т.к. интенсивный ввод-вывод снижает ресурс SSD, лучше избегать использовать SSD для хранения временных файлов. Например, раздел /tmp можно расположить в оперативной памяти. Временно это можно сделать при помощи такой команды:
# mount -t tmpfs tmpfs -o relatime,nodev,nosuid,noexec,mode=1777 /tmp
Если нужно ограничить максимальный размер файлов во временной файловой системе, к опциям noatime и nosuid можно добавить опцию size с указанием этого размера:
# mount -t tmpfs tmpfs -o relatime,nodev,nosuid,noexec,mode=1777,size=1G /tmp
Если временную файловую систему нужно монтировать автоматически при загрузке системы, нужно добавить в файл /etc/fstab соответствующую строчку:
tmpfs /tmp tmpfs relatime,nodev,nosuid,noexec,mode=1777,size=1G 0 0
Если файловая система уже не смонтирована, то теперь смонтировать её можно простой командоу:
# mount /tmp
Аналогичным образом можно монтировать другие временные файловые системы. Например:
tmpfs /var/tftp tmpfs relatime,nodev,nosuid,noexec,uid=tftp,gid=tftp,mode=0760,size=32M 0 0

Использованные материалы