開発環境でRailsのログをFluentdでElasticsearchに送ってKibanaで見るための環境をDockerで作る

## 目的

fluentdやkibanaの使い方を知らなかったので、勉強のために開発環境にDockerでfluentdやkibanaを触れる環境を作ってみました。
この記事のゴールは、fluentdを通してElasticsearchにログを送りKibanaでRailsのログを見ることです。

記事の最後まで進めると、Kibanaでこんな風にRailsのログが表示されます。
![スクリーンショット 2021-01-27 18.37.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/818fb5af-628c-7921-9a6d-45448c063db8.png)


### 概要

![104429333-d7f65f80-55c8-11eb-9f20-81ea36226fcb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/761b6511-3b51-d674-050d-238afab7916d.png)


### フォルダ構成
トップにあるDockerfileはRails用です。
dockerフォルダにはElasticsearch、fluentd、Kibana、MySQL、NginxのDockerfileをおいています。

```shell
$ ls
Dockerfile docker-compose.yml docker/ entrypoint.sh
```

```shell
$ tree -L 2 docker
docker
├── elasticsearch
│   └── Dockerfile
├── fluentd
│   ├── Dockerfile
│   └── fluent.conf
├── kibana
│   └── Dockerfile
├── mysql
│   └── Dockerfile
└── nginx
    ├── Dockerfile
    └── nginx.conf
```


### バージョン


|対象|バージョン|
|----|----|
|ruby|2.7.2 |
|rails|6.1 |
|mysql|5.7 |
|nginx|1.19.6|
|elasticsearch| 7.10.1|
|kibana| 7.10.1|
|fluentd| 1.12-1|
|docker|20.10.2|


## Rails環境作成

まずはRailsを動かすためにファイルを作っていきます。DBの設定もここで書いていきます。

#### 1. まずは以下のコマンドで必要なファイルを作ります

```shell
touch {Dockerfile,Gemfile,Gemfile.lock,entrypoint.sh,docker-compose.yml,.dockerignore}
```

#### 2. Dockerfileの中身を書いていきます


手順1で作ったDockerfileに以下のコードを書いてください

```Dockerfile
FROM ruby:2.7-slim-buster

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    vim locales build-essential \
    libpq-dev libmariadb-dev curl npm && \
    npm install -g yarn n && \
    n 15.6

ENV APP_ROOT /app
WORKDIR $APP_ROOT

COPY Gemfile Gemfile.lock $APP_ROOT/

RUN gem update --system && gem install bundler:2.1.4

RUN RAILS_ENV=development bundle install && yarn install --frozen-lockfile


COPY . $APP_ROOT/
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
```

#### 3. Gemfileの作成
手順1で作ったGemfileに以下を記述

```ruby
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.7.2'

gem 'rails', '~>6'
```

#### 4. entrypoint.sh

手順1で作ったentrypoint.shに以下を記述

```shell
#!/bin/bash
set -e
rm -f /app/tmp/pids/server.pid
exec "$@"
```

#### 5. dockerフォルダを作ってそこにMySQLのDockerfileを作成する

docker/mysqlフォルダ作成し

```shell
mkdir -p docker/mysql
```

mysql用のDockerfileを作成し

```shell
touch docker/mysql/Dockerfile
```

docker/mysql/Dockerfileには以下を記述します

```Dockerfile
FROM mysql:5.7
```

#### 6. docker-compose.ymlを作成

```yml

version: "3.7"
services:
  db:
    build: ./docker/mysql/
    environment:
      MYSQL_DATABASE: root
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
      TZ: Asia/Tokyo
    expose:
      - 3306
    volumes:
      # 初期データを投入するSQLが格納されているフォルダ
      - ./docker/mysql/mysql_init:/docker-entrypoint-initdb.d
      # 永続化するときにマウントするフォルダ
      - ./docker/mysql/mysql_data:/var/lib/mysql
  app:
    build: .
    command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -b '0.0.0.0'"
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 3306
      DATABASE_USER: root
      RAILS_ENV: development
    expose:
      - 3000
    port:
      - 3000:3000
    depends_on:
      - db
    #ポート待受していないコンテナがすぐ終了してしまうのを防ぐ
    tty: true
    # コンテナの標準に入力をオープンにする
    stdin_open: true
    volumes:
      - .:/app
```

#### 7. .dockerignoreファイルに以下を記述

```
tmp/
!tmp/pids/
node_modules/
vendor/bundle/
```

#### 8. Dockerでrails newを実行

```shell
docker-compose run --no-deps app rails new . --force --database=mysql
```

これの実行が完了するまで数分の時間がかかるので気長に待ちましょう...

#### 9. DBの作成

先程のコマンドの実行が完了したらconfig/database.ymlというファイルが作られています。そのファイルのhostを以下のように編集します。
localhostからdbという名前に変えてください。

```yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
- host: localhost
+ host: db
```


そしたらrailsをDockerの立ち上げて

```
docker-compose up --build
```


別のターミナルでDBを作成するコマンドを実行します。

```shell
$ docker-compose run app rails db:create
Starting rails-monitoring_test_db_1 ... done
Created database 'app_development'
Created database 'app_test'
```

これが完了したら、http://localhost:3000 にアクセスしてみましょう。

この画面が出たら起動成功です！

![スクリーンショット 2021-01-27 16.50.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/c5da0205-677a-a0c8-5f00-197fea058945.png)



## Nginxを作成

続いてNginxの環境を作って行きます。

#### 1. Nginxに関する設定を置くフォルダを作成

```shell
mkdir -p docker/nginx/
```

#### 2. Nginx用のDockerfileと設定ファイルを作成

```shell
touch docker/nginx/{Dockerfile,nginx.conf}
```

#### 3. NginxのDockerfileを書きます

docker/nginx/Dockerfileに以下を記述

```docker
FROM nginx:1.19.6
# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
COPY ./nginx.conf /etc/nginx/
```

#### 4. docker/nginx/nginx.confに設定を書きます

```nginx
user  nginx;
worker_processes  auto;

events {
    worker_connections  1024;
}

http {
    upstream app {
        server unix:///app/tmp/sockets/puma.sock;
    }

    log_format ltsv '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';

    server {
        listen 80;
        server_name localhost;

        root /app/public;
        try_files  $uri/index.html $uri @app;

        access_log /var/log/nginx/access.log ltsv;
        error_log /var/log/nginx/error.log;

        location @app {
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_pass http://app;
        }
    }
}
```

#### 5. config/puma.rbを編集

rails newをしたときにconfig/puma.rbというファイルが作成されています。
puma.rbファイルの一番下に以下のコードを記述します。

```ruby
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

# ↓これを記述
bind "unix://#{Rails.root}/tmp/sockets/puma.sock"
```

またpuma.rbの以下の行をコメントアウトします

```ruby

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
# ↓この行をコメントアウト
- port ENV.fetch("PORT") { 3000 }
+ # port ENV.fetch("PORT") { 3000 }
```

#### 6. docker-compose.ymlを編集

docker-compose.ymlのapp部分について編集していきます。
1つは、書いてあったここの部分の ` -b '0.0.0.0'`を削除します。

```yml
- command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s -b '0.0.0.0'"
+ command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s"
```

2つめは、volumesに `- tmp-data:/app/tmp/sockets`を追加です。

```yaml
    volumes:
      - .:/app
      - tmp-data:/app/tmp/sockets
```

3つめは、portsの指定を削除です

```yml
    ports:
      - 3000:3000
```


続いては、nginxについての記述を追加します

```yml
  nginx:
    build: ./docker/nginx/
    # Nginxをforegroundで動かすため、daemonをoff
    command: nginx -g 'daemon off;'
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
      - tmp-data:/app/tmp/sockets
    ports:
      - 80:80
    links:
      - app

volumes:
    tmp-data:
```

上記のコードを追加した結果、docker-compose.yml 全体は今こうなっています

```yaml
version: "3.7"
services:
  db:
    build: ./docker/mysql/
    environment:
      MYSQL_DATABASE: root
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
      TZ: Asia/Tokyo
    expose:
      - 3306
    volumes:
      # 初期データを投入するSQLが格納されているフォルダ
      - ./docker/mysql/mysql_init:/docker-entrypoint-initdb.d
      # 永続化するときにマウントするフォルダ
      - ./docker/mysql/mysql_data:/var/lib/mysql
  app:
    build: .
    command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s"
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 3306
      DATABASE_USER: root
      RAILS_ENV: development
    expose:
      - 3000
    depends_on:
      - db
    #ポート待受していないコンテナがすぐ終了してしまうのを防ぐ
    tty: true
    # コンテナの標準に入力をオープンにする
    stdin_open: true
    volumes:
      - .:/app
      - tmp-data:/app/tmp/sockets

  nginx:
    build: ./docker/nginx/
    # Nginxをforegroundで動かすため、daemonをoff
    command: nginx -g 'daemon off;'
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
      - tmp-data:/app/tmp/sockets
    ports:
      - 80:80
    links:
      - app

volumes:
    tmp-data:
```

#### 7. Nginx込みでdocker-compose.ymlを立ち上げてみる

```shell
docker-compose up --build
```

起動が確認できたら [http:localhost/](http:localhost/) にアクセスしてみましょう。`:3000` なしのURLにアクセスできたら成功です！

![スクリーンショット 2021-01-27 16.50.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/ade4fa25-30b8-5912-2e57-45b0ea926e17.png)

## Elasticsearchの作成

#### 1. docker/elasticsearch/Dockerfileの作成

```shell
mkdir docker/elasticsearch/
```

```shell
touch docker/elasticsearch/Dockerfile
```

`docker/elasticsearch/Dockerfile`には以下の記述だけです

```yaml
FROM elasticsearch:7.10.1
```

#### 2. elasticsearchをdocker-compose.ymlに追加

```yaml
  elasticsearch:
    build: ./docker/elasticsearch/
    environment:
      - discovery.type=single-node
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - 9200:9200
    volumes:
      - ./docker/elasticsearch/data:/usr/share/elasticsearch/data
```

#### 3. Dockerを立ち上げて動作確認

```shell
docker-compose up
```

起動が確認できたら http://localhost:9200/ にアクセスしてみましょう。こんな画面が出たら成功です！
![スクリーンショット 2021-01-27 17.46.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/42678984-8b38-00f5-80c7-3a3e4a0c240e.png)

## fluentdの作成

#### 1. docker/fluentd/Dockerfileを作成します

```shell
mkdir -p docker/fluentd/
```

```shell
touch docker/fluentd/{Dockerfile,fluent.conf}
```

`docker/fluentd/Dockerfile`には以下を記述します。

```yaml
FROM fluent/fluentd:v1.12-1

USER root
RUN gem install fluent-plugin-elasticsearch
```


#### 2. docker/fluentd/fluent.confを作成します

以下を記述すればOKです。

```conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<filter nginx>
  @type parser
  key_name log
  <parse>
    @type ltsv
  </parse>
</filter>

<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>

<filter rails>
  @type parser
  key_name messages
  <parse>
    @type json
  </parse>
</filter>

<match rails>
  @type elasticsearch
  host elasticsearch
  buffer_type memory
  port 9200
  type_name rails
  logstash_format true
  logstash_prefix rails.access
</match>
```

#### 3. docker-compose.ymlにfluentdを追加

```yml
  fluentd:
    build: ./docker/fluentd/
    volumes:
      - ./docker/fluentd/fluent.conf:/fluentd/etc/fluent.conf
    ports:
      - 24224:24224
    depends_on:
      - elasticsearch
```

続けて既存のdocker-compose.ymlにも編集を加えます

まずはappのdepends_onの箇所に `- fluentd` を加えます。

```yml
  app:
    depends_on:
      - db
      - fluentd # これを追加
```

次にnginxも編集します。

```yml
  nginx:
    build: ./docker/nginx/
    # Nginxをforegroundで動かすため、daemonをoff
    command: nginx -g 'daemon off;'
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
      - tmp-data:/app/tmp/sockets
    ports:
      - 80:80
    links:
      - app
      # ↓ここから下を追加
      - fluentd
    logging: # ロギング（ログ保存）ドライバをfluentdが行う
      driver: fluentd
      options:
        tag: nginx
        # 初期接続をブロックしない
        fluentd-async-connect: "true"
```

fluentdの設定はこれで完了です。次のKibanaの設定で最後になります！


## Kibanaの作成


#### 1. docker/kibana/Dockerfileを作成します

```shell
mkdir -p docker/kibana/
```

```shell
touch docker/kibana/Dockerfile
```

`docker/kibana/Dockerfile`には以下を記述します。

```yaml
FROM kibana:7.10.1
```

#### 2. docker-compose.ymlにkibanaを追加します

```yml
  kibana:
    build: ./docker/kibana/
    ports:
      - 5601:5601
    depends_on:
      - elasticsearch
```

これで完成です！

上記までの記述でdocker-compose.ymlは以下のようになりました

```yml
version: "3.7"
services:
  db:
    build: ./docker/mysql/
    environment:
      MYSQL_DATABASE: root
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
      TZ: Asia/Tokyo
    expose:
      - 3306
    volumes:
      # 初期データを投入するSQLが格納されているフォルダ
      - ./docker/mysql/mysql_init:/docker-entrypoint-initdb.d
      # 永続化するときにマウントするフォルダ
      - ./docker/mysql/mysql_data:/var/lib/mysql

  app:
    build: .
    command: /bin/sh -c "rm -f /app/tmp/pids/server.pid && bundle exec rails s"
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 3306
      DATABASE_USER: root
      RAILS_ENV: development
    expose:
      - 3000
    depends_on:
      - db
      - fluentd
    #ポート待受していないコンテナがすぐ終了してしまうのを防ぐ
    tty: true
    # コンテナの標準に入力をオープンにする
    stdin_open: true
    volumes:
      - .:/app
      - tmp-data:/app/tmp/sockets

  nginx:
    build: ./docker/nginx/
    # Nginxをforegroundで動かすため、daemonをoff
    command: nginx -g 'daemon off;'
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
      - tmp-data:/app/tmp/sockets
    ports:
      - 80:80
    links:
      - app
      - fluentd
    logging:
      # ロギング（ログ保存）ドライバをfluentdが行う
      driver: fluentd
      options:
        tag: nginx
        # 初期接続をブロックしない
        fluentd-async-connect: "true"

  elasticsearch:
    build: ./docker/elasticsearch/
    environment:
      - discovery.type=single-node
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - 9200:9200
    volumes:
      - ./docker/elasticsearch/data:/usr/share/elasticsearch/data

  fluentd:
    build: ./docker/fluentd/
    volumes:
      - ./docker/fluentd/fluent.conf:/fluentd/etc/fluent.conf
    ports:
      - 24224:24224
    depends_on:
      - elasticsearch

  kibana:
    build: ./docker/kibana/
    ports:
      - 5601:5601
    depends_on:
      - elasticsearch

volumes:
    tmp-data:
```

ここまでできたら動作確認をしてみましょう。

```shell
docker-compose up
```

Kibanaは http://localhost:5601/app/home/ にアクセスして以下のような画面が表示されたら成功です！

![スクリーンショット 2021-01-27 17.44.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/e0d7c71c-b9ad-a902-8bd8-a6fff1d3c652.png)


## KibanaでIndexPatternを作成する

RailsのログをKibanaで見れるようにするためにもう少しだけRailsに手を加えます。

#### 1. gemを追加
Gemfileに以下の2つを追加してください

```ruby
# Railsのログをfluentdに送る
gem 'act-fluent-logger-rails'
gem 'lograge'
```

#### 2. config/application.rbに設定追加

```ruby
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.1

    # ↓これを追加
    # Fluentdのログ設定
    config.log_level = :info
    config.logger = ActFluentLoggerRails::Logger.new
    config.lograge.enabled = true
    config.lograge.formatter = Lograge::Formatters::Json.new
    # ↑ここまで
  end
```


#### 3. config/fluent-logger.ymlファイルを作成

```
touch config/fluent-logger.yml
```

`config/fluent-logger.yml`ファイルに以下のコードを追記します。

```yml
development:
  fluent_host:   fluentd
  fluent_port:   24224
  tag:           'rails'
  messages_type: 'string'

test:
  fluent_host:   '127.0.0.1'
  fluent_port:   24224
  tag:           'rails'
  messages_type: 'string'

production:
  fluent_host:   '127.0.0.1'
  fluent_port:   24224
  tag:           'rails'
  messages_type: 'string'
```

またRailsのいろいろなログを見るためにscaffoldでCRUD処理を追加しておきます。

```shell
docker-compose run app rails g scaffold User name:string
docker-compose run app rails db:migrate
```



これでRails側の設定は完了です。


再度 `docker-compose up --build`をして起動しましょう。

http://localhost/users このページにアクセスしてScaffoleで作られたページが見れたら成功です。

![スクリーンショット 2021-01-27 18.30.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/ae6c0d26-1ae6-6c99-885d-2af26bfaefe1.png)


### IndexPatternを作成

#### 1. kibanaにアクセス

```
docker-compose up --build
```

これでKibanaが起動したら
http://localhost:5601/app/management/kibana/indexPatterns/create
にアクセスします。

先程のScaffoldで作られたページ(http://localhost/users )にアクセスをすると、Railsが動くようになり、その結果Kibanaで`raisl.access-**`というIndex patternを作れるようになります。

![スクリーンショット 2021-01-27 18.31.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/bfd152a9-9f85-d4f3-cb58-d2ea0895355e.png)

#### 2. Index pattern nameというテキストフォームに `rails.access-*`を入力してNext spepに進みます

![スクリーンショット 2021-01-27 18.34.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/bc277df8-3f1a-8ce5-1f89-ccd80bddc750.png)


#### 3. Time fieldの値を `@timestamp`にして「Create index pattern」ボタンを押して完成です

![スクリーンショット 2021-01-27 18.34.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/474b2a22-9c6a-9566-515c-0a78a57d018b.png)


#### 4. IndexPatterの作成が終わったらログを実際に見てみましょう

http://localhost:5601/app/discover/ にアクセスすると、以下のように直近15分のログが確認できます。

![スクリーンショット 2021-01-27 18.37.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/780598de-11ef-fdbb-cb18-1be459306e2d.png)

これでKibanaでRailsのログを見るための環境をDockerで作ることに成功しました。

## おわりに

これでRails×MySQL×Nginx×Fluentd×Elaasticsearch×Kibbanaの環境ができました。

fluentdやKibanaを触ってみることが目的でしたが、Dockerの勉強にもなって楽しかったです。まだ触りたてなので、もっと適切な記述方法などがあればコメントで優しく教えていただけたら幸いです。

参考：https://chulip.org/entry/2019/08/18/233205, https://qiita.com/zgmf_mbfp03/items/0697cc827efa89e5d93e