Pull to refresh

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

Reading time 11 min
Views 37K
Для тех кто использует 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

/usr/sbin/modprobe sg
# dev_type so limit? becose i can`t test it on corresponding controller, /usr/local/etc/smartdev.lst can use for set dev_type manual
DEV_TYPE=(sat scsi ata)
DEV_LST='/usr/local/etc/smartdev.lst'
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
		/usr/sbin/smartctl -A -d $i $DEV | grep -q 'ID#'
		if [[ $? == 0 ]]; then
			DEV=$(basename $DEV)
			if [ -f $DEV_LST ]; then			
				grep -q $DEV $DEV_LST 
				if [[ $? != 0 ]]; then
					echo "$DEV $i"
				fi
			fi
			break
		fi
	done
done  < <(/usr/bin/sg_map)
if [ -f $DEV_LST ]; then
	cat $DEV_LST
fi


Он поищет для нас устройства (ищет утилитами и сверяет найденное с файлом /usr/local/etc/smartdev.lst, если совпадение найдено то в последствии используется значения из файла, это позволит временно обойти отсутствие возможности проверить работу с некоторыми контролерами, к примеру 3ware) и выдаст список в виде пар значений: <имя устройства> <тип подключения>
Дальше мы передадим этот список другому скрипту (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'
AGENT_CFG='/etc/zabbix/zabbix_agentd.conf'
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]} - | /usr/bin/zabbix_sender -c $AGENT_CFG -i -
done < /dev/stdin


Дальше нужно только разрешить sudo для некоторых скриптов, поместить задание в cron и импортировать шаблон на Zabbix Server.
Готовый комплект можно получить с официального портала Zabbix Share, где это все выложено для всех желающих: S.M.A.R.T. monitoring with smartmontools (LLD,Trapper)

Основным преимуществом перед другими подобными шаблонами\скриптами, можно назвать то, что загружаются все атрибуты, которые вы в последствии используете по желанию, без изменения скриптов, только добавив их на сервере.
Tags:
Hubs:
+10
Comments 0
Comments Leave a comment

Articles