Docker-composeでECS上にコンテナを起動してログ収集・分析基盤(ELK)を構築する

# Docker-composeでECS上にコンテナを起動してログ収集・分析基盤(ELK)を構築する


## はじめに  
公開しているWebアプリケーションのアクセスログを収集したく、ログの収集・分析基盤としてELK環境を構築しました。自前でサーバを立てるのも面倒だったので、AWSとDockerのお勉強がてら、Docker-composeファイルを用意して、ECS上でコンテナを起動し、システムを構築しました。なお、構築時のエラー・トラブルについては別記事でまとめたいと思います。

## この記事の対象者  
- Elasticsearch, Logstash, Kibana環境をDockerで構築するのが初めて
- ECSを使ったことがなく、docker-compose.ymlからECS上でコンテナを建てたい人

## この記事を見てできること
- ECS上でdocker-composeを使って、コンテナを起動できる
- EC2上にELK環境を構築できる（厳密には、ElasticsearchとLogstashのコンテナをEC2上で動かせる）
- アプリケーションからPOSTされたJSONデータをKibanaで可視化できる

## システム情報
- Elasticsearch 7.0.0
- Logstash 7.0.0
- Kibana 7.0.0
- docker-compose v2
- EC2 t2.small メモリ2GB (無料枠対象外なのでご注意を)

## システム構成
色々試行錯誤した結果、以下のようなシステム構成となりました。
Kibanaをlocalhost上で建てているのは、EC2インスタンスのスペックが低すぎて（2GB)、EC2上でKibanaが起動しなかったためです。自分だけKibanaを見れればよかったので、Kibanaを確認したい時だけ、localhost上にKibanaをデプロイしています。  
![system.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/8ed8bec6-a82b-7656-30d8-77f2c2728f9c.jpeg)


## docker-compose.ymlを使って、ECS上でコンテナを起動
以下の色掛け部分の構築をします。
![system.002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/706cd9dc-8e66-d28a-949c-ae136ff00168.jpeg)

以下の記事が参考になりました。 
[公式](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_CLI.html) 
[ローカルで使用したdocker-compose.ymlを使ってECS上でコンテナを起動する](https://qiita.com/furu8ma/items/6a7bd4d85617ef55f2c6#ecs%E4%B8%8A%E3%81%A7%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B)
[ECS CLIでAmazon ECSを操作してみた](https://dev.classmethod.jp/cloud/aws/using-ecs-cl/)
[AWS CLIを使用するための初期設定](https://www.skyarch.net/blog/?p=7583)

### ECS-CLIをインストール

```
brew install amazon-ecs-cli
バージョン確認
aws --version
aws-cli/1.16.156 Python/3.7.0 Darwin/18.2.0 botocore/1.12.146
```

### ECS-CLIで使用するAWSの情報を設定

```
aws configure
AWS Access Key ID [None]: hogehoge
AWS Secret Access Key [None]: hogehoge
Default region name [None]: [リージョン名]
Default output format [None]: json
```
[リージョン名はここから検索](https://docs.aws.amazon.com/ja_jp/general/latest/gr/rande.html)


ここのIDとAccess Keyに何を設定すればいいかが分からない方は、おそらくIAMユーザを作成する必要があります。一旦、以下の公式ページに一通り目を通しましょう。
IAMユーザ作成→IAMユーザーのアクセスキーIDおよびシークレットアクセスキーの取得という手順をふむ必要があります。
※既に作成済みならば読み飛ばしてください。

[公式：AWS CLI の設定](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html)

### docker-compose.yml（Logstash, Elasticsearch用）を準備

```docker-compose.yml
version: "2"
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.0.0
    mem_limit: 1g
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    networks:
      - elastic-stack

  logstash:
    image: gogogonkun/logstash_pub
    mem_limit: 768m
    ports:
      - "5044:5044"
    environment:
      - "LS_JAVA_OPTS=-Xms512m -Xmx512m"
    networks:
      - elastic-stack

networks:
  elastic-stack:
```

- Dockerイメージについて  
logstashのDockerイメージは、事前に設定ファイルに変更を加えたり、confファイルを仕掛けたかったため、公式のDockerイメージを一部変更したものを使用しています。経緯や詳細は[こちらの記事](https://qiita.com/gogogonkun/items/b336f00b0cda3d811109)で紹介します。

- メモリサイズについて  
t2.smallインスタンスのメモリが2GBしかないため、mem_limitをそれぞれ1g, 768mと設定して、自重しています。ちなみに2GBを超えてしまうと、コンテナが起動してくれません。 経緯や詳細は上記と同様に[こちらの記事](https://qiita.com/gogogonkun/items/b336f00b0cda3d811109)で紹介します。



とりあえずローカルで起動

```
docker-compose up -d
```

動作確認

```
$: curl localhost:9200
{
  "name" : "77e042e7f49d",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "Ocgt84ZaReWsBhgjzMQUSA",
  "version" : {
    "number" : "7.0.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "b7e28a7",
    "build_date" : "2019-04-05T22:55:32.697037Z",
    "build_snapshot" : false,
    "lucene_version" : "8.0.0",
    "minimum_wire_compatibility_version" : "6.7.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
```

止める

```
docker-compose stop
```

### ECSクラスタを起動(t2.smallインスタンスは有料です)

```
ecs-cli up --keypair KEY_NAME --capability-iam --size 1 --instance-type t2.small
```

### docker-compose.ymlからECS上でコンテナを起動

```
ecs-cli compose -f docker-compose.yml up
```

確認

```
$: ecs-cli ps   
Name                                                State    Ports                                                         TaskDefinition  Health
6d644480-39a0-4367-b3ed-ac82b0d630d9/logstash       RUNNING xx.xxx.xxx.xxx:5044->5044/tcp                                 ELK:80          UNKNOWN
6d644480-39a0-4367-b3ed-ac82b0d630d9/elasticsearch  RUNNING  xx.xxx.xxx.xxx:9300->9300/tcp, xx.xxx.xxx.xxx:9200->9200/tcp  ELK:80          UNKNOWN
```

ここまでで、ECS上にコンテナを起動することが出来ました。

### 少し寄り道... 固定のIPアドレスの取得  
アプリケーションからLogstashへログをPOSTする際の送り先、KibanaでElasticsearchからログを取得する際の取得先のIPアドレスが、EC2インスタンスが再起動する度に変わってしまうと、その度にコンテナの設定変更が必要となります。
![system.003.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/f0580c9c-157e-c373-1581-933a2abba61a.jpeg)


でもそんなのは面倒なので、EC2インスタンスのIPアドレスを固定します。Elastic IPsでは固定のIPアドレスを取得して、EC2インスタンスに紐づけることができます。
以下の記事を参考に、EC2インスタンスにIPアドレスを紐づけます。 
[公式](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)
[AWS EC2インスタンスにElastic IP(固定グローバルIPアドレス)を割り当てる](https://ac-5.net/aws/aws_elasticip_allocation)

## docker-compose.yml（Kibana用）を使って、localhost上でコンテナを起動
先ほどまでとは異なり、以下の色掛け部分の環境を構築します。
![system.004.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/83b09eaa-5f56-82e5-2cd6-4c9142cc4263.jpeg)

### docker-compose.yml(Kibana用)を準備

``` docker-compose.yml
version: "2"
services:
  kibana:
    image: gogogonkun/kibana
    mem_limit: 3g
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_URL=http://XX.XXX.XXX.XXX:9200
      - ELASTICSEARCH_USER=NAME=kibana
      - ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
```

-  Dockerイメージについて
EC2インスタンス上に建てたコンテナと同様に、Dockerイメージを一部書き換えております。

- 環境変数について
    - ELASTICSEARCH_URL  
    先ほどElastic IPsで取得したIPアドレスを設定します。
    - ELASTICSEARCH_USER=NAME=kibana  
    Kibanaからインデックスを登録する際に、上手く書き込めなかったため、試行錯誤した結果です。 
    事象については、以下のリンクをご覧ください。  
    [Kibana Error Connecting to ES on Docker - Cannot Revive Connection](https://discuss.elastic.co/t/kibana-error-connecting-to-es-on-docker-cannot-revive-connection/133335)  
    [Cannot specify a default index pattern](https://github.com/elastic/stack-docker/issues/15)

### docker-compose.ymlからlocalhost上でコンテナを起動

```
 $: docker-compose up -d
```

確認

```
$: docker-compose ps   
     Name                   Command              State           Ports         
-------------------------------------------------------------------------------
kibana_kibana_1   /usr/local/bin/kibana-docker   Up      0.0.0.0:5601->5601/tcp
```

これで、EC2インスタンス上にElasticsearch、Logstashを、Localhost上にKibanaをそれぞれ建てることが出来ました。これで環境構築が完成したかのように思えますが、EC2インスタンスのセキュリティグループを設定していないため、アプリケーションやローカルの端末からEC2で建てているコンテナに接続することが出来ません。

### EC2インスタンスのSecurity Groups設定

特定のIPアドレスから、EC2で建てたコンテナの指定したポートに対する接続を許可します。
下の図のようなイメージです。

![system.005.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/4b59b622-e83f-7bf8-db37-6438465e1f34.jpeg)

この記事を参考にします。
[【AWS】セキュリティグループを設定してみた（そして関連する用語を調べてみた）](https://qiita.com/noko_qii/items/d4c19ec5e891264af462)

以下のような設定になるかと思います。
3番は動作確認のために、自分のIPアドレスから全てのポートにアクセスできるようにしています。

|  | Protocol | Port Range | Source |
|:------------:|:-----------:|:------------:|:------------:|
| 1 | TCP | 5044 | App IP |
| 2 | TCP | 9200 | My IP |
| 3 | TCP | 0 - 65535 | My IP |

上手くいかなかったら、とりあえず動作確認のために、全てのIPアドレスから全てのポートに対する接続を許可してみてから、接続を許可する範囲を狭めていくといいと思います。
※セキュリティホールになるので、最終的にはガバガバ設定にしないでください・・・

### 動作確認
- Elasticsearch  
\[EC2コンテナのIPアドレス]:9200　に接続します。  
JSONが返却されれば問題ないかと思います。

```
{
  "name" : "77e042e7f49d",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "Ocgt84ZaReWsBhgjzMQUSA",
  "version" : {
    "number" : "7.0.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "b7e28a7",
    "build_date" : "2019-04-05T22:55:32.697037Z",
    "build_snapshot" : false,
    "lucene_version" : "8.0.0",
    "minimum_wire_compatibility_version" : "6.7.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
```

- Kibana
localhost:5601　に接続します。
画面が表示されるかと思います。
<img width="1433" alt="スクリーンショット 2019-05-21 20.01.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/aafb7cd1-25b9-a515-ecd8-157b32f3f233.png">

- Logstash
\[EC2コンテナのIPアドレス]:5044　に接続します。  

```
ok
```
※Logstash用のconfファイルが置かれていなければ、何も返却されません。
 ちなみに今回は、Logstashがインストールされているコンテナの/usr/share/logstash/pipeline 配下に、以下のようなconfファイルを仕込んでいます。POSTされたJSONデータをそのままElasticsearchにぶち込むconfファイルです。

```movhis.conf
input {
    http {
        port => 5044
        response_headers => {
            "Access-Control-Allow-Origin" => "*"
            "Content-Type" => "text/plain"
            "Access-Control-Allow-Headers" => "Origin, X-Requested-With, Content-Type, Accept"
        }
    }    
}

output {
    elasticsearch {
        hosts => "http://[EC2インスタンスのIPアドレス]:9200"
        index => "movhis-%{+YYYY.MM}"
    }   
}

```

Logstashのコンテナを建てる度に、confファイルをちまちま設定するのが面倒な人は、以下の手順で、自分用のDockerイメージを使用しましょう。
1.  公式のDockerイメージを書き換える
2.  Dockerイメージのコミット
3.  DockerHubへプッシュ
4.  docker-compose.ymlでプッシュしたDockerイメージを指定する
※DockerHubへのプッシュまでについては[こちらの記事](https://qiita.com/gogogonkun/items/b336f00b0cda3d811109)を確認ください。

## logstashにhttpリクエストを投げて、Kibanaから確認してみる
※上記confファイルが設定されている前提の話です。

##### 先ほどの動作確認と同様に、ブラウザ上で\[EC2コンテナのIPアドレス]:5044　に接続

##### 次にKibanaからデータが登録されているか確認
1.インデックス選択
<img width="1426" alt="スクリーンショット 2019-05-21 20.23.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/537161f4-ed12-8130-1d24-0313f6ec4463.png">

<img width="1440" alt="スクリーンショット 2019-05-21 20.23.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/b51d548a-2c6c-0340-ac4e-b9a8349416ba.png">

2.データ確認
<img width="1437" alt="スクリーンショット 2019-05-21 20.24.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186355/67680005-35e6-f1b8-21e0-cfccd3e491cd.png">

データが登録されていることが確認出来ました。

## おわりに
ECSを利用してDocker-composeファイルからELKの環境を構築してきました。また、Logstashを通してElasticsearchに登録されたログデータを、Kibanaから確認することも出来ました。
今回はPOSTされたJSONデータをそのまま可視化するだけでしたが、システムのメトリクス・業務のログ等、いろんなログデータを登録して可視化することができます。自分が使いやすいように好きにカスタマイズして遊んでください。

後半は説明が適当になってしまったので、後で見直して、整理したいと思います。特に、ログを可視化できるようになるまで、割とハマったポイントがあったので、別途まとめて紹介したいと思います。

説明が長くなってしまいましたが、改善点やご意見お待ちしております。
ここまで見ていただき、ありがとうございました。