読者です 読者をやめる 読者になる 読者になる

たごもりすメモ

コードとかその他の話とか。

Fluentd Config DSLについての話

Fluentd に Config DSL を導入しろや! つまり設定をRubyのコードで書かせろや! という声は前から多かった。が、なんかいろいろ面倒な思惑が交錯し、あと「設定ファイルにrubyのコードでcallbackとか書いてプラグインの挙動を制御したい!」とか更に面倒なことを言いだした人がいたせいでDSL以外の設定書式でそれどうすんの……とコミッタの精神状態がどん底まで落ち込み長らく放置されていた。
みんなもOSSプロジェクトにfeature requestするときはあんまりステキすぎる機能のリクエストをする前に少し考えてみよう。まじで。

https://github.com/fluent/fluentd/pull/97

で、そんな数ヶ月ののち、ちょうど Fluentd v11 ハッカソンなるものがあったので、生ハム食べつつワイン飲んでたらConfig DSLのことを思い出した。まーv11ならさくっと実装しちゃって、機能足りない分はまた別途、ということにしちゃってもいいんじゃない? とか思ったのでガッと書いた。*1
一度書いてしまったら、なんかもう面倒になったのでこれを v10 にbackportすればいいじゃん! という感じでやっつけて、気付いたら v0.10.38 としてリリースされてた、ので、今の Fluentd ではなんと設定ファイルにRuby内部DSLが使えます! v10のあいだはEXPERIMENTALだという位置付けから変わることは無いと思うけど。

起動時の -c オプションで読み込む設定ファイルが .rb な拡張子ならRuby DSLとして解釈される。td-agentでサポートされることは多分無いと思う。すくなくとも当分の間はw

$ fluentd -c config.rb
例1: ループと変数

ログ転送先ホスト名を変数にセットし、それを設定内で使うとしよう。また連番で 0 から 9 まで番号のついたログファイルがあるから、そいつを in_tail で読み出したい。

# for config.rb with fluentd v0.10.x
hostname = "myhostname.local"

(0..9).each do |i|
  source {
    type :tail
    path "/var/log/httpd/access.part#{i}.log"
    format '/^...$/'       # hash available in pattern! yay!
    time_format "%d/%b/%Y:%H:%M:%S %z"
    tag "foo.x"
  }
end

match ('{foo,bar}.**') {
  type :copy
  store {
    type :stdout
  }
  store {
    type :forward
    flush_interval 1
    server {
      host "myserver2.local"
    }
    server {
      host hostname  #=> "myhostname.local"
      port 24223
      standby true
      # backup fluentd process
    }
  }
}

Fluentd v10の設定ファイル書式は設定項目の値として単一の文字列しか受け取らない*2ので、Ruby DSLでも実際には文字列しか与えられない。この設定例では数値とかシンボルとかtrueを渡してるように見えるが、実際のところは内部で文字列化されることにちょっと注意が必要。
まあ見た目からかけ離れたことにはあんまりならない。

なおfluentdを起動すると出力される設定のダンプはDSLではなく通常書式のフォーマットで行われる。これを見れば意図した通りの設定ができているかどうかがわかるだろう。

$ fluentd -c example.rb 
2013-09-09 18:44:33 +0900 [info]: starting fluentd-0.10.38
2013-09-09 18:44:33 +0900 [info]: reading config file path="example.rb"
2013-09-09 18:44:33 +0900 [info]: using configuration file: <ROOT>
  <source>
    type tail
    path /var/log/httpd/access.part0.log
    format /^...$/
    time_format %d/%b/%Y:%H:%M:%S %z
    tag foo.x
  </source>
  <source>
    type tail
    path /var/log/httpd/access.part1.log
    format /^...$/
    time_format %d/%b/%Y:%H:%M:%S %z
    tag foo.x
  </source>
# 中略
  <source>
    type tail
    path /var/log/httpd/access.part9.log
    format /^...$/
    time_format %d/%b/%Y:%H:%M:%S %z
    tag foo.x
  </source>
  <match {foo,bar}.**>
    type copy
    <store>
      type stdout
    </store>
    <store>
      type forward
      flush_interval 1
      <server>
        host myserver2.local
      </server>
      <server>
        host myhostname.local
        port 24223
        standby true
      </server>
    </store>
  </match>
</ROOT>
例2:外部リストの読み込み

tailしたいファイルのリストはどっかのパスに置いておいてそれを読み込みたい! とか、forward先のfluentdノードリストはネットワークのどこかに置いておいて、それをHTTPで読み込んで使いたい! とか、きっとそういうことを考える。もちろんできる。

myhostname = ::Kernel.open("|hostname"){|o| o.read.chop}

logfiles = ::Kernel.open("/path/to/logfile_list"){|f| f.readlines.map(&:chop) }
logfiles.each do |file|
  source {
    type :tail
    path "/var/log/httpd/#{file}"
    format '/^...$/'       # hash available in pattern! yay!
    time_format "%d/%b/%Y:%H:%M:%S %z"
    tag "foo.#{myhostname}"
  }
end

::Kernel.require 'open-uri'
hosts = ::Kernel.open("htts://hostdata.local/fluentd.forward_list.txt"){|list|
  list.readlines.map(&:chop)
}
match ('{foo,bar}.**') {
  type :forward
  flush_interval 1
  hosts.each do |forward_host|
    server {
      host forward_host
    }
  end
}

繰り返しも外部リソース取得も思いのまま! べんり!!!!!!

とはいえ

いちいち ::Kernel って書くのまじうざいなーといま思ったところなので、それはそのうちどうにかしようと思う。
という決意をいちおう残しておいた。

https://github.com/fluent/fluentd/issues/195

何かこういう関数も無いと困るんスけど、というのがあったらぜひコメントでぶらさげておいてください。

*1:実際にはその後にちょこちょこ直したけど。

*2:プラグインはそれが更にparseされた値を受け取る