UMGUM.COM (лучше) 

Zabbix + Bacula ( Мониторинг состояния компонентов и задач централизованной системы резервного копирования "Bacula". )

12 декабря 2018

OS: "Linux Debian 8/9 (Jessie/Stretch)", "Linux Ubuntu 16/18 (Xenial/Bionic) LTS".
Application: "Bacula Director v7", "Zabbix v3.4".

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

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

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

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

Наличие сервисов "Bacula Dir", "Bacula SD" & "Bacula DB" (item/trigger, every 30sec);
Статус задач (item, every 5min);
Длительность исполнения задач (item/graph, every 2hour);
Объём загруженных при исполнении задач данных (item/graph, every 2hour);
Количество загруженных при исполнении задач файлов (item/graph, every 2hour);
Уведомление о неудачном завершении задач (trigger);
Уведомление об активности задач в данный момент (trigger);
Уведомление о длительном отсутствии данных о статусе задач (trigger, 6hour);
Уведомление о длительном перерыве в исполнении задач (trigger, 15 day).


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

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

# aptitude install zabbix-agent bconsole jq

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


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

# sudo -u zabbix -s zabbix_get -s bacula.example.net -k "proc.num[bacula-dir]"

Настраиваем аутентификацию между компонентами.

Прежде чем приступать к выстраиванию логики сбора данных, обеспечим к таковым ограниченный доступ (только "на чтение", и не ко всем подсистемам) агенту мониторинга "Zabbix".

В конфигурации "Bacula Director" заведём отдельную точку входа со своими параметрами аутентификации для утилиты "bconsole", через которую к "Bacula" будет подключатся "Zabbix Agent":

# vi /etc/bacula/bacula-dir.conf

....
# # Блок описания подсистем управления и мониторинга:

Console {
  Name = "zabbix-agent.local"
  Password = "zabbixConsolePassword"

  # Разрешаем исполнение только перечисленных команд
  Command ACL = show,list,llist,quit

  # Явно разрешаем получать данные о любых задачах и обращаться к любым каталогам хранения метаданных
  Job ACL = *all*
  Catalog ACL = *all*
}
....

Проверяем корректность конфигурации средствами самого "Bacula" и применяем таковую:

# bacula-dir -c /etc/bacula/bacula-dir.conf -t
# /etc/init.d/bacula-director reload

Мне представляется естественным, что клиентский конфигурационный файл утилиты "bconsole" должен быть расположен поближе к источнику запросов - в директории настроек "Zabbix Agent"-а:

# vi /etc/zabbix/bconsole.conf && chown zabix:zabbix /etc/zabbix/bconsole.conf && chmod o-rwx /etc/zabbix/bconsole.conf

#
# Bacula Console Configuration File, intended for Zabbix-Agent
#

# Указываем на сервис "Director Daemon", к которому нужно подключится:
Director {
   Name = bacula.example.net
   Address = bacula.example.net
   DIRport = 9101

   # (несмотря на то, что в действительности аутентификация осуществляется в контексте блока "Console", в блоке описания "Director" тоже требуется указание параметра "Password" - он не должен ничему соответствовать, просто необходимо присутствие его определения)
   Password = "XXXXXXXXXXX"
}

# Параметры аутентификации клиента при подключении к "Director Daemon":
Console {
   Name = zabbix-agent.local
   Password = "zabbixConsolePassword"
   Director = bacula.example.net
}

Проверяем корректность конфигурации средствами самого CLI-агента "Bacula":

# bconsole -c /etc/zabbix/bconsole.conf -t

Есть смысл сразу удостовериться, действуют ли ограничения - уж не буду здесь о методике.

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

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

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

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

UserParameter=bacula.jobs.discovery, /etc/zabbix/scripts/bacula_discovery.sh
UserParameter=bacula.jobs.check[*], /etc/zabbix/scripts/bacula_check_job.sh $1 $2 $3 $4

Подстраховываясь, закрываем к настройкам и скриптам "Zabbix" доступ посторонним:

# chown -R zabbix:zabbix /etc/zabbix
# chmod o-rwx /etc/zabbix

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

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

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

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

# cd /etc/zabbix/scripts
# vi ./bacula_discovery.sh && chown zabix:zabbix ./bacula_discovery.sh && chmod ug+x ./bacula_discovery.sh

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

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

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

# Формируем список имён активных заданий "Bacula"
JOBS=$(echo -e "show job\n.\nquit" | ${BCON} | grep -i -E "^job:.*enabled[ \t]*=[ \t]*1" | awk '{print $2}' | grep -i -E "^name[ \t]*=[ \t]*.*$" | awk -F "=" '{print $2}')

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

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

  # Перебираем имена заданий
  for JOB in ${JOBS} ; do

    # Пропускаем обработку некоторых задач (заготовку для "восстановления", например)
    [ $(echo ${JOB} | grep "restore") ] && continue

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

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

  done

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

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

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

exit ${?}

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

{
  "data":[
    {"{#JOB}":"client0.example.net"},
    {"{#JOB}":"clientX.example.net"}
  ]
}

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

# sudo -u zabbix -s zabbix_get -s bacula.example.net -k "bacula.jobs.discovery"

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

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

# cd /etc/zabbix/scripts
# vi ./bacula_check_job.sh && chown zabix:zabbix ./bacula_check_job.sh && chmod ug+x ./bacula_check_job.sh

#!/bin/bash
# usage: ./bacula_check_job.sh "jobName" "jobParam" {jobLevel}

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

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

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

# Запрашиваем упрощённую статистику задания, опираясь на его имя
JOBLINE=$(echo -e "list job=${JINAME}\n.\nquit" | ${BCON})

# Первым отрабатываем самые простые запросы о статусах текущего (последнего в списке) задания
if [ "${JIPARAM}" == "jobstatus" ] ; then
  ANSWER=$(echo -e "${JOBLINE}" | grep '^|' | tail -n1 | awk -F "|" '{print $9}' | sed -e 's/[ ]*//g')

elif [ "${JIPARAM}" == "lastexecution" ] ; then
  STARTTIME=$(echo -e "${JOBLINE}" | grep '^|' | tail -n1 | awk -F "|" '{print $4}' | xargs -I{} date -d "{}" +%s)
  let "ANSWER = $(date +%s) - STARTTIME"

# (для дальнейшей выгрузки статистики по отработанным задачам необходимо указание их уровня)
elif [ ! -z "${JILEVEL}" ] ; then

  # Для получения статистики выясняем идентификатор последнего успешно завершённого задания
  JOBID=$(echo -e "list job=${JINAME}\n.\nquit" | ${BCON} | grep '^|' | grep -i -E ".*\|[ \t]*B[ \t]*\|[ \t]*${JILEVEL}[ \t]*\|.*\|[ \t]*T[ \t]*\|[ \t]*$" | tail -n1 | awk -F "|" '{print $2}' | sed -e 's/[ \t]*//g' | sed -e 's/,//g')
  if [ ! -z "${JOBID}" ] ; then

    # Запрашиваем расширенную статистику задания, опираясь на его идентификатор
    JOBLONG=$(echo -e "llist jobid=${JOBID}\n.\nquit" | ${BCON})

    # Формируем запросы и обрабатываем ответы с вычленением значений запрашиваемых параметров
    if [ ! -z "${JOBLONG}" ] ; then
      case "${JIPARAM}" in
      "jobbytes")
        ANSWER=$(echo -e "${JOBLONG}" | grep -i -E "^[ \t]*jobbytes:" | awk -F ":" '{print $2}' | sed -e 's/[ \t]*//g' | sed -e 's/,//g')
      ;;
      "jobfiles")
        ANSWER=$(echo -e "${JOBLONG}" | grep -i -E "^[ \t]*jobfiles:" | awk -F ":" '{print $2}' | sed -e 's/[ \t]*//g' | sed -e 's/,//g')
      ;;
      "duration")
        STARTTIME=$(echo -e "${JOBLONG}" | grep -i -E "^[ \t]*starttime:" | awk -F "starttime:" '{print $2}' | xargs -I{} date -d "{}" +%s)
        ENDTIME=$(echo -e "${JOBLONG}" | grep -i -E "^[ \t]*endtime:" | awk -F "endtime:" '{print $2}' | xargs -I{} date -d "{}" +%s)
        let "ANSWER = ENDTIME - STARTTIME"
      ;;
      *) echo "${DATE}: Запрос: \"$(basename $0) $@\". Некорректно указан запрашиваемый параметр задания резервного копирования. Процедура запроса статуса задания резервного копирования прервана." >> ${LOG}; exit 1; ;;
      esac

    else
      echo "${DATE}: Запрос: \"$(basename $0) $@\". Не получен ответ на запрос идентификатора и расширенного описания задания от подсистемы резервного копирования. Процедура запроса статуса задания резервного копирования прервана." >> ${LOG}; exit 1;
    fi
  fi

else
  echo "${DATE}: Запрос: \"$(basename $0) $@\". Не указан уровень задания резервного копирования. Процедура запроса статуса задания резервного копирования прервана." >> ${LOG}; exit 1;
fi

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

exit ${?}

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

# sudo -u zabbix -s zabbix_get -s bacula.example.net -k "bacula.jobs.check[client0.example.net,jobbytes,F]"

Оптимизация оборота файлов журналов событий.

Наши самодельные скрипты пишут в свой файл журнала событий, а кроме того конфигурационный файл "Logrotate" для "Zabbix Agent" несовременный - заменяем его на свой, всеобъемлющий:

# vi /etc/logrotate.d/zabbix-agent

/var/log/zabbix-agent/*.log {
  weekly
  rotate 7
  compress
  delaycompress
  missingok
  notifempty
  copytruncate
  su zabbix zabbix
}

Проверяем корректность конфигурации "Logrotate":

# logrotate -d /etc/logrotate.d/zabbix-agent


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


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