Кэширование метрик в Zabbix

25 декабря 2018

  • Насколько нам известно, Zabbix-сервер не умеет запрашивать у Zabbix-агента несколько метрик одновременно. Один запрос = одна метрика.
  • Если значения метрик получаются от внешней программы, то для получения каждой из них выполняется отдельный вызов этой программы.
  • Типичный пример такого подхода:
  • UserParameter=mysql.uptime,mysqladmin      -umysqluser -pmysqlpasswd status | cut -f2 -d":" | cut -f1 -d"T" | tr -d " "
    UserParameter=mysql.threads,mysqladmin     -umysqluser -pmysqlpasswd status | cut -f3 -d":" | cut -f1 -d"Q" | tr -d " "
    UserParameter=mysql.questions,mysqladmin   -umysqluser -pmysqlpasswd status | cut -f4 -d":" | cut -f1 -d"S" | tr -d " "
    UserParameter=mysql.slowqueries,mysqladmin -umysqluser -pmysqlpasswd status | cut -f5 -d":" | cut -f1 -d"O" | tr -d " "
    UserParameter=mysql.qps,mysqladmin         -umysqluser -pmysqlpasswd status | cut -f9 -d":" | tr -d " "
    
  • Если каждая из этих метрик обновляется хотя бы раз в минуту, то получается, что «mysqladmin status» будет вызываться каждые 10 секунд.
  • Увеличение количества сервисов, метрик и частоты опроса нередко приводит к тому, что Zabbix-агент является главным пожирателем ресурсов на системе, за которой предназначен следить.

У нас возникло логичное желание:

  • кэшировать в агенте результаты вывода громоздких внешних команд;
  • при запросе метрики читать значение из кэша;
  • сделать кэширование не только производительным, но и предельно простым для использования.

Детали реализации:

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

Что требуется от сценария чтения метрик, использующего кэширование:

  • определить функцию Print_data, которая вызывает внешнюю команду, выводящую значения метрик;
  • подключить файл CACHE_HELPERS из текущего каталога;
  • прочесть нужное значение из кэш-файла «$CACHE», в котором сохранён вывод последнего вызова Print_data;
  • всё остальное происходит внутри CACHE_HELPERS.

На примере DNS-сервера Unbound:

  • /etc/zabbix/scripts/unbound.sh
  • #!/bin/sh
    
    which unbound-control >/dev/null || exit 1
    
    test $# = 1 || { echo "Usage: ${0##*/} metric"; exit 1; }
    
    Print_data() { unbound-control stats_noreset; }
    
    . "$(dirname "$0")/CACHE_HELPERS"
    
    awk -F= "/^$1=/ { print \$2; exit }" "$CACHE"
    
  • /etc/zabbix/zabbix_agentd.d/unbound.conf
  • UserParameter=dns.unbound[*],/etc/zabbix/scripts/unbound.sh $1
  • Не забудьте включить пользователя «zabbix» в группу «unbound», иначе unbound-control будет выдавать ошибку:
  • usermod -G "$(id -Gn zabbix | tr ' ' ,),unbound" zabbix

Замечания по установке Zabbix-агента:

  • В CentOS доступны два разных пакета zabbix-agent: более старый из EPEL и более новый из repo.zabbix.com
  • У этих пакетов не только разные версии, но и разные структуры каталогов.
  • Например, в заббиксовском пакете заранее подготовлен подкаталог /etc/zabbix/zabbix_agentd.d и ссылка на него из zabbix.conf Вроде бы мелочь, а приятно, когда кто-то заранее позаботился о том, чтобы в конфигурации не устраивали свалку.
  • По этим причинам мы всегда используем только сборку от Заббикса.
  • Текущей стабильной версией сейчас является 3.4 4.0:
    • yum install -y https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm
    • yum install -y zabbix-agent

Наш текущий вариант /etc/zabbix/scripts/CACHE_HELPERS:

#!/bin/sh -- : actually not needed, but enables syntax coloring in mcedit

: ${MAXAGE:=30}          # ..when to pre-update in foreground?
: ${MAXBGAGE:=$MAXAGE}   # ..when to post-update in background?

: ${CACHE:=${0##*/}}
FLOCK="/var/run/zabbix/${CACHE%.*}.flock"
CACHE="/var/run/zabbix/${CACHE%.*}.cache"
USRID="$(id -un)"

Update_cache() {
  (
    if flock -x -w 1 200; then
      #date "+%Y-%m-%d_%H:%M:%S  Update $CACHE ..."
      Print_data > "$CACHE.new" && mv "$CACHE.new" "$CACHE" || :
      test "$USRID" = "zabbix" || chown "zabbix:zabbix" "$CACHE" || :   # ..required when running by root!
    fi
  ) 200>"$FLOCK"
  test "$USRID" = "zabbix" || chown "zabbix:zabbix" "$FLOCK" || :
}

if test -s "$CACHE"; then
  t1="$(stat -c '%Y' "$CACHE")"
  t2="$(date +%s)"
  delta="$(expr "$t2" - "$t1")"
  if test "$delta" -ge "$MAXAGE"; then
    Update_cache
  elif test "$delta" -ge "$MAXBGAGE"; then
    ( sleep 1; Update_cache ) &
  fi
else
  Update_cache
fi


← Назад в Блог