LoginSignup
4
4

More than 1 year has passed since last update.

絶対忘れるのでFluentdの設定内容とその意味をまとめました

Posted at

業務でロギング機構を作ったのですが、しばらく経ったら設定内容の意味を忘れることが目に見えているので先にまとめておきます。
よければ参考にしてください。
パートごとに抜粋しているので、設定ファイル全体は記事の一番最後を見てください。

ログの取得

fluent.conf
<source>
  @type  forward
  port  24224
</source>

forward

TCP経由でログ情報を受け取るinputプラグイン
ついでにハートビートによる死活監視も行っているらしい

ログの整形

fluent.conf
<filter **>
  @type grep
  <exclude>
    key log
    pattern /^$/
  </exclude>
</filter>

<filter rails>
  @type parser
  key_name log
  reserve_data true
  <parse>
    @type regexp
    expression /^\[(?<@timestamp>[^\]]*)\] \[(?<log_level>[^\]]*)\] \[(?<request_id>[^\]]*)\] (?<log>.*)$/
    time_format %Y-%m-%d %H:%M:%S.%L%z
  </parse>
</filter>

<filter nginx>
  @type parser
  key_name log
  reserve_data true
  <parse>
    @type nginx
  </parse>
</filter>

<filter **>

すべてのタグが対象

<exclude>

grepしてパターンマッチしたイベントを除外する

key log

logというフィールドにgrepをかける

pattern

grepのパターン(今回は空文字にマッチするように指定)
Railsのリクエスト処理完了後、なぜか空文字のログが最後に付いてくるのでfilterプラグインで除去している
空のログはノイズになるので、他のコンテナから送られきた場合も想定して<filter **>で全てのタグにマッチするようにしている

<filter xxx>,

xxxというtagだけが対象

<@type>

regexp expressionに独自に正規表現を定義してパースする
nginx デフォルトのNginxのログ用のパーサーを使う

key_name log

log フィールドの値をパースする

reserve_data true

trueにしないとその他に元からあったフィールドが消えるので注意
Fluentdが自動で設定してくれているcontainer_nameのフィールドも消えてしまうので、Elasticsearchでどのコンテナのログかわからなくなって困る

expression

正規表現のパターン
railsのログは
[2022-12-01T01:23:45.678+0900] [INFO] [030388f7-1d2c-416f-bfb8-50f3d8349155] log_message
というフォーマットを想定
Elasticsearchはスペース入りのタインプスタンプフォーマットはサポートしていないので、Tは必要な子
正規表現で時刻、ログレベル、リクエストID、ログ内容を抜き、それぞれ@timestamp, log_level, request_id, log フィールドとして設定
元からあるlogフィールドは上書き

Elasticsearch, S3への送信

fluent.conf
<match **>
  @type copy

  <store>
    @type elasticsearch
    host elasticsearch
    port 9200
    user "#{ENV['ELASTIC_USER']}"
    password "#{ENV['ELASTIC_PASSWORD']}"
    scheme https
    ca_file path/to/ca_file.crt
    logstash_format true
    include_timestamp false
    logstash_prefix fluentd
    logstash_prefix_separator _
    logstash_dateformat %Y%m%d
    log_es_400_reason true

    <buffer>
      @type file
      flush_mode interval
      flush_interval 10s
      path /var/log/td-agent/elasticsearch
      chunk_limit_size 512m
      flush_at_shutdown true
    </buffer>
  </store>

  <store>
    @type s3
    aws_key_id "#{ENV['AWS_ACCESS_KEY_ID']}"
    aws_sec_key "#{ENV['AWS_SECRET_ACCESS_KEY']}"
    s3_bucket log-bucket
    s3_region "#{ENV['AWS_REGION']}"
    path logs
    time_slice_format %Y%m%d_%H%M%S
    s3_object_key_format %{path}%{time_slice}.%{file_extension}
    check_object false

    <buffer time>
      @type file
      flush_mode lazy
      path /var/log/td-agent/s3/logs.*
      timekey 1d # 1 day
      timekey_wait 2m
      chunk_limit_size 5096m
      flush_at_shutdown true
    </buffer>
  </store>
</match>

@type copy

Elasticsearch, S3の2つにログを保存するため、copyプラグインを使うことで送信先を複数指定できる

scheme https, ca_file path/to/ca_file.crt

HTTPSでElasticsearchと通信するための設定
ElasticSearch7.x系と8.5.0でインストール手順のリファレンス内容が結構変わってたので、エラー出たら公式参照

logstash_format truelogstash_dateformat %Y%m%d

このあたりはElasticsearchのインデックス名のフォーマットの設定
最終的に、fluentd_20221201のようなフォーマットになる

include_timestamp false

filterセクションにて、パースしたログを元に@timestampのフィールドを設定しているため、falseにして上書きされないようにしている
ログのタイムスタンプはアプリケーション側で付与された時刻にしたいので、Fluentd側では付与しないこと
アプリケーションからFluentdにログを送信する際に何らかの事情で遅延した場合、Fluentd側でタイムスタンプを振っていると、アプリケーションがログを出力した時刻とズレが発生してしまう

log_es_400_reason true

falseだとElasticSearchでエラーが発生してもログに出力されない場合があるのでtrue

<buffer>, <buffer time>

bufferプラグインでログの書き込みを即時に行うのではなく、一時的にファイルに溜め込んでからまとめて書き込むようにし負荷を低減している
メモリに書き込むようにもできるが、耐久性の観点から基本的にはファイルが公式から推奨されている

For the usual workload, the file buffer plugin is recommended. It is more durable for the general use-cases.

<buffer>の引数に何も指定しなければ、すべてのイベントが一つのバッファファイルに書き込まれる
S3の方はtimeを指定しているので、timekeyで指定した単位でバッファファイルが分けられる
いずれの場合でも、書き込み後はバッファファイルは自動的に削除される

flush_mode lazy, timekey 1d, timekey_wait 2m

1日単位でバッファファイルを分けて送信先へ書き込み
0時の時点で即座には書き込まず、2分待ってから書き込み(デフォルトだと10分)
1日単位でログローテーションしているイメージ
<buffer>の引数にtimeを指定すれば、flush_modeはデフォルトでlazyになる

flush_mode interval, flush_interval 10s

10秒間隔でバッファから送信先へ書き込み
<buffer>の引数に何も指定しなければ、flush_modeはデフォルトでintervalになる

chunk_limit_size

チャンクというイベントの集合ごとにバッファファイルが作られるので、その最大サイズの指定
このサイズを超えた量のイベントが書き込まれると、バッファオーバーフローのエラーが発生する
その際は後述の<label @ERROR>にイベントが送信される

flush_at_shutdown true

ファイルにバッファリングしているとはいえ、コンテナが消えたり再作成されるとそのファイル自体が消えてしまうため、シャットダウン時にバッファの内容を送信するようにしている

path logs

パスの指定には/をつけないこと

time_slice_format %Y%m%d_%H%M%S

オブジェクト名の日付日時フォーマット
20221120_0123の形式

s3_object_key_format

S3に保存されるパス、オブジェクト名の指定
path logs, time_slice_format %Y%m%dを指定しているため、以下のように展開される
%{path}/%{time_slice}.%{file_extension}

logs/20221201_0123.gz

check_object false

オブジェクト名を設定するときに、S3のバケットに現在存在しているオブジェクト一覧をAPI経由で取得しないように指定
S3のオブジェクト一覧の取得にはわずかながら課金が発生するので、false推奨
デフォルトではtrueになっているが、なぜこんなことをしているかと言うと、オブジェクト名が重複しないように日付の後ろに連番を付しているから
そのため、falseにする場合は、オブジェクト名が一意になるようにs3_object_key_formatを設定しないといけない
今回は連番の代わりに秒までのタイムスタンプを付しているが、不安ならチャンクIDを付す等がよいと思われる

プラグインのソースではtime_slice, hms_sliceを組み合わせて秒までのタイムスタンプを取っているが、time_sliceはJST、hms_sliceはUTCになる現象が発生したので、time_slice_formatで秒まで指定した方がいらぬバグを踏まずにいいかと

Fluentd自体のログ

<label @FLUENT_LOG>
  <match **>
    @type stdout
  </match>
</label>

<label @FLUENT_LOG>

@FLUENT_LOGはFluentd自体のログに付されるラベル
とりあえず標準出力に出すだけ

fluent.**のようなtagではなく、labelで制御するのが推奨されている
上記のようなタグで制御しているとワーニングが出る

エラーイベントの制御

<label @ERROR>
  <match **>
    @type s3
    aws_key_id "#{ENV['AWS_ACCESS_KEY_ID']}"
    aws_sec_key "#{ENV['AWS_SECRET_ACCESS_KEY']}"
    s3_bucket log-bucket
    s3_region "#{ENV['AWS_REGION']}"
    path logging-errors
    time_slice_format %Y%m%d_%H%M%S
    s3_object_key_format %{path}/%{time_slice}.%{file_extension}
    check_object false

    <buffer>
      @type file
      path /var/log/td-agent/s3/logging-errors.*
      timekey 1d # 1 day
      timekey_wait 2m
      chunk_limit_size 2048m
      flush_at_shutdown true
    </buffer>
  </match>
</label>

<label @ERROR>

Fluentdで発生したエラーイベントに付されるラベル
パースエラーや、バッファオーバーフローのようなエラーが起こった際に元のイベントの情報に@ERRORのラベルが付される
とりあえずエラーログ専用のバッファとバケットを用意してS3に格納する形(設定はほぼ同じ)
ここでエラー処理を行った後、relabelして再エミットするなりどこかしらに保存するなりすればエラーが起きてもログが欠損することはない
エラーの際は、@FLUENT_LOGにどんなエラーが起こったかというデバッグ情報が渡ってきて、@ERRORにはエラーが起こった元のイベントがそのまま渡ってくる

@FLUENT_LOG (エラーのデバッグ情報)

2022-11-21 16:53:17.780796000 +0900 fluent.warn: {"error":"#<Fluent::Plugin::Parser::ParserError: pattern not matched with data 'StandardError'>","location":null,"tag":"rails","time":1669017197,"message":"send an error event to @ERROR: error_class=Fluent::Plugin::Parser::ParserError error=\"pattern not matched with data 'StandardError'\" location=nil tag=\"rails\" time=1669017197"}

@ERROR (エラー元イベントそのまま)

`2022-11-21T16:53:17+09:00 rails {"container_id":"e62322ce694ee13ed9a74a664ff622805f51b81eb970dea6f7550a6a41f12bf9","container_name":"/rails","source":"stdout","log":"StandardError"}`

さいごに

Fluentdはバージョンによって結構仕様が変わるので、この記事も遅かれ早かれ陳腐化します
公式ドキュメントを参考にするのが結局一番早い場合があるので、まずはそちらを確認しましょう

設定内容全体

fluent.conf
<source>
  @type  forward
  port  24224
</source>

<filter **>
  @type grep
  <exclude>
    key log
    pattern /^$/
  </exclude>
</filter>

<filter rails>
  @type parser
  key_name log
  reserve_data true
  <parse>
    @type regexp
    expression /^\[(?<@timestamp>[^\]]*)\] \[(?<log_level>[^\]]*)\] \[(?<request_id>[^\]]*)\] (?<log>.*)$/
    time_format %Y-%m-%d %H:%M:%S.%L%z
  </parse>
</filter>

<filter nginx>
  @type parser
  key_name log
  reserve_data true
  <parse>
    @type nginx
  </parse>
</filter>

<match **>
  @type copy

  <store>
    @type elasticsearch
    host elasticsearch
    port 9200
    user "#{ENV['ELASTIC_USER']}"
    password "#{ENV['ELASTIC_PASSWORD']}"
    scheme https
    ca_file path/to/ca_file.crt
    logstash_format true
    include_timestamp false
    logstash_prefix fluentd
    logstash_prefix_separator _
    logstash_dateformat %Y%m%d
    log_es_400_reason true

    <buffer>
      @type file
      flush_mode interval
      flush_interval 10s
      path /var/log/td-agent/elasticsearch
      chunk_limit_size 512m
      flush_at_shutdown true
    </buffer>
  </store>

  <store>
    @type s3
    aws_key_id "#{ENV['AWS_ACCESS_KEY_ID']}"
    aws_sec_key "#{ENV['AWS_SECRET_ACCESS_KEY']}"
    s3_bucket log-bucket
    s3_region "#{ENV['AWS_REGION']}"
    path logs
    time_slice_format %Y%m%d_%H%M%S
    s3_object_key_format %{path}%{time_slice}.%{file_extension}
    check_object false

    <buffer time>
      @type file
      flush_mode lazy
      path /var/log/td-agent/s3/logs.*
      timekey 1d # 1 day
      timekey_wait 2m
      chunk_limit_size 5096m
      flush_at_shutdown true
    </buffer>
  </store>
</match>

<label @FLUENT_LOG>
  <match **>
    @type stdout
  </match>
</label>

<label @ERROR>
  <match **>
    @type s3
    aws_key_id "#{ENV['AWS_ACCESS_KEY_ID']}"
    aws_sec_key "#{ENV['AWS_SECRET_ACCESS_KEY']}"
    s3_bucket log-bucket
    s3_region "#{ENV['AWS_REGION']}"
    path logging-errors
    time_slice_format %Y%m%d_%H%M%S
    s3_object_key_format %{path}/%{time_slice}.%{file_extension}
    check_object false

    <buffer>
      @type file
      path /var/log/td-agent/s3/logging-errors.*
      timekey 1d # 1 day
      timekey_wait 2m
      chunk_limit_size 2048m
      flush_at_shutdown true
    </buffer>
  </match>
</label>
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4