アプリケーションエンジニアの西辻です。
今回のブログでは、弊社のローカル開発環境を Docker
化した話をご紹介したいと思います。
このブログでは、なぜローカル開発環境を Docker
化する考えに至ったのかに始まり、
具体的にどのような方法で Docker
化を進めていったかを振り返りながら書いていきます。
また、Docker
化したことで受けた恩恵などを最後に書いて終わります。
Overview
大きく以下の項目について書いていこうと思います。
- 自分の仮想化環境への考え方について
- 今の現場に
Docker
開発環境を導入する判断について - 実運用している構成例を用いての説明
Docker for Mac
のファイルI/Oのパフォーマンス改善方法- Tips:
Docker
コンテナに対してbinding.pry
を利用する - ローカル開発環境を
Docker
化した恩恵
自分の仮想化環境への考え方について
まず、なぜローカル開発環境を Docker
化したのかを説明したいと思います。
ただ流行っているから Docker
化した訳ではないのと、
こういう話は同じチームメンバーに言語化して伝えた方がいいと思い
この場を借りて自分の仮想環境に対する考え方を共有したいと思います。
前置きが長くなりましたが自分の仮想化環境に対する考え方について書いていきます。
前職で新卒で入った当時は本当に無知で自分のローカル開発環境を整えることに多くの時間を割きました。
支給されていた Windows
マシンをセットアップして様々なツールや言語をインストールしていき、
プログラムがうまく動かない時にローカル開発環境に依存した問題に当たることも多かったです。
また、デプロイする先が Linux
サーバなのでそことの環境差分による問題もよくあった記憶があります。
その当時からローカル開発環境に対して、クリーンで誰でも再現性のある環境を作って
その中で開発をしていきたいという思いが芽生えました。
開発チームも変わり VirtualBox
に Linux
デスクトップ環境を作って
それを配布するという経験をしました。
簡単に作って壊せる環境なのとスナップショットという機能で何か問題が起きても
比較的容易にロールバックできるということで Windows
上に VirtualBox
で仮想環境を構築するのが
当時のクリーンな開発環境として自分の中で認識されました。
デスクトップ環境だったのは Eclipse
などの IDE
を利用したかった理由が大きいです。
ただ、デスクトップ環境となると比較的イメージサイズが大きくなりました。
Vagrant
などを利用してなるべくイメージに入れる必要のないものを外に逃して作成時にインストールする流れでした。
Vagrant
は非常に便利なツールでちょっとしたことを試す際には非常に重宝した記憶があります。
また、中の環境がファイルとして記述できるのにも感動しました。
VirtualBox
でやろうとすると構築手順等どうしてもドキュメントの更新が遅れたりして
結局最新のイメージを誰かからもらうみたいな運用になってた気がします。
そんなこんなで Docker
というツールには驚かされました。
まず、起動が速い。
docker-compose
によるコンテナ同士の連携がやりやすい。
環境を作って壊すことに対して抵抗が全くなくなる。
他の人へ配布が簡単。
ファイルの書き方もシンプルでした、次世代感がありました。
とまあ、Docker
への印象は最高の一言でした。
自分がローカル開発をする環境は Docker
を置いて他にないと感じました。
今の現場に Docker
開発環境を導入する判断について
前述の通り、新しい環境で開発するならローカル開発環境は Docker
がいいなーと思ってました。
結果から言うとだいたい1ヶ月くらいで Web アプリ近辺は全て Docker
化できました。
これはシステム構成がどのようになっているかを把握しているかどうかでかかる時間は大きく変わると思います。
自分の場合だと入社してすぐの取り組みだったので全体のシステム構成の理解をしながらやれたのがよかったですね。
Docker
化するにあたって以下の条件を満たしていたのが大きいです。
Docker for Mac
が十分実用レベルだと思った- 全員
Mac
で開発を行っている(Windows
でも動作はするはずだけど揃ってた方が楽なのは間違いない) - 基本的にシンプルな構成の
rails
アプリが多かった rails
メインなのでRuby Mine
をホストマシンから利用してコンテナ内のソース変更で十分だった(Linux Destop環境は不要)- メンバーの
Docker
への抵抗が少ない(ここが一番重要かもしれない)
Docker
化するにあたり、まずは依存度が少ないものを選びました。
ここはあまり一般化できないかもしれないですが、単独で動作する API などが狙い目ですね。
実運用している構成例を用いての説明
ここからは実際の運用例をご紹介したいと思います。
全体的なディレクトリ構成は以下になります。
今回は api
ディレクトリに注目して説明を進めていきます。
api
ディレクトリを理解できれば
他のディレクトリも同じ考え方で作成できると思います。
docker-compose.yml
を起点にして、各アプリディレクトリ(api, admin, batch)にある
Dockerfile
を利用して全体を docker-compose
として起動するのが基本の考え方になります。
後述しますが、Docker for Mac
は大量のファイルI/Oに対して極度にパフォーマンスが落ちます。
rails precompile
などを利用しているとファイルの同期量で一気に遅くなるので
docker-sync
というツールを利用して上記問題を解消しています。
docker-sync
で利用するファイルが
docker-compose-dev.yml
と docker-sync.yml
の2ファイルになります。
├── README.md
├── cleanup.sh
├── db
│ ├── Dockerfile
│ ├── init.sql
│ └── my.cnf
├── docker-compose-dev.yml
├── docker-compose.yml
├── docker-sync.yml
├── api
│ ├── Dockerfile <- 各アプリディレクトリに置きます
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── app
│ ├── bin
│ ├── bitbucket-pipelines.yml
│ ├── config
│ ├── config.ru
│ ├── coverage
│ ├── lib
│ ├── log
│ ├── public
│ ├── scripts
│ ├── spec
│ ├── tmp
│ └── vendor
├── admin
│ ├── Dockerfile
│ ├── 長いので省略
├── batch
├── Dockerfile
├── 長いので省略
基本的な考え方は docker-compose.yml
に build:
したいイメージ単位で記載します。
公式のイメージがそのまま使えるものは image:
をそのまま指定するだけです、簡単ですね。
以下に実際に利用しているものを簡易化した docker-compose.yml
を記載します。
version: '3'
services:
admin:
build: admin # admin ディレクトリにある Dockerfile を利用することを明記
command: bundle exec rails s -p 4000 -b '0.0.0.0'
volumes:
- ./admin:/admin # admin ディレクトリをアプリディレクトリとしてマウント
ports:
- "4000:4000"
api:
build: api
command: bundle exec rails s -p 3001 -b '0.0.0.0'
volumes:
- ./api:/api
ports:
- "3001:3001"
tty: true # binding.pry で利用
stdin_open: true # binding.pry で利用
environment:
REDIS_HOST: redis # 環境変数を設定できます
DATABASE_HOST: db
DATABASE_USER: user
DATABASE_PASSWORD: password
db:
build: db # database 作成や my.cnf をカスタマイズするので独自の Dockerfile を利用します
restart: always
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: dummy
MYSQL_USER: dummy
MYSQL_PASSWORD: dummy
redis:
image: redis:3.0.7 # 公式イメージが利用できるものはそのまま利用します
ports:
- "6379:6379"
api
ディレクトリにある Dockerfile
は以下のようになります。
特になんの変哲も無いよくある rails
アプリ用の Dockerfile
になります。
以下のような感じで必要に応じて各アプリディレクトリ(api, batch, admin など)に Dockerfile
を設置します。
様々なツールをあらかじめインストールしている必要があるものは
DockerHub のプライベートリポジトリに Automated Build
として登録して利用したりしてます。
FROM ruby:2.4.1 # 色んなツールが入ったカスタムされたもの利用したりもしてます
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
# To chache gems
WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install -j8
WORKDIR /api
ADD ./ /api
ちなみに db
は以下のような Dockerfile
になります。
init.sql
を /docker-entrypoint-initdb.d
以下に
設置しておくと docker-compose build
時に勝手に sql
を流してくれるので楽です。
FROM mysql:5.6
COPY my.cnf /etc/mysql/conf.d/
ADD init.sql /docker-entrypoint-initdb.d
Docker for Mac のファイルI/Oのパフォーマンス改善方法
前述しましたが、Docker for Mac
は大量のファイルI/Oに弱いです。
API など View を持たないものはそのままでも問題ないのですが、
View を持つ admin, front などは rails precompile
の影響もありブラウザレンダリングが極端に遅くなります。
上記問題を解決するために docker-sync
というツールを利用します。
https://github.com/EugenMayer/docker-sync
docer-sync
はシンプルな設定ファイルを設置するだけで
ファイルI/Oの問題を解決してくれます。
公式ドキュメントも充実しているので細かいオプションなどは
上記の github を参照するのがいいでしょう。
必要なファイルは docker-compose-dev.yml
, docker-sync.yml
の2ファイルだけです。
docker-compose-dev.yml
の例は以下になります。
sync
させたいディレクトリパスを service
に記載します。
version: '3'
services:
admin:
volumes:
- admin-sync:/admin:nocopy
api:
volumes:
- api-sync:/api:nocopy
front:
volumes:
- front-sync:/front:nocopy
volumes:
admin-sync:
external: true
api-sync:
external: true
front-sync:
external: true
docker-sync.yml
の例は以下になります。
sync
から除外したいディレクトリを指定したりできます。
syncs
以下で命名している名前と docker-compose-dev.yml
内の名前は揃える必要があります。
version: '2'
syncs:
admin-sync:
src: './admin'
sync_excludes: [ '.git', '.idea', 'tmp', 'log' ]
sync_excludes_type: 'Name'
api-sync:
src: './api'
sync_excludes: [ '.git', '.idea', 'tmp', 'log' ]
sync_excludes_type: 'Name'
front-sync:
src: './front'
sync_excludes: [ '.git', '.idea', 'node_modules', 'tmp', 'log' ]
sync_excludes_type: 'Name'
ここまで揃うと docker-sync
に内包されているコマンドの
docker-sync-stack start
で docker-compose.yml
を起点に置いた
各アプリが起動します。
docker-sync-stack start
コマンドは docker-compose up
に sync
がついてる感じです。
ファイルを更新すると Sync Log
が流れます。
Sync Log: UNISON 2.48.4 started propagating changes at 19:15:01.22 on 23 Oct 2017
Sync Log: [BGN] Updating file app/controllers/application_controller.rb from /host_sync to /app_sync
Sync Log: [END] Updating file app/controllers/application_controller.rb
Sync Log: UNISON 2.48.4 finished propagating changes at 19:15:01.22 on 23 Oct 2017
Sync Log: Synchronization complete at 19:15:01 (1 item transferred, 0 skipped, 0 failed)
docker-sync
が優れているところは
docker-compose-dev.yml
で docer-sync
に必要な設定を上書きしていることです。
つまり、いつか Docker for Mac
のファイルI/O問題が解決したら docker-compose-dev.yml
を消すだけで
従来の docker-compose up
での起動に切り替えができます。
Tips: Docker コンテナに対して binding.pry を利用する
binding.pry
を利用方法をチームメンバーが見つけてくれたので設定方法を書いて置きます。
以下のように設定しておくと docker attach
で対象のコンテナにて binding.pry
が利用できます。
version: '3'
services:
admin:
tty: true # binding.pry で利用
stdin_open: true # binding.pry で利用
ローカル開発環境を Docker 化した恩恵
長々と書きましたが、最後にローカル開発環境を Docker
化した恩恵を書いてまとめにしようと思います。
個人的には Docker
という仮想化技術は今後ますます様々なプラットフォームで利用されていくと思います。
現に前回のブログで書いた Bitbucket Pipelines
なども Docker
化していることでだいぶ楽に導入できました。
また、現在は ECS で一部運用しているサービスもあり、それらも Docker
の知識があることで
すんなり入れた気がします。
別エントリにはなると思いますが、全てを ECS 化する話も社内では上がっており、
土台としてローカル環境が Docker
化できているのは
導入にあたっての障壁を一段減らせていると思います。
今回のブログが皆さんのローカル開発環境改善の手助けになれば幸いです。
Housmartでは不動産業界を変えるカウルを支えるエンジニアを募集しています。
今話題のReTech!業界を変えるカウルを支えるエンジニアをWanted!