Usual Software Engineer

よくあるソフトウェアエンジニアのブログ

fluentdの負荷分散のためにmulti processを有効にする

ログ -> fluentd -> fluentd -> ストレージ のような流れで fluentd を酷使していると、1コアしか使えない fluentd が悲鳴を上げて そこがボトルネックとなってスループットが上がらない問題にぶつかります。
そこで使えるのが fluent-plugin-multiprocess というプラグインです。
他の input plugin と同様に source を定義して複数の fluentd の子プロセスを呼び出すことでマルチコアの利用が可能になります。

docker-fluentd-multiprocess/fluent.conf at master · innossh/docker-fluentd-multiprocess · GitHub

<source>
  @type multiprocess

  <process>
    cmdline -c /fluentd/etc/fluent1.conf --log /fluentd/log/fluent1.log
    pid_file /fluentd/run/fluentd1.pid
  </process>
  <process>
    cmdline -c /fluentd/etc/fluent2.conf --log /fluentd/log/fluent2.log
    pid_file /fluentd/run/fluentd2.pid
  </process>
</source>

文字通り ruby で子プロセスを呼び出しているので、それぞれ log や pid の指定をしてあげて logrotate などを設定する必要があります。
また、よく使うであろう monitor_agent の設定も各子プロセスごとに別のポートで定義する必要があります。

こうして multiprocess 設定を行う時に、なんとか複数の設定ファイルをうまいディレクトリ構造に配置できないか考えたところ、次のようになりました。

├── fluent.conf : メインの設定ファイル
├── fluent.d : 全子プロセス共通の設定ファイル用ディレクトリ
│   └── out-forward.conf
├── fluent1.conf : 子プロセス1の設定ファイル
├── fluent1.d : 子プロセス1の設定ファイル用ディレクトリ
│   ├── in-forward.conf
│   └── include.conf
├── fluent2.conf : 子プロセス2の設定ファイル
└── fluent2.d : 子プロセス2の設定ファイル用ディレクトリ
    ├── in-forward.conf
    └── include.conf

プロセスの数が増えると結局地獄が待っているのですが、設定を増やす時に対象のディレクトリに置くだけで良いというメリットがあります。 include.conf の中身はこのようになっています。

docker-fluentd-multiprocess/include.conf at master · innossh/docker-fluentd-multiprocess · GitHub

@include ../fluent.d/*.conf

また一つのポイントとして、 全子プロセス共通の設定ファイル用ディレクトリ があることで、設定ファイルの数を減らすことができます。(やりたい設定の数*プロセスの数 から (やりたい設定の数-共通化できる設定の数)*プロセスの数+共通化できる設定の数に減る)
tail input plugin などは1つのファイルを読むのを多重化することはできないので共通化できない設定となりますが、 output plugin では負荷分散が目的の場合、大抵のものは同じ設定を複数のプロセスで読み込んでくれた方が管理が楽なわけです。

しかしここで問題となるのが buffer_path の設定です。 mysql, postgresql, s3, elasticsearch にしても何にしてもデータをロストさせないためにbuffer fileの設定をしているかと思います。
buffer_type を file にしている場合に、先ほどの共通化できる設定で buffer_path を定義していると、複数の子プロセスで同じ buffer_path を参照してしまってエラーが発生してしまいます。
困ったもんだということで、その解決方法は以下です。

  • buffer_path に tag 名を含めるようにして、各子プロセスで別々の tag を設定する
  • 設定の共通化を諦めるw 別々の設定ファイルで別々の buffer_path を設定する

前者は今回のサンプルリポジトリを参考にしてください。ただ tag 書き換えてるだけなんですけど。

github.com

もっとうまい方法はないのだろうかと環境変数を使う方法なども考えてはみましたがうまくできませんでした。
やっぱり plugin で実現しているだけあってつらみが多いです。とはいえちゃんとマルチコアを利用できてスループットが上がったので良かったのですが。

もんもんとしたままでベストな方法が見つからなかったのですが、 fluentd バージョン0.14系では本体で multiprocess 化が対応しています。やったね!
forward input plugin を複数定義して別々のポート設定してみたいなことが必要なくなり、それはつまり転送元の forward output plugin で複数のポートへ転送する設定を定義しなくてよいことにもなります。さらにv0.14.15からは <worker N> で指定した worker だけで動かす source の定義も可能です。
また monitor_agent も自動で worker ごとにポートを連番で設定してくれるので特に定義する必要がありません。
サンプルではv0.14の設定ファイルがとても簡潔になっています。

docker-fluentd-multiprocess/fluent.conf at master · innossh/docker-fluentd-multiprocess · GitHub

<system>
  workers 2
</system>
<source>
  @type monitor_agent
  port 25000
</source>
<source>
  @type forward
  port 24224
</source>
<filter docker.**>
  @type parser
  format /^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) .*$/
  time_format %d/%b/%Y:%H:%M:%S %z
  key_name log
</filter>
<filter docker.**>
  @type record_transformer
  remove_keys container_id,container_name,source,remote,host,user
</filter>
<match docker.**>
  @type forward
  <server>
    host fluentd-out
    port 24224
  </server>
  buffer_type file
  buffer_path /fluentd/log/buffer/
</match>

ちなみに buffer file のディレクトリ構造はこんな感じです。

# v0.12 : tag の設定でがんばったやつ
fluentd-multi/log/buffer
fluentd-multi/log/buffer/child1.docker.dockerfluentdmultiprocess_nginx_1
fluentd-multi/log/buffer/child1.docker.dockerfluentdmultiprocess_nginx_1/.child1.docker.dockerfluentdmultiprocess_nginx_1.b550bf3334898a188.log
fluentd-multi/log/buffer/child2.docker.dockerfluentdmultiprocess_nginx_1

# v0.14
fluentd14-multi/log/buffer
fluentd14-multi/log/buffer/worker0
fluentd14-multi/log/buffer/worker0/buffer.b550bf3334ce14b5232c3c1601c6c06b9.log
fluentd14-multi/log/buffer/worker0/buffer.b550bf3334ce14b5232c3c1601c6c06b9.log.meta
fluentd14-multi/log/buffer/worker1

良き fluentd ライフを。