Для тех кто использует Zabbix, и хочет научится делать свои шаблоны и мониторить не стандартные системы (которых еще нет в Zabbix), а также,
кому нужен расширенный мониторинг S.M.A.R.T., и кого не устроили уже существующие шаблоны, прошу под кат.
Все началось с того, что уже существующий шаблон для S.M.A.R.T. меня не устроил. Он позволял смотреть довольно ограниченное число атрибутов, и наращивание его до приемлемого для меня уровня становилось накладным. Особенно из-за того, что он использовал простые поля в Zabbix Agent, и при увеличении их числа становилось как-то не по себе. Давайте глянем на одну строчку в конфиге, с запросом параметра (подобных там много):
UserParameter=uHDD[*], sudo smartctl -A /dev/$1| grep "$2"| tail -1| cut -c 88-|cut -f1 -d' '
Все хорошо, если у вас только этот параметр, ну или парочка, но если у вас их десять? И дисков к примеру десяток? На каждый такой параметр мы будем дергать smartctl (лишний раз подергивая диск)? Кроме того, каждый такой параметр, это отдельный запрос от Zabbix Server (ну или групповой запрос с параметрами подставляемыми вместо *). В такой ситуации, к сожалению решения нет, Zabbix Agent не поддерживает другой способ получения данных, но нам на помощь приходит Zabbix Trapper и утилита zabbix_sender, которые позволяют отправить целую пачку параметров.
Вот подготовкой данных для них мы и займемся.
Начнем с поиска устройств, которые вообще отдают нам S.M.A.R.T., для чего нам понадобится:
- Драйвер sg (modprobe sg), он позволяет кроме всего прочего, увидеть диски за рядом RAID контроллеров (в частности у меня Adaptec)
- Утилита sg_map, которая даст нам список устройств ассоциированных через драйвер sg
- И конечно smartctl
#!/bin/bash
# require: sg module and sg_map util
# Get know generic scsi device from sg_map or from /usr/local/etc/smartdev.lst (is prefered used),
# and then try to read some S.M.A.R.T. attribue, if success, echo output combination to SDTOUT
modprobe sg
RUNPATH=$(dirname $0)
DEV_TYPE=(sat scsi ata)
while read -r -a attr; do
if [ -z "${attr[1]}" ]; then
DEV=${attr[0]}
else
DEV=${attr[1]}
fi
for i in "${DEV_TYPE[@]}";do
smartctl -A -d $i $DEV | grep -q 'ID#'
if [[ $? == 0 ]]; then
DEV=$(basename $DEV)
grep -q $DEV /usr/local/etc/smartdev.lst
if [[ $? != 0 ]]; then
echo "$DEV $i"
fi
break
fi
done
done < <(sg_map)
cat /usr/local/etc/smartdev.lst
Он поищет для нас устройства (ищет утилитами и сверяет найденное с файлом /usr/local/etc/smartdev.lst, если совпадение найдено то в последствии используется значения из файла) и выдаст список в виде пар значений: <имя устройства> <тип подключения>
Дальше мы передадим этот список другому скрипту (zabbix_smart_discovery.sh), который сформирует JSON для Zabbix:
#!/bin/bash
# Formating discovering device list to JSON format for zabbix
echo -e "{nt"data":["
LN=0
while IFS=' ' read -r -a attribute; do
if [[ $LN != 0 ]]; then
echo ","
fi
echo -e "tt{ "{#DEVNAME}":"${attribute[0]}", "{#DEVTYPE}":"${attribute[1]}" }c"
LN=1
done < /dev/stdin
echo -e "nt]n}"
Вывод будет примерно такой:
{
"data":[
{ "{#DEVNAME}":"sg1", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg2", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg3", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg4", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg5", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg6", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg7", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sg8", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sdb", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sdc", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sdd", "{#DEVTYPE}":"sat" },
{ "{#DEVNAME}":"sde", "{#DEVTYPE}":"sat" }
]
}
{#DEVNAME} и {#DEVTYPE} это макросы, которые будут использованы Zabbix для подстановок.
Скрипт smart2zabbix.sh сформирует данные для Zabbix Trapper
#!/bin/bash
# Format output from smartctl to zabbix_sender input
# $1 is path for examine device
# $2 type of device is used in smartctld -d paramentr
# $3 hostname of monitoring system, can set to '-', if using -s or -c paramentr in zabbix_sender
DEV_PATH=$1
DEV_TYPE=$2
HOSTNAME=$3
HEADERS=(id attribute_name flag value worst thresh type updated when_failed raw_value)
DEVICE=$(basename $DEV_PATH)
SECTION=''
while IFS='' read -r line; do
case $line in
'=== START OF INFORMATION SECTION ===')
SECTION='INFO'
continue
;;
'=== START OF READ SMART DATA SECTION ===')
SECTION='HEALF'
continue
;;
'ID#'*)
SECTION='ATTR'
continue
;;
esac
case $SECTION in
'INFO')
if [ -z "$line" ]; then
SECTION=''
else
IFS=':' read -r -a attribute <<< "$line"
PRE="$HOSTNAME smartctl.info[$DEVICE,"
ATTR_V=$( echo ${attribute[1]} | sed -e 's/^[ t]*//' )
ATTR_N=$(echo ${attribute[0]} | tr '[:upper:]' '[:lower:]' | sed 's/ /_/' )
case ${attribute[0]} in
'Model Family')
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
'Device Model')
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
'Serial Number')
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
'Firmware Version')
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
'User Capacity')
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
'Sector Size' | 'Sector Sizes')
ATTR_N=$(echo 'Sector Size' | tr '[:upper:]' '[:lower:]' | sed 's/ /_/' )
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
'Rotation Rate')
echo "${PRE}$ATTR_N] "$ATTR_V""
;;
esac
fi
;;
'HEALF')
if [ -z "$line" ]; then
SECTION=''
else
IFS=':' read -r -a attribute <<< "$line"
PRE="$HOSTNAME smartctl.smart[$DEVICE,"
ATTR=$( echo ${attribute[1]} | sed -e 's/^[ t]*//' )
case ${attribute[0]} in
'SMART overall-health self-assessment test result')
echo "${PRE}test_result] "$ATTR""
;;
esac
fi
;;
'ATTR')
if [ -z "$line" ]; then
SECTION=''
else
read -r -a attribute <<< "$line"
PRE="$HOSTNAME smartctl.smart[$DEVICE,"
for i in "${!attribute[@]}";do
if [[ $i == 0 ]]; then
continue
fi
case ${attribute[$i]} in
''|*[!0-9]*) ATTR=""${attribute[$i]}"" ;;
*) ATTR="$(echo ${attribute[$i]} | sed 's/0*//')" ;;
esac
if [ -z "$ATTR" ]; then
ATTR=0
fi
echo "${PRE}${attribute[0]},${HEADERS[$i]}] $ATTR"
done
fi
;;
esac
done < /dev/stdin
Вывод будет примерно такой:
test.local smartctl.info[sg1,model_family] "Western Digital RE4 (SATA 6Gb/s)"
test.local smartctl.info[sg1,device_model] "WDC WD2000FYYZ-01UL1B1"
test.local smartctl.info[sg1,serial_number] "WD-WCC1P1175320"
test.local smartctl.info[sg1,firmware_version] "01.01K02"
test.local smartctl.info[sg1,user_capacity] "2 000 398 934 016 bytes [2,00 TB]"
test.local smartctl.info[sg1,sector_size] "512 bytes logical/physical"
test.local smartctl.info[sg1,rotation_rate] "7200 rpm"
test.local smartctl.smart[sg1,test_result] "PASSED"
test.local smartctl.smart[sg1,1,attribute_name] "Raw_Read_Error_Rate"
test.local smartctl.smart[sg1,1,flag] "0x002f"
test.local smartctl.smart[sg1,1,value] 200
test.local smartctl.smart[sg1,1,worst] 200
test.local smartctl.smart[sg1,1,thresh] 51
test.local smartctl.smart[sg1,1,type] "Pre-fail"
test.local smartctl.smart[sg1,1,updated] "Always"
test.local smartctl.smart[sg1,1,when_failed] "-"
test.local smartctl.smart[sg1,1,raw_value] 0
test.local smartctl.smart[sg1,3,attribute_name] "Spin_Up_Time"
test.local smartctl.smart[sg1,3,flag] "0x0027"
test.local smartctl.smart[sg1,3,value] 169
test.local smartctl.smart[sg1,3,worst] 169
test.local smartctl.smart[sg1,3,thresh] 21
test.local smartctl.smart[sg1,3,type] "Pre-fail"
test.local smartctl.smart[sg1,3,updated] "Always"
test.local smartctl.smart[sg1,3,when_failed] "-"
test.local smartctl.smart[sg1,3,raw_value] 6508
test.local smartctl.smart[sg1,4,attribute_name] "Start_Stop_Count"
test.local smartctl.smart[sg1,4,flag] "0x0032"
test.local smartctl.smart[sg1,4,value] 100
test.local smartctl.smart[sg1,4,worst] 100
test.local smartctl.smart[sg1,4,thresh] 0
test.local smartctl.smart[sg1,4,type] "Old_age"
test.local smartctl.smart[sg1,4,updated] "Always"
test.local smartctl.smart[sg1,4,when_failed] "-"
test.local smartctl.smart[sg1,4,raw_value] 36
test.local smartctl.smart[sg1,5,attribute_name] "Reallocated_Sector_Ct"
test.local smartctl.smart[sg1,5,flag] "0x0033"
test.local smartctl.smart[sg1,5,value] 200
test.local smartctl.smart[sg1,5,worst] 200
test.local smartctl.smart[sg1,5,thresh] 140
test.local smartctl.smart[sg1,5,type] "Pre-fail"
test.local smartctl.smart[sg1,5,updated] "Always"
test.local smartctl.smart[sg1,5,when_failed] "-"
test.local smartctl.smart[sg1,5,raw_value] 0
test.local smartctl.smart[sg1,7,attribute_name] "Seek_Error_Rate"
test.local smartctl.smart[sg1,7,flag] "0x002e"
test.local smartctl.smart[sg1,7,value] 200
test.local smartctl.smart[sg1,7,worst] 200
test.local smartctl.smart[sg1,7,thresh] 0
test.local smartctl.smart[sg1,7,type] "Old_age"
test.local smartctl.smart[sg1,7,updated] "Always"
test.local smartctl.smart[sg1,7,when_failed] "-"
test.local smartctl.smart[sg1,7,raw_value] 0
test.local smartctl.smart[sg1,9,attribute_name] "Power_On_Hours"
test.local smartctl.smart[sg1,9,flag] "0x0032"
test.local smartctl.smart[sg1,9,value] 79
test.local smartctl.smart[sg1,9,worst] 79
test.local smartctl.smart[sg1,9,thresh] 0
test.local smartctl.smart[sg1,9,type] "Old_age"
test.local smartctl.smart[sg1,9,updated] "Always"
test.local smartctl.smart[sg1,9,when_failed] "-"
test.local smartctl.smart[sg1,9,raw_value] 15927
test.local smartctl.smart[sg1,10,attribute_name] "Spin_Retry_Count"
test.local smartctl.smart[sg1,10,flag] "0x0032"
test.local smartctl.smart[sg1,10,value] 100
test.local smartctl.smart[sg1,10,worst] 253
test.local smartctl.smart[sg1,10,thresh] 0
test.local smartctl.smart[sg1,10,type] "Old_age"
test.local smartctl.smart[sg1,10,updated] "Always"
test.local smartctl.smart[sg1,10,when_failed] "-"
test.local smartctl.smart[sg1,10,raw_value] 0
test.local smartctl.smart[sg1,11,attribute_name] "Calibration_Retry_Count"
test.local smartctl.smart[sg1,11,flag] "0x0032"
test.local smartctl.smart[sg1,11,value] 100
test.local smartctl.smart[sg1,11,worst] 253
test.local smartctl.smart[sg1,11,thresh] 0
test.local smartctl.smart[sg1,11,type] "Old_age"
test.local smartctl.smart[sg1,11,updated] "Always"
test.local smartctl.smart[sg1,11,when_failed] "-"
test.local smartctl.smart[sg1,11,raw_value] 0
test.local smartctl.smart[sg1,12,attribute_name] "Power_Cycle_Count"
test.local smartctl.smart[sg1,12,flag] "0x0032"
test.local smartctl.smart[sg1,12,value] 100
test.local smartctl.smart[sg1,12,worst] 100
test.local smartctl.smart[sg1,12,thresh] 0
test.local smartctl.smart[sg1,12,type] "Old_age"
test.local smartctl.smart[sg1,12,updated] "Always"
test.local smartctl.smart[sg1,12,when_failed] "-"
test.local smartctl.smart[sg1,12,raw_value] 30
test.local smartctl.smart[sg1,183,attribute_name] "Runtime_Bad_Block"
test.local smartctl.smart[sg1,183,flag] "0x0032"
test.local smartctl.smart[sg1,183,value] 100
test.local smartctl.smart[sg1,183,worst] 100
test.local smartctl.smart[sg1,183,thresh] 0
test.local smartctl.smart[sg1,183,type] "Old_age"
test.local smartctl.smart[sg1,183,updated] "Always"
test.local smartctl.smart[sg1,183,when_failed] "-"
test.local smartctl.smart[sg1,183,raw_value] 0
test.local smartctl.smart[sg1,192,attribute_name] "Power-Off_Retract_Count"
test.local smartctl.smart[sg1,192,flag] "0x0032"
test.local smartctl.smart[sg1,192,value] 200
test.local smartctl.smart[sg1,192,worst] 200
test.local smartctl.smart[sg1,192,thresh] 0
test.local smartctl.smart[sg1,192,type] "Old_age"
test.local smartctl.smart[sg1,192,updated] "Always"
test.local smartctl.smart[sg1,192,when_failed] "-"
test.local smartctl.smart[sg1,192,raw_value] 29
test.local smartctl.smart[sg1,193,attribute_name] "Load_Cycle_Count"
test.local smartctl.smart[sg1,193,flag] "0x0032"
test.local smartctl.smart[sg1,193,value] 200
test.local smartctl.smart[sg1,193,worst] 200
test.local smartctl.smart[sg1,193,thresh] 0
test.local smartctl.smart[sg1,193,type] "Old_age"
test.local smartctl.smart[sg1,193,updated] "Always"
test.local smartctl.smart[sg1,193,when_failed] "-"
test.local smartctl.smart[sg1,193,raw_value] 6
test.local smartctl.smart[sg1,194,attribute_name] "Temperature_Celsius"
test.local smartctl.smart[sg1,194,flag] "0x0022"
test.local smartctl.smart[sg1,194,value] 125
test.local smartctl.smart[sg1,194,worst] 96
test.local smartctl.smart[sg1,194,thresh] 0
test.local smartctl.smart[sg1,194,type] "Old_age"
test.local smartctl.smart[sg1,194,updated] "Always"
test.local smartctl.smart[sg1,194,when_failed] "-"
test.local smartctl.smart[sg1,194,raw_value] 25
test.local smartctl.smart[sg1,196,attribute_name] "Reallocated_Event_Count"
test.local smartctl.smart[sg1,196,flag] "0x0032"
test.local smartctl.smart[sg1,196,value] 200
test.local smartctl.smart[sg1,196,worst] 200
test.local smartctl.smart[sg1,196,thresh] 0
test.local smartctl.smart[sg1,196,type] "Old_age"
test.local smartctl.smart[sg1,196,updated] "Always"
test.local smartctl.smart[sg1,196,when_failed] "-"
test.local smartctl.smart[sg1,196,raw_value] 0
test.local smartctl.smart[sg1,197,attribute_name] "Current_Pending_Sector"
test.local smartctl.smart[sg1,197,flag] "0x0032"
test.local smartctl.smart[sg1,197,value] 200
test.local smartctl.smart[sg1,197,worst] 200
test.local smartctl.smart[sg1,197,thresh] 0
test.local smartctl.smart[sg1,197,type] "Old_age"
test.local smartctl.smart[sg1,197,updated] "Always"
test.local smartctl.smart[sg1,197,when_failed] "-"
test.local smartctl.smart[sg1,197,raw_value] 0
test.local smartctl.smart[sg1,198,attribute_name] "Offline_Uncorrectable"
test.local smartctl.smart[sg1,198,flag] "0x0030"
test.local smartctl.smart[sg1,198,value] 200
test.local smartctl.smart[sg1,198,worst] 200
test.local smartctl.smart[sg1,198,thresh] 0
test.local smartctl.smart[sg1,198,type] "Old_age"
test.local smartctl.smart[sg1,198,updated] "Offline"
test.local smartctl.smart[sg1,198,when_failed] "-"
test.local smartctl.smart[sg1,198,raw_value] 0
test.local smartctl.smart[sg1,199,attribute_name] "UDMA_CRC_Error_Count"
test.local smartctl.smart[sg1,199,flag] "0x0032"
test.local smartctl.smart[sg1,199,value] 200
test.local smartctl.smart[sg1,199,worst] 200
test.local smartctl.smart[sg1,199,thresh] 0
test.local smartctl.smart[sg1,199,type] "Old_age"
test.local smartctl.smart[sg1,199,updated] "Always"
test.local smartctl.smart[sg1,199,when_failed] "-"
test.local smartctl.smart[sg1,199,raw_value] 0
test.local smartctl.smart[sg1,200,attribute_name] "Multi_Zone_Error_Rate"
test.local smartctl.smart[sg1,200,flag] "0x0008"
test.local smartctl.smart[sg1,200,value] 200
test.local smartctl.smart[sg1,200,worst] 200
test.local smartctl.smart[sg1,200,thresh] 0
test.local smartctl.smart[sg1,200,type] "Old_age"
test.local smartctl.smart[sg1,200,updated] "Offline"
test.local smartctl.smart[sg1,200,when_failed] "-"
test.local smartctl.smart[sg1,200,raw_value] 0
А дальше просто отправим все это Zabbix Trapper:
#!/bin/bash
# Sending collected data to the zabbix server
# Get device list and type from STDIN, produced by smartdiscovery.sh
PREFIX='/usr/local/bin'
while IFS=' ' read -r -a attr; do
smartctl -A -H -i -d ${attr[1]} /dev/${attr[0]} | $PREFIX/smart2zabbix.sh /dev/${attr[0]} ${attr[1]} - | zabbix_sender -c /etc/zabbix/zabbix_agentd.conf -i -
done < /dev/stdin
Дальше нужно только разрешить sudo для некоторых скриптов, поместить задание в cron и импортировать шаблон на Zabbix Server.
Готовый комплект можно получить с официального портала Zabbix Share, где это все выложено для всех желающих: S.M.A.R.T. monitoring with smartmontools (LLD,Trapper)
Основным преимуществом перед другими подобными шаблонамискриптами, можно назвать то, что загружаются все атрибуты, которые вы в последствии используете по желанию, без изменения скриптов, только добавив их на сервере.
Автор: MagicGTS