Мониторим S.M.A.R.T. в Zabbix

от автора

Для тех кто использует 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

Напишем такой скрипт (smartdiscovery.sh):

#!/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:

zabbix_smart_discovery.sh

#!/bin/bash # Formating discovering device list to JSON format for zabbix  echo -e "{\n\t\"data\":[" LN=0 while IFS=' ' read -r -a attribute; do	 	if [[ $LN != 0 ]]; then 		echo "," 	fi 	echo -e "\t\t{ \"{#DEVNAME}\":\"${attribute[0]}\", \"{#DEVTYPE}\":\"${attribute[1]}\" }\c" 	LN=1 done  < /dev/stdin echo -e "\n\t]\n}" 

Вывод будет примерно такой:

smartctl.discovery

{         "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

smart2zabbix.sh

#!/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:

zabbix_smartctl.sh

#!/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)

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

ссылка на оригинал статьи http://habrahabr.ru/post/274391/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *