Muninの動作を詳細解説してみる

## この記事について
以下の記事を清書したものになります。
内容は当時(2014.11.26)のものとなります。
https://blog.mikalab.info/archives/644092.html

## Muninの起動と処理のフロー
Munin全体は以下の図のように、cronによりキックされ、munin-cronにより順番にスクリプトが実行されます。
また、エージェントとしてmunin-nodeを監視対象へインストールし、デフォルトで4949/tcpをLISTENします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53593/413e7397-5ee4-4361-1cfd-71f1fac3aac8.png)

## 設定ファイル
Muninは、以下の設定ファイル、およびディレクトリを使用します
- `/etc/munin/munin.conf`
- `/etc/munin/conf.d`

### Nodeの設定
Nodeは、以下の書式で設定します
```
[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`によりプロセスの優先順位を下げられている。
1. 各スクリプトが異常終了した場合でも、munin-cronは正常終了してしまう為、`|| exit 1`により明示的に戻り値を定義している。 

## 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です。
シンプルかつ小さなコードですが、生成するページが多いため、多重ループが多く、munin-cronの実装からも、全体的に重たいスクリプトである事が伺えます。

## 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-nodeは、プラグインと標準入出力でやり取りし、結果をMuninへ返します。

### プロトコル及び実装
MuninとMunin-nodeは、TCPソケット用いて通信します。
よって、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と標準入出力を介してやりとりします。
標準入出力を扱うことができ、先のプロトコルに準ずるアウトプットをするのであれば、特に言語を問いません。
実際、自分の場合ShellScriptやPHPでプラグインを実装する事が多いです。

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

muninプラグインを作成 - SEEDS BLOG 
http://www.seeds-std.co.jp/seedsblog/671.html

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

### プラグインのデバッグ
プラグインのデバッグには、プラグインを単体で実行する`munin-run`コマンドが用意されています。
ここでは`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
```