Fluentd を利用して、ログから Prometheus メトリクスを生成する (1/2)

先日、Fluentd DaemonSet for Kubernetes を利用する機会があり、Prometheus Plugin が組み込まれているのを発見しました。最初は、"Fluentd 自体のメトリクスが Prometheus で見れるんだー" と単純に思っていたのですが、その後、この Plugin を使うと、ログからメトリクスを生成できることが分かりました。

そこで、Fluentd を使って、WAS Liberty のアクセス・ログからメトリクスを生成する検証をしてみました。

## どんなメトリクスを生成するか
今回はアクセス・ログからメトリクスを生成しますが、まずは、どんなメトリクスがあると役に立つか考えてみました。システム運用中にその場で素早く確認したい情報が良さそうなので、以下のメトリクスをアクセス・ログから生成することにします。

|メトリクス|付加するタグ|説明|
|:--|:--|:--|
|アクセス数|ステータス・コード<br>パスの一部|アクセス数がステータス・コード別やパス別に集計できる。|
|レスポンス・サイズの合計|ステータス・コード<br>パスの一部|レスポンス・サイズがステータス・コード別やパス別に集計できる。<br>アクセス数で割ると平均のレスポンス・サイズが分かる。|
|レスポンス時間の合計|ステータス・コード<br>パスの一部|レスポンス・サイズと同様|

取得されるメトリクス数は、ラベルがとりうる値の数の依存します。ステータス・コードはさほど多くなりませんが、パスの数は多くなる可能性があります。メトリクス数が巨大にならないように、指定するラベルには注意が必要そうです。

## WAS Liberty のメトリクスと重複していないか確認
ちょっと話はそれますが、
既に存在するメトリクスをわざわざ生成しても意味がないので、念のため、WAS Liberty のメトリクスと重複がないか確認しておきます。
WAS Liberty は以下のようなメトリクスを提供しているようです。

* メトリクスの種類
 * Web アプリケーション・メトリック
 * スレッド・プール・メトリック
 * セッション管理メトリック
 * 接続プール：メトリック
 * JAX-WS メトリック
* 参考
 - [Open Liberty: Metrics reference list](https://openliberty.io/docs/21.0.0.1/metrics-list.html)
 - [WAS Liberty: MicroProfile Metrics 1.1 ベンダー・メトリック](https://www.ibm.com/support/knowledgecenter/ja/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/rwlp_monitor_metrics_rest_api.html)

今回実装しようとしているものと被るのは "Web アプリケーション・メトリック" の部分のようですが、提供されているメトリクスは RequestCount (サーブレット毎のアクセス数) と ResponseTimeDetails (サーブレット毎のレスポンス時間の合計) なので重複は無さそうです。

## WAS Liberty 側の準備
Fluentd 側の実装が簡単に済むように、Apache2 combined 形式のアクセス・ログを WAS Liberty が出力するようにしておきます。
定義内容としては以下の通りです。２番目の出力項目は本来は %l ですが、ここでは "-" に置き換えています。

```
<httpEndpoint host="*" httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint">
    <accessLogging enabled="true"
                   filePath="/logs/http_access.log"
                   logFormat='%h - %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i"'/>
</httpEndpoint>
```

アクセス・ログ /logs/http_access.log は Fluentd が読み込めるボリュームに出力するようにしておきます。

## アクセス・ログから Prometheus メトリクスを生成
前置きが長くなりましたが、本題に入ります。
今までの話の内容を実現する fluent.conf の内容を上から順番に部分ごとに載せていきます。

最初は、Prometheus Plugin の基本的な構成です。
内容は、Fluentd DaemonSet for Kubernetes で使用されているそのままです。この内容だけだと、Fluentd 自体のメトリクスが Prometheus から取得できるだけです。

```xml:fluent.conf-(1/5)
<source>
  @type prometheus
  @id in_prometheus
  bind "0.0.0.0"
  port 24231
  metrics_path "/metrics"
</source>

<source>
  @type prometheus_output_monitor
  @id in_prometheus_output_monitor
</source>
````

次は、アクセス・ログを tail し、パースする部分の定義です。Fluentd 組み込みの apache2 パーサーでアクセス・ログをパースします。
Fluentd でも、アクセス・ログが /logs/http_access.log としてアクセスできる構成を前提としています。

```xml:fluent.conf-(2/5)
<source>
  @type tail
  follow_inodes
  # read_from_head true
  path     /logs/http_access.log
  pos_file /logs/http_access.log.pos
  tag *
  <parse>
    @type apache2
  </parse>
</source>
```

以下のような定義を定義を追加すると、途中の状態を確認できます。
(この定義は最終的には不要なものです。残しておくと、Fluentd から不要なログが出ることになります。)

```xml:fluent.conf-(debug)
<filter logs.http_access.log>
  @type stdout
  # format single_value
</filter>
````

apache2 パーサーでパースした結果を上記の方法で確認すると、次のようになっています。

```json:パース結果の例（出力を見やすいようにjqで整形）
{
  "host": "172.17.0.1",
  "user": null,
  "method": "POST",
  "path": "/ResourceEater/faces/HeapEater.xhtml",
  "code": 200,
  "size": 3815,
  "referer": "http://localhost:9080/ResourceEater/faces/HeapEater.xhtml",
  "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
}
```

path を２つの部分にパースする処理を追加します。
path_head は path に含まれる最後の "/" までの部分で、path_rest は残りの部分になります。

```xml:fluent.conf-(3/5)
<filter logs.http_access.log>
  @type parser
  key_name path
  reserve_data true
  <parse>
    @type regexp
    expression /^(?<path_head>.*\/)(?<path_rest>.*)$/
  </parse>
</filter>
```

ログのパースが済んだので、Prometheus のメトリクスを生成する定義を追加します。
ここで使用しているのは値が合計されていく counter タイプのメトリクスです。アクセス数の方は単純に件数をカウントするだけなので、key を指定していません。レスポンス・サイズの合計の方は key に size を指定することでサイズが合計されていくように指定しています。
何れのメトリクスも、ラベルとして code と path_head を指定することで、ステータス・コードとパス別に値が合計されるようにしています。

```xml:fluent.conf-(4/5)
<filter logs.http_access.log>
  @type prometheus
  <metric>
    name liberty_access_total
    desc The total number of access
    type counter
    <labels>
      code ${code}
      path ${path_head}
    </labels>
  </metric>
</filter>

<filter logs.http_access.log>
  @type prometheus
  <metric>
    name liberty_response_size_total
    desc The total response size
    type counter
    key size
    <labels>
      code ${code}
      path ${path_head}
    </labels>
  </metric>
</filter>
```

ログの内容はどこかに出力するわけではないので、最後に以下の指定を記述します。

```xml:fluent.conf-(5/5)
<match logs.http_access.log>
  @type null
</match>
```

## 動作を確認する
WAS Liberty と Fluentd を起動し、WAS Liberty 上のアプリにアクセスします。
curl で Fluentd の Prometheus Plugin のポートにアクセスし、メトリクスを取り出します。

```bash:メトリクス取得ー１回目
curl -s http://localhost:24231/metrics  | grep liberty
# TYPE liberty_access_total counter
# HELP liberty_access_total The total number of access
liberty_access_total{code="200",path="/InfraTest/"} 2.0
liberty_access_total{code="404",path="/"} 2.0
liberty_access_total{code="200",path="/ResourceEater/faces/"} 2.0
liberty_access_total{code="200",path="/InfraTest/rest/"} 1.0
liberty_access_total{code="200",path="/"} 2.0
liberty_access_total{code="200",path="/js/"} 2.0
liberty_access_total{code="200",path="/images/"} 3.0
liberty_access_total{code="200",path="/nls/ja/"} 1.0
# TYPE liberty_response_size_total counter
# HELP liberty_response_size_total The total response size
liberty_response_size_total{code="200",path="/InfraTest/"} 11366.0
liberty_response_size_total{code="404",path="/"} 29429.0
liberty_response_size_total{code="200",path="/ResourceEater/faces/"} 7630.0
liberty_response_size_total{code="200",path="/InfraTest/rest/"} 1241.0
liberty_response_size_total{code="200",path="/"} 12965.0
liberty_response_size_total{code="200",path="/js/"} 12795.0
liberty_response_size_total{code="200",path="/images/"} 23568.0
liberty_response_size_total{code="200",path="/nls/ja/"} 1875.0
```

更にアクセスしてからメトリクスを取得すると、アクセス数とレスポンス・サイズが合計されていく様子が確認できます。

```bash:メトリクス取得ー２回目
curl -s http://localhost:24231/metrics  | grep liberty
# TYPE liberty_access_total counter
# HELP liberty_access_total The total number of access
liberty_access_total{code="200",path="/InfraTest/"} 3.0
liberty_access_total{code="404",path="/"} 4.0
liberty_access_total{code="200",path="/ResourceEater/faces/"} 3.0
liberty_access_total{code="200",path="/InfraTest/rest/"} 2.0
liberty_access_total{code="200",path="/"} 2.0
liberty_access_total{code="200",path="/js/"} 2.0
liberty_access_total{code="200",path="/images/"} 3.0
liberty_access_total{code="200",path="/nls/ja/"} 1.0
liberty_access_total{code="200",path="/ResourceEater/"} 1.0
# TYPE liberty_response_size_total counter
# HELP liberty_response_size_total The total response size
liberty_response_size_total{code="200",path="/InfraTest/"} 17049.0
liberty_response_size_total{code="404",path="/"} 88287.0
liberty_response_size_total{code="200",path="/ResourceEater/faces/"} 13661.0
liberty_response_size_total{code="200",path="/InfraTest/rest/"} 2482.0
liberty_response_size_total{code="200",path="/"} 12965.0
liberty_response_size_total{code="200",path="/js/"} 12795.0
liberty_response_size_total{code="200",path="/images/"} 23568.0
liberty_response_size_total{code="200",path="/nls/ja/"} 1875.0
liberty_response_size_total{code="200",path="/ResourceEater/"} 274.0
```

### 終わりに
WAS Liberty が出力するログからメトリクスを生成できることが確認できました。
今回は、レスポンス時間の合計のメトリクスまで手が回らなかったので、この部分は別の記事にしたいと思います。

コンテナのログを分析する場合、ログを Elasticsearch などに転送し、Kibana などで分析・表示するというパターンなどが多いと思います。しかし、アクセス・ログのように多量に出力されるログを Elasticsearch などに転送・保存するのは、キャパシティーやパフォーマンスの面から二の足を踏むことがあります。
利用頻度が高そうな定型的な分析を Fluentd で行い、その結果を Prometheus メトリクスとして収集・蓄積し、Grafana で確認できるなら、ちょっと敷居が低くなるかもしれません。

アプリケーションが出力するログからメトリックを生成するなど、いろいろな場面で利用できそうです。

* 参考
 - [Fluentd: Monitoring by Prometheus](https://docs.fluentd.org/monitoring-fluentd/monitoring-prometheus)
 - [GitHub: fluent / fluent-plugin-prometheus](https://github.com/fluent/fluent-plugin-prometheus)
 - [GitHub: fluent / fluentd-kubernetes-daemonset](https://github.com/fluent/fluentd-kubernetes-daemonset)