UMGUM.COM 

Zabbix + LDAP 389-DS ( Мониторинг состояния компонентов LDAP-сервиса "389 Directory Server". )

16 декабря 2019  (обновлено 15 марта 2020)

OS: "Linux Debian 8/9 (Jessie/Stretch)", "Linux Ubuntu 16/18 (Xenial/Bionic) LTS".
Application: "LDAP 389-AS/DS v1.3", "Zabbix v3/4".

Задача: наладить посредством системы мониторинга "Zabbix" отслеживание текущего состояния LDAP-сервиса "389 Directory Server", хранения статистики и уведомления о недоступности критически важных компонентов.

Общий принцип действия выработанного решения таков:

1. Раз в час "Zabbix" обращается за списком актуальных объектов мониторинга (в данном случае репликационных связей) к "Zabbix Agent"-у на стороне сервера "389-AS/DS", ожидая его в JSON-массиве.
2. Для полученного перечня объектов мониторинга сервером "Zabbix", в соответствии с заготовками в специализированном шаблоне, по спецификации "Low-Level Discovery (LLD)" создаются необходимые элементы.
3. Практически все запросы обрабатываются запускаемыми "Zabbix Agent"-ом самодельными скриптами, извлекающими данные через CLI-утилиту "ldapsearch".

Получившееся полностью автоматизированное решение отслеживает состояние локального LDAP-сервера и репликационных связей с другими LDAP-серверами по следующим позициям:

Наличие процесса сервиса "389-DS" (item/trigger, every 30sec);
Наличие открытого порта TCP:636 (item/trigger, every 30sec);
Доступность LDAP-сервиса для клиентских подключений (trigger, every 120sec);
Статистика количества текущих клиентских соединений (item/graph, every 60sec);
Статистика запрошенных клиентом и выполненных сервером операций (item/graph, every 60sec);
Статус каждого соединения репликации по отдельности (item/trigger, every 5min);
Задержки между репликациями для каждого соединения (item/graph, every 5min);
Информирование о длительном периоде без репликации (trigger, every 5hour).


Предварительная подготовка.

На стороне LDAP-сервера должен быть установлен "Zabbix Agent" и CLI-утилиты для обращения к LDAP, а также утилита проверки синтаксиса JSON:

# aptitude install zabbix-agent ldap-utils jq

Перед всеми дальнейшими работами потребуется применить заранее подготовленный мною специализированный шаблон для системы мониторинга:


Очень желательно сразу проверить возможность прохождения запроса от сервера мониторинга "Zabbix" к запущенному на стороне LDAP-сервера "Zabbix Agent"-у:

# sudo -u zabbix -s zabbix_get -s ldap0.example.net -k "proc.num[ns-slapd]"

Настраиваем "Zabbix Agent".

Учитывая то, что процедуры сбора данных и отправки их на сервер мониторинга осуществляется клиентом "Zabbix", то конфигурационные файл и скрипты расположим в его директории:

# mkdir -p /etc/zabbix/scripts
# mkdir -p /etc/zabbix/zabbix_agents.conf.d

Определяем параметры "Zabbix-Agent"-а, запросы к которым будут обслуживаться внешними приложениями:

# vi /etc/zabbix/zabbix_agentd.conf.d/ldap389.conf

UserParameter=ldap389.discovery, /etc/zabbix/scripts/ldap389_discovery.sh
UserParameter=ldap389.stat[*], /etc/zabbix/scripts/ldap389_stat.sh $1 $2 $3 $4

Установленной по умолчанию двухсекундной задержки при ожидании ответа от "Zabbix-Agent"-а на практике недостаточно - продлеваем до пяти секунд:

# vi /etc/zabbix/zabbix_agentd.conf

....
### Option: Timeout
Timeout=5
....

Для применения изменений в конфигурации "Zabbix Agent" необходимо перезапустить:

# /etc/init.d/zabbix-agent restart

Настраиваем "389-AS/DS".

Создаём пароль, с которым "Zabbix Agent" будет подключаться к LDAP, и сохраняем его рядом с использующим его скриптами, в отдельном текстовом файле:

# vi /etc/zabbix/scripts/.389-ds

Закрываем посторонним доступ к файлу с паролем:

# chown zabbix:zabbix /etc/zabbix/scripts/.389-ds
# chmod go-rwx /etc/zabbix/scripts/.389-ds

Создаём пользователя, предназначенного для подключения системы мониторинга:

$ ldapmodify -h 127.0.0.1 -D "cn=Directory Manager" -W

dn: cn=Zabbix Monitor,cn=config
changetype: add
cn: Zabbix Monitor
objectClass: inetorgperson
objectClass: person
objectClass: top
sn: Zabbix Monitoring System Bind DN Entry
userPassword: ***
passwordExpirationTime: 20380119031407Z
passwordGraceUserTime: 0
nsIdleTimeout: 0

^d

Наделяем пользователя минимально необходимыми правами только чтения записей со статистикой и сведениями о состоянии репликационных связей:

$ ldapmodify -h 127.0.0.1 -D "cn=Directory Manager" -W

dn: cn=monitor
changetype: modify
add: aci
aci: (targetattr = "*") (version 3.0; acl "Allow ROnly for Zabbix Monitoring System";
  allow (read,compare,search) (userdn = "ldap:///cn=Zabbix Monitor,cn=config");)

dn: cn=mapping tree,cn=config
changetype: modify
add: aci
aci: (targetattr = "*") (version 3.0; acl "Allow ROnly for Zabbix Monitoring System";
  allow (read,compare,search) (userdn = "ldap:///cn=Zabbix Monitor,cn=config");)

^d

Есть смысл сразу проверить возможность подключения нашего специализированного пользователя к объекту мониторинга:

$ ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w `head -n1 /etc/zabbix/scripts/.389-ds` -b "cn=monitor" -s sub "(objectClass=*)"

$ ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w `head -n1 /etc/zabbix/scripts/.389-ds` -b "cn=mapping tree,cn=config" -s sub "(objectClass=nsDS5ReplicationAgreement)"

Пишем и тестируем скрипт "Zabbix Auto Discovery (LLD)".

Подготовим простейший Bash-скрипт, по запросу подключающийся к "389-DS", запрашивающий перечень объектов мониторинга и формирующий соответствующий требования "Zabbix Auto Discovery" JSON-массив:

# cd /etc/zabbix/scripts
# vi ./ldap389_discovery.sh

#!/bin/bash
# usage: ./ldap389_discovery.sh

# Задаём переменные рабочего окружения
LOG="/var/log/zabbix-agent/zabbix-ldap389-error.log"
DATE=$(date +"%Y-%m-%d.%H:%M:%S")

# Проверяем наличие ожидаемых утилит
[ -x "$(command -v ldapsearch)" ] && [ -x "$(command -v jq)" ] || { echo "${DATE}: Не обнаружены необходимые для работы утилиты. Процедура создания списка активных связей реплицирования прервана." >> ${LOG}; exit 1; }

# Запрашиваем у LDAP-сервиса перечень его связей реплицирования
REPLS=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w `head -n1 /etc/zabbix/scripts/.389-ds` -b "cn=mapping tree,cn=config" -s sub "(objectClass=nsDS5ReplicationAgreement)" dn | awk 'match($0, /^dn:\s*cn=(.*),cn=replica,/, result) {print result[1];}')

# Если перечень связей реплицирования не пуст, то формируем JSON для "Zabbix Low-Level Discovery (LLD)"
if [ ! -z "${REPLS}" ] ; then

  # Задаём начало JSON
  JSONZLLD="{\"data\":["
  FIRST=1

  # Перебираем блоки конфигурации
  for REPLICA in ${REPLS} ; do

    # Проставляем разделитель между элементами JSON
    if [ ${FIRST} == 0 ] ; then
      JSONZLLD=${JSONZLLD}","
    fi
    FIRST=0

    # Вводим имя задания в качестве элмента JSON
    JSONZLLD=${JSONZLLD}"{\"{#REPLICA}\":\"${REPLICA}\"}"
  done

  # Завершаем JSON
  JSONZLLD=${JSONZLLD}"]}"

  # Проверяем синтаксическую корректность JSON
  if jq -e . 1>/dev/null 2>&1 <<< "${JSONZLLD}" ; then

    # Отдаём JSON на STDOUT
    echo ${JSONZLLD}
  fi
fi

exit ${?}

Переводим файл во владение "Zabbix Agent"-а и делаем его исполняемым:

# chown zabbix:zabbix ./ldap389_discovery.sh && chmod ug+x,o-rwx ./ldap389_discovery.sh

Пример получаемого в ответ на запрос JSON-файла для "Zabbix LLD", с обнаруженными активными объектами мониторинга:

{
  "data":[
    {"{#REPLICA}":"ldap1.example.net"},
    {"{#REPLICA}":"ldap2.example.net"}
  ]
}

Проверяем корректность отрабатывания скрипта "Auto Discovery (LLD)":

# sudo -u zabbix -s zabbix_get -s ldap0.example.net -k "ldap389.discovery"

Пишем и тестируем скрипт получения запрашиваемых параметров.

Создаём специализированный Bash-скрипт, принимающий от "Zabbix Agent"-а запросы на получение данных по ряду интересующих нас параметров, обращающийся посредством CLI-утилиты "ldapsearch" к серверу "389-DS" и нормализующий их перед выдачей:

# cd /etc/zabbix/scripts
# vi ./ldap389_stat.sh

#!/bin/bash
# usage: ./ldap389_stat.sh "targetObjectName" "objectParam" {Level}

# Задаём переменные рабочего окружения
LOG="/var/log/zabbix-agent/zabbix-ldap389-error.log"
PSWD=$(head -n1 /etc/zabbix/scripts/.389-ds)
DATE=$(date +"%Y-%m-%d.%H:%M:%S")
unset ANSWER

# Проверяем наличие ожидаемых утилит
[ -x "$(command -v ldapsearch)" ] || { echo "${DATE}: Не обнаружены необходимые для работы утилиты. Процедура запроса статистики прервана." >> ${LOG}; exit 1; }

# Проверяем корректность вводимых данных и выводим подсказку в журнал событий при необходимости
OITARGET="${1}"
OIPARAM="${2}"
#
[ ! "${OITARGET}" ] && { echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан субъект. Процедура запроса статуса прервана." >> ${LOG}; exit 1; }
[ ! "${OIPARAM}" ] && { echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан запрашиваемый параметр. Процедура запроса статуса прервана." >> ${LOG}; exit 1; }

# В зависимости от целевого объекта применяем разные подходы
if [[ "${OITARGET}" == "local" ]] ; then

  # Запрашиваем глобальную статистику сервера
  if [ "${OIPARAM}" == "currentconnections" ] ; then

    # Выдаём сведения о количестве клиентских подключений в данный момент
    ANSWER=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=monitor" -s base "(objectClass=*)" currentconnections | grep -i currentconnections | awk '{print $2}')
  elif [ "${OIPARAM}" == "opsinitiated" ] ; then

    # Выдаём сведения о количестве инициированных клиентами операций
    ANSWER=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=monitor" -s base "(objectClass=*)" opsinitiated | grep -i opsinitiated | awk '{print $2}')
  elif [ "${OIPARAM}" == "opscompleted" ] ; then

    # Выдаём сведения о количестве выполненных сервером операций
    ANSWER=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=monitor" -s base "(objectClass=*)" opscompleted | grep -i opscompleted | awk '{print $2}')
  fi

else

  # Проверяем наличие запрашиваемого набора параметров
  RCHECK=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=mapping tree,cn=config" -s sub "(&(objectClass=nsDS5ReplicationAgreement)(cn=${OITARGET}))" dn)
  if [ ! -z "${RCHECK}" ] ; then

    # Запрашиваем статистику связи репликации
    if [ "${OIPARAM}" == "lastupdatestart" ] ; then

      # Выдаём дату и время в удобном для восприятия человеком формате
      ANSWER=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=mapping tree,cn=config" -s sub "(&(objectClass=nsDS5ReplicationAgreement)(cn=${OITARGET}))" nsds5replicaLastUpdateSTart | grep -i nsds5replicaLastUpdateStart | awk '{print $2}' | sed -E "s/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})Z/\1-\2-\3 \4:\5:\6Z/" | date '+%Y-%m-%d %H:%M:%S' -f- 2>/dev/null)
    elif [ "${OIPARAM}" == "timesincelastupdate" ] ; then

      # Выдаём временную задержку от последней репликации до текущего момента (в секундах)
      STARTTIME=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=mapping tree,cn=config" -s sub "(&(objectClass=nsDS5ReplicationAgreement)(cn=${OITARGET}))" nsds5replicaLastUpdateStart | grep -i nsds5replicaLastUpdateStart | awk '{print $2}' | sed -E "s/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})Z/\1-\2-\3 \4:\5:\6Z/" | date +%s -f- 2>/dev/null)
      let "ANSWER = $(date +%s) - STARTTIME"
    elif [ "${OIPARAM}" == "lastupdatestatus" ] ; then

      # Выдаём преобразованный в удобрый для "Zabbix" формат статус последней репликации
      # (https://directory.fedoraproject.org/docs/389ds/FAQ/replication-update-status.html)
      RSTATUS=$(ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=mapping tree,cn=config" -s sub "(&(objectClass=nsDS5ReplicationAgreement)(cn=${OITARGET}))" nsds5replicaLastUpdateStatus | grep -i nsds5replicaLastUpdateStatus | grep -i "Error\s(0)\s\|Error\s(1)\s")
      if [ ! -z "${RSTATUS}" ] ; then
        ANSWER="1"
      else
        ANSWER="0"

        # Для понимания, что пошло не так, сохраняем в журнале неположительный статус репликации
        ldapsearch -x -LLL -h 127.0.0.1 -D "cn=Zabbix Monitor,cn=config" -w ${PSWD} -b "cn=mapping tree,cn=config" -s sub "(&(objectClass=nsDS5ReplicationAgreement)(cn=${OITARGET}))" nsds5replicaLastUpdateStatus | grep -i nsds5replicaLastUpdateStatus >> "${LOG}"
      fi
    fi
  fi
fi

# Отдаём значение запрашиваемого параметра (ничего не отвечаем, если параметр не поддерживается)
# (важно отдавать значение не заключая его в кавычки, иначе нулевое значение до "Zabbix" не доходит)
[ ! -z "${ANSWER}" ] && echo ${ANSWER}

exit ${?}

Переводим файл во владение "Zabbix Agent"-а и делаем его исполняемым:

# chown zabbix:zabbix ./ldap389_stat.sh && chmod ug+x,o-rwx ./ldap389_stat.sh

Аналогично предыдущим этапам проверяем корректность прохождения запросов к "389-DS" от сервера мониторинга "Zabbix":

# sudo -u zabbix -s zabbix_get -s ldap0.example.net -k "ldap389.stat[ldap0.example.net,lastupdatestatus]"

Эксплуатация.

Как легко понять из вышеприведённых скриптов механизм актуализации перечня объектов мониторинга происходит полностью автоматически максимум спустя один час после изменения конфигурации LDAP-сервера "389-DS" - это удобно. Каких-то особых замечаний к схеме нет и она легко дополняется желаемыми параметрами.


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )