ElasticBeanstalk Multi-Container Dockerで本番運用したいンゴ

こんにちは〜
今年のクリスマスは第一次世界大戦の戦場(Battle Field1)で過ごそうと思っている@mekemo_daoです。
いや〜、クリスマスは戦場に出るに限りますね(泣)

さて、この記事は [Docker2 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/docker2) の10日目です。

今回はタイトルにある通り、AWSのElasticBeanstalk Multi-Container Dockerを使って、Dockerで本番運用できないか試行錯誤してみました。

まだまだ発展途上ですが、githubにわかりやすいサンプルを上げました。
こちらをcloneしてもらえると話が早いです。READMEのコマンド打つだけで簡単に動くはずです。
https://github.com/takundao71/rails_for_docker

これをたたき台にして変更していくと捗ると思います。
もっと複雑な構成にしたり、CircleCIを絡めたり、何かイケてる改良あればpullreq下さい(震え声)

以下githubからcloneしていることを前提に、解説していきます。

#要件
やりたいのは以下のとおりです

**テスト環境**

* Docker for macでRails・Mysql・Postfixをコンテナで運用する
 
* macでRailsのコンテナを動かすと、ファイルシステムの問題でブラウジングが超重いのでdocker-syncを使う

**本番環境**

* ElasticBeanstalk Multi-Container Dockerを使う

* RailsはDockerコンテナ、データベースはElastic BeanstalkのRDS、メールはSES使う(今回は割愛 普通に設定するだけです)

* プライペートレポジトリはECR(Amazon EC2 Container Registry)を使う

* ログはCloud Watch Logsと連携させて、エラー通知もCloud Watchを使いたい。

#テスト環境

macでRailsのコンテナを動かすと、ファイルシステム監視の問題で超重いので
docker-syncは必須です。この辺りが詳しいです。

[docker-syncでホスト-コンテナ間を爆速で同期する](http://qiita.com/pocari/items/0340049742927f3a94b7)

以下コマンドを打って下さい。docker-sync.ymlはgithubのファイルにあります。

```
gem install docker-sync
brew install fswatch
brew install unison
```

docker-compose.ymlはテスト環境でのみ使用する感じです。
これもgithubのファイルにありますが、mysqlのパスワードは適宜変えて下さい。

```YAML:docker-compose.yml
#dev用
version: '2'
services:
  web:
    build:
      context: .
      dockerfile: "Dockerfile.dev"
    volumes:
      - sync-volume:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - mail

  db:
    build: config/docker/mysql/
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: hogehoge

  mail:
    build: config/docker/mail/
    ports:
      - "25:25"
volumes:
  sync-volume:
    external: true
```

テスト用のDockerfileはDockerfile.devです。
mysqlのパスワードとDB名は変えて下さい。

```Dockerfile.dev
FROM ruby:2.3.3
ENV LANG C.UTF-8
ENV RAILS_ENV development

RUN apt-get update -qq && apt-get install -y build-essential mysql-client libv8-dev vim

RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock

RUN gem install bundler
RUN bundle install -j4 --path /usr/local/bundle
ADD . /myapp

EXPOSE 3000

ENV MYSQL_DB_NAME fugafuga_dev
ENV MYSQL_ROOT_PASSWORD hogehoge
ENV MYSQL_PRODUCTION_DB_HOST dummy

ADD start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
```

時間かかりますが、以下のコマンドで出来上がるはず。

```
docker-compose build
docker volume create --name=sync-volume
docker-compose run web rake db:create
docker-sync-stack start
```

これでhttp://localhost:3000 にアクセスすれば、Railsの画面が出てくるはず。。
dockerすごい。。(今更)

ちなみにローカル(ホスト側)のファイルを更新した後は以下のコマンドで同期できます。

```
docker-sync start
```

エラーが出て動かなかったら以下のコマンドでコンテナにログインして調べてみて下さい。

```
docker-compose run --rm web /bin/bash
```

試行錯誤しすぎてmacのストレージ容量が圧迫されたら、どんどん消しましょう。
[Docker for Macを使っているとストレージ容量不足になる問題をなんとかする](http://qiita.com/ktsujichan/items/726e0f896e30b355fee1)

# テスト環境での注意点
Railsはdocker環境で動かす方針ですので、mac環境ではbundle installしないで下さい。

mac環境でbundle installして生成されたGemfile.lockをコピーすると、libv8・therubyracerなどのOSが絡んだ依存関係でエラーが出ます。docker環境でbundle installして生成されたGemfile.lockをリポジトリに追加して下さい。

#本番環境
本番環境へのデプロイの流れですが、まずはBeanstalkのMulti-Docker環境を作成します。

BeanstalkはDockerとMulti-Dockerを選択できますが、いずれ複数コンテナに拡張したくなった時のために、大は小を兼ねるということでMulti-Dockerでいいと思います。今からDockerを使う必要はないんじゃないかな。

それからECRというAWSのDockerのプライベートリポジトリを作成します。
ECSの画面からリポジトリを選択すると設定できます。

AWS側の設定は以下の記事が詳しく、読めばわかると思います。
[Docker イメージを作って AWS EC2 Container Registory (ECR) にプッシュし、ElasticBeanstalk でデプロイする](http://qiita.com/ytyng/items/84f056260a5ede67a431)

それからDockerfileを編集し、RDSのエンドポイントをMYSQL_PRODUCTION_DB_HOSTに入れてあげて下さい。例のごとくmysqlのパスワードとDB名は変更して下さい。

```Dockerfile
FROM ruby:2.3.3
ENV LANG C.UTF-8
ENV RAILS_ENV production

RUN apt-get update -qq && apt-get install -y build-essential mysql-client libv8-dev vim

RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock

RUN gem install bundler
RUN bundle install -j4 --path /usr/local/bundle
ADD . /myapp

EXPOSE 3000

ENV MYSQL_DB_NAME fugafuga_production
ENV MYSQL_ROOT_PASSWORD hogehoge
ENV MYSQL_PRODUCTION_DB_HOST mogemoge.ap-northeast-1.rds.amazonaws.com

ENV SECRET_KEY_BASE hogehogehoge
ENV DEVISE_SECRET_KEY mogemogemoge

RUN RAILS_ENV=$RAILS_ENV DB_ADAPTER=nulldb bundle exec rake assets:precompile assets:clean

ADD start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
```
その後、ECRのpushのコマンド通りにDockerfileでbuildして、ECRにpushします。
(ちなみにassets:precompileはbuild時にやっています)

pushが完了したら、Dockerrun.aws.jsonを編集して
push先のリポジトリを設定してあげて下さい。

```json:Dockerrun.aws.json
{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "rails_for_docker",
      "image": "mogemogemoge.dkr.ecr.ap-northeast-1.amazonaws.com/testtest:latest",
      "essential": true,
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 3000
        }
      ],
      "memory": 256,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-region": "ap-northeast-1",
          "awslogs-group": "eb-docker"
        }
      }
    }
  ]
}
```

デプロイ前にCloud Watch Logsのコンソール画面でeb-dockerというロググループを作成して下さい。(これを行わないとデプロイがコケるらしいです。)

もし変更したい場合はDockerrun.aws.jsonに記述してあるawslogs-groupを変更して下さい。

詳細はこちらが詳しいです。
[Elastic Beanstalk DockerでLogging Driversを使う](http://dev.classmethod.jp/cloud/aws/elastic-beanstalk-docker-logging-drivers/)

push後はElastic Beanstalkの画面でDockerrun.aws.jsonをアップロードします。
これで本番反映できたはず。ブラウザから確認して下さい。

動かない場合はコンソールからログを見るか、インスタンスに入って、直接ログを見て調査して下さい。

```
ssh -i 鍵 ec2-user@インスタンスIP #インスタンスにログイン
sudo docker ps
sudo docker exec -it コンテナID /bin/bash #コンテナにログイン
```

#本番環境のコンテナのログについて

デプロイ時にロググループを追加しましたが、これはCloud Watch Logsと連携させるためです。Cloud Watch Logsに連携すると、Railsの標準出力、エラー出力がCloud Watch Logsにリアルタイムに反映されるので捗ります。

こちらのログに対してAlarmをセットすれば、Airbrakeやerrbitなどのエラー通知サービスは不要になります。Cloud Watch LogsなのでAWS CloudTrailやAmazon Kinesis StreamsなどのAWSのサービスとの連携も可能で、拡張性も優れています。(使ったこと無いけど)

#まとめ
これで更にAutoScaleやローリングデプロイを設定すれば、立派な本番環境です。
ブルーグリーンデプロイも簡単に出来ますし、Capistrano書かなくていいし、要らなくなった環境を丸ごと消せるし便利ですね。

今のところ、AWSでDocker本番運用するには、この方法が学習コストが低く、現実的な選択肢だと思います。

ElasticBeanstalk Multi-Container Dockerは敷居が高くとっつきづらいですが、便利すぎて感動するので、是非体験してみて下さい。