MuninのグラフをCGIリアルタイム生成して負荷軽減する方法(munin-cgi-graph)

MuninのグラフをCGIリアルタイム生成して負荷軽減する方法(munin-cgi-graph) はてなブックマーク - MuninのグラフをCGIリアルタイム生成して負荷軽減する方法(munin-cgi-graph)


munin-cgi-graph の導入方法をまとめました。

◆管理台数が増えたら、グラフの動的生成で負荷軽減

汎用リソース・モニタリングツールの Munin 。監視対象ノードを増やしていくと突き当たるのが、負荷対策です。Munin は各種のリソースをグラフで表示する為に、5分に1回画像を生成します。管理台数が1台であれば、1度に生成される画像は約40程度です(標準的なRHELで自動認識されるプラグイン数、かつ、’by day’ のみ。実際には、’by week’ ‘by month’ ‘by year’の画像生成も重なると、倍の画像を生成する事になります)。

Munin 管理ノードが1台であれば、40個の PNG 形式の画像(グラフ)を生成することは、今日日さほどサーバ・マシンの負荷にはなりません。ですが、管理対象が10台になれば、1度に生成されるグラフは約400個。100であれば、約4000個になってしまいます。

Muninのstatグラフ。CGI化後は、サーバに与える負担も減少

管理ノードが増えれば、比較的処理性能の高いイマドキの CPU でも、常時4000個程度の画像を生成するのは、負荷になりかねません。というより、十分負荷になります。Munin は、5分ごとに rrdtool で画像生成を行います。このとき呼び出されるスクリプト munin-graph は nice 付きで実行されるので、サーバ内の他の処理よりも優先されがちです。そのため、多くのノードが増えると、Munin の入っているサーバが重たくなってしまいます。

Munin で専用のサーバをたてていればともかく、他のサービスと併用してる場合は無視の出来ないレベルでしょう。まして、常時誰かがグラフを参照しているならともかく、偶にしか参照しないのであれば、リソースが無駄になってしまいます。

そこで有効なのが、グラフを動的に生成する方法です。Munin には動的に画像を生成する munin-cgi-graph という CGI が付属しています。Munin 標準の cron でグラフを定期生成する方法から、画面を表示したときに CGI を呼び出す方法に切り替えると、CPU 負荷の大幅な軽減に貢献できます。

一方でデメリットもあります。グラフが都度生成となるため、画面が表示されるまで重たいと感じてしまう事です。慣れてしまえば「こんなものか」と思えるのですが、慣れるまでは「なんとなくモッサリ」した印象を受けてしまうでしょう。また、大人数で画面を同時参照したり、常にグラフ参照している監視環境であれば、cron による定期生成のほうが良いかもしれません。

以下の図は、とあるMuninサーバでの導入前(左)と導入後(右)の Load Average 比較です。対象ノード約200。CPUは Xeon L3426 1.88GHz(4core)、メモリ4GB、HDDはSATA 7,200rpm。

平均して 4 前後の Load Average が、1以下に下がりました。次は CPU の使用状況です。

これまでは、オレンジ色の nice による処理で、CPU の半分を消費していました。導入後は、ほとんどスカスカな状態です。

では、以下は切り替え方法です(オフィシャル trac の CgiHowto を参考にしました)。

◆作業手順( Munin ver 1.4.6 の場合 )

Munin 1.4.6 かつ、パスは Red Hat Enterprise Linux 5 の場合で記述しています。他のディストリビューションではパスや所有者等が微妙に異なると思いますので、適時読み替えてください。各種ファイルや設定ファイルを編集する場合は、事前にバックアップを取って、何かあれば戻せるようにして作業してくださいね。

※前提知識:Apache の httpd.conf や CGI の実行、linux ファイルシステムの一般的な権限についての理解。

1. Munin の設定ファイルを開き(vi /etc/munin/munin.conf)、以下コメントアウトを削除。記述が無い場合は、追加。

graph_strategy cgi
cgiurl_graph /cgi-bin/munin-cgi-graph

「/cgi-bin~」の箇所は、動的にグラフを生成するスクリプトの実体 munin-cgi-graph に、ブラウザがアクセスするための URL 指定になります。上記の記述では /cgi-bin/ ですが、任意の場所に変更する事も可能です。

2. munin-cgi-graph を cgi-bin に設置

httpd.conf の「 ScriptAlias /cgi-bin/ 」で指定された場所にファイルを置きます(1. で指定した場所が任意のものであれば、その場所に変更します)。ファイルを置かないと、Apache のエラーログには「script not found or unable to stat: /var/www/cgi-bin/munin-cgi-graph」といった記録が表示されます。

# cp /var/www/html/munin/cgi/munin-cgi-graph /var/www/cgi-bin/

ファイル munin-cgi-graph のパーミッションは 755(rwxr-xr-x)です。ScriptAlias で指定したディレクトリでは、CGI が実行可能な状態に httpd.conf の記述を行います(ScriptAlias 指定があれば、デフォルトで動くと思いますが、もし error_log で CGI が実行できないエラーが出る場合は確認点となります)。

3. 所有者やパーミッションの変更

CGI がグラフを生成できるように、所有者の変更を行います。以下の例では「apache」グループにしていますが、これは Apache の実行グループ権限(httpd.conf の Group で定義)が、RHEL では「apache」のためです。もし nobody や他のユーザであれば、そちらに置き換えます。

chgrp apache /usr/share/munin/munin-graph
chgrp apache /var/log/munin /var/log/munin/munin-graph.log
chmod g+w /var/log/munin /var/log/munin/munin-graph.log
chgrp -R apache /var/www/html/munin/
chmod -R g+w /var/www/html/munin/

4. logrotate の設定変更

ログローテート時にファイル所有者が書き換わってしまうので、都度、適切なパーミッションを指定するように設定変更をします。まず、設定ファイルを開きます。

# vi /etc/logrotate.d/munin

次に、ファイルのグループを Apache の実行グループ権限に変更します。デフォルトでは「create 660 munin adm」となっている4箇所を、次のように変更します。

create 660 munin apache

5. CGI 版のバグに対する対処(ここがハマりどころ)

trac に記述されている方法では、実は不完全です。一部の画像を生成(ディスクのようにネストするプラグインや、comparsion系)しようとしても”Use of uninitialized value in substitution (s///) at /var/www/cgi-bin/munin-cgi-graph line 68″や、”Unsuccessful stat on filename containing newline at /var/www/cgi-bin/munin-cgi-graph line 328″ とエラーが出てしまいます。

1.4 系では、本件に対するパッチ情報が公開されていますが、まだ stable 版にマージされていません。そこで、手動でバグに対する対処を行う事になります。

作業前にバックアップを取ります(perlのライブラリのパスは、各々の環境におきかえてください)。対象ファイルは Utils.pm GraphOld.pm HTMLOld.pm の3ファイルです。

# cp -p /usr/lib/perl5/vendor_perl/5.8.8/Munin/Master/Utils.pm \
 /usr/lib/perl5/vendor_perl/5.8.8/Munin/Master/Utils.pm.orig
# cp -p /usr/lib/perl5/vendor_perl/5.8.8/Munin/Master/GraphOld.pm \
 /usr/lib/perl5/vendor_perl/5.8.8/Munin/Master/GraphOld.pm.orig
# cp -p /usr/lib/perl5/vendor_perl/5.8.8/Munin/Master/HTMLOld.pm \
 /usr/lib/perl5/vendor_perl/5.8.8/Munin/Master/HTMLOld.pm.orig

以下 URL を参照して、ファイルを修正します。

#831: HTMLOld.pm.diff – Munin – Trac
http://munin-monitoring.org/attachment/ticket/831/HTMLOld.pm.diff
“HTMLOld.pm”

#832: fix_munin832.patch – Munin – Trac
http://munin-monitoring.org/attachment/ticket/832/fix_munin832.patch
“a/master/lib/Munin/Master/GraphOld.pm” および “a/master/lib/Munin/Master/Utils.pm”

diff の差分は、以下の通りです。左の .orig がオリジナル、右が編集後です。

# diff -bc HTMLOld.pm.orig HTMLOld.pm
*** HTMLOld.pm.orig     2011-02-09 12:03:52.000000000 +0900
--- HTMLOld.pm  2012-02-08 12:21:16.000000000 +0900
***************
*** 932,937 ****
--- 932,942 ----
          $srv{'imgmonth'} = $config->{'cgiurl_graph'} . "/$path-month.png";
          $srv{'imgyear'}  = $config->{'cgiurl_graph'} . "/$path-year.png";

+         $srv{'cimgday'}   = $config->{'cgiurl_graph'} . "/$path-day.png";
+       $srv{'cimgweek'}  = $config->{'cgiurl_graph'} . "/$path-week.png";
+       $srv{'cimgmonth'} = $config->{'cgiurl_graph'} . "/$path-month.png";
+       $srv{'cimgyear'}  = $config->{'cgiurl_graph'} . "/$path-year.png";
+
          if (munin_get_bool($service, "graph_sums", 0)) {
              $srv{'imgweeksum'}
                  = $config->{'cgiurl_graph'} . "/$path-week-sum.png";
# diff -bc Utils.pm.orig Utils.pm
*** Utils.pm.orig       2011-02-09 12:03:52.000000000 +0900
--- Utils.pm    2012-02-07 18:44:37.000000000 +0900
***************
*** 54,59 ****
--- 54,60 ----
 'munin_get_rrd_filename',
 'munin_get_node_name',
 'munin_get_parent_name',
+            'munin_get_node_fqn',
 'munin_get_node_loc',
 'munin_get_node',
 'munin_set_var_loc',
***************
*** 517,522 ****
--- 518,542 ----
 }
 }

+ sub munin_get_node_fqn
+ {
+     my $hash = shift;
+
+     if (ref ($hash) eq "HASH") {
+         my $fqn = "";
+         if (defined $hash->{'#%#name'}) {
+                 $fqn = $hash->{'#%#name'};
+         }
+         if (defined $hash->{'#%#parent'}) {
+                 # Recursively prepend the parent, concatenation with /
+                 $fqn = munin_get_node_fqn ($hash->{'#%#parent'}) . "/" . $fqn;
+         }
+         return $fqn;
+     } else {
+         return;
+     }
+ }
+

 sub munin_get_picture_loc {
 my $hash = shift;
# diff -bc GraphOld.pm.orig GraphOld.pm
*** GraphOld.pm.orig    2010-06-17 19:53:40.000000000 +0900
--- GraphOld.pm 2012-02-07 18:42:50.000000000 +0900
***************
*** 145,150 ****
--- 145,151 ----
 # Limit graphing to certain hosts and/or services
 my @limit_hosts    = ();
 my @limit_services = ();
+ my $only_fqn;

 my $watermark = "Munin " . $Munin::Common::Defaults::MUNIN_VERSION;

***************
*** 175,180 ****
--- 176,182 ----
 "lazy!"         => \$force_lazy,
 "host=s"        => \@limit_hosts,
 "service=s"     => \@limit_services,
+                 "only-fqn=s"    => \$only_fqn,
 "config=s"      => \$conffile,
 "stdout!"       => \$stdout,
 "day!"          => \$draw{'day'},
***************
*** 1339,1351 ****
 return $fieldname;
 }

 sub skip_service {
 my $service = shift;
!     my $sname   = munin_get_node_name($service);

 # Skip if we've limited services with cli options
!     return 1 if (@limit_services and !grep /^$sname$/, @limit_services);

 # Always graph if --force is present
 return 0 if $force_graphing;
--- 1341,1366 ----
 return $fieldname;
 }

+ sub ends_with {
+         my ($src, $searched) = @_;
+         DEBUG "[DEBUG] ends_with($src, $searched)\n";
+
+         my $is_ending = (substr($src, - length($searched)) eq $searched);
+         return $is_ending;
+ }
+

 sub skip_service {
 my $service = shift;
!     my $fqn     = munin_get_node_fqn($service);
!
!     # Skip if we've limited services with the omnipotent cli option only-fqn
!     return 1 if ($only_fqn and ! ends_with($fqn, $only_fqn));
!     DEBUG "[DEBUG] $fqn is in ($only_fqn)\n";

 # Skip if we've limited services with cli options
!     return 1 if (@limit_services and ! (grep { ends_with($fqn, $_) } @limit_services));
!     DEBUG "[DEBUG] $fqn is in (" . join(",", @limit_services) . ")\n";

 # Always graph if --force is present
 return 0 if $force_graphing;

なお、戻す場合は逆の手順で OK です。事前に各種ファイルのバックアップがあれば、それを戻しても大丈夫です。

※official trac には、「5. Remove munin-graph from munin-cron 」と、手順がありますが、ここは飛ばしても構いません。cron で実行される /usr/bin/munin-cron のスクリプト中に、”nice /usr/share/munin/munin-graph –cron”と記述がありますが、特にコメントアウト不要です。munin-graph は cron モードで動作する時は、何もせず処理を完了します。気になるようであれば、コメントアウトしても構いません。

※なぜか動かない時は、munin のログや Apache の error_log を見直すことをおすすめします。たとえば、Apache のエラーログに “Can’t open /var/log/munin/munin-graph.log (Permission denied) at /usr/lib/perl5/vendor_perl/5.8.8/Log/Log4perl/Appender/File.pm line 103” と出た場合は、/var/log/munin/munin-graph.log の書き込み権限が無い事が原因です。ローテート設定を忘れると、翌日これが出ます。

◆管理台数が3ケタに突入したら CGI 版

さて、これまで CGI 版の導入方法をまとめてきました。実際に入れ替えるタイミングは「100台」ぐらいかな、と思っています。先に書きましたように、監視対象ノードが増えれば増えるほど、CPU の使用率(nice) が増えていくからです。最近の CPU でしたら、管理台数が数台程度であれば、5分に1回 munin の cron をまわしても負荷と感じることはさほど無いと思います。ですが、流石に三桁台に入ったり、Load Average が高く、通常の運用に支障が出てくるようであれば、CGI を検討する価値はあるでしょう。

一方で CGI 版に常にすべきかというと、そうではありません。グラフを動的生成にすると、常に CGI 経由での画像生成となるため、見る方としてはストレスになってしまう場合もありますので注意が必要です(fastcgi版を使えば、ある程度の速度面で改善が期待出来るかもしれません)。

経験的ですが、管理ノードの台数が100台を超えたあたりが、CGI 版にするかどうかの判断点ではないでしょうか。予算があれば、監視用のマシンを強化するのが手っ取り早いですが、そうではない時は、CGI 版を導入するのも1つの方法だと思います。他には、管理者が少人数の場合は CGI 版が望ましいのではないでしょうか。プロジェクト毎に Munin 監視サーバを立てているのであれば、監視サーバの集約にも繋がると思います。

Reference

CgiHowto – Munin – Trac
http://munin-monitoring.org/wiki/CgiHowto

#831: HTMLOld.pm.diff – Munin – Trac
http://munin-monitoring.org/attachment/ticket/831/HTMLOld.pm.diff

#831 (Comparison pages have no graphs with graph_strategy cgi) – Munin – Trac
http://munin-monitoring.org/ticket/831

#832: fix_munin832.patch – Munin – Trac
http://munin-monitoring.org/attachment/ticket/832/fix_munin832.patch

#832 (Nested groups and munin-cgi-graph don’t mix) – Munin – Trac
http://munin-monitoring.org/ticket/832

 

 

# diff -bc HTMLOld.pm.orig HTMLOld.pm
*** HTMLOld.pm.orig     2011-02-09 12:03:52.000000000 +0900
— HTMLOld.pm  2012-02-08 12:21:16.000000000 +0900
***************
*** 932,937 ****
— 932,942 —-
$srv{‘imgmonth’} = $config->{‘cgiurl_graph’} . “/$path-month.png”;
$srv{‘imgyear’}  = $config->{‘cgiurl_graph’} . “/$path-year.png”; 

+         $srv{‘cimgday’}   = $config->{‘cgiurl_graph’} . “/$path-day.png”;
+       $srv{‘cimgweek’}  = $config->{‘cgiurl_graph’} . “/$path-week.png”;
+       $srv{‘cimgmonth’} = $config->{‘cgiurl_graph’} . “/$path-month.png”;
+       $srv{‘cimgyear’}  = $config->{‘cgiurl_graph’} . “/$path-year.png”;
+
if (munin_get_bool($service, “graph_sums”, 0)) {
$srv{‘imgweeksum’}
= $config->{‘cgiurl_graph’} . “/$path-week-sum.png”;

One thought on “MuninのグラフをCGIリアルタイム生成して負荷軽減する方法(munin-cgi-graph)

  1. Pingback: CentOS6にmuninをインストールしnginxで表示する – グラフの動的生成もするよ! | (っ´∀`)っ ゃー | nullpopopo