Nginx のアクセスカウントを Fluentd を使って Grafana で可視化する

7 min read読了の目安(約6300字

nginx のアクセスカウントを Fluentd を使って Grafana で可視化する

今回は表題の OSS 群をとりあえず使ってみて慣れることを目標に、Docker(Compose) を使って、簡単なハンズオンをやってみました。

今回使用した OSS の主な役割は以下のとおりです。

  • nginx: テスト用のアクセスログを生成します。
  • Fluentd: nginx のアクセスログを Elasticsearch に転送します
  • Elasticsearch: Fluentd から取得したデータを提供するサービスを提供します
  • Grafana: Elasticsearch からアクセスログデータを取得し、アクセスカウントをダッシュボード上に表示します。

本稿で作成したコードはすべて以下のリポジトリにまとめてありますので、ご自由にお使いください。Pull Request 等も歓迎です。

https://github.com/ks6088ts/example-access-count-visualizer

nginx

docker-compose.yml

services:
  nginx:
    image: nginx:1.19.7-alpine
    container_name: nginx
    ports:
    # バインドするポート番号は設定値として吐き出す
    - ${NGINX_PORT}:80
    volumes:
    # ローカルで作成した `nginx.conf` をコンテナにマウントして設定を反映させます
    - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
    # アクセスログ等をローカルから参照できるようにします
    - ./outputs/nginx:/var/log/nginx
    # コンテナ間通信を有効にするため、適当な共通の名前のネットワークを設定します
    networks:
    - shared-network

nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format main 'time:$time_iso8601\t'
                    'remote_addr:$remote_addr\t'
                    'request_method:$request_method\t'
                    'request_length:$request_length\t'
                    'request_uri:$request_uri\t'
                    'https:$https\t'
                    'uri:$uri\t'
                    'query_string:$query_string\t'
                    'status:$status\t'
                    'bytes_sent:$bytes_sent\t'
                    'body_bytes_sent:$body_bytes_sent\t'
                    'referer:$http_referer\t'
                    'useragent:$http_user_agent\t'
                    'forwardedfor:$http_x_forwarded_for\t'
                    'request_time:$request_time\t'
                    'upstream_response_time:$upstream_response_time\t'
                    'host:$host';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

nginx の Docker イメージをデフォルト状態で起動した状態のファイルをベースにしています。

Fluentd でアクセスログを ElasticSearch に集約するため、log_format をタブ区切りの文字列として出力するようにしました。

docker-compose up -d nginx で nginx サービスが起動します。
http://localhost:${NGINX_PORT} にアクセスすると、outputs/nginx/access.log に以下の通りタブ区切りのアクセスログが出力されました。

time:2021-03-26T05:26:19+00:00	remote_addr:172.18.0.1	request_method:GET	request_length:726	request_uri:/	https:	uri:/index.html	query_string:-	status:200	bytes_sent:850	body_bytes_sent:612	referer:-	useragent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36	forwardedfor:-	request_time:0.000	upstream_response_time:-	host:localhost

Fluentd

docker-compose.yml

services:
  fluentd:
    build:
      context: .
      dockerfile: ./docker/fluentd/Dockerfile
    container_name: fluentd
    ports:
    - ${FLUENTD_PORT}:24224
    - ${FLUENTD_PORT}:24224/udp
    volumes:
    # 設定ファイルをマウントします。(詳細は後述)
    - ./docker/fluentd/fluent.conf:/fluentd/etc/fluent.conf
    # nginx のアクセスログを参照できるようにします。
    - ./outputs/nginx:/var/log/nginx
    networks:
    - shared-network

Dockerfile

FROM fluent/fluentd:v1.3-1

# https://qiita.com/tamanobi/items/a57f2802c7fd1236ea52
USER root
# Elasticsearch のプラグインをイメージのビルド時にインストールしておきます。
RUN gem install fluent-plugin-elasticsearch

fluent.conf

タブ区切りの nginx のアクセスログを Elasticsearch に転送します。

<source>
  type tail
  format ltsv
  path /var/log/nginx/access.log
  tag nginx
  pos_file /var/log/nginx/access.log.pos
</source>

<match nginx>
  type elasticsearch
  host elasticsearch
  buffer_type memory
  port 9200
  index_name fluentd
  type_name nginx
  logstash_format true
  logstash_prefix nginx.access
</match>

Elasticsearch

docker-compose.yml

Elasticsearch の公式イメージをシングルノードで構築します。

services:
  elasticsearch:
    image: elasticsearch:7.10.1
    container_name: elasticsearch
    ports:
    - ${ELASTICSEARCH_PORT}:9200
    environment:
    - discovery.type=single-node
    networks:
    - shared-network

試しにアクセスすると以下の json が返ってきました。

curl -sL http://localhost:9200/ | jq .
{
  "name": "d8e38c95c00d",
  "cluster_name": "docker-cluster",
  "cluster_uuid": "UNfF0G0yRS-aGCpmGUpWCA",
  "version": {
    "number": "7.10.1",
    "build_flavor": "default",
    "build_type": "docker",
    "build_hash": "1c34507e66d7db1211f66f3513706fdf548736aa",
    "build_date": "2020-12-05T01:00:33.671820Z",
    "build_snapshot": false,
    "lucene_version": "8.7.0",
    "minimum_wire_compatibility_version": "6.8.0",
    "minimum_index_compatibility_version": "6.0.0-beta1"
  },
  "tagline": "You Know, for Search"
}

Grafana

docker-compose.yml

ユーザ名、パスワード等はそれぞれ ${GRAFANA_SECURITY_ADMIN_USER}${GRAFANA_SECURITY_ADMIN_PASSWORD} で設定できるようにしました。

http://localhost:${GRAFANA_PORT} で Grafana が起動します。

services:
  grafana:
    image: grafana/grafana:7.4.3
    container_name: grafana
    ports:
    - ${GRAFANA_PORT}:3000
    environment:
    - GF_SECURITY_ADMIN_USER=${GRAFANA_SECURITY_ADMIN_USER}
    - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_SECURITY_ADMIN_PASSWORD}
    volumes:
    # datasources をマウント
    - ./docker/grafana/datasources:/etc/grafana/provisioning/datasources
    networks:
    - shared-network

datasources.yml

datasource の設定は都度手動で設定しても良いですが、設定が静的であれば YAML ファイルとして書き出し、Grafana サービスにファイルをマウントするとよいです。
ファイルが適切にマウントされて Grafana が起動すると datasource が反映された状態でサービスが立ち上がってくれて便利です。
datasource の定義方法は、https://github.com/grafana/grafana/blob/master/devenv/datasources.yaml を参考にしました。
今回定義した datasource は以下の通りです。

apiVersion: 1

datasources:
  - name: elasticsearch
    type: elasticsearch
    access: proxy
    database: "[nginx.access-]YYYY.MM.DD"
    url: http://elasticsearch:9200
    jsonData:
      timeInterval: 10s
      interval: Daily
      timeField: "@timestamp"
      esVersion: 70

Dashboard から Elasticsearch を選択し、適切なクエリを設定すると nginx のアクセスログを Grafana のダッシュボードから確認できます。
なお、Elasticsearch への接続に失敗する場合は、テーブルが作成されていないことに起因するため、Nginx サーバにアクセスしてテーブルが生成されることを確認してください。