На последнем вебинаре нас просили рассказать, как мы работаем с логами с помощью monit.d.
Хоть и с большой задержкой, все же отвечаем.

Вот пример правила, которое обрабатывает системный лог /var/log/messages и шлет алерт в случае нахождения в нем определенной записи:
Пример обработки /var/log/messages
check file messages with path /var/log/messages
    if match 'OOM killed process' then alert
    if match 'temperature above threshold' then alert
    if match 'table full, dropping packet' then alert
    if match 'OOM killed process' for 2 cycles then exec "/bin/bash -c '/usr/bin/monit unmonitor messages && /bin/sleep 3600 && /usr/bin/monit monitor messages'"
    if match 'temperature above threshold' for 2 cycles then exec "/bin/bash -c '/usr/bin/monit unmonitor messages && /bin/sleep 3600 && /usr/bin/monit monitor messages'"
    if match 'table full, dropping packet' for 2 cycles then exec "/bin/bash -c '/usr/bin/monit unmonitor messages && /bin/sleep 3600 && /usr/bin/monit monitor messages'"
    if match 'time wait bucket table overflow' for 2 cycles then exec "/bin/bash -c '/usr/bin/monit unmonitor messages && /bin/sleep 3600 && /usr/bin/monit monitor messages'"
    if match 'blocked for more than 120 seconds' for 2 cycles then exec "/bin/bash -c '/usr/bin/monit unmonitor messages && /bin/sleep 3600 && /usr/bin/monit monitor messages'"



В следующем примере monit.d нужен только для запуска скрипта в определенных условиях.
Например, если LA сервера превышает 20 в течение 2 циклов проверки, то вызывается скрипт. В monit это выглядит так:

if loadavg (5min) > 20 for 2 cycles then exec "/srv/southbridge/bin/highload-report.sh"

Теперь о самом срипте. Допустим, что у нас есть сервер apache + nginx + mysql. В какой-то момент нагрузка резко возросла. Monit засечет это и запустит скрипт:

Запуск скрипта по алерту
#!/bin/sh

PATH="/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin"

RRUN=`ps ax | grep highload-report.sh | grep -v grep | wc -l`
RRUN=0$RRUN
if [ $RRUN -gt 2 ]; then
  echo "Highload Report alredy running"
  exit
fi

STAMP=`date +%H%M%S`
FLAGD=`date +%s`
REPORT=""


if [ -f /tmp/highload-report.flag ]; then
  FLAGL=`cat /tmp/highload-report.flag | head -1`
  CNTL=`cat /tmp/highload-report.flag | tail -1`
  DELTA=$((FLAGD-FLAGL))
  if [ $DELTA -gt 280 -a $CNTL -eq 1 ]; then
    echo $FLAGD > /tmp/highload-report.flag
    echo 5 >> /tmp/highload-report.flag
    REPORT="5"
    DELTA=0
  fi
  if [ $DELTA -gt 280 -a $CNTL -ne 10 ]; then
    echo $FLAGD > /tmp/highload-report.flag
    echo 10 >> /tmp/highload-report.flag
    REPORT="10"
    DELTA=0
  fi
  if [ $DELTA -gt 1180 ]; then
    echo $FLAGD > /tmp/highload-report.flag
    echo 1 >> /tmp/highload-report.flag
    REPORT="100"
  fi
else
  echo $FLAGD > /tmp/highload-report.flag
  echo 1 >> /tmp/highload-report.flag
  REPORT="1"
fi

echo "<html><body>" >> /tmp/$STAMP.tmp
echo "<h3>load average</h3>" >> /tmp/$STAMP.tmp
echo "<p><pre>" >> /tmp/$STAMP.tmp
echo >> /tmp/$STAMP.tmp
top -b | head -5 >> /tmp/$STAMP.tmp 2>&1
echo >> /tmp/$STAMP.tmp
echo "</pre></p>" >> /tmp/$STAMP.tmp

if [ -f "/root/.mysql" ]; then
    echo "<h3>mysql processes</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp
    mysql -u root -p`cat /root/.mysql` -e "SHOW FULL PROCESSLIST" | awk '$5 != "Sleep" && $7 != "NULL"' | sort -n -k 6 >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "</pre></p>" >> /tmp/$STAMP.tmp
fi

if [ -f "/root/.postgresql" ]; then
    echo "<h3>postgresql processes</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp

    if [ -f "/etc/init.d/pgbouncer" ]; then
        PORT="5454"
    else
        PORT="5432"
    fi

    echo "SELECT datname,procpid,current_query FROM pg_stat_activity;" | psql -U postgres --port=$PORT >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "</pre></p>" >> /tmp/$STAMP.tmp
fi

echo "<h3>memory process list (top100)</h3>" >> /tmp/$STAMP.tmp
echo "<p><pre>" >> /tmp/$STAMP.tmp
echo >> /tmp/$STAMP.tmp
#ps -ewwwo size,command --sort -size | head -100 | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=2 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' >> /tmp/$STAMP.tmp 2>&1
ps -ewwwo pid,size,command --sort -size | head -100 | awk '{ pid=$1 ; printf("%7s ", pid) }{ hr=$2/1024 ; printf("%8.2f Mb ", hr) } { for ( x=3 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' >> /tmp/$STAMP.tmp 2>&1
echo >> /tmp/$STAMP.tmp
echo "</pre></p>" >> /tmp/$STAMP.tmp

echo "<h3>process list (sort by cpu)</h3>" >> /tmp/$STAMP.tmp
echo "<p><pre>" >> /tmp/$STAMP.tmp
echo >> /tmp/$STAMP.tmp
ps -ewwwo pcpu,pid,user,command --sort -pcpu >> /tmp/$STAMP.tmp 2>&1
echo >> /tmp/$STAMP.tmp
echo "</pre></p>" >> /tmp/$STAMP.tmp

LINKSVER=`links -version | grep "2.2" | wc -l`
if [ $LINKSVER -gt 0 ]; then
    echo "<h3>apache</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp
    links -dump -retries 1 -receive-timeout 30 http://localhost:8080/apache-status | grep -v "OPTIONS \* HTTP/1.0" >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "</pre></p>" >> /tmp/$STAMP.tmp

    echo "<h3>nginx</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp
    links -dump -retries 1 -receive-timeout 30 http://localhost/nginx-status >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
else
    echo "<h3>apache</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp
    links -dump -eval 'set connection.retries = 1' -eval 'set connection.receive_timeout = 30' http://localhost:8080/apache-status >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "</pre></p>" >> /tmp/$STAMP.tmp

    echo "<h3>nginx</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp
    links -dump -eval 'set connection.retries = 1' -eval 'set connection.receive_timeout = 30' http://localhost/nginx-status >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "</pre></p>" >> /tmp/$STAMP.tmp
fi

echo "<h3>connections report</h3>" >> /tmp/$STAMP.tmp
echo "<p><pre>" >> /tmp/$STAMP.tmp
echo >> /tmp/$STAMP.tmp
netstat -plan | grep :80 | awk {'print $5'} | cut -d: -f 1 | sort | uniq -c | sort -n >> /tmp/$STAMP.tmp 2>&1
echo >> /tmp/$STAMP.tmp
echo "</pre></p>" >> /tmp/$STAMP.tmp

echo "<h3>syn tcp/udp session</h3>" >> /tmp/$STAMP.tmp
echo "<p><pre>" >> /tmp/$STAMP.tmp
echo >> /tmp/$STAMP.tmp
netstat -n | egrep '(tcp|udp)' | grep SYN | wc -l >> /tmp/$STAMP.tmp 2>&1
echo >> /tmp/$STAMP.tmp
echo "</pre></p>" >> /tmp/$STAMP.tmp

if [ -f "/root/.mysql" ]; then
    echo "<h3>mysql status</h3>" >> /tmp/$STAMP.tmp
    echo "<p><pre>" >> /tmp/$STAMP.tmp
    echo >> /tmp/$STAMP.tmp
    mysql -u root -p`cat /root/.mysql` -e "SHOW STATUS where value !=0" >> /tmp/$STAMP.tmp 2>&1
    echo >> /tmp/$STAMP.tmp
    echo "</pre></p>" >> /tmp/$STAMP.tmp
fi

SUBJECT="`hostname` HighLoad report"

echo "</body></html>" >> /tmp/$STAMP.tmp

if [ -n "$REPORT" ]; then
cat - /tmp/$STAMP.tmp <<EOF | sendmail -oi -t
To: root
Subject: $SUBJECT
Content-Type: text/html; charset=utf8
Content-Transfer-Encoding: 8bit
MIME-Version: 1.0

EOF

fi

rm /tmp/$STAMP.tmp

if [ "$1" = "apache-start" ]; then
    sems=$(ipcs -s | grep apache | awk --source '/0x0*.*[0-9]* .*/ {print $2}')
    for sem in $sems
    do
      ipcrm sem $sem
    done
    /etc/init.d/httpd start
fi

if [ "$1" = "apache-stop" ]; then
    killall -9 httpd
fi

if [ "$1" = "force-restart" ]; then
    killall -9 httpd
    sleep 2
    sems=$(ipcs -s | grep apache | awk --source '/0x0*.*[0-9]* .*/ {print $2}')
    for sem in $sems
    do
      ipcrm sem $sem
    done
    /etc/init.d/httpd start
fi

exit 1



В результате выполнения генерируется письмо, которое содержит:

— Текущие запросы к БД.
— Список процессов отсортированных по потребленной памяти
— Список процессов отсортированный по потребленному CPU
— Список запросов к apache
— Информацию о состояние nginx
— Количество новых tcp/udp сессий
— Статус mysql

Собственно, делается это все штатными утилитами. ps, mysql netstat и т.д.

В итоге отправляется письмо на специальный адрес, почта на котором парсится скриптом, и создается задача в нашей системе redmine.

Комментарии (4)


  1. TaHKucT
    18.09.2015 01:06
    +2

    А почему критичная LA 20? А если на сервере 2 x E7-8890V3 по 18 реальных ядер на брата?

    А почему в логах реагируете только на те проблемы, которые раньше уже стречали. Правильней «не мониторить известные некритичные сообщение и реагировать на все известные критичные сообщение и на все неизвестные сообщение».

    ps aux | grep foo | grep -v grep можно заменить на ps aux | grep [f]oo

    Ну я уж молчу про то, что в одном файле смешивается bash и html, а в одной строке awk и cut, причем подряд, а links по вашей версии есть на каждом сервере.


    1. akhaustov
      18.09.2015 11:08
      +1

      Спасибо за комментарии.
      На самом деле, это только примеры.
      Под каждый проект и сервер есть свои собственные настройки и условия.
      Скрипт тоже приведён как пример.


  1. Sleuthhound
    21.09.2015 15:18

    Скрипт далеко не универсален в том виде, что есть и по сути это самопал под конкретную систему и конкретные задачи. Шаг влево шаг вправо и он уже не будет работать.
    Например вы определяете версию links, вот только непонятно зачем? Например в Links 2.7 или 2.8 нет опции -eval, где Вы её взяли?
    Я уже не говорю о том, что наличие mysql и postgresql Вы определяете по наличию файлов /root/.mysql и /root/.postgresql — вообще жуть, не проще ли сделать проверку по наличию открытого стандартного порта? (netstat -ltupn |grep 3306)
    А кучу повторяющихся файлов типа "/tmp/highload-report.flag" вынести как параметр в отдельный файл конфигурации, например так:

    CONFFILE="/etc/highload-report.conf"

    if [! -f ${CONFFILE} ]
    then
    echo «Please place config file:${CONFFILE}»
    exit
    else
    . ${CONFFILE}
    fi

    В общем жуть.


    1. LuckySB
      01.10.2015 00:06

      Ну да. это скрипт под конкретный темплейт контейнера.
      Ему не надо быть универсальным.

      Код в него добавляли несколько лет несколько разных человек, поэтому такое спаггети и получилось
      links я пожалуй, на curl заменю

      а по поводу файлов /root/.mysql и .postgresql — это просто принятая у нас система флагов.
      при назначении контейнеру роли сервера БД, эти флаги создаются в контенере.

      А по поводу проверки наличия открытого стнадартного порта — вот это уже ужас-ужас.

      В конфиге сервера у нас обычно стоит skip-networking
      А что делать, если порт не открыт? У нас на хосте нету сервера БД вообще, или сервер БД упал?