С программированием приходится порой сталкиваться и в системном администрировании. Сегодня я хочу рассказать об одной такой истории.

Захотелось мне однажды увидеть единую и наглядную картину об электропитании серверов на работе, а также получать оперативные уведомления в случае его перебоев. В силу своей симпатии к системам на базе Linux решил двигаться в этом направлении. Дома я уже имел удачный опыт использования Apcupsd — демона для контроля ИБП фирмы APC, но здесь меня ждала несколько иная задача.

Дело в том, что данный демон не предусматривает работу с несколькими ИБП, а на работе у меня их три, и не хотелось каждый мониторить разным сервером. Решил поближе познакомиться с устройством этого демона.

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

$ /sbin/apcupsd -f <file>

Но такую организацию не поддерживает init-скрипт демона. Во всяком случае, в Debian 6, на базе которого все и производилось. Код этого скрипта (/etc/init.d/apcupsd):

#!/bin/sh
 
### BEGIN INIT INFO
# Provides:             apcupsd
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Should-Start:         $local_fs
# Should-Stop:          $local_fs
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Starts apcupsd daemon
# Description:          apcupsd provides UPS power management for APC products.
### END INIT INFO
 
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/sbin/apcupsd
CONFIG=/etc/default/apcupsd
NAME=apcupsd
DESC="UPS power management"
 
test -x $DAEMON || exit 0
test -e $CONFIG || exit 0
 
set -e
 
. $CONFIG
 
if [ "x$ISCONFIGURED" != "xyes" ] ;
then
        echo "Please check your configuration ISCONFIGURED in /etc/default/apcupsd"
        exit 0
fi
 
 
case "" in
        start)
                echo -n "Starting $DESC: "
 
                rm -f /etc/apcupsd/powerfail
 
                if [ "`pidof apcupsd`" = "" ]
                then
                        start-stop-daemon --start --quiet --exec $DAEMON
                        echo "$NAME."
                else
                        echo ""
                        echo "A copy of the daemon is still running.  If you just stopped it,"
                        echo "please wait about 5 seconds for it to shut down."
                        exit 0
                fi
                ;;
 
        stop)
                echo -n "Stopping $DESC: "
                start-stop-daemon --stop --oknodo --pidfile /var/run/apcupsd.pid || echo "Not Running."
                rm -f /var/run/apcupsd.pid
                echo "$NAME."
                ;;
 
        restart|force-reload)
                {CONTENT} stop
                sleep 10
                {CONTENT} start
                ;;
 
        status)
                #/sbin/apcaccess status
                $APCACCESS status
                ;;
 
        *)
                N=/etc/init.d/$NAME
                echo "Usage: $N {start|stop|restart|force-reload}" >&2
                exit 1
                ;;
esac
 
exit 0

Демон, как видно, запускается без параметров и использует конфигурационный файл по умолчанию (/etc/apcupsd/apcupsd.conf).

Реализация поддержки нескольких устройств демоном Apcupsd

Каждый экземпляр демона должен иметь свой конфигурационный файл, в котором описаны параметры определенного ИБП. В /etc/apcupsd/ создаем папки ups0, ups1 и так далее. Количество папок должно соответствовать количеству контролируемых устройств. Далее, в каждую папку копируем следующие файлы из /etc/apcupsd/:

apccontrol
apcupsd.conf
changeme
commfailure
commok
killpower
offbattery
onbattery
ups-monitor

Теперь необходимо переписать init-скрипт /etc/init.d/apcupsd, задача которого — запустить нужное количество демонов, передав каждому свой путь к конфигурационному файлу. Вот что получилось у меня:

#!/bin/bash
 
### BEGIN INIT INFO
# Provides:             apcupsd
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Should-Start:         $local_fs
# Should-Stop:          $local_fs
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Starts apcupsd daemon
# Description:          apcupsd provides UPS power management for APC products.
### END INIT INFO
 
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/sbin/apcupsd
CONFIG=/etc/default/apcupsd
NAME=apcupsd
DESC="UPS power management"
 
test -x $DAEMON || exit 0
test -e $CONFIG || exit 0
 
set -e
 
. $CONFIG
 
if [ "x$ISCONFIGURED" != "xyes" ] ;
then
        echo "Please check your configuration ISCONFIGURED in /etc/default/apcupsd"
        exit 0
fi
 
 
case "" in
        start)
 
                for conf in /etc/apcupsd/ups[0-9]/apcupsd.conf ; do
                        inst=${conf:13:4}
                        rm -f /etc/apcupsd/$inst/powerfail
                        echo -n "Starting $DESC ($inst): "
                        if [ -e /var/run/apcupsd-$inst.pid ]
                        then
                                echo ""
                                echo "A copy of the daemon is still running.  If you just stopped it,"
                                echo "please wait about 5 seconds for it to shut down."
                                exit 0
                        else
                                start-stop-daemon --start --quiet --name $inst --exec $DAEMON -- -f $conf -P /var/run/apcupsd-$inst.pid
                                #sleep 1
                                echo "$NAME."
                        fi
                done
                ;;
 
        stop)
 
                for conf in /etc/apcupsd/ups[0-9]/apcupsd.conf ; do
                        inst=${conf:13:4}
                        echo -n "Stopping $DESC ($inst): "
                        start-stop-daemon --stop --oknodo --pidfile /var/run/apcupsd-$inst.pid || echo "Not Running."
                        rm -f /var/run/apcupsd-$inst.pid
                        echo "$NAME."
                done
                ;;
 
        restart|force-reload)
                {CONTENT} stop
                sleep 10
                {CONTENT} start
                ;;
 
 
        *)
                N=/etc/init.d/$NAME
                echo "Usage: $N {start|stop|restart|force-reload}" >&2
                exit 1
                ;;
esac
 
exit 0

Осталось отредактировать каждый конфигурационный файл (apcupsd.conf). Нас интересуют следующие строки в нем:

#UPSNAME
...
UPSCABLE usb
...
UPSTYPE usb
DEVICE 
...
SCRIPTDIR /etc/apcupsd
...
PWRFAILDIR /etc/apcupsd
...
NOLOGINDIR /etc
...
NISPORT 3551
...
EVENTSFILE /var/log/apcupsd.events

В них задается имя устройства, тип подключения, путь к файлу устройства, номер прослушиваемого демоном порта и пути к различным папкам и файлам (папка со скриптами, файл для записи событий и т.д.).

Как я уже говорил, у меня к серверу подключено три ИБП. Два из них через USB, а третий пятиметровым COM-кабелем из другого шкафа. Значимые строки в конфигурационных файлах соответственно следующие:

/etc/apcupsd/ups0/apcupsd.conf:

UPSNAME ups0
...
UPSCABLE usb
...
UPSTYPE usb
DEVICE /dev/usb/hiddev0
...
SCRIPTDIR /etc/apcupsd/ups0
...
PWRFAILDIR /etc/apcupsd/ups0
...
NOLOGINDIR /etc/apcupsd/ups0
...
NISPORT 3551
...
EVENTSFILE /var/log/apcupsd.ups0.events

/etc/apcupsd/ups1/apcupsd.conf:

UPSNAME ups1
...
UPSCABLE usb
...
UPSTYPE usb
DEVICE /dev/usb/hiddev1
...
SCRIPTDIR /etc/apcupsd/ups1
...
PWRFAILDIR /etc/apcupsd/ups1
...
NOLOGINDIR /etc/apcupsd/ups1
...
NISPORT 3552
...
EVENTSFILE /var/log/apcupsd.ups1.events

/etc/apcupsd/ups2/apcupsd.conf:

UPSNAME ups2
...
UPSCABLE smart
...
UPSTYPE apcsmart
DEVICE /dev/ttyS0
...
SCRIPTDIR /etc/apcupsd/ups2
...
PWRFAILDIR /etc/apcupsd/ups2
...
NOLOGINDIR /etc/apcupsd/ups2
...
NISPORT 3553
...
EVENTSFILE /var/log/apcupsd.ups2.events

Пробуем стартовать демон:

$ sudo /etc/init.d/apcaccess start
Starting UPS power management (ups0): apcupsd.
Starting UPS power management (ups1): apcupsd.
Starting UPS power management (ups2): apcupsd.

Отлично, стартовало три экземпляра демона. Каждый слушает свой TCP-порт, по которому можно подключиться и запросить информацию об ИБП:

$ sudo netstat -lnp | grep apcupsd
tcp        0      0 127.0.0.1:3551          0.0.0.0:*               LISTEN      24386/apcupsd   
tcp        0      0 127.0.0.1:3552          0.0.0.0:*               LISTEN      24389/apcupsd   
tcp        0      0 127.0.0.1:3553          0.0.0.0:*               LISTEN      24392/apcupsd

Получить информацию можно следующей командой:

$ sudo apcaccess status <host>:<port>

Допустим, по третьему ИБП:

$ sudo apcaccess status localhost:3553
APC      : 001,050,1208
DATE     : 2011-07-31 16:15:07 +1100  
HOSTNAME : sh01s02.eao.drsk.ru
VERSION  : 3.14.8 (16 January 2010) debian
UPSNAME  : ups2
CABLE    : Custom Cable Smart
MODEL    : Smart-UPS 2200 RM
UPSMODE  : Stand Alone
STARTTIME: 2011-07-31 15:17:14 +1100  
STATUS   : ONLINE 
LINEV    : 230.4 Volts
LOADPCT  :  31.8 Percent Load Capacity
BCHARGE  : 100.0 Percent
TIMELEFT :  31.0 Minutes
MBATTCHG : 5 Percent
MINTIMEL : 3 Minutes
MAXTIME  : 0 Seconds
MAXLINEV : 231.8 Volts
MINLINEV : 230.4 Volts
OUTPUTV  : 230.4 Volts
SENSE    : High
DWAKE    : 000 Seconds
DSHUTD   : 090 Seconds
DLOWBATT : 02 Minutes
LOTRANS  : 208.0 Volts
HITRANS  : 253.0 Volts
RETPCT   : 000.0 Percent
ITEMP    : 27.9 C Internal
ALARMDEL : 5 seconds
BATTV    : 55.1 Volts
LINEFREQ : 50.0 Hz
LASTXFER : Automatic or explicit self test
NUMXFERS : 0
TONBATT  : 0 seconds
CUMONBATT: 0 seconds
XOFFBATT : N/A
SELFTEST : NO
STESTI   : 336
STATFLAG : 0x07000008 Status Flag
REG1     : 0x00 Register 1
REG2     : 0x00 Register 2
REG3     : 0x00 Register 3
MANDATE  : 05/02/06
SERIALNO : JS0619000289
BATTDATE : 05/02/06
NOMOUTV  : 230 Volts
NOMBATTV :  48.0 Volts
EXTBATTS : 0
FIRMWARE : 665.6.I
APCMODEL : FWI
END APC  : 2011-07-31 16:15:51 +1100

Я все это дело привязал к системе мониторинга Cacti, благо, существуют готовые шаблоны и сделать это не составляет особого труда. Получилась вот такая наглядная картина:

Графики состояний ИБП в Cacti

Настройка уведомлений

Демон Apcupsd позволяет выполнять пользовательский код при наступлении определенного события (например, отключения питания). В каждой папке с конфигурационными файлами устройств имеется файл apccontrol, в котором прописаны стандартные действия на события. Пользовательские действия на определенное событие помещаются в отдельный файл с именем, соответствующим имени события. Список доступных событий можно найти в официальной документации.

При наступлении события сначала выполняются пользовательские действия (если файл с действиями существует). В конце выполнения нужно вернуть ноль (exit 0), в этом случае apccontrol выполнит стандартные действия после пользовательских (например, выключит компьютер по таймауту при отключении питания). Если необходимо отклонить выполнение стандартных действий, нужно вернуть 99 (exit 99) в конце выполнения пользовательских действий.

Вернемся к уведомлениям. Самым удобным и полезным я считаю вариант СМС-уведомлений, так как телефон всегда под рукой, в отличие от компьютера. Для отправки СМС я использую email2sms шлюз. Инструкция по настройке услуги для моего оператора. Суть услуги: создается электронный ящик, при отправке на него письма на телефон прилетает СМС с текстом письма. Похожая услуга должна быть и у других операторов.

Для отправки писем в bash-скриптах я использую программку ssmtp, она есть в репозиториях ArchLinux, Ubuntu, Debian, на счет остальных дистрибутивов не знаю. Для отправки почты нужно завести какую-нибудь учетную запись на почтовом сервере, затем отредактировать конфигурационный файл /etc/ssmtp/ssmtp.conf:

root=e-mail
mailhub=smtp-сервер
rewriteDomain=домен e-mail’а

FromLineOverride=YES

authuser=имя пользователя
authpass=пароль

Задача — отправлять СМС-уведомления при отключении и возобновлении питания. События в Apcupsd, соответственно onbattery и offbattery.

Скрипт onbattery:

#!/bin/bash
 
(
  echo "To: <*****@sms.megafondv.ru>"
  echo "From: <*****@joker-jar.ru>"
  echo "Subject: UPS message `date \"+%d.%m.%Y %H:%M:%S\"`"
  echo " "
  echo "Power failure on UPS. Running on batteries."
) | ssmtp *****@sms.megafondv.ru
 
exit 0

Скрипт offbattery:

#!/bin/bash
 
(
  echo "To: <*****@sms.megafondv.ru>"
  echo "From: <*****@joker-jar.ru>"
  echo "Subject: UPS message `date \"+%d.%m.%Y %H:%M:%S\"`"
  echo " "
  echo "Power returned to UPS."
) | ssmtp *****@sms.megafondv.ru
 
exit 0

На этом, пожалуй, все. Удачного администрирования!