ども、2015 年も Slack of the year の cloudpackかっぱ (@inokara) です。

はじめに

monit による障害検知の通知を Slack に飛ばす: Slack
Monit って alert オプションでメールを飛ばすことが出来ますが Slack に飛ばせないかなって思ってぐぐったら以下のような先人の知恵がアップされていました。

有難い。

先人に負けじと

試してみました。

環境

# cat /etc/system-release
Amazon Linux AMI release 2014.09
# monit -V
This is Monit version 5.2.5
Copyright (C) 2000-2011 Tildeslash Ltd. All Rights Reserved.

monit のインストールについては割愛します。

監視対象

何の変哲も無い Apache のプロセス名(httpd)を監視します。

check process httpd with matching "httpd"
  start program = "/etc/init.d/httpd start"
  stop program = "/etc/init.d/httpd stop"
  if does not exist
    then exec "/opt/bin/post_to_slack.rb"
  else if succeeded for 1 cycle then exec "/opt/bin/post_to_slack.rb"
  group https

上記の設定では httpd という名前のプロセス名が存在しているか否かを監視しています。もし does not exist の場合には exec コマンドにて任意のスクリプト(/opt/bin/post_to_slack.rb)が実行されます。今回のキモは exec です。

monit は上記例のプロセス名のチェック以外にも PID ファイルの有無や任意のコマンドの実行結果でアクション(通知、プロセスの再起動)を行うことが可能です。

詳細は上記のドキュメントをご確認ください。

ザクッと以下の通り。

  • EXISTENCE TESTING
  • RESOURCE TESTING
  • FILE CHECKSUM TESTING
  • TIMESTAMP TESTING
  • FILE SIZE TESTING
  • FILE CONTENT TESTING
  • FILESYSTEM FLAGS TESTING
  • SPACE TESTING
  • INODE TESTING
  • PERMISSION TESTING
  • UID TESTING
  • GID TESTING
  • PID TESTING
  • PPID TESTING
  • PROCESS UPTIME TESTING
  • PROGRAM STATUS TESTING
  • NETWORK LINK STATUS TEST
  • NETWORK LINK CAPACITY TEST
  • NETWORK SATURATION TEST
  • NETWORK BANDWIDTH TEST
  • NETWORK PACKETS TEST
  • NETWORK PING TEST
  • CONNECTION TESTING

それぞれのテストに対してどのようなアクションが行われるかについては以下のコマンドにて確認することが出来ます。

/usr/bin/monit -vI

以下のように出力されます。

Runtime constants:
 Control file       = /etc/monit.conf
 Log file           = /var/log/monit
 Pid file           = /var/run/monit.pid
 Debug              = True
 Log                = True
 Use syslog         = False
 Is Daemon          = True
 Use process engine = True
 Poll time          = 60 seconds with start delay 0 seconds
 Expect buffer      = 256 bytes
 Mail from          = (not defined)
 Mail subject       = (not defined)
 Mail message       = (not defined)
 Start monit httpd  = False

The service list contains the following entries:

Process Name          = httpd
 Group                = httpd
 Match                = httpd
 Monitoring mode      = active
 Start program        = '/etc/init.d/httpd start' timeout 30 second(s)
 Stop program         = '/etc/init.d/httpd stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then exec '/opt/bin/post_to_slack.rb' timeout 0 cycle(s) else if succeeded 1 times within 1 cycle(s) then exec '/opt/bin/post_to_slack.rb' timeout 0 cycle(s)
 Pid                  = if changed 1 times within 1 cycle(s) then alert
 Ppid                 = if changed 1 times within 1 cycle(s) then alert

System Name           = system_ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal
 Monitoring mode      = active

-------------------------------------------------------------------------------
monit daemon at 25226 awakened

上記の例では Existence チェックのアクションは Slack への通知スクリプトが実行されます。また、Pid チェックについては alert とありますので alert で定義されたメール通知が行われます。

(追記)
ちなみに alert オプションはメール通知のみですが、メール以外が利用出来るといいような気がします。上記の参考記事以外にも HipChat への通知方法も掲載されていますが、あくまでも exec オプションを利用しての通知となります。尚、試した範囲では exec を利用した場合には「通知した後で restart のような事が monit 単体では出来ない」点が残念な感じでした。

Slack 通知スクリプト

Slack の API を叩く Ruby のスクリプトを以下のように作成します。

#!/usr/bin/env ruby

require 'net/https'
require 'json'

uri = URI.parse("https://hooks.slack.com/services/xxxxxxx/yyyyyyyyy/zzzzzzzzzzzzzzzzzzzzzzzzz")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri, {'Content-Type' => 'application/json'})
request.body = {
  "channel"    => "#notify-monit",
  "username"   => "monit",
  "text"       => "[#{ENV['MONIT_HOST']}] #{ENV['MONIT_SERVICE']} - #{ENV['MONIT_DESCRIPTION']}",
  "icon_emoji" => ":icon:"
}.to_json
response = http.request(request)
puts response.body

# restart process
if ENV['MONIT_DESCRIPTION'].include?("not")
  system("/etc/init.d/httpd restart")
end

API は Body に JSON で以下のような情報を付加します。

request.body = {
  "channel"    => "#notify-monit",
  "username"   => "monit",
  "text"       => "[#{ENV['MONIT_HOST']}] #{ENV['MONIT_SERVICE']} - #{ENV['MONIT_DESCRIPTION']}",
  "icon_emoji" => ":icon:"
}.to_json

上記の例では #{ENV['MONIT_HOST']} には monit が稼働されるホスト名が入ります。また、#{ENV['MONIT_SERVICE']}#{ENV['MONIT_DESCRIPTION']} には監視するサービスや詳細が入ります。以下は SlackAPI ドキュメントの一部です。

今回は利用していませんが attachments を利用すれば詳細な情報も通知に含めることが出来そうです。 尚、今回は参考にさせて頂いたサイトに倣って Ruby で作りましたが JSON が生成出来れば bash 等でも実装かと思います。

試す

monit が起動した状態で Apache を止めてみましょう。

$ sudo /etc/init.d/monit start
$ sudo /etc/init.d/monit status
monit (pid  25226) is running...
$ sudo /etc/init.d/http stop
Stopping httpd:                                            [  OK  ]

念の為に monit のログを確認しましょう。

[UTC Jan  6 14:56:31] error    : 'httpd' process is not running
[UTC Jan  6 14:56:31] info     : 'httpd' exec: /opt/bin/post_to_slack.rb

post_to_slack.rb が実行されたのが判ります。また、再起動も自動で行われる為、しばらくすると以下のように出力されます。

[UTC Jan  6 14:57:31] info     : 'httpd' process is running with pid 25753
[UTC Jan  6 14:57:31] info     : 'httpd' exec: /opt/bin/post_to_slack.rb

再起動が行われ、プロセスが稼働していることも検知されて通知も合わせ行われます。

Slack には以下のように通知が行われています。
monit による障害検知の通知を Slack に飛ばす: Slackへ通知の動作確認
おお、ちゃんと通知が。

monit おもろい

CONNECTION TESTING

ドキュメント を読んでいたら PID の存在チェックやプロセス名監視だけではなく Web サービスの特定の URL にアクセス出来るか否かのチェックも出来るようです。

例えば以下のように monit の設定を行います。

check host localhost with address localhost
  start program = "/etc/init.d/httpd start"
  stop program = "/etc/init.d/httpd stop"
  if failed
     port 80 protocol http
     request "/icons/apache_pb2.gif"
    then exec "/opt/bin/post_to_slack.rb"
  else if succeeded for 1 cycle then exec "/opt/bin/post_to_slack.rb"
  group httpd

localhost の Apache に対してアクセスを行い正常にアクセスが出来なかったら(if failed)となり exec でスクリプトが実行され、以下のようなログが記録されます。

[UTC Jan  6 15:27:08] error    : 'localhost' failed, cannot open a connection to INET[localhost:80/icons/apache_pb2.gif] via TCP
[UTC Jan  6 15:27:08] info     : 'localhost' exec: /opt/bin/post_to_slack.rb
[UTC Jan  6 15:28:08] info     : 'localhost' connection succeeded to INET[localhost:80/icons/apache_pb2.gif] via TCP
[UTC Jan  6 15:28:08] info     : 'localhost' exec: /opt/bin/post_to_slack.rb

また、Slack にも以下のような通知が発生します。
monit による障害検知の通知を Slack に飛ばす: Slackへ通知が発生した様子
monit の監視状況は以下のような出力となります。

$ /usr/bin/monit -vI
Runtime constants:
 Control file       = /etc/monit.conf
 Log file           = /var/log/monit
 Pid file           = /var/run/monit.pid
 Debug              = True
 Log                = True
 Use syslog         = False
 Is Daemon          = True
 Use process engine = True
 Poll time          = 60 seconds with start delay 0 seconds
 Expect buffer      = 256 bytes
 Mail from          = (not defined)
 Mail subject       = (not defined)
 Mail message       = (not defined)
 Start monit httpd  = False

The service list contains the following entries:

Remote Host Name      = localhost
 Group                = httpd
 Monitoring mode      = active
 Start program        = '/etc/init.d/httpd start' timeout 30 second(s)
 Stop program         = '/etc/init.d/httpd stop' timeout 30 second(s)
 Existence            = if does not exist 1 times within 1 cycle(s) then restart else if succeeded 1 times within 1 cycle(s) then alert
 Port                 = if failed localhost:80/icons/apache_pb2.gif [HTTP via TCP] with timeout 5 seconds 1 times within 1 cycle(s) then exec '/opt/bin/post_to_slack.rb' timeout 0 cycle(s) else if succeeded 1 times within 1 cycle(s) then exec '/opt/bin/post_to_slack.rb' timeout 0 cycle(s)

System Name           = system_ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal
 Monitoring mode      = active

----------------------------------

おお。

その他にも…

特定のファイルのチェックサムやファイルの中身などをチェックすることが出来るようなので目的に応じて使い分けましょう。

ということで…

メール通知はもう古い!と言うわけではありませんが Chat による運用が選択肢の一つとなった昨今で monit のような従来から利用されているツールで Slack への通知がお手軽に行えるのは嬉しいですね。ただ、メールと同様に頻繁に通知が飛んでしまうとオオカミ少年のようになってしまい通知の意味が無くなってしまいますので通知を飛ばすシチュエーションや通知先は吟味したいですな。

あと monit はプロセスをたたき起こすだけのツールではなくて監視ツールとしての側面もあると思いますので適材適所で使っていければと考えています。

元記事はこちらです。
monit による障害検知の通知を Slack に飛ばすメモ