FluentdをWindowsで動かそうとして挫折しなかった記録。その1

WindowsでFluentdを動かそうとして調べていった記録のメモです。
きちんとまとめていないので起こった順に書いています。

同じような事を試みて挫折した人が居たので、この意思を勝手に引き継いで3秒以上頑張ってみました。

概要としては、fluentdのRuby実装Python実装(fluenpy)、fluent-agent-liteのPerl実装F#実装を確認した事と、Windowsでのファイル処理の排他制御について分かったことをまとめています。Windowsのファイルについては別の記事でまとめる事にして、この記事では簡単に触れる程度にします。

まずOriginalのFluentd。Ruby実装なのでWindowsのRubyかJRubyで動くかなとも思っていたのですが、Cool.ioというライブラリに依存しており、これがNGとのこと。in_tailだけに依存しているとかならまだしも、あちこちで使っているっぽいのでひとまず諦め。

次に調べたのはKLabのfluenpy。これはFluentd Casual Talksで話しているのを聞いたので、動くかどうか調べてみました。
残念ながらすんなりとWindowsでは動きませんでした。最初にPython2.7の64Bit版で動かそうと思ったのですが、何かどうでもいいところでこけるので32bit版で確認しました。OSはWindows7Proの64Bitです。
まず、インストーラーで普通にpythonをインストールしました。

そして、VS2010 C++ Expressを入れます。VS2010を入れると環境変数「VS100COMNTOOLS」が設定されるのですが、msgpack-pythonがどうもVS2008のビルドを想定しているのか(もしくはpipがその環境変数を参照するのか)「VS90COMNTOOLS」の環境変数を参照します。なので、環境変数「VS90COMNTOOLS」をVS100COMNTOOLSの内容で作ります。

次に以下のコマンドを実行して必要なものを入れました。なお、他にpythonを使う用途も無かったのでvirtualenvは使っていません。インストールした環境を直接弄りました。

$ cd C:\Python27\Scripts\
$ easy_install pip
$ pip install msgpack-python
$ pip install http://gevent.googlecode.com/files/gevent-1.0b2.tar.gz
$ pip install https://github.com/KLab/fluenpy/tarball/master

これだけだとgdbmが無くて起動しません。
gdbmに依存しているPluginを使う予定はないので、今回は除外する事で対応。「plugins/in_multilog.py」をpluginsディレクトリから除きました。

これで起動するようにはなりましたが、in_tailでpythonがエラーを出して動きません。 理由は、os.O_NONBLOCKをWindowsではサポートしていないため(参考)。そこで、O_RDONLYに変更しました。これでノンブロッキング処理ではなくなりますが、別にこれでも動く事は動きます。

これで出来るかなと思いきや、次は延々とRotateされた時の処理が走っていました。(毎回読み直しが走っていた)
原因は、is_current関数で、os.stat(path)とos.fstat(fd)のst_inoを比較しており、この値が異なっていたらローテーションされたと判断していますが、Windowsではos.stat(path)は0が返ってきてos.fstat(fd)はそれっぽい値が返ってきます。そのため、常にファイルが変わったと判断されていました。

こちらの対策方法は2つあります。
1つはos.stat(path)をやめて毎回openし、os.fstat(fd).st_inoと比較するようにすること。
もう1つはWin32のAPIをコールしてきちんとNTFSに沿った処理に変更することです。
そもそも、Windowsでは(NTFSでは)Linuxと違い、inodeという概念はありません。 Windowsでinodeと同じようなものを取得するには以下の2つの方法があります。

1. DeviceIoControlをFSCTL_GET_OBJECT_ID FSCTL_CREATE_OR_GET_OBJECT_IDで呼び出し、FILE_OBJECTID_BUFFERのObjectIdというメンバを参照する。 (ただしAdmin権限必須)。これはボリューム内で一意性を保証してくれます。
2. GetFileInformationByHandleで取得するBY_HANDLE_FILE_INFORMATIONのnFileIndexHigh, nFileINdexLowを参照する。こちらは再起動、ボリュームのマウントしなおしで値が変わります。

どこまで読み込んだのかを記録しないのであれば2でも良いと思います。が、そんなわけにもいかないと思うので1の方法できっちり対応するのが良いと思います。なお、pythonからコールするにはpywin32を使います。pipでインストールできないので、sourceforgeからインストーラーを落として入れました。
もしくはActivePythonをインストールすると標準で付いてきそうです。(まだ確認していないです)

なお、ファイルを開いている間にWindowsでのrename, ファイル移動(volume内、volume間)を行った場合にどういう挙動になるのかは別エントリにまとめます。

さて、最後の難関です。これがいろんな実装を確認したところの壁になりました。
Windowsでは単純にファイルをopenするとファイルの削除やrenameが出来なくなります。という事は、ファイルのローテーションが出来なくなってしまい動かしているアプリを阻害する事になってしまいます。これを回避するためには、Win32APIのCreateFileA,CreateFileWでFILE_SHARE_READとFILE_SHARE_WRITEとFILE_SHARE_DELETEを指定する必要があります。

pythonの2.7.3と3.3のソースを見てみたのですが、そーいう実装にはなっていませんでした。そもそもWin32APIは叩いておらず、POSIX準拠のopenを呼んでいるようです。POSIX準拠のopenではこのオプションを指定できません。Windows固有の処理なので、まぁ当然と言えば当然ですが。(参考はこちら
RubyやPerlのWindows実装はまだ見ていないのでわかりません。

というわけで、これを回避するためには、win32pyのwin32fileを使ってCreateFileWでファイルをopenし、ReadFileでファイルを読み込む必要があります。

これを対応すればfluenpyはそれなりに動くようになります。
ただruby版のin_tailと違ってposを保存していないので、そこは追加で実装する必要があります。またout_forwardの実装が、毎回socketを開いているのでそれは良くないなーと。ConnectionPoolしないとひどい事になりそうです。 

ここまで調べて、前職で大変お世話になったmhnsrさんに、fluentd-agent-windows-liteというものを教えていただきました。
F#の実装のようです。やばい、短いソースなのにぜんぜん読めない!!!ので勘を頼りに見てみました。.netのFileStreamを使っていますが、以下のように呼び出しており、FileShare..Deleteが指定されておりません。(参考:MSDN)

new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)

これは、もともとローテーションを考慮していないような気もするので(すいません、本気でF#処理の流れがおえないです・・・)そーいうものなのかなと。という訳でこれを使うのも諦め。

最悪Javaで再実装しようとも思ったのですが、結局のところはWindowsのopenの排他制御で詰む予感がぷんぷんするので、fluenpyをベースにやって、どうしてもダメだったらC#でfluent-agent-liteのようなものを実装しようかなと思います。とりあえずfluenpyのwin対応は落ち着いたらgithubにあげます。
現状ではfluenpyをwindowsで動かすことには成功していますが。きちんとまとめる時間が取れないので・・(´・ω・`)

あとはPerlとRubyのWin版のソースを調べてFILE_SHARE_DELETEを使えるかどうかの確認をしておこうかなと思います。Win32APIなんて久しぶりに見ました・・。WinNT4, Win98の時に隠しAPI含めてだいたい把握していましたが、久しぶりにやるとサッパリですね(´・ω・`)。それにしても、Win32APIのほげほげExというセンス(ry

ひとまず、その1はここまで。 次のエントリへ。

追記(2012/10/15 21:30):
Node.jsでも使われているlibuvは対応しています。
pythonは http://bugs.python.org/issue15244 こちらにIssueがあります。

※勘違いも多いと思うので、全然違うよ!!と気がついた場合はTwitterやコメントで叱りつけてくれると嬉しいです(´・ω・`)

新しいサイトもよろしくお願いします!