nginxのアクセスログをfluentd + mysql + metabaseで可視化しよう

# はじめに
nginxのアクセスログをfluentdでmysqlに流し込み、metabaseで解析・可視化できるようにします。

# セットアップ
## Metabase
```
$ sudo docker run -d -p 3000:3000 --name metabase metabase/metabase
$ sudo firewall-cmd --add-port=3000/tcp --permanent
$ sudo firewall-cmd --reload
```
docker起動してポート開けて終わりです。

:::note warn
dockerや配布jar版のmetabaseは、表示できる列数がデフォルトで2000になっています。
これはハードコードなのでコンフィグから変更することはできません。
:::

<details><summary>自分でビルドする場合</summary>
まずgitからソースをクローンします。

```
$ git clone https://github.com/metabase/metabase.git
$ cd metabase/
```
ソースをクローン後、列数制限している部分を変更します。
``` src/metabase/query_processor/middleware/constraints.clj
(ns metabase.query-processor.middleware.constraints
  "Middleware that adds default constraints to limit the maximum number of rows returned to queries that specify the
  `:add-default-userland-constraints?` `:middleware` option."
  (:require
   [metabase.models.setting :as setting]
   [metabase.util.i18n :refer [deferred-tru]]))

(def ^:private ^:const default-max-results-bare-rows <任意の桁数に変更>)

;; NOTE: this was changed from a hardcoded var with value of 2000 (now moved to [[default-max-results-bare-rows]])
;; to a setting in 0.43 the setting, which allows for DB local value, can still be nil, so any places below that used
;; to reference the former constant value have to expect it could return nil instead
(setting/defsetting max-results-bare-rows
  (deferred-tru "Maximum number of rows to return specifically on :rows type queries via the API.")
  :visibility     :authenticated
  :type           :integer
  :database-local :allowed)

(def ^:private max-results
  "General maximum number of rows to return from an API query."
  <任意の桁数に変更>)

(defn default-query-constraints
```

DockerHubから、すでにClojure環境構築済みのコンテナイメージを使用します。


> https://hub.docker.com/_/clojure

bashをコンテナ側に渡すことで、ターミナルから直接操作できるようになります。

```
$ sudo docker run -it --rm -v $(pwd):/tmp/metabase --workdir /tmp/metabase clojure:openjdk-11-tools-deps-slim-bullseye /bin/bash
```

必要なモジュールをインストールします。

```
# apt update && apt install -y curl gnupg

# curl -sL https://deb.nodesource.com/setup_lts.x | bash -
# apt update && apt install -y nodejs

# curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg |  apt-key add -
# echo "deb https://dl.yarnpkg.com/debian/ stable main" |  tee /etc/apt/sources.list.d/yarn.list
# apt update && apt install -y yarn
```

モジュールをアップデートします。
```
# npx update-browserslist-db@latest
# npx browserslist@latest --update-db
# yarn upgrade
```

ビルドします。
```
# yarn
# ./bin/build.sh

Copyright © 2023 Metabase, Inc. 

Metabase Enterprise Edition extensions are PRESENT.

Usage of Metabase Enterprise Edition features are subject to the Metabase Commercial License. See https://www.metabase.com/license/commercial/ for details.
        Finished compilation in 74.0 seconds.
      Copy resources
        Copy src
        Copy shared/src
        Copy resources
      Create uberjar
Building uber jar: /tmp/metabase/target/uberjar/metabase.jar
        Created uberjar in 32.0 seconds.
      Update META-INF/MANIFEST.MF
      Built target/uberjar/metabase.jar in 110.8 seconds.
    Uberjar built successfully.
  All build steps finished.
```
`/tmp/metabase/target/uberjar`にjarファイルが出力されるので、dockerコンテナからexitして
```
$ java -jar ./target/uberjar/metabase.jar 
```
で実行できます。
</details>


## Nginx
```
$ nginx -version
nginx version: nginx/1.24.0
```
ログファイル形式
``` /etc/nginx/nginx.conf
http {

    log_format main "time:$time_iso8601\t"
                    "client:$remote_addr\t"
                    "request_method:$request_method\t"
                    "status:$status\t"
                    "url:$scheme://$http_host$request_uri\t"
                    "referer:$http_referer\t"
                    "forwarded_for:$http_x_forwarded_for\t"
                    "request_body:$request_body\t"
                    "body_bytes_sent:$body_bytes_sent\t"
                    "user_agent:$http_user_agent\t"
                    "authorization:$http_authorization";

    access_log /var/log/nginx/access.log main;
```
LTSVフォーマットで記述しています。
各値は`"<key>:value\t"`の形式で、`<key>`を後ほど使うのでメモでもしておいてください。

## MariaDB
```
$ mysql --version
mysql  Ver 15.1 Distrib 10.11.3-MariaDB, for Linux (x86_64) using readline 5.1
```

## Fluentd
Fluentdは普通に検索して出てくる記事だとv1やv2など古いバージョンが出てきてインストールでコケます。
ここでは最新のv4をインストールします。

### 本体
```
$ curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent4.sh | sh
```

### MariaDBライブラリ 
これがないとFluentdとMySQLの連携でコケます。
```
$ sudo yum install mariadb-devel
```

### FluentdとMySQLの連携ライブラリ
やたらインストールに時間がかかりますが、大人しく待ちましょう。
```
$ sudo td-agent-gem install fluent-plugin-mysql
```

### ファイルディスクリプタ
ファイルディスクリプタの最大数設定を変更します。
```
$ vim /etc/security/limits.conf
```
末尾に追記
``` /etc/security/limits.conf
root soft nofile 65536
root hard nofile 65536
*    soft nofile 65536
*    hard nofile 65536
```
保存後、`ulimit`で確認します。
```
$ ulimit -n
65536
```

### ログアクセス設定
`td-agent(fluentd)`がアクセスログを読み込めるように設定します。
```
$ sudo usermod -aG adm td-agent
```

### td-agent.confの編集
`td-agent`の動作を設定します。
以下を追記します
``` /etc/td-agent/td-agent.conf
<source>
  @type tail
  path /var/log/nginx/access.log
  pos_file /var/log/td-agent/nginx.access.log.pos
  tag nginx.access
  format ltsv
  time_format %Y-%m-%dT%H:%M:%S%z
</source>
<match nginx.access>
  @type mysql_bulk
  host <DB_host>
  port <DB_port>
  database <DB_name>
  username <DB_username>
  password <DB_password>
  column_names id,time,client,request_method,status,url,referer,forwarded_for,request_body,body_bytes_sent,user_agent,authorization
  key_names id,${time},client,request_method,status,url,referer,forwarded_for,request_body,body_bytes_sent,user_agent,authorization
  table <DB_table>

  flush_interval 5s
</match>

####
## Examples:
##
```
`<source>`ディレクティブのtailでログファイルの更新を監視し、新しい行が追加されるたびに取得します。
`<match>`ディレクティブは、データを指定したMySQLデータベースのテーブルに格納します。

`column_names`パラメータでカラム名を指定し、`key_names`パラメータでnginxのログファイルに記述した`<key>`に対応させる形で指定します。
`${time}`パラメータでログ取得時刻を設定して`datetime`型の`time`カラムに挿入します。
イベント発生時刻ではなくログ取得時刻が`time`カラムに挿入されますが、SQLの更新を5秒単位で行っているのでここでの誤差は無視します。(~~datetime形式に整形してログ出力する方法がわからなかったとかではないです~~)

設定を変更したらサービスを起動します。
```
$ sudo systemctl enable td-agent
$ sudo systemctl start td-agent
```

## 出力先テーブルの作成
Fluentdにテーブルが存在しない場合の処理はないため、事前に出力したデータを格納するテーブルを作成します。
以下の仕様でSQLデータベースを作成してください。
```
>CREATE DATABASE <DB_name>;

>CREATE TABLE <DB_table> (
id                INT AUTO_INCREMENT PRIMARY KEY,
time              datetime,
client            varchar(16),
request_method    varchar(4),
status            INT(11),
url               TEXT,
referer           TEXT,
forwarded_for     TEXT,
request_body      TEXT,
body_bytes_sent   varchar(20),
user_agent        TEXT,
authorization     varchar(30)
);
```

データベースを作成したあとは適当なページにアクセスすればテーブルに値が入っているはずです。

### metabase
`http://<server address>:3000`にアクセスすると、metabaseの初期セットアップが始まるので、プロンプトに従って設定を完了してください。
日本語はやや怪レいですが、十分使えるレベルです。