JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (2)

この記事では、複数のDockerコンテナで構成される環境を構築するための設定例を具体的に解説します。ワークショップのフォローアップではありますが、一般的な事柄を扱いますので、ワークショップに参加されていない方にもお読み頂ける内容です。

前回の記事： [JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (1)](http://qiita.com/wizpra-koyasu/items/679b7c360e1427318dab)

# 春ですね

こんにちは。Emotion Techの子安です。ようやく暖かくなってきましたね。やっぱり春といえば桜ですかね！

![sakura.jpg](https://qiita-image-store.s3.amazonaws.com/0/169518/afb3eeaf-5985-77ea-cb7e-2b11e93687d5.jpeg)

恐縮です。

# 前回のつづき

さて、[前回](http://qiita.com/wizpra-koyasu/items/679b7c360e1427318dab)はテーマとする環境の構成を説明し、利用するコンテナ定義ファイル [docker-compose.yml](https://github.com/akirakoyasu/elastic-beanstalk-workshop/blob/master/docker-compose.yml) の枠組みを解説しました。更に `web` `app` `db` コンテナのうち、 `web` `db` コンテナの設定を見ました。今回はアプリケーションそのものである `app` コンテナを見ていきます。

# 各コンテナの設定 ２
## appコンテナ

```docker-compose.yml
app:
  image: phusion/passenger-ruby23:0.9.20
  environment:
    APPLICATION_ENV: development
    APPLICATION_ROLE: app
    PASSENGER_APP_ENV: development
  labels:
    eb.workshop.role: app
  networks:
    - eb.workshop
  depends_on:
    - db
  volumes:
    - ./app/init/40_setup.sh:/etc/my_init.d/40_setup.sh:ro
    - ./app/passenger/passenger.conf:/etc/nginx/sites-available/default:ro
    - ./app/rails-app:/var/www/rails-app
```

Ruby on Railsのアプリを実行するappコンテナです。[phusion/passenger-docker](https://github.com/phusion/passenger-docker)イメージを利用しています。このイメージはさまざまな機能を持っていますが、まずは設定内容を見ましょう。

- `image`: phusion/passenger-dockerイメージを指定しています。
- `environment`: イメージ側で `PASSENGER_APP_ENV` 環境変数を読んでRailsの環境を切り替えてくれる機能があります。
- `volumes`: ここで3つのマウントを指定しています。コンテナ側のパスを見てください。
  - `/etc/my_init.d/40_setup.sh:ro`: `/etc/my_init.d` 配下に置いたスクリプトを初期化時に実行してくれる機能があります。実行権限を忘れずにつけるようにします。 `:ro` は読み取り専用の意味です。
  - `/etc/nginx/sites-available/default:ro`: ここにnginxの設定ファイルが置かれています。
  - `/var/www/rails-app`: Railsのアプリケーションをここに置きます。これは任意の場所で構いません。上記のnginx設定ファイルに記述しています。

## phusion/passenger-docker

phusion/passenger-dockerは、Phusion社がメンテナンスしているDockerイメージです。このイメージは、さらに別のイメージ [phusion/baseimage-docker](https://github.com/phusion/baseimage-docker) をベースとして作られています。このphusion/baseimage-dockerには、いくつかの有用な機能が含まれています。

- 正しいinitプロセス： `/sbin/my_init` にinitプロセスを内蔵しています。本来initプロセスには、親プロセスのいなくなった子プロセス（孤児）を里親として引き受ける役割があります。加えて `SIGTERM` を受け取った際に、他のサービスを終了させる機能を持ちます。
- スーパーバイザ： 軽量なスーパーバイザ [runit](http://smarden.org/runit/) を内蔵しています。
- ログマネージャ： syslogデーモン [syslog-ng](https://syslog-ng.org/) を内蔵しています。logrotateも設定済みです。
- cron： cronデーモンを内蔵しています。

重要なことは、これらの機能が協調動作するように設定されていることです。 `/sbin/my_init` がrunitを立ち上げ、runitがsyslog-ngやcronを管理します。デフォルトのコンテナ起動コマンドが `/sbin/my_init` に設定されていますので、

```
docker run phusion/baseimage
```

として起動することで、最小限のコンテナ化されたLinux(Ubuntu)環境を手に入れることができます。他にも、今回も利用している `/etc/my_init.d` 配下のスクリプトを初期化時に実行する機能、root以外でコマンドを実行するための `setuser` コマンドなど、様々な機能や仕組みを備えています。詳しくはWebページを参照ください。

phusion/passenger-dockerは、このphusion/baseimage-dockerの上に、nginxやrubyのアプリケーションサーバである [Phusion Passenger](https://www.phusionpassenger.com/) をインストールしてあります。nginxはrunitの配下に設定されていますので、すぐにスーパーバイズされたデーモンとして稼働させることができます。

## コンテナ初期化スクリプト

可能なことはコンテナ自身に判断させるのが良い設計です。そのためにコンテナ起動時に実行するスクリプトを組み込むことがよく行われます。今回 `app` コンテナで設定している初期化スクリプトの内容を見てみましょう。 [/app/init/40_setup.sh](https://github.com/akirakoyasu/elastic-beanstalk-workshop/blob/master/app/init/40_setup.sh) を参照ください。

このスクリプトの主な役割は4つです。

- nginxを起動可能にする
- Railsアプリのセットアップ
- 環境変数の引き渡し
- DBマイグレーション

ハンズオンのため、本来はコンテナ初期化スクリプトに記述すべきでない処理もここに入っていますが、続けて詳しく説明します。

```/app/init/40_setup.sh
rm -f /etc/service/nginx/down
```

これはrunitの機能です。サービスを定義するディレクトリ（今回は `/etc/service/nginx` ）の直下に `down` という名前のファイルがあると、自動起動しません。phusion/passenger-dockerイメージ側で配置されているものですが、自動起動してほしいのでファイルを削除しています。

```/app/init/40_setup.sh
chown -R app:app /var/www
cd /var/www/rails-app
setuser app bundle install --path=../vendor/bundle
if [ "$PASSENGER_APP_ENV" = 'production' ]; then
  setuser app bin/rails assets:precompile
  setuser app bundle install --path=../vendor/bundle --without test development --deployment --clean
fi
```

このあたりは実環境であればCIで、ビルドの一処理として適切に実施すべきものです。アプリケーションのファイルパーミッションを適切に設定し、ライブラリのインストールを行います。更にproductionモードであれば、アセットのプリコンパイルと不要なライブラリの削除を行います。

```/app/init/40_setup.sh
echo "passenger_env_var 'SECRET_KEY_BASE' '$SECRET_KEY_BASE';" >> /etc/nginx/conf.d/10_setenv.conf
echo "passenger_env_var 'RDS_HOSTNAME' '$RDS_HOSTNAME';" >> /etc/nginx/conf.d/10_setenv.conf
```

ここは環境変数をpassengerへ引き渡すための処理です。コンテナでは環境変数を扱う機会が多くなりますので、利用するミドルウェアに合わせて設定が必要です。

```/app/init/40_setup.sh
RAILS_ENV=$PASSENGER_APP_ENV setuser app bin/rails db:create db:migrate
```

こちらはDBのマイグレーションです。この処理も実環境であればコンテナ初期化スクリプトではなく、デプロイの手順の一貫として実施すべきものです。

# 次回へ
ここまでが `app` コンテナの設定でした。長くなりましたので、つづきは次回にしたいと思います。次回はElastic Beanstalkの設定ファイル、Dockerrun.aws.jsonを解説する予定です。

次回の記事： [JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (3)](http://qiita.com/wizpra-koyasu/items/c241a452af0e8b916cca)