はじめに
こんにちは、OthloTechの@pokohideです。
この記事はOthloTech Advent Calendar 2016の最終日(25日)の記事です!
世間はクリスマスムードで、こんな記事を読んでいる人もいないとは思いますが...最近MacBookProを購入して、新しいのを買ったら環境汚染しないぞ!と思っていたので、これを機に勉強してみました。なので、ステップごとに自分なりの解説コメント等付きますが、ご了承ください。
事前知識
docker
とは
Docker Inc.が提供している仮想化のためのオープンソースソフトウェアです。
『Docker実戦活用ガイド』p.7 よりVagrantなどの仮想マシンとは違って、dockerのコンテナはOSレベルでの仮想化を行ってそのOS上でプロセスを動かすのですが、仮想マシンはマシンレベルで仮想化を行います。なので、以下のような特徴があります。
コンテナ(Docker) | 仮想マシン | |
---|---|---|
起動 | 早い | 遅い |
リソース消費 | 少ない | 多い |
OS | Linuxのみ | 何でも可 |
vagrantはマシンレベルで構築するので、ベースとなるOSは特に限定されませんが、dockerはLinux上でのみしか動きません。しかし、dockerはLinuxの機能を借りて仮想環境を実現しているので必要なリソースは少なくて起動が早いのが特徴です。逆に仮想マシンはOS単位で仮想化しているのでリソース消費量が高く起動は遅いです。
以前はVirtualBoxが必要だったり環境設定が必要だったり、色々面倒だったそうですが、今はDocker社が公式で出している Docker for (Mac/Windows/Linux)をインストールするとすぐに使えるので、まだインストールをしていない人は入れておきましょう。
docker-compose
とは?
dockerをインストールするとdockerコマンドが使用できて、それを用いることで色々なコンテナを立ち上げることができるのですが、Railsアプリの環境を構築するのにPostgresだったり、Rubyだったり、Redisだったり複数のコンテナからなるサービスを手作業で作るのは大変。ということで、docker-composeというツールを用いることにします。
docker-composeとは複数のコンテナからなるdockerアプリケーションを定義・実行るツールで、compose用の設定ファイルを用意してコマンドを実行することで、記述されたコンテナを一気に起動できます
docker-composeはリリースページにアクセスして最新のバージョンをcurl
コマンドを使ってインストールしてください。インストールの方法はリリースページに書かれているので、コンソールにコピペすれば大丈夫だと思います。
Dockerfile
/ docker-compose.yml
dockerコマンドでイメージの作成やコンテナの起動などを実行してもいいのですが、コンソールから入力するのは疲れるし、作業を一括して行いたくなってくるのでそんな時に使うのがDockerfile
です。makefile
のようなものですが、個人的にはそれ以上に取っ付きやすいくて気に入りました。
使えるコマンドも10種類程度しかないので、すぐなれると思います。
Dockerfileのコマンド
docker-compose.yml
はdocker-composeというツールを使うための設定ファイルで、ここにアプリケーションを構成する各サービスを定義します。
検証環境
検証はmac上でdockerは1.12, docker-composeは1.8.1を使用してdockerの環境を構築しています。2016年12月現時点の最新バージョンだと思います。
$ docker version
Client:
Version: 1.12.3
API version: 1.24
Go version: go1.6.3
Git commit: 6b644ec
Built: Wed Oct 26 23:26:11 2016
OS/Arch: darwin/amd64
Server:
Version: 1.12.3
API version: 1.24
Go version: go1.6.3
Git commit: 6b644ec
Built: Wed Oct 26 23:26:11 2016
OS/Arch: linux/amd64
$ docker-compose version
docker-compose version 1.8.1, build 878cff1
docker-py version: 1.10.3
CPython version: 2.7.9
OpenSSL version: OpenSSL 1.0.2h 3 May 2016
Dockerを活かす原則
1) 1 Container 1 Service
1つのコンテナには1つのサービスを動作させるようにしましょう。たとえば、今回用いるPostgresとRedisを同じコンテナで動かすことも可能ですが、コンテナが複雑化してしまうので、極力シンプルに保つため意識しましょう。
2) Dockerfileに書く
コンソールでdockerコマンドでコンテナを作成することも可能ですが、結局誰かと共有するにはその作成手順を明示しなくてはいけないので、Dockerfileに統一しましょう。
Railsアプリの開発環境を構築してみる
今回の設定として、開発中のRailsアプリ(Postgres+Redis)をDockerの仮想環境に移行することを考えます。ここで個人的な前提として、開発環境ではsqlite3を使っていたのでそこでの変更手順もまとめてあります。
- ruby: 2.3.3
- Ruby on Rails: 5.0.1.rc2
Rails用のDockerfileを用意する
Dockerfileはrailsアプリのルートディレクトリに配置してください。
FROM ruby:2.3.3
ENV APP_ROOT /my_app
RUN apt-get update -qq && apt-get install -y nodejs build-essential libpq-dev postgresql-client
RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT
ADD Gemfile ${APP_ROOT}/Gemfile
ADD Gemfile.lock ${APP_ROOT}/Gemfile.lock
RUN bundle install
ADD . $APP_ROOT
Dockerfileの最初はFROM IMAGE:TAG
コマンドで、使用するベースイメージを選択しなくてはいけません。使用するイメージはCentOS
だったり、りRedis
だったり他人の作成したイメージを指定できますが基本的には、公式のイメージリストから選択すればいいでしょう。
Ubuntu
を入れて、apt-get
で1からrubyの環境を構築することも出来ますが、今回はあらかじめrubyの入っているイメージを選択しました。
ENV
コマンドは環境変数を設定するコマンドでAPP_ROOT
という変数に/my_app
という文字列を格納しています。
RUN
コマンドはを今回だとrubyの入ったコンテナ内のコンソールで実行する感じで、apt-get
でnodejs
などをインストールしています。
コンテナ内に、作業用にディレクトリを作ってそこをWORKDIR
コマンドで起点とします。その作業ディレクトリにADD
コマンドで自分のローカルのGemfile
とGemfile.lock
をコピーして、コンテナ内でbundle install
を実行した後、他のローカルのファイルすべてをコンテナ内にコピーしています。
メモ
他のサイトではGemfile
を/tmp
フォルダにコピーしてbundle install
をすることで高速化している例がありますが、Docker 1.1以上からはADD
コマンドにキャッシュ機能が増えたので、変更がなければ、再ビルドをしても高速で行えます。
同様の理由で、ADD . $APP_ROOT
コマンドで、ローカルの作業ディレクトリを全てコンテナ内にコピーをしているのですが、ここでログファイル等もコピーしてしまうと、変更を加えていないのに再ビルドが走る可能性があるのでコピー対象から明示的に外しておきましょう。これは.dockerignore
で実現できます。
## Rails
public/assets/*
.ruby-version
.env
## Environment normalisation:
.bundle
vendor/bundle
.git*
## log
log/*
tmp/*
## OSX
.DS_Store
## docker
.dockerignore
docker-composeでオーケストレーション
ちなみに...
オーケストレーションとは、複雑なコンピュータシステム/ミドルウェア/サービスの配備/設定/管理の自動化を指す用語
お恥ずかしいですが、意味が分からなかったのでググりました。docker-composeで色々なサービス(Postgres, Redis, Nginx, etc...)などを一括で設定・起動するので、しっくりきますね。
version: '2'
services:
db:
image: postgres
ports:
- "5432"
redis:
image: redis:latest
ports:
- 6379:6379
web:
build: .
command: bundle exec rails s -p 3000 -b 0.0.0.0
ports:
- "3000:3000"
links:
- db
- redis
environment:
DATABASE_USER: postgres
DATABASE_PASSWORD:
DATABASE_PORT: 5432
DATABASE_HOST: db
REDIS_URL: redis://redis:6379
version '2'
や、はじめのservices
はDocker1.5系以降のバージョンで必要なおまじないだと思ってください。services
の下の階層のdb
, redis
, web
はそれぞれサービス名を意味していて、この名前のコンテナが立ち上がって、それ以下の内容がそのコンテナ内で実現される感じです。
RedisコンテナとDBコンテナは、redis
とpostgres
は公式のイメージが用意されているので、それをそのまま利用しています。
RailsコンテナはDockerfileで先程準備したコンテナをbuild: .
で指定して、links
でDBコンテナとRedisコンテナを同時に起動するように設定しています。environment
は、そのコンテナ内で使用できる環境変数を定義していてます。
docker-composeで定義したserviceは同一のネットワーク配下に置かれるので、
links
で定義しなくても、お互いのホスト名として利用できるので、記述しなくても大丈夫なそうです。
@hidekuro さんにコメントで訂正していただきました。ありがとうございます。
メモ
image: postgres
と公式のpostgresイメージを使用していますが、正式な書き方はイメージ名:タグ名
です。このようにタグ名を省略した場合は、postgres:latest
と同じとみなされて最新のバージョンが適用されます。なので、バージョン指定が必要な場合は、以下から見つけるといいでしょう。
ports
はDocker内外にポートを開放する設定でRailsコンテナはブラウザからアクセスしたりするので、ports
でポートを開放する必要がありますが、Redis
コンテナやDB
コンテナはRails
コンテナからしかアクセスしないなら、expose
でDocker内の他のコンテナに向けてポートを開放すべきかもしれないです。
postgres
とredis
の公式イメージはports
を指定しなくても、デフォルトでそれぞれ5432
と6379
を指しますが、明示しておきます。
RailsのDBの設定
今回は前提として、開発環境ではsqlite3を使用していたので、まずそれをpostgresに変更します。その次に、RailsコンテナとDockerで用意したDBコンテナは別のコンテナなので、database.yml
を書き換えて接続の設定を行います。
sqlite3 postgres
Gemfile
からsqlite3
を削除して、本番環境のみに記述していたpg
を上に持ってきました。
+ gem 'pg'
group :development, :test do
- gem 'sqlite3'
end
group :production do
- gem 'pg'
end
database.yml
環境変数を多様していますが、この中身は先ほどdocker-compose.yml
で定義しているので、改めて確認します。
DATABASE_USER: postgres
DATABASE_PASSWORD:
DATABASE_PORT: 5432
DATABASE_HOST: db
HOST名やUSER, PASSWORDはこれじゃないといけないの?と思った方は下記のメモ欄に書いているので参考にしてください。自分も結局他の方法はよくわからなかったのでデフォルトの設定で行きました...
default: &default
adapter: postgresql
encoding: unicode
timeout: 5000
port: <%= ENV.fetch('DATABASE_PORT') { 5432 } %>
host: <%= ENV.fetch('DATABASE_HOST') { 'localhost' } %>
username: <%= ENV.fetch('DATABASE_USER') { 'root' } %>
password: <%= ENV.fetch('DATABASE_PASSWORD') { 'password' } %>
pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
redis
の設定
database.yml
で環境変数にREDIS_URL: redis://redis:6379
と設定しました。なので、redisの初期設定を行っているファイルを以下のように変更しました。
REDIS ||= Redis.new(url: ENV['REDIS_URL'] || 'redis://localhost:6379')
ここは適宜、自分の環境に合わせて変更してください。
メモ
今回は公式のPostgres
を使用しましたが、本家ドキュメントの日本語訳によると
デフォルトでは、データベースは localhost で実行するとみなされます。そのため、db コンテナに指示しなくてはいけません。postgres イメージにデフォルトで設定されている database と username に変更する必要があります。
と書かれています。これに気付かずかなりハマりました。そのデフォルトのdatabaseとusernameは
development: &default
adapter: postgresql
encoding: unicode
database: postgres
pool: 5
username: postgres
password:
host: db
となっているので、これを参考に database.ymlを変更しました。
また、上記でREDISに接続するために、redis://redis:6379
でRedisコンテナに、database.yml
ではhost: db
でDBコンテナにRailsコンテナから接続していますが、これにもDockerのおかげです。
docker-compose.yml
でlinksを書くと、コンテナ内の/etc/hosts
に指定したサービス名とそのipアドレスが追加されます。以下は例です。
172.17.0.6 db
172/17/0.4 redis
このように、今回指定したサービス名がエイリアスとなって、ipアドレスを参照できるようになるので、host: db
とコンテナ名を入力したり、redis://redis:6379
で他のコンテナにアクセス出来ました。わざわざipアドレスとか気にしなくていいのは楽ですね!!
実行してみよう!
これで設定は完了です。
$ docker-compose build
... # これで設定したpostgresやrubyやら色々なイメージとかを取ってくる
... # 何もエラーが無ければ、環境構築完了なので、RailsアプリのDB設定を行います
$ docker-compose run web rake db:create
$ docker-compose run web rake db:migrate
... # docker-compose run <サービス名> <コマンド>
... # 上の構文でサービス名のコンテナ内でコマンドを実行できる
$ docker-compose up
これで全てのコンテナが起動して、以下のようなログが出力されると思います。
web_1 | => Booting Puma
web_1 | => Rails 5.0.1.rc2 application starting in development on http://0.0.0.0:3000
web_1 | => Run `rails server -h` for more startup options
web_1 | Puma starting in single mode...
web_1 | * Version 3.6.2 (ruby 2.3.3-p222), codename: Sleepy Sunday Serenity
web_1 | * Min threads: 5, max threads: 5
web_1 | * Environment: development
web_1 | * Listening on tcp://0.0.0.0:3000
web_1 | Use Ctrl-C to stop
ログの通り、http://0.0.0.0:3000
にアクセスすれば、ちゃんと動いているのが確認できました!!これにて完了...ではないんです実は(´・ω・`)すいません。
このままだとbuildするたびにデータが消えてしまう!!
このままだと、RedisやPostgresのデータベースを再起動するたびに中に入っていたデータが消えてしまいます。なので、Dockerの標準のAPIを用いてvolumesで設定を行います。今回はpostgresとredisのデータを永続化してほしいので、その設定を行います。
version: '2'
services:
db:
image: postgres
ports:
- "5432"
volumes:
- ./data/postgres:/var/lib/postgresql/data
redis:
image: redis:latest
ports:
- 6379:6379
volumes:
- ./data/redis:/data
command: redis-server --appendonly yes
DBコンテナとRedisコンテナにvolumesとRedisコンテナにはcommandを付与しました。volumesはオプションとしてホスト側のパスとコンテナ側のパスを指定して、ホスト側のそのパスにコンテナ側のパスをマウントします。こうすることで、コンテナが再起動した時に、自分のローカルの方にも保存されたデータがコンテナ側にも同期されて、それを読み込んだ状態からスタートする...みたいな感じになるので、データを永続化できます。
volumes: <ホストのパス>:<コンテナのパス>
redis
は初期起動時にコマンドを変更して--appendonly yes
というオプションをつけています。これをつけないとデータが生成されないらしいので、気をつけてください。
redis
は/data
に、postgres
は/var/lib/postgresql/data
にデータをマウントすればいいので、以上のように編集すれば、完了です!
ハマった事一覧
A server is already running
Dockerの環境が出来て,docker-compose build
も出来て、いざdocker-compose up
じゃあ~!ってなったら顔を出したエラー。
web_1 | => Booting Puma
web_1 | => Rails 5.0.1.rc2 application starting in development on http://0.0.0.0:3000
web_1 | => Run `rails server -h` for more startup options
web_1 | A server is already running. Check /myapp/tmp/pids/server.pid.
web_1 | Exiting
すでにサーバーが立っているよという。でも、pa aux | grep 3000
で探してみても、docker ps -a | grep 3000
で探してみても別にプロセスは立っていなさそう。。
これの原因はRailsのWEBサーバが正常に終了出来ていないことによるそうで、本来ならpidはWEBサーバを起動する時にtmp/pids/server.pid
に書き込まれて、終了する時に削除されるはずだけれど、そのファイルの中に何か書かれているがために、WEBサーバが起動中であると誤認されていることによるエラーのよう。
rm /tmp/pids/server.pid
でファイルを消せばサーバを起動できました。server.pid
はなければ、生成されるので消しても大丈夫です。
Bundler::GemRequireError: There was an error while trying to load the gem 'uglifier'.
Dockerfileを実行時のbundle install
でこんなエラーが発生しました。ググったら、node.jsが入っていれば大丈夫って書いてあったので、Dockerfile
にapt-get install nodejs
を追記したら直りました。これは上では対応済みです!
dockerのTips
エラーが起きたらログを見よう
docker-compose logs
で実行中のコンテナ内のログが見れます。サービス名を指定したい場合は、docker-compose logs db
とすれば、DBコンテナ内のログが見れます。
不要なプロセスとイメージを削除する
いま起動中のコンテナはdocker images
コマンドで、実行中のプロセスはdocker ps
コマンドで分かります。しかし、起動していなかったり、すでに必要のないプロセスを削除しないと以外と容量を食ってしまうので、定期的に不要なプロセスやコンテナを削除するといいでしょう。
ステータスがexitedのプロセスを削除して、イメージ名がとなっているコンテナを削除するワンライナーです。
docker ps -aq -f "status=exited" | xargs docker rm -v && docker images -q -f "dangling=true" | xargs docker rmi
docker-compose
コマンドを短くする
基本的に今まで普通に実行していたrake db:migrate
などのコマンドは今回構成したDockerコンテナ上だとdocker-compose run web rake db:migrate
というようにかなり長くなってしまいます。なので、.zshrcや.bashrcに以下のように記述してエイリアス化しましょう。
alias fig='docker-compose'
さいごに
かなり長文になってしまい申し訳ありません...。自分なりに後から読み返しても納得できるように詳しく書いたつもりなので、もし間違っていたら訂正リクエスト等していただけると嬉しいです!
dockerの公式ドキュメントはかなり充実しているので、迷ったらまずそこを見るのがいいと思います。といって、全て英語なので、もしいきなりは厳しいという方がいましたら、日本語訳のドキュメントもあるのでぜひ一度は読んでみてください!
あと、書いていて完全にOthloTechのAdventCalendarということを忘れていましたが、OthloTechは来年も東海のIT界隈を盛り上げるために楽しく活動していきますので、応援していただけると嬉しいです\(^o^)/