Nginxのアクセスログをfluentd経由でMySQLに保存する
「nginx実践入門」を呼んでやってみたかったことの一つ、fluentdに入門してみようと思います。
環境構築は https://itoka.hatenadiary.com/entry/2022/03/04/004453 がベースです。
環境
- Apple M1 Pro
- macOS Monterey 12.1
- Docker Engine 20.10.12
- Docker Compose v2.2.3
- Ruby 3.1.1
- Ruby on Rails 7.0.2
- Nginx 1.21.6
- fluentd 1.14
fluentdのDockerfile
Docker Hub > fluentd > How to build your own image > 3.1 For current images を参考に、mysqlプラグインが入ったDockerfileを作成していきます。
ディレクトリはapp/docker/fluentd/
にしました。
ちなみに、&& apk add --no-cache mariadb-dev \
がないと、ビルド時にERROR: Error installing fluent-plugin-mysql:
で失敗しました。
FROM fluent/fluentd:v1.14-1 # Use root account to use apk USER root # below RUN includes plugin as examples elasticsearch is not required # you may customize including plugins as you wish RUN apk add --no-cache --update --virtual .build-deps \ sudo build-base ruby-dev \ && apk add --no-cache mariadb-dev \ # これが無いとビルド時エラー && sudo gem install fluent-plugin-mysql --no-document \ # mysqlプラグインの指定 && sudo gem sources --clear-all \ && apk del .build-deps \ && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem
docker-compose.yml
version: '3' services: web: build: context: . dockerfile: ./docker/web/Dockerfile volumes: - .:/app - public:/app/public - tmp:/app/tmp depends_on: - db stdin_open: true tty: true environment: DB_ROOT_USERNAME: root DB_USERNAME: development DB_PASSWORD: password TZ: Asia/Tokyo DB_HOST: db db: platform: linux/x86_64 image: mysql:8.0 environment: - MYSQL_DATABASE=development - MYSQL_USER=development - MYSQL_PASSWORD=password - MYSQL_ROOT_PASSWORD=passwordpassword - TZ=Asia/Tokyo ports: - '3306:3306' command: --default-authentication-plugin=mysql_native_password volumes: - ./volumes/mysql/data/db/:/var/lib/mysql - ./volumes/mysql/conf/db/my.cnf:/etc/my.cnf.d/my.cnf nginx: build: context: . dockerfile: ./docker/nginx/Dockerfile volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf - ./docker/nginx/conf.d:/etc/nginx/conf.d - public:/app/public - tmp:/app/tmp - http-log:/var/log/nginx # 追加 ports: - "80:80" depends_on: - web fluentd: # 追加 build: context: . dockerfile: ./docker/fluentd/Dockerfile ports: - 24224:24224 volumes: - ./docker/fluentd/fluent.conf:/fluentd/etc/fluent.conf - http-log:/var/log/nginx log_db: # 追加 platform: linux/x86_64 image: mysql:8.0 environment: - MYSQL_DATABASE=log - MYSQL_USER=log - MYSQL_PASSWORD=password - MYSQL_ROOT_PASSWORD=passwordpassword - TZ=Asia/Tokyo ports: - '3307:3306' command: --default-authentication-plugin=mysql_native_password volumes: - ./volumes/mysql/data/log_db/:/var/lib/mysql - ./volumes/mysql/conf/log_db/my.cnf:/etc/my.cnf.d/my.cnf - ./docker/fluentd/initdb.d:/docker-entrypoint-initdb.d volumes: public: tmp: http-log: # 追加
- nginxとfluentdでログを共有するため、
http-log
ボリュームを追加しました - nginxについて、個別設定ファイルを作成するため、
./docker/nginx/conf.d:/etc/nginx/conf.d
をvolumeに指定しました - ログ保管用に
log_db
というMySQLコンテナを立てます log_db
の/docker-entrypoint-initdb.d
にログ保管用のCREATE TABLE文を記載したSQLを指定し、コンテナ起動時にテーブルが作成されるようにします
nginxのログフォーマットを変更する
使いやすいLTSVにします。
conf.d
に格納し、nginx.conf
でincludeするようにします。
docker/nginx/conf.d/log_format.conf
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; log_format ltsv 'time:$time_iso8601\t' 'remote_addr:$remote_addr\t' 'status:$status\t' 'request_method:$request_method\t' 'request_uri:$request_uri\t' 'request_length:$request_length\t' 'https:$https\t' 'uri:$uri\t' 'query_string:$query_string\t' 'bytes_sent:$bytes_sent\t' 'body_bytes_sent:$body_bytes_sent\t' 'http_referer:$http_referer\t' 'useragent:$http_user_agent\t' 'forwardedfor:$http_x_forwarded_for\t' 'request_time:$request_time\t' 'upstream_addr:$upstream_addr\t' 'upstream_response_time:$upstream_response_time\t' 'upstream_cache_status:$upstream_cache_status';
docker/nginx/nginx.conf
http {
upstream puma {
server unix:///app/tmp/sockets/puma.sock;
}
include /etc/nginx/conf.d/*.conf;
access_log /var/log/nginx/access.log ltsv;
# ...
また、/var/log/nginx/access.log
や/var/log/nginx/error.log
ですが、nginx
のdocker imageだとこれらのファイルは標準出力へのシンボリックリンクとなっており、ファイルに出力してくれないようでした。
https://imanengineer.net/docker-compose-nginx-log/
ログのファイル名を別のものに変えると、ファイルに出力してくれます。
docker/nginx/nginx.conf
# ... error_log /var/log/nginx/error_dev.log; # error_dev.log http { upstream puma { server unix:///app/tmp/sockets/puma.sock; } include /etc/nginx/conf.d/*.conf; access_log /var/log/nginx/access_dev.log ltsv; # access_dev.log # ...
ログ保管用MySQLテーブル
カラム定義は適当です。LTSVのフォーマットに合わせて作成します。
docker/fluentd/initdb.d/Log.sql
DROP SCHEMA IF EXISTS log; CREATE SCHEMA log; USE log; DROP TABLE IF EXISTS nginx_access_log; CREATE TABLE nginx_access_log ( id INT AUTO_INCREMENT PRIMARY KEY, time DATETIME, remote_addr VARCHAR(200), status INT, request_method VARCHAR(10), request_uri TEXT(3000), request_length INT, https VARCHAR(10), uri TEXT(3000), query_string TEXT(3000), bytes_sent INT, body_bytes_sent INT, http_referer TEXT(3000), useragent TEXT(500), forwardedfor TEXT(500), request_time VARCHAR(20), upstream_addr VARCHAR(200), upstream_response_time VARCHAR(20), upstream_cache_status VARCHAR(20) );
fluent.conf
fluentdの設定ファイルを用意します。
docker/fluentd/fluent.conf
<source> @type tail format ltsv path /var/log/nginx/access_dev.log pos_file /var/log/nginx/access_dev.log.pos tag nginx.access time_key time time_format %Y-%m-%dT%H:%M:%S%z </source> <match nginx.access> @type mysql_bulk host log_db port 3306 database log username log password password table nginx_access_log column_names time,remote_addr,status,request_method,request_uri,request_length,https,uri,query_string,bytes_sent,body_bytes_sent,http_referer,useragent,forwardedfor,request_time,upstream_addr,upstream_response_time,upstream_cache_status key_names ${time},remote_addr,status,request_method,request_uri,request_length,https,uri,query_string,bytes_sent,body_bytes_sent,http_referer,useragent,forwardedfor,request_time,upstream_addr,upstream_response_time,upstream_cache_status flush_interval 10s </match>
source
@type
にはtail
を指定しますformat
にはLTSVを指定しますtime_format
は、後ほどMySQLのDATETIMEカラムに格納しやすくするために指定しています(ログフォーマットでせっかくtime:$time_iso8601
を指定したのに意味がない・・?)
match nginx.access
source
でnginx.access
のタグをつけ、match nginx.access
でMySQLに飛ばしていきます- プラグインでMySQLに送りつける場合、
@type
はmysql_bulk
のようです - host~passwordはログデータを格納するDB接続情報を指定します(ここでは
log_db
に指定した接続情報 column_names
にはログを保管するMySQLのカラムを指定しますkey_name
でcolumn_names
とLTSVのラベルをマッピングさせます
動作確認
ここまで対応したらdocker-compose up
をしたうえでrailsアプリケーションにアクセス(http://localhost
)し、アクセスログがMySQLに送られていることを確認できると思います。
# MySQLにログイン $ docker-compose exec log_db bash -c 'mysql -u ${MYSQL_USER} -p${MYSQL_PASSWORD}' mysql> use log mysql> select * from nginx_access_log\G *************************** 1. row *************************** id: 1 time: 2022-03-16 14:04:59 remote_addr: 172.18.0.1 status: 304 request_method: GET request_uri: / request_length: 1534 https: uri: / query_string: - bytes_sent: 788 body_bytes_sent: 0 http_referer: - useragent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 forwardedfor: - request_time: 0.506 upstream_addr: unix:///app/tmp/sockets/puma.sock upstream_response_time: 0.506 upstream_cache_status: - 26 rows in set (0.01 sec)
おまけ
本当はMySQLに取り込んだレコードをMetabaseに読み込んでSQL発行までしたかったのですが、コンテナの起動がうまくいったりいかなかったりしたので、おまけ程度にdocker-compose.yml
を載せておきます。。
# ... log_db: platform: linux/x86_64 image: mysql:8.0 environment: - MYSQL_DATABASE=log - MYSQL_USER=log - MYSQL_PASSWORD=password - MYSQL_ROOT_PASSWORD=passwordpassword - TZ=Asia/Tokyo ports: - '3307:3306' command: --default-authentication-plugin=mysql_native_password volumes: - ./volumes/mysql/data/log_db/:/var/lib/mysql - ./volumes/mysql/conf/log_db/my.cnf:/etc/my.cnf.d/my.cnf - ./docker/fluentd/initdb.d:/docker-entrypoint-initdb.d metabase: # 追加 image: metabase/metabase volumes: - "./docker/metabase/data:/metabase-data" environment: MB_DB_FILE: /metabase-data/metabase.db MB_DB_TYPE: mysql MB_DB_DBNAME: log MB_DB_PORT: 3306 MB_DB_USER: log MB_DB_PASS: password MB_DB_HOST: log_db ports: - "3000:3000" depends_on: - log_db