被制御デーモンの登録 (monitrc)

どのデーモンをどのように制御するかは、MONIT の主設定ファイルである /etc/monitrc で管理する。デフォルトのサンプルを読めば分かるので、わかりにくい部分だけ述べる。なお、 monitrc ファイルは必ず、持ち主しか読み書きできないようパーミションを設定する。つまり、普通は root で管理するので、root:root 600 が妥当だ。

set logfile ディレクティブ

set logfile /var/log/monit

上記の場合、ログは syslog デーモンを介さずに MONIT が /var/log/monit に直接書き出すが、

set logfile syslog facility log_local1

こちらの方法を勧める。この場合、MONIT はログを syslog に渡し、実際の書き出しは syslog に任せる。facility は 'LOCAL1' などでなく上記のように書かなくてはいけない。
呼応して syslog.conf に次のように追記すれば、 MONIT のログはすべて syslog 経由で /var/log/monit に送られる:

local1.* /var/log/monit

なお、この場合、ログファイル monit は際限なく膨らんでいくので、 logrotate の設定もしたほうがいいだろう。

set mail-format ディレクティブ

ここで指定したフォーマットは、各サービスで個別指定しなかった時のデフォルト設定となる。個別設定の中でも指定すれば、デフォルトと重複する項目 (例えばsubject) は個別側の項目で上書きされる。フォーマットの中では MONIT の定義する以下の変数が利用できる。

$EVENT alert のトリガとなったイベント。`checksum failed', `connection failed', `timeout', `does not exist' など
$SERVICE monitrc で定義に使用されたサービス名
$DATE 起こった日時
$HOST MONIT の稼働しているホストの名前
$ACTION MONIT の取った行動。 alert, monitor, unmonitor, start, stop, restart, exec のいずれか
$DESCRIPTION どういう条件がチェックに引っ掛かったか

設定例:

from: monit@host1.hoge.cxm
subject: $SERVICE $ACTION on $HOST
message: Action:$ACTION taken
because $DESCRIPTION
 
    Date:   $DATE
    Host:   $HOST
    Event:  $EVENT

ヘッダ区分や message: 直後のスペースは除き、本文内のスペース、タブ、改行などは記述の通りに送信される。

set alert ディレクティブ

上記にも絡むが、どういう時にだけ報告メールを送るかというデフォルトセッティングを規定しておくことができる。この設定をしない場合、MONIT 自体の終了や起動、再起動の時にも報告が来る。これを鬱陶しいと思うのは筆者だけだろうか。レポートするイベントを下記のように明示しておけばそれをやめさせることができる。

オフィシャルドキュメントでも man でも、どのテストに失格するとどのイベントが発生するかが明記されていない。あらかたのイベントは察しがつくが、実験によって今のところ判明している対応を挙げておく;

下記では読みやすいようにイベント部を 2 行に分割しているが、実際にやってみると 1 行に連ねなければならないようだ。このまま書いてしまうと、最初の改行まで (例では nonexist から checksum まで) しか認識されない。 only は意味的に読みやすくするためだけのノイズキーワードであり、実際はMonitによって無視される。もっと言えば、実際は on もノイズだ。

set alert hoshu@host1.hoge.cxm only on {
  nonexist, invalid, timeout, resource, checksum,
  connection, permission, uid, gid
}

また、 Monit 4.9 からは、警告メールを送らない除外イベントを指定することも可能になった。例えば、下記の単純な記述で、 Monit 自体の start や restart (`Monit INSTANCE changed' イベント) を除く全てのイベントでアラートメールを送らせることができる;

set alert hoshu@host1.hoge.cxm not on {
  INSTANCE
}

set httpd ディレクティブ

MONIT内蔵の httpd サーバの設定。"monit start all", "monit stop service " など、ほとんどの有用なコマンドライン命令で内蔵 httpd が使われているため、WEB インターフェイスを使わないからといって内蔵 httpd サーバを OFF にするわけには行かない。

設定例1 (ローカルホスト上のみ許可):

set httpd port 2812 and
  use address localhost
  allow 127.0.0.1

`monit status' などのコマンドラインを使うために allow の 127.0.0.1 (または localhost) は常に必須といえるだろう。このように `use address localhost' を指定すると、内蔵 httpd はループバックアドレスにしか聞き耳を立てなくなるので、他のマシンからアクセスすることは基本的に不可能となる。

設定例2 (特定サブネットを許可、クリアテキストパスワード使用):

set httpd port 2812 and
  allow 127.0.0.1
  allow 192.168.0.0/24
  allow hoshu:secret

他のホストからもアクセスしたい場合は、設定例 2 のようにそのIPアドレス (またはリゾルブ可能な名前) も指定する。ご覧の通りサブネットマスク (255.255.255.0 のようなオクテット書式, CIDR とも可) で範囲指定することも可能だ。その時には use 句は指定してはいけない。書かないことで、内蔵 httpd はマシン上で機能している全てのインターフェイスアドレスをリッスンするようになる。 4 行目のようにユーザ名と生パスワードを使って認証を掛けることもできる。パスワードを生で書いておくのに抵抗がある人は、次のやり方を使う。

設定例3 (特定サブネットを許可し、外部パスワードファイル使用):

set httpd port 2812 and
  allow 127.0.0.1
  allow 192.168.0.0/24
  allow md5 /etc/monitpasswd hoshu
  allow dummyuser:b77e86f629174381d57e8f1895732d

設定例 3 ではサブネット等は設定例 2 と同じ。ただし、外部パスワードファイル (ここでは /etc/monitpasswd) を使用している点が異なる。パスワードを暗号化して格納しておきたい場合には、こうして外部ファイルを指定しなければならない。上記で `md5' としているところはパスワードの暗号化に使ったハッシュアルゴリズムで、お勧めはしないがより簡易な crypt も使用できる。外部ファイルは必ず Apache の htpasswd 書式のもので、行単位で user :password の記述されたファイルとなる。password 部分は md5 ハッシュされていればいいと言うわけではなく、`$id$salt$digest' の形になっていなければならない。

外部パスワードファイルを作る一番手早な方法は、まさに Apache パッケージの提供する htpasswd コマンドそのものを使うことだ。詳しくは htpasswd の man を参照して頂きたいが、 /etc/monitpasswd ファイルを初めて作成してパスワードを md5 ハッシュした hoshu ユーザの定義を書き込むなら;

root# htpasswd -c /etc/monitpasswd  hoshu

といった塩梅だ (md5 は htpasswd のデフォルトハッシュ方式なので -m オプションは不要)。

「設定例 3 の 5 行目はいったいナンダ?!」という疑問は当然至極。 MONIT は実は、クリアテキストの定義がないと `monit status' などのコマンドライン命令が受け付けられない。そこでダミーのクリアテキスト認証定義を少なくともひとつは書いておく必要があるのだ。パスワードは複雑怪奇で実際にはタイプ困難なもの (例は`detarame' を md5sum ユーティリティでハッシュしたもの) にしておくべきだ。

例 3 の dummyuser が万が一ログインできてしまった場合の安全性をさらに高められないかと、 `allow dummyuser:xxx... read-only' としてみたのだが、やはりコマンドラインの `monit restart apache' などが機能しなくなってしまった。 read-only は WEB インターフェイスの時だけでなくコマンドラインにも作用するようだ。

バージョン4.2.1 で試した時にはパスワード認証に問題があった。"allow host" と "allow password" は AND 条件ではなく、 OR として処理されていたのだ。つまり、パスワード認証の行が定義されていると、ユーザ名とパスワードさえ正しく入力されれば、あらゆるホストからアクセスできた。この問題はバージョン4.7 では解決されており、アドレスとパスワードの両方を満たさない限りアクセスは受け入れられなくなっている。

なお、 MONIT 4.0 以前は "httpd" というサービス名は内蔵 httpd に予約されていて apache などのコントロール定義名として monitrc 内で使えなかったが、その制限はもうなくなった。

include ディレクティブ

monitrc ファイル内で

include file

という記述が出てくると、その位置に別の設定ファイルが読み込まれる。これによって、設定ファイルを幾つかに分割でき、管理がしやすくなる。例えば、 monitrc 本体にはグローバルな設定項目だけを書いておき、被制御デーモン個別の設定はそれぞれ別のファイルから読み込ませるというやりかたができる。標準サンプルでは /etc/monit.d/ にある全てのファイルを読み込む例が書かれている。ただし、サンプルのような

include /etc/monit.d/*

ではディレクトリにある全く全部のファイルを読んでしまうので、実用的には

include /etc/monit.d/*.rc

のようにして拡張子で限定しておいた方が便利だ。こうすれば、例えば分割ファイル hoge.rc の内容をしばらく使いたくない時に、 hoge.back などにリネームして読み込み対象から外すことができる。この場合の monit.d ディレクトリ下に置くファイルのパーミションは特にチェックされないようだが、メインの設定ファイルと同じく root 所有の 0600 とするのが最善だ。

PostgreSQLコネクションテスト

筆者の書いたプロトコルテストなので詳しく説明しておかなければなるまい。 MONIT 4.7 以前の場合には pgsqlパッチ を当てないと使えないテストだ。

MONIT の機能のひとつ、コネクションテストとは、特定のサービスのポートに対してコネクションを開き、返事が返ってくるかどうかでサービスの稼働を確認するもの。「ポート」は TCP か UTP のポートでもいいし、 UNIX ソケットに対してテストを行うこともできる。まず、MONIT 一般の書式にさらっと触れておかなくてはならないだろう;

DNSサービスの例:

if failed host localhost port 53 type udp protocol dns with timeout 10
  then restart

上記の host 句は省略でき、その時にはデフォルトで localhost が対象となる。 type は デフォルトが TCP で、TCP の場合ならば省略しても構わない。 protocol は APACHE-STATUS, DNS, DWP, FTP, HTTP, IMAP, LDAP2, LDAP3, MYSQL, PGSQL, NNTP, NTP3, POP, POSTFIX-POLICY, RDATE, RSYNC, SMTP, SSH, TNS のいずれか (MONIT 4.8 時点)、省略すると汎用のテストが用いられる。 timeout はサービスから返事が来るまでの猶予 (秒) で、デフォルトは 5 秒だ。

`pgsql' テストも構文の要領は上記の例と同じだが、ちょうどいいので、PostgreSQL の開く UNIX ソケットに対して接続テストを行う例を示すことにする;

PostgreSQLサービスの例 (対UNIXソケット):

if failed unixsocket /tmp/.s.PGSQL.5432 proto pgsql
  with timeout 15
  then restart
PGSQLテストのための下準備

PostgreSQL は接続するだけでも認証や権限を要求するので、コネクションテストを稼働させる前に下準備が必要だ。やらなくても、PostgreSQL が返答として認証を要求してきたり「そんなユーザはいない」というエラーを返した時には、それは少なくとも「稼働」を意味するので成功とみなすように作ったが、Postgres のログを極めてきれいに保つために (そもそも、このモジュールを書いたのはそれが動機) 以下をやっておくことをお勧めする。 root という DB ユーザを作るのは、MONIT が通常 root 権限で動作しているから。以下の例は PostgreSQL 8.x の場合で、バージョン 7 シリーズではアドレスのサブネットの書き方などが異なるので注意;

  1. データベースユーザ `root' を作る。
  2. 上記 root の所有する `root' という名前のデータベースを作る。内容はカラで構わない。
  3. pg_hba.conf に以下を追加する;
    host   root  root  127.0.0.1/32  trust          <= TCPポートテストの場合
    local  root  root                ident sameuser <= UNIXソケットテストの場合

depends on ディレクティブ

"depends on" というキーワードを使用して、「あるサービスに $EVENT が起きたら別のサービスを action する」という動作が可能。

注意!:
主プロセスの起動や再起動時 (厳密には主プロセスを起動する前) には、依存先プロセスも起動/再起動される構造なので、ひとつのサービスを複数のサービスから依存参照するのは避けたほうがよい。依存関係のループにも注意。ループが検出された場合 MONIT は警告を出して即 exit する。

デフォルトサンプルに倣った例:

######## 主プロセス ########
check process apache with pidfile /var/run/httpd.pid
  start program = "/etc/service/httpd start"
  stop program = "/etc/service/httpd stop"
  if failed host localhost port 80
    with timeout 10 seconds then restart
  if cpu > 60% for 2 cycles then alert
  if cpu > 80% for 5 cycles then restart
  if loadavg(5min) > 10 for 8 cycles then restart
  if 3 restarts within 5 cycles then timeout
  depends on apache_bin
  alert hoshu@host1.hoge.cxm on {
   restart, stop, timeout
  }
######### 依存先 #########
check file apache_bin with path /usr/sbin/httpd
  if failed checksum then unmonitor
  if failed permission 755 then unmonitor
  if failed uid root then unmonitor
  if failed gid root then unmonitor
  alert hoshu@host1.hoge.cxm on {
    checksum, permission, uid, gid, unmonitor
  } with mail-format {
    subject: Alarm! $SERVICE hucked
  }

「apacheバイナリが改変されたことを発見したら、apache プロセスをストップする」のかと 思いきや 、実際やってみたら apache プロセスは unmonitor されるだけだった。つまり、依存先に異常があった際には、依存先で指定した action (この場合ummonitor) が、主プロセスにも適用されるのだ。 unmonitor されたプロセスは、停止すれば再び起動をかけられることはないが、能動的に「緊急停止」されるわけではない。緊急停止したい場合は、依存先の "unmonitor" をすべて "stop" に換える。「apache_bin ファイルを stop する」というのは文脈的にはおかしいが、問題ではない。

checksumを適用している場合の注意:
MONIT管理しているデーモンを up2date などでバージョンアップする際には、前もって MONIT を止める。さもないと、MONIT からchecksum エラーが報告される。止め忘れて checksum エラーでサービスが stop や unmonitor されてしまったら、MONIT を再起動するか、
monit restart allmonit monitor <service> すれば正常な監視状態に戻る。

group ディレクティブ

上記に網羅したディレクティブ以外で便利なものに group がある。例えば、qmail のページで解説した qmail, qsmtpd, qpop3d をそれぞれ monitrc に定義し、いずれにも

group qmaild

というディレクティブを入れておくと、それら 3 つのプロセスをまとめて止めたりスタートすることも可能となる。例えば止める場合なら:

root# monit -g qmaild stop all

とコマンドすれば qmail, qsmtpd, qpop3d がいっぺんにストップできる。最後の all を忘れずに。

every ディレクティブ

Monit をデーモンモードで動作させている時、或る check ブロックだけチェック頻度を少なくしたい場合に使用する。例えば、グローバルセクションで

set daemon 60

としている時、 apache のチェックだけ

check process apache with pidfile /var/run/httpd.pid
  every 2 cycles
  ...

としておくと、この評価は 120 秒間隔で行われるようになる。

デーモンがchkconfig非対応の場合の対処法

付録として、そのデーモンの起動スクリプトが chkconfig 非対応だったり、そこまで rcスクリプトを作り込みたくない場合の対処法を取り上げる。実を言うと、筆者はつい最近まで、chkconfig 対応のデーモンまで全部、こちらのやり方で管理していたのだが、考え直してみると、無駄な手間だったのでやめた。

当該サービスのrcスクリプトを配置

MONIT 被制御デーモンを一括管理するディレクトリを作り、各デーモンの起動スクリプトをそこに配置する。例えば /etc/service ディレクトリにまとめる。

root# mkdir /etc/service
root# mv /script/of/service /etc/service
root# chmod 755 /etc/service/service

monit起動スクリプトの手直し

当手法では、システム起動時にまずシステム init から monit が起動され、次に monit が /etc/service 内のサービスを立ち上げる。 rcスクリプトをこのようにシステム init ディレクトリ以外で管理する場合には、MONIT 自体の chkconfig のパラメータを調整する必要があるかもしれない。例えばメールサーバを MONIT で管理する場合、他のデーモンの起動関連メッセージが管理者にメールで送られるケースがあるので、MONIT の起動がデフォルトの 98 では遅すぎる。それには、MONIT 自体の起動スクリプトの以下の部分を変更:

# chkconfig: 345 85 02

monitrc は通常通りに準備する。

被制御デーモンをKILLする追加のrcスクリプト

あとひとつ問題を解決しなくてはならない。 MONIT は、 "service monit stop" しても、 MONIT デーモンそのものが終了するだけであって、 exit する前に被制御デーモンを自動的に終了してくれるわけではない。標準の sysvinit とは違って、マシンの停止/再起動や、シングルユーザモードに移る際、それらをどうやって自動停止させるかが問題となる。そこで、被制御サービスを終了させるためのスクリプトを作った。2 種類あるので、どちらか好きな方を、標準サービスとしてシステムの init に登録して使っていただきたい。

案1: MONIT 自体を使って終了させる手法

monitjob スクリプトは、被制御デーモンを止めるために MONIT 自体を使用しているので、このスクリプトは MONIT の KILL より前に stop で呼び出されなくてはならない。そのために /etc/monitrc のchkconfig パラメータを調整して、 MONIT の終了を遅らせる必要がある。 monitjob の KILL 順位は 02 にしてあるので、以下の部分を少なくとも 03 以上に。

# chkconfig: 345 85 03

案2: 被制御サービスの起動スクリプトで直接 STOP する手法

monitjob2 は、あとから考えた、もう少しスマートな手法のスクリプト。被制御デーモンの起動スクリプトを集めたディレクトリ (当記事の例では /etc/service) を find でスキャンして、パーミション 755 のレギュラーファイルだけを "stop" 引数を付けて次々にコールしている。こちらは MONIT 自体の生き死にには関係なく動作するが、どちらかといえば MONIT が終了されてから stop を掛けたほうが好ましいので、 KILL の優先順位は 35 にしてある。 MONIT 自体の chkconfig の KILL パラメータを遅らせる必要はない。

キラースクリプトの組み込み

案1、2 とも chkconfig 互換のフォーマットで書いてあるので、以下のようにすれば各 run レベルに組み込むことができる:

root# cp <monitjob または monitjob2> /etc/rc.d/init.d/monitjob
root# chmod 755 /etc/rc.d/init.d/monitjob
root# chkconfig --add monitjob

なお、monitjob, monitjob2 とも、 start を引数に付けて呼ぶと /var/lock/subsys にダミーのロックファイルを touch するようにしてある。 init は、 initレベル 0, 1 や 6 (つまりシャットダウンやシングルユーザモードや再起動) へ移行する際のサービス自動停止の時、 rcファイルと同名のロックファイルの存在していないサービスは KILL してくれない。よって、上記手順のあと再起動せずに次回の終了時にしっかり働いてもらいたいのなら:

root# service monitjob start

を一度実行しておこう。次回のマシン起動や rcレベル推移以降には、monitjob スクリプトがまず start で呼ばれて自動的にロックファイルを確保するので、以後は手動でロックファイルを作る必要は無い。