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をデプロイしています。

docker-compose.ymlを使って、ECS上でコンテナを起動

以下の色掛け部分の構築をします。

以下の記事が参考になりました。
公式
ローカルで使用したdocker-compose.ymlを使ってECS上でコンテナを起動する
ECS CLIでAmazon ECSを操作してみた
AWS CLIを使用するための初期設定

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

リージョン名はここから検索

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

公式:AWS CLI の設定

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イメージを一部変更したものを使用しています。経緯や詳細はこちらの記事で紹介します。

  • メモリサイズについて

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

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

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インスタンスが再起動する度に変わってしまうと、その度にコンテナの設定変更が必要となります。

でもそんなのは面倒なので、EC2インスタンスのIPアドレスを固定します。Elastic IPsでは固定のIPアドレスを取得して、EC2インスタンスに紐づけることができます。
以下の記事を参考に、EC2インスタンスにIPアドレスを紐づけます。
公式
AWS EC2インスタンスにElastic IP(固定グローバルIPアドレス)を割り当てる

docker-compose.yml(Kibana用)を使って、localhost上でコンテナを起動

先ほどまでとは異なり、以下の色掛け部分の環境を構築します。

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イメージを一部書き換えております。

  • 環境変数について

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で建てたコンテナの指定したポートに対する接続を許可します。
下の図のようなイメージです。

この記事を参考にします。
【AWS】セキュリティグループを設定してみた(そして関連する用語を調べてみた)

以下のような設定になるかと思います。
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 に接続します。
    画面が表示されるかと思います。

  • 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へのプッシュまでについてはこちらの記事を確認ください。

logstashにhttpリクエストを投げて、Kibanaから確認してみる

※上記confファイルが設定されている前提の話です。

先ほどの動作確認と同様に、ブラウザ上で[EC2コンテナのIPアドレス]:5044 に接続
次にKibanaからデータが登録されているか確認

1.インデックス選択

2.データ確認

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

おわりに

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

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

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