【Docker】NginxのアクセスログをFluentd+Elasticsearch+Kibanaで可視化する

# やること

Nginx２台をコンテナで構築し、コンテナごとにIndexを分けてElasticsearchに送るようにします。

Nginx２台を構築すると、ポートを切り替えてアクセスする必要があります。
しかし、構成的に綺麗ではないので、
Trafikというリバプロを利用してパスベースでアクセス先のNginxを振り分けられるようにしました。これにより、Traefikの80番ポートだけ公開すれば良くなります。

![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518403/7bbb2f8d-44e3-cd55-b839-906c2dd95a26.png)

Trafikの構成については以下を参考にしました。

- [docker-composeでリバースプロキシとしてtraefikを使う](https://cloudandbuild.jp/blog/article-7)

# ソース

ソースは以下にアップしています。

- [v1tam1nb2/traefik-nginx-fluentd-elasticsearch](https://github.com/v1tam1nb2/traefik-nginx-fluentd-elasticsearch)

# Quick Start

```bash
git clone https://github.com/v1tam1nb2/traefik-nginx-fluentd-elasticsearch.git
cd traefik-nginx-fluentd-elasticsearch
docker-compose build
docker-compose up -d
curl http://localhost/service-a.html
curl http://localhost/service-b.html
```
アクセス後、Kibanaにアクセスしてログを確認できるようにします。
初回は以下でデータビューの作成が必要になります。

- http://localhost:5601/app/management/kibana/dataViews

データビューについては以下を参考にしてください。

- [Create a data view](https://www.elastic.co/guide/en/kibana/master/data-views.html)

最終的にこのようにログが確認できればOKです。

![es.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518403/ec350662-3989-cf4f-6afd-1695f3f7380a.png)


# ソース

## Traefik

以下がTraefikのセクションです。ポートの`18000:8080`はTraefikのDashboardにアクセスするためのものです。今回はあまり気にしなくてもOKです。

```yaml
    # リバプロ
    traefik:
      image: traefik:latest
      container_name: "traefik"
      ports:
        - "80:80"
        - "18000:8080"
      volumes:
        - "/var/run/docker.sock:/var/run/docker.sock:ro"
        - ./src/traefik/traefik.yml:/etc/traefik/traefik.yml
```

```yaml:traefik.yml
# Provider
providers:
  docker:
    exposedByDefault: false

# エントリーポイント
entryPoints:
  http:
    address: ":80"
  # clickhouse:
  #   address: ":9000"
  # udp-test:
  #   address: ":3179/udp"

api:
  # Dashboard有効化
  dashboard: true
  insecure: true

# ログレベル
log:
  level: DEBUG
```

## Nginx

```yaml
    nginx_a:
      image: nginx
      container_name: nginx_a
      hostname: nginx_a
      volumes:
        - ${PWD}/src/nginx/service-a.html:/usr/share/nginx/html/service-a.html
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.service-a.rule=PathPrefix(`/service-a`)"
        - "traefik.http.routers.service-a.entrypoints=http"
      depends_on:
        - fluentd
      logging:
        driver: "fluentd"
        options:
          fluentd-address: localhost:24224
          tag: nginx.a.access
          # 起動時に接続を確立的無くても contaienr は起動する設定
          fluentd-async-connect: "true"

    nginx_b:
      image: nginx
      container_name: nginx_b
      hostname: nginx_b
      volumes:
        - ${PWD}/src/nginx/service-b.html:/usr/share/nginx/html/service-b.html
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.service-b.rule=PathPrefix(`/service-b`)"
        - "traefik.http.routers.service-b.entrypoints=http"
      depends_on:
        - fluentd
      logging:
        driver: "fluentd"
        options:
          fluentd-address: localhost:24224
          tag: nginx.b.access
          # 起動時に接続を確立的無くても contaienr は起動する設定
          fluentd-async-connect: "true"
```

`labels`でTraefikによるルーティング設定を定義しています。以下は`http://localhost/service-a.html`にアクセスすると、`nginx_a`コンテナの`service-a.html`を見るようにする設定です。

`traefik.http.routers.service-a.entrypoints`には`traefik.yml`に定義した`entrypoint`を指定します。今回は`http`という名前で定義しています。

```yaml
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.service-a.rule=PathPrefix(`/service-a`)"
        - "traefik.http.routers.service-a.entrypoints=http"
```

以下はコンテナのログをFluentdに転送するための設定です。コンテナごとにIndexを分けるために、`tag`で識別できるようにしています。

```yaml
      logging:
        driver: "fluentd"
        options:
          fluentd-address: localhost:24224
          tag: nginx.a.access
          # 起動時に接続を確立的無くても contaienr は起動する設定
          fluentd-async-connect: "true"
```

## Fluentd

Elastisearch用のプラグインをインストールしています。

```Dockerfile:Dockerfile
# https://docs.fluentd.org/output/elasticsearch
FROM fluent/fluentd:v1.15.0-debian-1.0

USER root

# FluentdとElasticsearchを連携させるためのプラグインをインストール
RUN gem install fluent-plugin-elasticsearch --no-document --version 5.2.4

USER fluent
```

```yaml
    fluentd:
      build: ./src/fluentd
      container_name: fluentd
      hostname: fluentd
      restart: always
      ports:
        - "24224:24224"
        - "24220:24220"
        - "24224:24224/udp"
      # depends_on:
      #   - es
      volumes:
        #- ${PWD}/log:/fluentd/log
        - ${PWD}/src/fluentd/fluent.conf:/fluentd/etc/fluent.conf:ro
```

confでは、`tag`ごとにアウトプット先のIndexを分けるようにしています。`nginx_a`コンテナのログはIndex `nginx-a-log*`に、`nginx_b`コンテナのログはIndex `nginx-b-log*`に入れるようにします。

```conf:fluent.conf
# Fluentdの内部メトリックをHTTPで取得できる設定
# https://docs.fluentd.org/input/monitor_agent
<source>
  @type monitor_agent
  bind 0.0.0.0
  port 24220
</source>

# データのInput設定
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

# データのOutput設定 (FluentdからElasticsearch)
# tag "nginx.a"にマッチするログをESに転送
<match nginx.a.**>
  @type copy

  <store>
    @type elasticsearch
    host elasticsearch
    port 9200
    logstash_format true
    logstash_prefix nginx-a-log
    logstash_dateformat %Y%m%d
    include_tag_key true
    type_name access_log
    tag_key @log_name
    flush_interval 1s
  </store>

  <store>
    @type stdout
  </store>

  # バッファの定義
  <buffer>
    @type file
    flush_mode interval
    flush_interval 10s
    path /var/log/fluentd/elasticsearch
    chunk_limit_size 512m
    flush_at_shutdown true
  </buffer>
</match>

# データのOutput設定 (FluentdからElasticsearch)
# tag "nginx.b"にマッチするログをESに転送
<match nginx.b.**>
  @type copy

  <store>
    @type elasticsearch
    host elasticsearch
    port 9200
    logstash_format true
    logstash_prefix nginx-b-log
    logstash_dateformat %Y%m%d
    include_tag_key true
    type_name access_log
    tag_key @log_name
    flush_interval 1s
  </store>

  <store>
    @type stdout
  </store>

  # バッファの定義
  <buffer>
    @type file
    flush_mode interval
    flush_interval 10s
    path /var/log/fluentd/elasticsearch
    chunk_limit_size 512m
    flush_at_shutdown true
  </buffer>
</match>
```


## Elasticsearch/Kibana

今回は`8.4.3`を利用しました。簡単にするためにシングルノードで構築している点に注意してください。


```yaml
    # Elasticsearchコンテナ
    elasticsearch:
      image: docker.elastic.co/elasticsearch/elasticsearch:8.4.3
      container_name: elasticsearch
      hostname: elasticsearch
      restart: always
      environment:
        - discovery.type="single-node"
        - cluster.name="es-cluster"
        - node.name="elasticsearch"
        - bootstrap.memory_lock=true
        - xpack.security.enabled=false
        - TZ=Asia/Tokyo
      ulimits:
        memlock:
          soft: -1
          hard: -1
      # volumes:
      #   # データを永続化
      #   - ${PWD}/data:/usr/share/elasticsearch/data
      ports:
        - 9200:9200

    # Kibanaのコンテナ
    kibana:
      image: docker.elastic.co/kibana/kibana:8.4.3
      container_name: kibana
      hostname: kibana
      ports:
        - 5601:5601
      environment:
        - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
        - TZ=Asia/Tokyo
        - I18N_LOCALE=ja-JP
      depends_on:
        - elasticsearch
      restart: always
```

# まとめ

コンテナのログをFluentd経由でElasticsearchに転送し、Kibanaで確認することができました。
Fluentdのtagを利用することでアウトプット先のIndexを分けることができました。

今回はログの加工などは行いませんでしたが、機会があればやってみたいと思います。