こんにちは、cloudpack津村です。

冗談はさておき、久々に Munin を調べたのですが、みんなプラグインを書いて満足してるようなので、体力勝負で割りとガチで解説してみます。
Muninとはいえ人の子なので、運用面などについてもソースコードから紐解いてみました。
ちゃんとMuninの動作しってる人、どれだけ居るんだろ?

事前準備

ここでは、CentOS6のAMIにepelリポジトリをインストールし、そこからmuninをインストールします。
まず、CentOS6系のAMIを適宜用意します。
この際、謝罪(SELinux)、武装解除(iptables, ip6tables)、及び更新(yum update)を済ませておいてください。

EPELリポジトリをインストールします。CentOS6系の場合、el6のrpmをインストールします。
EPEL – FedoraProject

尚、以下のコマンドを実行すると、明示的に指定(--enablerepo=epel)されない限り、epelリポジトリを参照しなくなります。

# mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.orig
# cat /etc/yum.repos.d/epel.repo.orig | sed -e "s/enabled=1/enabled=0/" | tee /etc/yum.repos.d/epel.repo

今回はソースコードを軽く読む事が目的なので、とりあえず本体をインストールします。
もしWebUIを観たい場合は、事前にApacheをインストール・設定しておきます。

# yum --enablerepo=epel install munin munin-node -y

Muninの起動と処理のフロー

詳細は後ほど解説しました、Munin全体は以下の図のように、cronによりキックされ、munin-cronにより順番にスクリプトが実行されます。
また、エージェントとしてmunin-nodeを監視対象へインストールし、デフォルトで4949/tcpをLISTENします。
Munin徹底入門: 起動と処理のフロー

Munin設定ファイル

munin.confは、以下のファイル、及びサブディレクトリを使用します。

# less /etc/munin/munin.conf
(snip)
# (Exactly one) directory to include all files from.
includedir /etc/munin/conf.d

次に、Configファイルには以下のようにホストを定義します。
Groupsの定義については、munin-updateにて紹介します。

[localhost]
    address 127.0.0.1
    use_node_name yes

CRONによる起動

munin-cronは、以下のcronファイルより5分毎にコールされます。
5分より短くする事も可能ですが、RRD Toolsが5分毎に描画するよう設定されている為、特に意味を成しません。

# ls -lah /etc/cron.d | grep munin
-rw-r--r--   1 root root  113 2014-10-26 15:22 munin
# cat /etc/cron.d/munin
*/5 * * * *     munin test -x /usr/bin/munin-cron && /usr/bin/munin-cron

munin-cron

munin-cronの実体はShellScriptであり、/usr/bin以下に配置されています。

# ls -lah /usr/bin | grep munin
-rwxr-xr-x   1 root root   4.2K 2014-10-26 15:22 munin-check
-rwxr-xr-x   1 root root    650 2014-10-26 15:21 munin-cron
-rwxr-xr-x   1 root root   3.2K 2014-10-26 15:21 munindoc

# cat munin-cron | grep -v ^#
/usr/share/munin/munin-update $@ || exit 1
/***-> Configのロードと新規ノード・プラグインのチェック、及びRRDへの反映 ***/
/usr/share/munin/munin-limits $@
/*** -> munin-htmlの為の値チェック ***/
nice /usr/share/munin/munin-html $@ || exit 1
/*** -> HTML生成 ***/
nice /usr/share/munin/munin-graph --cron $@ || exit 1
/*** -> RRDからのグラフ生成 ***/

このスクリプトのポイントは2点です。

  1. 負荷の高いmunin-html、munin-graphについては、明示的にプロセスの優先順位を下げる為、niceコマンドが用いられる。
  2. 各スクリプトが異常終了した場合でも、munin-cronは正常終了してしまう為、「|| exit 1」により明示的に異常終了及び戻り値を定義している。

また、munin-cronを手でコールした際に引数を引き渡せるようにもなっています。

munin-update

munin-updateの実体はPerlScriptであり、Configファイルのパースと初期化、及びmunin-nodeから値の取得を行います。

# less /usr/share/munin/munin-update
use Munin::Master::Update;
use Munin::Master::Config;
(snip)
sub main {
    exit_if_run_by_super_user();
    configure();
(snip)
sub configure {
           $config->parse_config_from_file($f);
}

上記のようにPerlModule及びメソッドがコールされていますが、これらは全て以下のディレクトリに格納されています。
/usr/share/perl5/vendor_perl/Munin/

Configファイルのパース部分のコメントを読むと、以下のように定義ができるようです。
また、ここではValidateを行っておらず、Configファイルをロードする際に各スクリプトでValidateを行っているようです。

sub _split_config_line {
    #   Group;address
    #   Group;Group;address
    #   Group;Host:address
    #   Group;Host:if_eth0.in.value
    #   Group;Host:snmp_foo_if_input.snmp_foo_if_input_0.value
    # Now left with (1:1 with cases above)
    #   address
    #   address
    #   Host:address
    #   Host:if_eth0.in.value
    #   Host:snmp_foo_if_input.snmp_foo_if_input_0.value
}

munin-limit

munin-htmlの為の値チェックのようです。munin-graphには関係しません。
スクリプトのコメントには、以下のようにあります。

# less /usr/share/munin/munin-limits
munin-limits - A program to check for any off-limit values

munin-html

munin-htmlは、出力先ディレクトリに表示用のHTMLを生成します。実体は同じくPerlScriptです。
シンプルかつ小さなコードですが、生成するページが多い為多重ループが多く、先のniceコマンドからも全体的に重たいスクリプトである事が伺えます。

munin-graph

munin-graphは、出力先ディレクトリに表示用のグラフ画像を生成します。実体は同じくPerlScriptです。
実装としては小さくシンプルで、munin-updateが生成したRRDファイルを、RRD Toolをコールして描画します。
RRD Toolのパラメータの定義はmunin-updateが担っており、munin-node経由でプラグインへ初回アクセスした際、及び描画する値(カラム)が増えた際に更新されます。
よって、munin-graphは純粋にRRD Toolをコールし、グラフ画像を所定のディレクトリに出力するのみです。

muninのロックファイル

muninは5分毎に定期的にコールされますが、値のポーリング及び描画等で実行時間が5分を超える場合があります。
このため、/var/run/munin以下にロックファイルが出力されます。
但し、何かの手違いでmuninを殺してしまった場合(他殺)、ロックファイルが残りデッドロックとなります。
この際、/var/run/munin以下のファイルを削除する事で復活します。

muninの実行権限

muninは全体的にmunin専用ユーザで動作しています。
epelリポジトリからインストールした場合、以下のように設定されていました。

# cat /etc/group | grep munin
munin:x:499:
# cat /etc/passwd | grep munin
munin:x:498:499:Munin user:/var/lib/munin:/sbin/nologin

また、munin-updateに以下のような定義があり、rootで実行する事は許されていません。

sub main {
    exit_if_run_by_super_user();
}

この関数が実行された場合、以下のコメントが表示され異常終了します。

This program will easily break if you run it as root as you are
trying now. Please run it as user 'nobody'. The correct 'su' command
on many systems is 'su - munin --shell=/bin/bash'
Aborting.

また、仮にrootで実行できた場合、RRDファイルなどのパーミッションが変わってしまい、muninユーザから上書きする事ができなくなります。
(過去にやっちゃいました。テヘペロ☆)

Munin-node

Munin-nodeとは、監視対象へインストールするエージェントとなります。
実装は同じくPerlScriptですが、4949/tcpをオープンする為、注意が必要です。
尚、ポート及び簡易的なACLを、Configファイルで定義可能です。

プロトコル及び実装

MuninとMunin-nodeは、暗号化されていない独自のプロトコルを用いて、複数のプラグインを1セッションでconfig, fetch通信します。
よって、telnet及びncコマンドで手で操作する事が可能です。

以下、munin-nodeにtelnetでアクセスし、cpuプラグインのconfig及びfetchを行った例です。

# telnet localhost 4949
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
# munin node at localhost.localdomain
 
# Unknown command. Try cap, list, nodes, config, fetch, version or quit
list
cpu df df_inode entropy forks fw_conntrack fw_forwarded_local fw_packets if_err_eth0 if_eth0 interrupts irqstats load memory netstat open_files open_inodes postfix_mailqueue postfix_mailvolume proc_pri processes swap threads uptime users vmstat
config cpu
graph_title CPU usage
graph_order system user nice idle iowait irq softirq
graph_args --base 1000 -r --lower-limit 0 --upper-limit 100
graph_vlabel %
graph_scale no
graph_info This graph shows how CPU time is spent.
graph_category system
graph_period second
system.label system
system.draw AREA
system.min 0
system.type DERIVE
system.info CPU time spent by the kernel in system activities
user.label user
user.draw STACK
user.min 0
user.type DERIVE
user.info CPU time spent by normal programs and daemons
nice.label nice
nice.draw STACK
nice.min 0
nice.type DERIVE
nice.info CPU time spent by nice(1)d programs
idle.label idle
idle.draw STACK
idle.min 0
idle.type DERIVE
idle.info Idle CPU time
iowait.label iowait
iowait.draw STACK
iowait.min 0
iowait.type DERIVE
iowait.info CPU time spent waiting for I/O operations to finish when there is nothing else to do.
irq.label irq
irq.draw STACK
irq.min 0
irq.type DERIVE
irq.info CPU time spent handling interrupts
softirq.label softirq
softirq.draw STACK
softirq.min 0
softirq.type DERIVE
softirq.info CPU time spent handling "batched" interrupts
steal.label steal
steal.draw STACK
steal.min 0
steal.type DERIVE
steal.info The time that a virtual CPU had runnable tasks, but the virtual CPU itself was not running
guest.label guest
guest.draw STACK
guest.min 0
guest.type DERIVE
guest.info The time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
.
fetch cpu
user.value 4118
nice.value 1598
system.value 5319
idle.value 510449
iowait.value 2031
irq.value 0
softirq.value 2
steal.value 1660
guest.value 0
.
quit
Connection closed by foreign host.

プラグインの実装

munin-nodeは、4949/tcpでmuninからのアクセスを待った上で、コマンドに応じ、各プラグインをコールし標準出力をmuninに戻します。
よって、プラグインはソケット通信を意識する必要はありません。
標準入出力を扱うことができ、先のプロトコルに準ずるアウトプットをするのであれば、特に言語を問いません。
実際、自分の場合ShellScriptやPHP Scriptでプラグインを実装する事が多いです。

プラグインの実装方法については、こちらのエントリに詳しく書かれていますので、参照してください。
muninプラグインを作成 – SEEDS BLOG

昔(2007年頃)は情報も少なく、Muninも流行り始めだったので、自分で試行錯誤した覚えがあります。

プラグインのデバッグ

プラグインのデバッグには、プラグインを単体で実行するmuninコマンドが用意されています。
ここではmunin-runコマンドを用いて、cpuプラグインをデバッグしてみます。

# munin-run cpu autoconf
yes

# munin-run cpu config
graph_title CPU usage
graph_order system user nice idle iowait irq softirq
graph_args --base 1000 -r --lower-limit 0 --upper-limit 100
graph_vlabel %
graph_scale no
graph_info This graph shows how CPU time is spent.
graph_category system
graph_period second
system.label system
system.draw AREA
system.min 0
system.type DERIVE
system.info CPU time spent by the kernel in system activities
user.label user
user.draw STACK
user.min 0
user.type DERIVE
user.info CPU time spent by normal programs and daemons
nice.label nice
nice.draw STACK
nice.min 0
nice.type DERIVE
nice.info CPU time spent by nice(1)d programs
idle.label idle
idle.draw STACK
idle.min 0
idle.type DERIVE
idle.info Idle CPU time
iowait.label iowait
iowait.draw STACK
iowait.min 0
iowait.type DERIVE
iowait.info CPU time spent waiting for I/O operations to finish when there is nothing else to do.
irq.label irq
irq.draw STACK
irq.min 0
irq.type DERIVE
irq.info CPU time spent handling interrupts
softirq.label softirq
softirq.draw STACK
softirq.min 0
softirq.type DERIVE
softirq.info CPU time spent handling "batched" interrupts
steal.label steal
steal.draw STACK
steal.min 0
steal.type DERIVE
steal.info The time that a virtual CPU had runnable tasks, but the virtual CPU itself was not running
guest.label guest
guest.draw STACK
guest.min 0
guest.type DERIVE
guest.info The time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.

# munin-run cpu
user.value 4423
nice.value 2568
system.value 5571
idle.value 550230
iowait.value 2155
irq.value 0
softirq.value 2
steal.value 1795
guest.value 0

おわりに

Muninと言えば@zembutsuさんですよね。
かくなる僕も、@zembutsuさんだと思います。
そして、恐らく日本でMuninエバンジェリストと言えば、@zembutsuさんです。
なので、ミスターMuninと言えば@zembutuさんだと思います。

…とはいえ、僕も一時期より使う頻度は落ちましたが(そもそも皆んな監視してくれてるのであまり困ってない。あと冗長化されてて困ってない。)、MRTGのように設定が面倒だったり、HinemosやZABBIXのように大それたソリューションを入れるのであれば、やっぱMuninかなぁ、と思いますはい。
あ、通知エージェントざびたんは別ですよ?(一応、初代のパッチコミッターしてました。。。)

元記事はこちらです。
Munin徹底入門〜ねぇMunin、ソース頂戴♡〜 – みからぼ。