fluentdとfluent-plugin-pghstoreとpandorafmsでログ収集、可視化、監視を行う

前回の記事で報告したように、fluent-plugin-pghstoreでログをPostgreSQLに貯めることができました。

次は可視化と監視を行います。ここで、最近使ってみているPandora FMSを使います。

pluginを準備

まずは以下のスクリプトを保存し、pandora/etc/pandora/plugins以下に置きます。DBやTABLEは適宜書き換えてください。また、hostnameやportも適宜変更でお願いします。

上の方にあるSQLは過去5分間のcodeが2XXや3XXなどの割合を出してくれます。その後、PandraFMSでのplugin形式のXMLにするように整形します。

ちなみに、一つのSQLで複数を同時にcount()する方法については 複数同時にcount() をどうぞ。

#!/usr/bin/env sh

DB=logdb
TABLE=apache_log

SQL=`cat <<EOT
SELECT \
        ROUND(100 * (s.C_2XX::numeric / s.all::numeric), 1) AS C_2, \
        ROUND(100 * (s.C_3XX::numeric / s.all::numeric), 1) AS C_3, \
        ROUND(100 * (s.C_4XX::numeric / s.all::numeric), 1) AS C_4, \
        ROUND(100 * (s.C_5XX::numeric / s.all::numeric), 1) AS C_5, \
        s.all AS count \
FROM( \
        SELECT  \
              count(*) AS all, \
              count(CASE WHEN record->'code' LIKE '2__' THEN 1 END) AS C_2XX, \
              count(CASE WHEN record->'code' LIKE '3__' THEN 1 END) AS C_3XX, \
              count(CASE WHEN record->'code' LIKE '4__' THEN 1 END) AS C_4XX, \
              count(CASE WHEN record->'code' LIKE '5__' THEN 1 END) AS C_5XX \
        FROM $TABLE WHERE time > (CURRENT_TIMESTAMP - interval '5 min') \
)s
EOT`

RESULT=`psql -At -F " " $DB -c "$SQL"`

count=0
for p in $RESULT
do
  case $count in
     0)
       name="http_status_2XX"
       desc="HTTP Status Code percentage"
     ;;
     1)
       name="http_status_3XX"
       desc="HTTP Status Code percentage"
     ;;
     2)
       name="http_status_4XX"
       desc="HTTP Status Code percentage"
     ;;
     3)
       name="http_status_5XX"
       desc="HTTP Status Code percentage"
     ;;
     4)
       name="http_access_count"
       desc="access count"
     ;;
  esac
  echo "<module>"
  echo "  <name><![CDATA[${name}]]></name>"
  echo "  <type><![CDATA[generic_data]]></type>"
  echo "  <data><![CDATA[${p}]]></data>"
  echo "  <description><![CDATA[$desc]]></description>"
  echo "</module>"
  count=`expr $count + 1`
done

あとは、pandora_agent.conf に以下の一行を足してください。

module_plugin http_access_status.sh

はい、終わりです。module作成は簡単ですね。

グラフ化

ここまで出来ればあとはPandoraFMS側でできます。

レポート管理 -> グラフビルダー で各ステータスコードの塗り潰しの積み上げグラフを作成します。

そうすると、こんな感じのグラフがリアルタイムで出てきます。

途中紫色が増えているのはアラートのテストを兼ねて試しに入れてみたものです。

監視

PandoraFMSは監視システムです。ですので、 4XXや5XXが25%を越えたら通知を出す、なんてことも簡単に出来ます。

ここでは詳しく述べませんので、 http://www.openideas.info/wiki/index.php?title=Pandora_3.0:Documentation_ja:Alerts こちらをご覧ください。

まとめ

fluentd + fluent-plugin-pghstore + PostgreSQL + Pandora FMSで

  • ログ収集
  • 可視化
  • 監視 + アラート

一気通貫で行えるようになりました。

今回試したものは、fluent-plugin-datacounter + out_growthforecast + growthforecast + nagiosで可能です。しかし、fluent-plugin-pghstoreを使った場合、SQLでいろいろな処理ができますので、アイデア次第で今回のStatus Code以外にも使えると思います。

fluent-plugin-pghstoreを書きました

fluentdをPostgreSQLのhstoreに書き出せるようにした、 fluent-plugin-pghstore というpluginを作成しました。

hstoreについては 前記事 を参照してください。

install

gem install fluent-plugin-pghstore

apache_log

例えば、tail pluginを使ってapacheaccess logをhstoreに出すようにする場合、こんなコンフィグを書きます。

<source>
  type tail
  path /var/log/apache/access_log_sym
  tag apache.access
  format apache
</source>

<match apache.*>
  type pghstore
  database test
</match>

そして、こんな感じになります。

       tag       |          time          |      record
 ----------------+------------------------+---------------------------------------
{apache,access} | 2012-04-01 22:55:15+09 | "code"=>"200", "host"=>"XXX.XXX.XXX.XXX", "path"=>"/", "size"=>"2608",
     "user"=>"-", "agent"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X
     10_6_8) AppleWebKit/535.11 (KHTML, like Gecko)
     Chrome/17.0.963.83 Safari/535.11", "method"=>"GET", "referer"=>"-"

tagは"."でsplitして、配列として格納します。はい、PostgreSQLは配列を扱えますので。
hstore型のrecordカラムには、ちょっと見にくいですが、各種keyが値とともに入っています。

http

さらに、こんなコンフィグを追加して、

<source>
  type http
  port 9880
</source>

curlで叩いてみると、

curl -F 'json={"log":"hoge"}' "http://localhost:9880/apache.curl"
      tag       |          time          |      record
----------------+------------------------+---------------------------------------
{apache,access} | 2012-04-01 22:55:15+09 | "code"=>"200", "host"=>"XXX.XXX.XXX.XXX",
{apache,curl}   | 2012-04-01 23:28:44+09 | "log"=>"hoge"

同じテーブルに追加されましたね。 hstoreではkeyは動的に追加できますので、どんな形式のinput pluginでも大丈夫です。

例えば

一度PostgreSQLに入ってしまえばあとはSQLでいろいろなことができます。

UserAgentの数:

SELECT
  COUNT(*) AS c,
  record->'agent'
FROM apache_log
GROUP BY record->'agent'
ORDER BY c;

過去10分間のアクセス数:

SELECT count(*) FROM apache_log WHERE time > (CURRENT_TIMESTAMP - interval '10 min')

過去10分間のstatus codeの数:

SELECT
  count(CASE WHEN record->'code' = '200' THEN 1 ELSE NULL END) AS OK_200,
  count(CASE WHEN record->'code' = '301' THEN 1 ELSE NULL END) AS MOVED_301,
  count(CASE WHEN record->'code' = '302' THEN 1 ELSE NULL END) AS FOUND_302,
  count(CASE WHEN record->'code' = '304' THEN 1 ELSE NULL END) AS NOTMODIFIED_304,
  count(CASE WHEN record->'code' = '401' THEN 1 ELSE NULL END) AS UNAUTHORIZED_401
FROM apache_log
WHERE time > (CURRENT_TIMESTAMP - interval '10 min')

制限

ただし、こんな感じの多段のJSONを入れようとすると、だめです。

'json={"log":"hoge", "nest":{"a":"hoge", "b":"hige"}}'

hstoreが対応していないので難しいです。これは9.2のJSON型を待つのがいいのなあぁと思っているところです。

また、コネクションを一つだけしか使っていないので、おそらく高負荷環境では取りこぼしなどが発生する可能性があります。コネクションプールなどを使えばいいと思うので、patch大歓迎です!

こういう感じで

mongodbなどもいいですが、PostgreSQLもいいですよ。

PostgreSQL hstoreでKVS

それPostgreSQLでできるよ、第二弾。

PostgreSQLにはhstoreという拡張があります。

これはkeyとvalueの対の集合を単一のレコードに格納することが出来るものです。 つまり、Key-Value-Storeですね。 これを使うと通常のテーブルのようにキーを事前に定義しておく必要がありません。

hstoreについては以下の資料をみてください。特に後者は今回書いていない、いろいろな演算子・関数を紹介していますのですごく参考になります。

そして、今回の記事はherokuのこの記事を元にしています。

herokuはpostgresユーザなんですよ。

hstoreを入れる

hstoreはcontribに入っています。今回はFreeBSDを使用したので、以下のように入れます。

% sudo portinstall databases/postgresql91-contrib

続いてDBにhstoreを入れます。9.1から簡単に拡張を入れられるようになりました。

% psql test -c "CREATE EXTENSION hstore;"

さて、これでhstoreを使う準備は出来ました。

hstoreのテーブルを定義

さっき定義する必要ないって言ってたじゃん、という声もありますが、hstoreという部分だけは定義する必要があります。 といっても、hstoreの中身を定義する必要はありません。

CREATE TABLE products (
  id serial PRIMARY KEY,
  name varchar,
  attributes hstore
);

今回はこうしてみました。herokuのblogのとおりですね。

データを入れる

INSERT INTO products (name, attributes) VALUES (
  'Geek Love: A Novel',
  'author    => "Katherine Dunn",
   pages     => 368,
   category  => fiction'
  );

データを入れるには key => value という構文を使 います。文字列は"で囲むと空白、=、>という記号も入れられます。

検索する

SELECT name as device
FROM products
WHERE attributes->'category' = 'fiction'

検索時はkeyを -> で指定します。

また、"?"を使うことで続く値がキーとしてあれば、という意味になります。

SELECT name, attributes->'pages'
  FROM products
  WHERE attributes ? 'pages'

その他

indexも作れます。

CREATE INDEX product_manufacturer
ON products ((products.attributes->'manufacturer'));

ただ、汎用転置インデックスのGINを使ったほうがいいという話もあります。

joinもできます。

SELECT manufacturers.country, products.name
FROM products, manufacturers
WHERE products.attributes -> 'manufacturer' = manufacturers.name;

今日はここまで

というわけで、PostgreSQLを使うと、こういう便利なことができるよ、という紹介でした。

次のネタに続く…

それPostgreSQLで出来るよ - twitter_fdw

pyfesで「それPostgreSQLでできるよ」ってつぶやいた手前、ちゃんと試さなければなりません。

PostgreSQL 9.1から外部データラッパ(FDW) という規格がサポートされました。 またさらに、このFDWを使ってTwitterAPIを叩いて結果をテーブルとして出 してくれる twiter_fdw という拡張が 公開されています。

準備

% sudo apt-get install libcurl4-openssl-dev (libjsonもいるかも)
% curl -O http://api.pgxn.org/dist/twitter_fdw/1.0.0/twitter_fdw-1.0.0.zip
% unzip twitter_fdw
% cd twitter_fdw
% make
% sudo su
# export USE_PGXS=1
# make install

DBの作成して、twitter拡張をDBに入れます。

% createdb twitter
% psql -c "CREATE EXTENSION twitter_fdw" twitter

さて、これで準備は終わりです。

使ってみる

% psql twitter
twitter=# SELECT from_user, created_at, text FROM twitter WHERE q = '#pyfes';

from_user  |     created_at      |  text
----------------------------------------------------
tw_ox      | 2012-03-19 13:43:58 | RT @zusaar: 【新着イベント】Python Developers Festa (一般枠) #pyfes #pyspa http://t.co/X9SyQ0hg #zusaar #イベント #eventjp
inoshiro   | 2012-03-19 10:46:58 | RT @shomah4a: 少ないけど写真上げました https://t.co/aqvDAjrC #pyfes
shomah4a   | 2012-03-19 10:32:03 | 少ないけど写真上げました https://t.co/aqvDAjrC #pyfes
tcsh       | 2012-03-19 10:14:06 | RT @tk0miya: Sphinx ハンズオンの資料(サンプル)です。  http://t.co/TzokaIJ2 #sphinxjp #pyfes

中身はTwitter APIを叩いているだけなので、残念ながら WHERE from_user ='' とは書けません。投稿者を探すにはqの中にfrom:をつけます。 詳しくは https://dev.twitter.com/docs/using-search を見てください。

twitter=# SELECT from_user, text FROM twitter WHERE q = 'from:voluntas' limit 5;

from_user | text
-----------------------------------------------
voluntas  | @Surgo お、一口書いとくね
voluntas  | @turky や一口とか提示しなかったので、再度確認します。イメージは一口 1000 円で問題ないです。
voluntas  | @turky ちょw ブログにまとめます。
voluntas  | 支援は一人1000円として 15000 円位か。
voluntas  | 姉に Twitter Bot の作り方を聞かれている

ちなみにdefaultでは15件までしか出ませんが以下のようにしてあげると100件まで出るようになります。

  - appendStringInfo(&buf, "q=%s",
  + appendStringInfo(&buf, "q=%s&rpp=100",

いろいろしてみる

# SELECT to_user, count(to_user) FROM twitter WHERE q =
'from:voluntas' GROUP BY to_user ORDER BY count(to_user) desc limit 10

      to_user     | count
----------------+-------
 tokoroten      |     8
 Seasons        |     7
 heavenshell    |     6
 sawonya        |     4
 turky          |     3
 tokibito       |     3
 yokatsuki      |     3
 mkouhei        |     3
 mopemope       |     3
 Surgo          |     2


fdwいいよfdw

pyfes 2012.3に参加してきました。

pyfes 2012.3に参加してきました。pyfesは初めてで緊張しました。

Mercurial ハンズオン

とりあえず朝から来たら最初はハンズオンなんですねー。知りませんでした。なにに行こうか迷いましたが、Mercurialハンズオンに参加しました。

実は藤原さんがもういろいろ公開されているのですが、その時に取ったメモを公開します。

tortoisehg

TortoiseHG便利です。というか、HG Workbenchですね。UNIX/Windows/Macどれでも動きます。Workbench使い始めてから、Linuxでもはっきり言ってコマンドラインをほぼ使わなくなりました。(必要であればHG Workbenchからコマンドシェルを開けるのでそこで叩きます。)

社内でgitとmercurialどちらを導入すべきかという議論になった時に決め手になったのはこのHG Workbenchが素晴らしく、かつ、Windowsで問題なく動くから、ということと言っても過言ではありません。ぜひ一度使ってみるといいと思います。

というか、むしろ「HG Workbenchの使い方」という資料を作ったほうがMercurialを使う人が増えるかもしれませんね。

Mergeのdiffを見る
A -+---- A1 -- A2 -- M --
   |                                |
   +- B1 -- B2 -------+

% hg update A2
% hg merge B2

というmergeをした時に綺麗にmergeされた場合、mergeコミット自体にはdiffが表示されない。

でも、ここでMで起きたmergeを知りたい場合、MとA2のdiffを取れば、それはすなわちBの変更全部とのdiffになる。

具体的にはこうやる感じ。

hg diff -r M^1 -r M

"^1" というのは第一親を指定する方式。Mの第一親はA2、第二親はB2となる。 仮に

hg diff -r M^2 -r M

とした場合、これはAのチェンジセット全体との差分となる。

qfinishした後にパッチに戻したい
hg qimport -r <リビジョン番号>

例: hg qimport -r 1:2 <-- 1と2がパッチに戻る
mqの更新を履歴管理する

qfoldとかするとパッチ領域(mq管理領域)自体を

  • hg init --mq で履歴管理開始
  • hg commit --mq でmq領域のcommit
  • hg push --mq でmq領域をpush
  • hg pull --mq でmq領域をpull
  • hg log --mq でmq領域のlogを見れる
  • hg qqueue で複数のmqも持てる

mqの履歴部分を消す場合は、 .hg/patches 自体を消す。やばいもうだめだと思ったら消してしまえばいい。

push/pullでmq領域自体をpush/pullできる。.hg/patches/.hg/hgrcを作って、mq領域の設定ができる。

mq領域は通常のレポジトリと完全に別なものとして意識することが重要。

作業領域の概念

commitしていない状態とは、「次にcommitするであろう候補」であると考えると分かりやすい。これを作業領域と呼ぶ。

agentのmoduleを書いてみる

Pandora FMSは監視対象のサーバにAgentを置くこともできます。このAgentはmoduleを実行して、その結果をServerに対して送ります。あ、なんせ使い出して日が浅いものでこの"module"という用語が適切かどうかはわかりませんが、まあ許してください。

このmoduleは普通のスクリプトです。標準出力に出した数字や文字列がそのままserverに送られます。

なお、スクリプトの内部にwarningやcritialの判定を入れる必要はありません。その判定はserver側で行います。

ウェブモニタリング

なんかウェブモニタリングはエンタープライズ版だけだそうなので、軽く書いてみました。

curlには --write-out という、かかった時間を出してくれるオプションがあります。

module_begin
module_name http_time_total
module_type generic_data
module_exec curl --output /dev/null --write-out "%{time_total}" --silent http://somewhere.example.co.jp/path/
module_description time_total to somewhere in milli sec
module_end

manをみれば、time_total以外にもいろいろありますので、必要に応じて変えるといいと思います。

PostgreSQLのアクセス統計

PostgreSQLは pg_stat_tables で各種統計情報が取れます。これを使ってPostgreSQLへのアクセスの統計を取ります。

pg_stat_user_tables から取れる情報は積算値なので、軽く適当なスクリプトを書いて単位時間(デフォルトでは300秒)あたりのアクセスにならします。
リモートのインスタンスから取る場合はpsqlの引数を適当に変えてください。

#!/usr/bin/env sh

DB=database
TABLE=target_table
HISTFILE=/tmp/pandora_pg_idx_scan_count.tmp

if [ ! -e $HISTFILE ]
then
   echo "0" > $HISTFILE
fi

prev=`cat $HISTFILE`
now=`psql -tA -c "select idx_scan from pg_stat_user_tables WHERE relname = '$TABLE';" $DB`

value=`expr $now - $prev`
if [ $value -le 0 ]
then
    echo "0"
else
    echo $value
fi

echo $now > $HISTFILE

pandora_agent.confにはこんな感じで書きます。

module_begin 
module_name scan_count
module_type generic_data
module_exec /path/to/pandora/etc/pandora/plugins/pg_scan_count.sh
module_max  100   # ここは好きに変えてください
module_min 0
module_description Table index scan count
module_end

これは idx_scan の数ですが、pg_stat_user_tables にはcommit数とかロールバック数とかも格納されているので適宜変更するといいと思います。

なお、module_maxは最大値です。自動的に判定してくれるので指定する必要は本当はないのですが、いろいろ試してたら指定しないといけなくなったのでしています。なお、 module_postprocess という設定を入れると、数値を指定した倍率でかけてくれます。 1024 なら1024倍、0.000976563なら 1/1024 にしてくれますので、適宜調整するといいと思います。

tomcatのfree memoryを取る

tomcatのmanagerを使ってメモリの空き容量を%表示で出します。tomcatが出す情報はXMLかHTMLのようなので、shellで書くのはあきらめてpythonを使いました。minidomを使っているので、試してませんが python 2.4 でも動くはずです。

なお、当たり前ですが事前にmanagerの認証設定をしておく必要があります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib
import urllib2

from xml.dom.minidom import parseString  # for 2.4, use minidom

URL = "http://localhost:8080/manager/status?XML=true"
USER = "user"
PASS = "password"

def get_xml(url, user, passwd):
    passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
    passman.add_password(None, url, user, passwd)

    auth_handler = urllib2.HTTPBasicAuthHandler(passman)
    opener = urllib2.build_opener(auth_handler)

    urllib2.install_opener(opener)
    f = urllib2.urlopen(url)

    xml = f.read()
    return xml

def get_jvm_memory(dom):
    for n in dom.getElementsByTagName("memory"):
        return {"max": float(n.attributes["max"].value),
                "free": float(n.attributes["free"].value)}

if __name__ == '__main__':
    xml = get_xml(URL, USER, PASS)

    dom = parseString(xml)
    mem = get_jvm_memory(dom)
    print((mem["free"] / mem["max"]) * 100 )

pandora_agent.confにはこんな感じで書きます。

module_begin
module_name tomcat_free_mem
module_type generic_data
module_exec python /path/to/pandora/etc/pandora/plugins/tomcat_free_mem.py 
module_description Tomcat free memory in MB
module_end

これだけです。

Agent側で動作

module_condition <評価式> <コマンド>

Pandora FMSのAgentは、module_conditionという値を設定することで、モジュールが特定の値を返す場合に指定したコマンドを実行できます。

  • > [値]: モジュールの値が指定された値よりも大きい場合
  • < [値]: モジュールの値が指定された値よりも小さい場合
  • = [値]: モジュールの値が指定された値と同じ場合
  • != [値]: モジュールの値が指定された値と異なる場合
  • =~ [正規表現]: モジュールの値が指定された正規表現にマッチする場合
  • (値, 値): モジュールの値が指定された値の範囲の場合

また、以下のように、同一のモジュールに複数の条件を設定することも可能です。

module_begin
module_name condition_test
module_type generic_data
module_exec echo 2.5
module_condition (1, 3) script_1.sh
module_condition > 5.5 script_2.sh
module_end

module_conditionの例を示します。

重複プロセスがあったらkillする
module_begin
module_name MyProcess
module_type generic_data
module_exec tasklist | grep MyProcess | wc -l
module_condition > 2 taskkill /IM MyProcess* /F
module_end
Logが大きくなったら削除する
module_begin
module_name PandoraLogSize
module_type generic_data
module_exec ls -la "c:\Archivos de programa\pandora_agent\pandora_agent.log" | gawk "{ print $5 }"
module_condition > 10000 del "c:\Archivos de programa\pandora_agent\pandora_agent.log"
module_end
Spoolerが落ちてたら再起動する
module_begin
module_name Service_Spooler
module_type generic_proc
module_service Spooler
module_condition = 0 net start Spooler
module_end
こんな感じで

なにか異常があったらメールでアラートを飛ばしつつ、自動的に出来る範囲であれば自力で復旧する、ということが簡単に書けるようです。