Fluent Bitにmruby Filter Pluginを追加し、フィルタ処理をmrubyで書けるようにする

Fluent Bitという、IoT・組み込み向けのCで書かれた軽量なFluentdがある。Cloud Native Meetup Tokyo #1 で存在を知ったのだが、知ったときに「mruby組み込めそうだな」と思ったのである。Cなので、プラグインを書いて組み込む場合は自分でビルドする必要があるが、mrubyでプラグインを書いて動的に読み込めるならさらに便利になるかもしれない(Goでプラグインを書くこともできるし、Lua Filter Pluginが既にあるが)。ということで興味を持っていたのだけどRubyKaigiなどでばたばたしており、仙台から戻った翌日くらいから少しずつFilter Pluginとして組み込んで昨日くらいにプロトタイプが動いた。Cを書くのが10年ぶりくらいで大変だったけど、楽しかった。

動作光景

なにはともあれどう動くのかを見てみる。まず、以下のようなtest.rbを用意する。fooメソッドの引数はこの3つで固定で、lua filter pluginと同じ。受け取ったレコードを加工して返す。

次にconfigファイル。filter pluginとしてmrubyを指定して、読み込むスクリプトファイルと実行するメソッドを設定している。

そして、これを実行すると、加工されたレコードがstdoutに出力されていることがわかる。

もちろん、このようなプラグインをCで書くこともできるが、mrubyで書けると便利だろう。

mruby Filter Pluginのメリット

FluentdはRubyでプラグインが書ける。また、DigdagやEmbulkでもRubyを使える(EmbulkはjRubyだっけ)。となるとFluent BitでもRubyが使えると、Rubyを書く人にとってうれしい。プラグイン部分を実行ファイルの外に置けるのも利点だろう。修正の度にビルドしなくてもよいというのはありがたいのではないか。mrbgemsの資産を使えるのも結構便利かもしれない(利用する際にビルドして組み込む必要があるが)。

mruby Filter Pluginのデメリット

mrubyなので、Rubyと違う部分でつまづくことはあるだろう。デメリットか、というとそこまででもないと思うけど。パフォーマンスはどうなんだろうか。測定してみないとわからないが、さすがにCで書いたプラグインに比べると遅くなるか?パフォーマンスが必要な場合はCやGoで書く、という手が使えるのでそこまでデメリットでもなさそう。Lua Filter Pluginと比べるとどうか?という話もある。

実装

Filter Pluginにはcb_init、cb_filter、cb_exitという3つのコールバックがある。fluent-bitの起動時にcb_initでmrb_stateを初期化、スクリプトの読み込みとロードを行う。cb_filterではスクリプト内のメソッドを呼び出して、Parser Pluginから渡されたレコードを処理する。cb_exitではmrb_stateを終了するなど、後片付けを行うという流れになるだろう。filter部分でmrb_stateの初期化やスクリプトの読み込みなどを行うことも可能だが、オーバーヘッドとなるのでinitで行うのがよいだろう。

コード全体はこのdiffを参照。

cb_mruby_init

initはそんなにややこしくない。コメントの通り。

cb_mruby_filter

大まかな流れは以下の3ステップ。

  1. messagepack形式のレコードが引数として渡されるので、これをmrb_value(mrubyのvalur)であるvalueやdouble型のタイムスタンプ、文字列型のタグに変換する。
  2. 次に、設定ファイルで指定したスクリプト内のメソッドを呼び出し、value、タイムスタンプ、タグを引数として渡す。
  3. mrb_valueであるメソッドの実行結果などを次のプラグインに渡すためにmessagepackに変換する。

messagepackをmrb_valueに変換するのがmsgpack_obj_to_mrb_value。書きかけで、文字列とマップ以外も実装する必要があるが、雰囲気は伝わるか。

スクリプトが返すmrb_valueをmessagepackに変換する。

cb_mruby_exit

closeしたりfreeしたりする。

個人の感想です

mrubyの組み込み自体はすんなりできた。Fluent Bitのビルドまわりやプラグインの仕組みが綺麗で、開発開始時点でほぼつまづかなかった(cmakeまわりで少し悩んだ)し、何もしない新しいプラグインを追加するだけならすぐにできた。ミドルウェアにmrubyを組み込む方法も非常に参考になった。苦戦したのはmessagepackからmrubyへの変換、その逆の所。おそらく一番苦戦するだろう、と思っていたが予想通りだった。とはいえ、Cに慣れていて、いきなり組み込み始めるのではなくある程度mrubyとmsgpackまわりを事前に調べていればそこまで苦戦はしないのではないかな。

今後について

思いつきで実装を始めたので、プロトイタイプができて結構満足してしまった… Fluent Bit自体をちゃんと使ってみて、必要そうなら機能提案する、というところか。どのくらい需要があるのだろうか?