CLOVER🍀

That was when it all began.

Fluent Bitを使って、DockerコンテナのログファイルをTailプラグインで読む

これは、なにをしたくて書いたもの?

前に、Fluent BitをDockerのlogging driverとして使ってみました。

Fluent BitをDocker logging driverとして使う - CLOVER🍀

今度は、DockerコンテナのログをTailプラグインで読み込んでみようと思います。

Tail - Fluent Bit: Official Manual

その時に、どれくらい情報が取れるのかを調べてみようかなと。まあ、この条件だと大したことができないことが確認できたの
ですが。

環境

今回の環境は、こちら。

$ uname -srvmpio
Linux 4.15.0-99-generic #100-Ubuntu SMP Wed Apr 22 20:32:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic


$ docker -v
Docker version 19.03.8, build afacb8b7f0


$ /opt/td-agent-bit/bin/td-agent-bit --version
Fluent Bit v1.4.4

お題

ふつうにnginxをDockerコンテナとして起動します。

$ docker container run -d -p 80:80 --name nginx nginx:1.17.10

この状態でDockerコンテナ内で標準出力に書き込まれたログは、デフォルトのJSON File logging driverを使ってストレージに
書き込まれることになります。

JSON File logging driver | Docker Documentation

About storage drivers | Docker Documentation

これをTailプラグインで読みましょう、と。

Tail - Fluent Bit: Official Manual

たとえば、ここでお題として起動したnginxコンテナのログは、以下にあることになります。

$ docker inspect nginx | grep LogPath
        "LogPath": "/var/lib/docker/containers/52c01ce2c45b60cb2718ca42a948393733b13b6d02514e543a61812890292471/52c01ce2c45b60cb2718ca42a948393733b13b6d02514e543a61812890292471-json.log",

Fluent Bitを、Dockerコンテナのログを読むように設定する

まずは、簡単に設定してみましょう。

$ grep -v '^ *#' /etc/td-agent-bit/td-agent-bit.conf 
[SERVICE]
    Flush        5

    Daemon       Off

    Log_Level    info

    Parsers_File parsers.conf
    Plugins_File plugins.conf

    HTTP_Server  Off
    HTTP_Listen  0.0.0.0
    HTTP_Port    2020

[INPUT]
    Name tail
    Path /var/lib/docker/containers/*/*-json.log
    Tag container.log

[OUTPUT]
    Name  stdout
    Match *
    Format json_lines

なんとなく、Tagは付けておきました…。

結果は標準出力に書いているので、journalctlで確認することにします。

$ sudo journalctl -u td-agent-bit.service -f

コンテナにアクセスしてみます。

$ curl localhost

こんな感じに出力されます。

May 17 13:06:56 ubuntu1804.localdomain td-agent-bit[5401]: {"date":1589720815.612513,"log":"{\"log\":\"172.17.0.1 - - [17/May/2020:13:06:55 +0000] \\\"GET / HTTP/1.1\\\" 200 612 \\\"-\\\" \\\"curl/7.58.0\\\" \\\"-\\\"\\n\",\"stream\":\"stdout\",\"time\":\"2020-05-17T13:06:55.612432829Z\"}"}

もともとのログとしては、こんな感じに入っているわけですね。

$ sudo cat /var/lib/docker/containers/52c01ce2c45b60cb2718ca42a948393733b13b6d02514e543a61812890292471/52c01ce2c45b60cb2718ca42a948393733b13b6d02514e543a61812890292471-json.log
{"log":"172.17.0.1 - - [17/May/2020:13:06:55 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.58.0\" \"-\"\n","stream":"stdout","time":"2020-05-17T13:06:55.612432829Z"}

Parserを使ってパースする

出力結果を見ると、「log」の中にログメッセージが全部入っていて、ちょっと扱いづらそうです。

May 17 13:06:56 ubuntu1804.localdomain td-agent-bit[5401]: {"date":1589720815.612513,"log":"{\"log\":\"172.17.0.1 - - [17/May/2020:13:06:55 +0000] \\\"GET / HTTP/1.1\\\" 200 612 \\\"-\\\" \\\"curl/7.58.0\\\" \\\"-\\\"\\n\",\"stream\":\"stdout\",\"time\":\"2020-05-17T13:06:55.612432829Z\"}"}
Dockerコンテナログとしてパースする

ここで、Parserの定義を見てみます。
/etc/td-agent-bit/parsers.conf

[PARSER]
    Name         docker
    Format       json
    Time_Key     time
    Time_Format  %Y-%m-%dT%H:%M:%S.%L
    Time_Keep    On
    # --
    # Since Fluent Bit v1.2, if you are parsing Docker logs and using
    # the Kubernetes filter, it's not longer required to decode the
    # 'log' key.
    #
    # Command      |  Decoder | Field | Optional Action
    # =============|==================|=================
    #Decode_Field_As    json     log

Docker用のものがあるので、こちらを使用しましょう。

このParserは、JSON Parserを使って定義されています。

JSON - Fluent Bit: Official Manual

Parserの指定場所ですが、TailプラグインではParserが指定できるようなので、こちらに直接設定してみます。

[INPUT]
    Name tail
    Path /var/lib/docker/containers/*/*-json.log
    Parser docker
    Tag container.log

Fluent Bitを再起動してから、nginxコンテナにアクセスしてログを確認してみましょう。

May 17 13:12:38 ubuntu1804.localdomain td-agent-bit[5465]: {"date":1589721153.619957,"log":"172.17.0.1 - - [17/May/2020:13:12:33 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.58.0\" \"-\"\n","stream":"stdout","time":"2020-05-17T13:12:33.61995679Z"}

コンテナログの、「log」、「stream」、「time」が独立したJSONの要素になったことが確認できます。

コンテナIDを入れる

ところで、今回のログからは、コンテナであることがまったくわかりません。

なにか情報はないかな?ということで、コンテナのIDがファイルパスに含まれることを利用して、「Path_Key」を使うことに
しました。

Tail - Fluent Bit: Official Manual

以下のように、「Path_Key」を使って「container_log_file_path」というキーでログファイルパスを保存するように変更。

[INPUT]
    Name tail
    Path /var/lib/docker/containers/*/*-json.log
    Path_Key container_log_file_path
    Parser docker
    Tag container.log

このファイルパスから、コンテナのIDを抽出できるように、Parserを書きましょう。 /etc/td-agent-bit/parsers.conf

[PARSER]
    Name    container-log-file
    Format  regex
    Regex   /var/lib/docker/containers/(?<container_id>[^/]+)/.+\.log

結果、こんな感じになりました。

[INPUT]
    Name tail
    Path /var/lib/docker/containers/*/*-json.log
    Path_Key container_log_file_path
    Parser docker
    Tag container.log

[FILTER]
    Name parser
    Match container.*
    Key_Name container_log_file_path
    Parser container-log-file
    Preserve_Key Off
    Reserve_Data On

「Reserve_Data」をOnにしておかないと、Parserが解析したキー以外がなくなってしまうので、要注意。

適用結果としてはこんな感じになり、「container_id」というキーでコンテナIDが追加されました。

May 17 13:36:27 ubuntu1804.localdomain td-agent-bit[5529]: {"date":1589722583.842574,"container_id":"52c01ce2c45b60cb2718ca42a948393733b13b6d02514e543a61812890292471","log":"172.17.0.1 - - [17/May/2020:13:36:23 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.58.0\" \"-\"\n","stream":"stdout","time":"2020-05-17T13:36:23.842574434Z"}
nginxのログとしてパースする

最後に、nginxのログとしてパースしておきましょう。

以下のFilterを加えます。nginxのParserは、事前定義されたものを使用しています。

[FILTER]
    Name parser
    Match container.*
    Key_Name log
    Parser nginx
    Preserve_Key Off
    Reserve_Data On
    Tag docker.nginx.log

全体は、こんな感じですね。

[INPUT]
    Name tail
    Path /var/lib/docker/containers/*/*-json.log
    Path_Key container_log_file_path
    Parser docker
    Tag container.log

[FILTER]
    Name parser
    Match container.*
    Key_Name container_log_file_path
    Parser container-log-file
    Preserve_Key Off
    Reserve_Data On

[FILTER]
    Name parser
    Match container.*
    Key_Name log
    Parser nginx
    Preserve_Key Off
    Reserve_Data On
    Tag docker.nginx.log

パースした結果は、こちら。

May 17 13:38:47 ubuntu1804.localdomain td-agent-bit[5567]: {"date":1589722727,"remote":"172.17.0.1","host":"-","user":"-","method":"GET","path":"/","code":"200","size":"612","referer":"-","agent":"curl/7.58.0","container_id":"52c01ce2c45b60cb2718ca42a948393733b13b6d02514e543a61812890292471","stream":"stdout","time":"2020-05-17T13:38:47.382578641Z"}

だいぶ整ったのではないでしょうか?

Kubernetes Filter

ところで、今回はコンテナIDを含めるのが精一杯でしたが、コンテナ名なども含められたらいいのにな?と思うわけですが。

こういうのには、Kubernetes Filterを使うのが正解みたいですね。

Kubernetes - Fluent Bit: Official Manual

Kubernetes内で、DaemonSetとして動かす例がよく挙がると思いますが、こちらで使われるのがこのKubernetes Filterのようです。

製品系統は違いますが、FilebeatsだとProcessorでできそうな感じですね。Filebeatsの場合は、Docker用のProcessorがあれば
コンテナ名やイメージ名まで取得できそうな模様。

Add Docker metadata | Filebeat Reference [7.7] | Elastic

Add Kubernetes metadata | Filebeat Reference [7.7] | Elastic

Fluent BitをDocker logging driverとして使う

これは、なにをしたくて書いたもの?

以前、FluentdをDockerのlogging driverとして使ってみたことがありました。

Docker環境で、コンテナのログをFluentdに出力する(Docker logging driverとして使う) - CLOVER🍀

今回は、Fluent BitをDockerのlogging driverとして使ってみたいと思います。

Fluentd logging driver

Dockerでは、Fluentdをlogging driverとして使うように設定できます。

Fluentd logging driver | Docker Documentation

ここでログの送信先をFluentdではなく、Fluent Bitに変更してみるのが今回やってみることです。

Fluent Bitの記事にも、Dockerコンテナから受け取ったログを、Elasticsearchに送信するサンプルが書かれたりしています。

Fluent Bit - Docker Logging & Elasticsearch

それでは、ちょっと試してみましょう。

環境

今回の環境は、こちら。

$ uname -srvmpio
Linux 4.15.0-99-generic #100-Ubuntu SMP Wed Apr 22 20:32:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic


$ docker -v
Docker version 19.03.8, build afacb8b7f0


$ /opt/td-agent-bit/bin/td-agent-bit --version
Fluent Bit v1.4.4

Ubuntu Linux 18.04、Fluent Bitは1.4.4を使用します。

お題

今回のお題は、nginxのDockerコンテナに対してFluentd logging driverを設定し、Fluent Bitに送信してみたいと思います。

Fluent BitとDockerは同じホスト(192.168.33.10)で動作させますが、Fluent Bitはネットワーク越しにログを受け取るように
設定します。

Fluent Bitを設定する

では、最初にFluent Bitの設定を行います。

結果は、こんな感じに。

$ grep -v '^ *#' /etc/td-agent-bit/td-agent-bit.conf 
[SERVICE]
    Flush        5

    Daemon       Off

    Log_Level    info

    Parsers_File parsers.conf
    Plugins_File plugins.conf

    HTTP_Server  Off
    HTTP_Listen  0.0.0.0
    HTTP_Port    2020


[INPUT]
    Name forward
    Host 0.0.0.0
    Port 24224

[OUTPUT]
    Name  stdout
    Match *
    Format json_lines

ポイントは、INPUTをfowardにしていることですね。

[INPUT]
    Name forward
    Host 0.0.0.0
    Port 24224

Forward - Fluent Bit: Official Manual

バインドするアドレスは、「0.0.0.0」で設定。

こちらで、Dockerコンテナのログを受け取ります。

結果は、標準出力で確認することにしました。

[OUTPUT]
    Name  stdout
    Match *
    Format json_lines

journalctlで、Fluent Bitのログを確認しておきます。

$ sudo journalctl -u td-agent-bit.service -f

Dockerコンテナを起動する

続いて、Fluent Bitにログを送信するDockerコンテナを起動します。

こんな感じで起動。

$ docker container run \
  -d -p 80:80 --name nginx \
  --log-driver=fluentd \
  --log-opt fluentd-address=tcp://192.168.33.10:24224 \
  --log-opt tag=docker.{{.ImageName}}.{{.Name}}.{{.ID}} \
   nginx:1.17.10

「--log-driver」を「fluentd」とすることで、Fluentd logging driverを利用することができます。

指定しているオプションは、このあたりを参照。

Fluentd logging driver / Options

今回は、Fluent Bitへの送信先を「fluentd-address」で、Fluent Bitで利用するタグを「tag」で指定しました。

タグはこちらのフォーマットで指定しているのですが、今回はFluent Bit側のMatchでタグを見ていないので、あんまり関係が
なかったり…。

Customize log driver output | Docker Documentation

確認する

それでは、確認してみます。

nginxコンテナにアクセス。

$ curl localhost

少し待っていると、Fluent Bit側のログ(というか標準出力)に、こんな感じで出力されれば成功です。

May 17 10:55:25 ubuntu1804.localdomain td-agent-bit[3848]: {"date":1589712924,"container_name":"/nginx","source":"stdout","log":"172.17.0.1 - - [17/May/2020:10:55:24 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.58.0\" \"-\"","container_id":"fa127da978b1895c40d20d851502d42d1bacee5053e9075e4bf6eeca02023379"}

コンテナ名や、コンテナのIDなども要素として含まれます。

ちなみに、この状態だと「docker container logs」コマンドでは、コンテナのログは見れなくなることに注意してください。

$ docker container logs nginx
Error response from daemon: configured logging driver does not support reading

ログをパースしてみる

これだけで終わっても少し物足りない気もするので、ここからさらに、Parserを使ってnginxのログをパースしてみましょう。

Fluent Bitのparsers.confに、nginx向けのParserが定義してあるので、こちらを使います。
/etc/td-agent-bit/parsers.conf

[PARSER]
    Name   nginx
    Format regex
    Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")
    Time_Key time
    Time_Format %d/%b/%Y:%H:%M:%S %z

Fluent Bitの設定は、このように変更。

[INPUT]
    Name forward
    Host 0.0.0.0
    Port 24224
    Tag docker.driver.log

[FILTER]
    Name parser
    Match *
    Key_Name log
    Parser nginx
    Preserve_Key Off
    Reserve_Data On
    Tag docker.nginx.log

[OUTPUT]
    Name  stdout
    Match *
    Format json_lines

先ほどの結果として、nginxのログそのものは「log」というキーで出力されているので

May 17 10:55:25 ubuntu1804.localdomain td-agent-bit[3848]: {"date":1589712924,"container_name":"/nginx","source":"stdout","log":"172.17.0.1 - - [17/May/2020:10:55:24 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"curl/7.58.0\" \"-\"","container_id":"fa127da978b1895c40d20d851502d42d1bacee5053e9075e4bf6eeca02023379"}

Parserの適用先の「Key_Name」を「log」に指定します。Parserで指定するのは、パーサーの名前ですね(今回はnginx)。

[FILTER]
    Name parser
    Match *
    Key_Name log
    Parser nginx
    Preserve_Key Off
    Reserve_Data On
    Tag docker.nginx.log

ここまで設定したらFluent Bitを再起動して、nginxに再度アクセスしてみます。

ログが以下のような感じに分解されて、Fluent Bit側で出力されます。

May 17 12:34:57 ubuntu1804.localdomain td-agent-bit[5033]: {"date":1589718895,"remote":"172.17.0.1","host":"-","user":"-","method":"GET","path":"/","code":"200","size":"612","referer":"-","agent":"curl/7.58.0","source":"stdout","container_id":"7ea3b9cc2b7ac65d2729e722bacbc2b8efc150ec51ccc37405ebf4f313156a42","container_name":"/nginx"}

デフォルトではTime_KeepがOffなのでアクセス時刻はdateフィールドに変換された後に削除されていますが、今回は
これでも良しとしましょう。

また、Parserを使った時に「Reserve_Data」をOnにしておかないと、パースした結果以外のキーは削除されてしまうので注意しましょう。
パース対象となったキー「log」は、今回は残らなくてもいいので「Preserve_Key」をOffにしています(これはデフォルトの挙動)。