はじめに
この記事はCrowdWorks Advent Calendar 2016 3日目の記事です
今回はクラウドワークスが運営しているクラウドソーシングサービスであるCrowdWorksのRails開発環境について紹介します。
Qiitaにもたくさん記事が投稿されているとおり、Dockerを開発環境に利用するのはだいぶ一般的になってきたように感じます。クラウドワークスでもローカルでDocker Composeを利用して開発環境を構築できるようにしています。
Docker化の範囲は、各人の好みがあるため2パターン用意しています。
- RailsはmacOS上で動かしてミドルウェアのみDocker化
- ミドルウェアに加えてRailsもDocker化
現状では「ミドルウェアのみDocker化している人」、「RailsもDocker化している人」、「まだ移行できていない人」の割合は1/3ずつぐらいです。新規にジョインするメンバーは基本的にDocker環境を利用しています。
本記事では上記の2パターンのDocker開発環境の構成とポイントについて紹介します。
ミドルウェアのみDocker化するパターン
構成
Railsに直接接続するのではなく、Nginxも設定してローカル開発環境で利用しています。
このRailsとNginxを除いたミドルウェア群をDocker化する構成です。
Nginxは通信の方向からDocker化できないためHomebrewでインストールしたものを利用しています。
この構成ではdocker-compose up
とするだけで、すべてのミドルウェアが起動するようになっています。
ポイント
ポート番号定義の分離
設定を定義したdocker-compose.yml
はリポジトリにコミットしています。ここにポート定義を含めてコミットしてしまうと、もしローカルで同一ポートを利用するサービスを立ち上げる場合に困ります。
そこで、Docker Composeはデフォルトでdocker-compose.yml
に加え、docker-compose.override.yml
を上書き用ファイルとして読み込むこと1を利用します。
docker-compose.yml
に公開用ポートを指定せずにサービスだけ定義します。
# ミドルウェアのみを定義したComposeファイル
version: '2'
services:
# 永続化領域
storage-elasticsearch:
image: busybox
volumes:
- /usr/share/elasticsearch/data
storage-mysql:
image: busybox
volumes:
- /var/lib/mysql
# ミドルウェア
elasticsearch:
image: quay.io/crowdworks/elasticsearch
volumes_from:
- storage-elasticsearch
elasticsearch-test:
image: quay.io/crowdworks/elasticsearch
fake_sqs:
image: quay.io/crowdworks/fake_sqs
hostname: crowdworks.localdomain
command: /usr/local/bundle/bin/fake_sqs --bind crowdworks.localdomain
memcached:
image: memcached
mysql:
image: quay.io/crowdworks/mysql
volumes_from:
- storage-mysql
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
そしてdocker-compose.override.yml
に公開用ポートを定義します。
# Composeファイルの設定を上書きする設定
#
# docker-compose.override.ymlにコピーして利用してください。
# Composeファイルが明示的に指定されていない場合は自動的に読み込まれます。
#
version: '2'
services:
# ホスト側にバインドするポート番号の設定
elasticsearch:
ports:
- "9200:9200"
elasticsearch-test:
ports:
- "9250:9200"
fake_sqs:
ports:
- "4568:4568"
memcached:
ports:
- "11211:11211"
mysql:
ports:
- "3306:3306"
docker-compose.override.yml
は.gitignore
に追加しておき、リポジトリにはdocker-compose.override.yml.sample
をコミットしておき、メンバーには初めに一度だけコピーして貰います。ポート番号を変更したい場合は、このdocker-compose.override.yml
とRails側の設定を変更します。
問題点
docker-compose.override.yml
は上書き用なのだから、docker-compose.yml
にデフォルトの定義をしておけばポート番号を変更しない限り不要なのでは?と考えます。しかし、それはうまくいきません。
例えば次のようにdocker-compose.yml
とdocker-compose.override.yml
を定義したとします。
version: '2'
services:
mysql:
image: mysql
ports:
- "3306:3306"
version: '2'
services:
mysql:
ports:
- "13306:3306"
このとき、公開用ポートの設定は上書きではなく追加され、両方とも有効になってしまいます。
$ docker-compose config
networks: {}
services:
mysql:
image: mysql
ports:
- 13306:3306
- 3306:3306
version: '2.0'
volumes: {}
そのため、docker-compose.yml
にデフォルトの定義ができないというわけです。
これに対し、上書き用の書式を用意して対応するという提案のPull Requestが出されています。
Add overwrite strategy options to config by CarstenHoyer · Pull Request #3939 · docker/compose
もし、このような機能が実装されれば、docker-compose.override.yml
はオプションにすることができます。
ミドルウェアに加えてRailsもDocker化するパターン
構成
ミドルウェアに加えて、RailsもDocker上で動かします。RailsがDocker化されることにより、通信の方向上Docker化できなかったNginxもDocker化できます。
この構成ではRailsのData-onlyコンテナも用意しています。このData-onlyコンテナの元となるDockerイメージには、あらかじめbundle install
されたGemファイルが含まれています。このData-onlyコンテナをvolumes_from
で指定することにより、コンテナを再構築する場合でも高速にbundle install
が完了します。
ポイント
ミドルウェア版とのComposeファイルの組合わせ
この構成では、先述したミドルウェア版のdocker-compose.yml
とRailsを定義したdocker-compose.rails.yml
を組み合わせて使います。
Docker Composeでは複数のComposeファイルを渡すことによって組み合わせて使うことが可能です。ミドルウェア版では自動的にdocker-compose.override.yml
が読み込まれることを利用していましたが、これを明示的に行うことで別のComposeファイルによる上書きが可能となっています。
# Railsアプリケーションを追加で定義したComposeファイル
version: '2'
services:
# 永続化領域
storage-rails:
image: quay.io/crowdworks/crowdworks
volumes:
- /usr/local/bundle
# ミドルウェア
nginx:
image: quay.io/crowdworks/nginx
ports:
- "80:80"
- "443:443"
volumes:
- .:/opt/app
- /sockets
# Railsアプリケーション
rails:
image: quay.io/crowdworks/rails
volumes:
- .:/usr/src/app
volumes_from:
- storage-rails
- nginx
links:
- elasticsearch:elasticsearch
- elasticsearch-test:test_elasticsearch
- fake_sqs:fakesqs.localdomain
- fluentd:fluentd
- memcached:memcached
- mysql:mysql
- nginx:nginx
command: bundle exec rails server
# ジョブ
delayed_job:
image: quay.io/crowdworks/rails
volumes:
- .:/usr/src/app
volumes_from:
- storage-rails
links:
- elasticsearch:elasticsearch
- fake_sqs:fakesqs.localdomain
- fluentd:fluentd
- memcached:memcached
- mysql:mysql
command: bundle exec rake jobs:work
shoryuken:
image: quay.io/crowdworks/rails
volumes:
- .:/usr/src/app
volumes_from:
- storage-rails
links:
- elasticsearch:elasticsearch
- fake_sqs:fakesqs.localdomain
- fluentd:fluentd
- memcached:memcached
- mysql:mysql
command: bundle exec shoryuken -R -C config/shoryuken.yml
上記のように定義したdocker-compose.rails.yml
と、ミドルウェアを定義したdocker-compose.yml
を-f
オプションで複数渡すことで指定します。
$ docker-compose -f docker-compose.yml -f docker-compose.rails.yml run --rm rails bundle exec rails console
しかし、このオプションを毎回指定するのは面倒です。そこでDocker Composeには環境変数COMPOSE_FILE
が用意されています。2
この環境変数にはComposeファイルを:
で区切ることにより、複数渡すことができます。(Windowsの場合は;
)
direnvなどを使ってあらかじめこの環境変数に設定しておけば、-f
オプションを毎回付与せずに済みます。
export COMPOSE_FILE=docker-compose.yml:docker-compose.rails.yml
ここで注意点として、-f
オプションやCOMPOSE_FILE
を指定した場合は、自動でdocker-compose.override.yml
は読み込まれなくなります。
この構成の場合、公開用のポートはdocker-compose.rails.yml
でNginxのポートだけを指定しており(このポートだけは専有してしまうことを許容してしまっています)、Dockerのlink機能によって通常はNginx以外の公開用ポートを指定する必要はありませんが、必要に応じてdocker-compose.override.yml
を渡すことでホスト側にポートを公開することが可能です。
問題点
この構成の場合、Railsアプリケーションのコードをホスト側から取得するため、Docker for Macを使うとコードが巨大な場合に遅くて使い物にならない問題があります。そのため、この構成はdinghyを使っているメンバーのみが選択しています。
次のPull Requestで~/Library/Containers/com.docker.docker/Data/database/
配下の設定を書き換えて改善させるワークアラウンドが紹介されていました。
Severe Docker 1.12.1 performance regression with DB2 images (~10x slower) · Issue #668 · docker/for-mac
しかし、fioを使ってベンチマークを取ってみましたが、劇的な改善は見られませんでした。
ベンチマーク用fioファイル:
[Sequential-Read] # jobの名前
rw=read # シーケンシャルでreadする
directory=/tmp/ # ベンチマークで使うディレクトリ (Docker上で測定する場合はマウントしたパスに変更)
size=1g # ベンチマークで使用するデータサイズ。キャッシュサイズを考慮して決める。
#ioengine=libaio # 非同期I/Oでテストする。指定しないとsync(同期I/O)になる。
[Sequential-Write]
rw=write # シーケンシャルでwriteする
directory=/tmp/
size=1g
#ioengine=libaio
[Random-Read]
rw=randread # ランダムでreadする
directory=/tmp/
size=1g
#ioengine=libaio
[Random-Write]
rw=randwrite # ランダムでwriteする
directory=/tmp/
size=1g
#ioengine=libaio
素のmacOS上の測定結果:
Run status group 0 (all jobs):
READ: io=2048.0MB, aggrb=49269KB/s, minb=24634KB/s, maxb=510007KB/s, mint=2056msec, maxt=42565msec
WRITE: io=2048.0MB, aggrb=44262KB/s, minb=22131KB/s, maxb=554508KB/s, mint=1891msec, maxt=47380msec
Docker for Macデフォルト:
Run status group 0 (all jobs):
READ: io=2048.0MB, aggrb=17763KB/s, minb=8881KB/s, maxb=375295KB/s, mint=2794msec, maxt=118058msec
WRITE: io=2048.0MB, aggrb=26915KB/s, minb=13457KB/s, maxb=13607KB/s, mint=77056msec, maxt=77915msec
Docker for Mac flush sync無効化:
Run status group 0 (all jobs):
READ: io=2048.0MB, aggrb=19849KB/s, minb=9924KB/s, maxb=340225KB/s, mint=3082msec, maxt=105653msec
WRITE: io=2048.0MB, aggrb=29665KB/s, minb=14832KB/s, maxb=15376KB/s, mint=68194msec, maxt=70693msec
あまり依存関係を増やしたくなかったため避けていましたが、3つ目の構成パターンとしてdocker-syncを使うパターンを追加してみる予定です。
まとめ
docker-compose.override.yml
や環境変数COMPOSE_FILE
を使って複数の構成パターンを実現する方法を紹介しましたが、これらに限らず、Dockerの進化は速いため気付くとオプションが追加されていたりします。ぜひDockerの便利機能を活用して、よいDockerライフをお送りください。
この記事はCrowdWorks Advent Calendar 2016 3日目の記事でした。明日は@tkoshidaのiOSな記事の予定です