Dockerの勉強を兼ねてruby:alpineベースのRails用イメージとMySQLイメージを使用したRailsプロジェクトを作ろうと思ったらすげーー難しいですねDocker。でも一応動くと思われる形ができたのでメモ。
これを書いてる時点で本番環境のことはまだ考えてないので開発用だけです。Web接続はrails serverを使います。
GitHub
試行錯誤の末できたテンプレートがこちら。
yoshi-self/docker-rails-template: Docker settings template for Rails + MySQL development
説明
README.mdに使い方は書いてありますが補足とか。
MySQL /var/lib/mysqlのvolume化
これはデータ永続化(コンテナ閉じてもDBデータが消えないようにする)のために必要です。
参考:library/mysql – Docker Hub
Dockerfile
–virtual=build-dependenciesでapk addを分けてるのは後々apk delで不要パッケージを消す目的のためだったんですが、bundle installを同じRUNに書くと(同じRUNに書かないと意味がない)Gemfile更新のたびにそのRUNのキャッシュが使えずapk addからになってしまうため、開発環境でファイルサイズにそこまでシビアにならなくていいかと思い中途半端なままになってます。
Gemfileのコピー先ワーキングディレクトリを分ける
これはこの記事などを参考にしました。
分けることでGemfileのみの変更と他のコード変更が別のキャッシュになります。
rails serverが起動失敗する
なぜだかわかりませんが、docker-compose downしても/app/tmp/pids/server.pidが消えず、次upした時に
A server is already running. Check /app/tmp/pids/server.pid.
というエラーが出ることがあります。なのでcommand:を
command: sh -c "rm -f /app/tmp/pids/server.pid && rails server -p 3000 -b '0.0.0.0'"
としています。rm -fはファイルが存在しなくてもエラーになりません。
参考:Rails server is still running in a new opened docker container – Stack Overflow
tty: trureとstdin_open: true
byebugなどのデバッガにアタッチする際に必要になります
参考:docker-compose上のRailsのデバッグを行う
gemを追加するときはrunからbundle installする
今回の設定では/usr/local/bundleにvolumeをマウントしています。
volumeには初回の作成・マウント時にコンテナ側のファイルをvolumeに統合する機能があるので、ビルド時にコンテナ内に入った/usr/local/bundleは初回のみvolumeに統合されます。
If you start a container which creates a new volume, as above, and the container has files or directories in the directory to be mounted (such as /app/ above), the directory’s contents are copied into the volume.
参考:Use volumes | Docker Documentation
しかしそれは初回だけなので、build内で何度もbundle installを行ってもコンテナに新しく入ったgemはマウントで隠れてしまいます。
なのでvolumeマウント後のコンテナで実行されるrun(もしくはexec)を使ってvolumeに流し込みます。
っていうと面倒事みたいに聞こえるかもしれないですがvolumeにしないと後述の仕組み上Gemfileが更新されるたびに全gemを再インストールしなきゃいけなくなります。それ対策のvolume化です。
その他のDocker注意点など
ここから先は自分がDockerを学ぶにあたって学んだ注意点など書き連ねます。
Dockerは理解しないとつまずく所が多くて難しい!
OverlayFS
(ここは自分もかなり把握曖昧なので間違ってたらすみません)
DockerのイメージはOverlayFSとかいうファイルシステムで作られており、ビルドの各ステップ毎にレイヤーが作られ、レイヤーの上に変更のレイヤーを重ねていくイメージで最終的なイメージができます。
これによりDockerfileの最後のほうをいじっても少し前のキャッシュが再利用され、apk addやapt-getからやり直しということがありません。
…が、ファイルシステムを意識していないと意図した動作をさせることが難しいことが多々あります。
Rails on Dockerプロダクションイメージの容量削減をしてみた # データを削除しているはずなのにイメージ容量が減らない
例えばこちらで書かれているように、RUNを分けてファイルを消してもイメージの容量は減らないみたいなことがあります。
またGemfileが書き換わっているだけでそのレイヤーが使えない扱いになりbundle installした変更ごと再作成になるため、そのgem1個追加すると全てのgemが再インストールになります。
runしたコンテナは残るので–rmオプション推奨
docker runでもdocker-compose runでも、実行してコマンドが終了して終わったと思ったコンテナは実はExitedとかいうステートで残っています。
例:
$ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker run 1a0e95c74c34 echo 1
1
$ $ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
523d4011cd82 1a0e95c74c34 "echo 1" 5 seconds ago Exited (0) 4 seconds ago musing_goldwasser
これらのコンテナは残ってるからといってdocker-compose upで再利用されるとかでもないので消すしかありません。
runに–rmというオプションをつけると終了時にコンテナが消えるので、単発で何かやりたいときは基本的につけたほうがよいと思います。
もしくは既にコンテナが動いている場合はdocker-compose execでも良いと思います。
volumes
volumeとはイメージともコンテナとも別に管理される外部ストレージ的なもので、コンテナを起動する際にオプションで指定した位置にマウントされます。
Dockerから専用に作ったvolumeをマウントしたりホストの特定ディレクトリをマウントしたり、あとS3とかもできるみたいです知らんけど。
初回作成&マウント時のみコンテナのファイルが統合されることについては上の方で記述しました。
ビルド時にはvolumeはマウントされていない
イメージが外部のvolumeに依存してたらおかしいので考えたら当然なんですが、ビルド中はvolumeはマウントされていないので全てのファイル変更はコンテナ内で起こります。
なのでそれを意識していないと前述の通りビルドでコンテナに入れたファイルがvolumeマウントで隠れたりします。
node_modulesディレクトリなどもこれを意識しないとハマるようです。
docker-compose.ymlのenvironmentはビルドには渡らない
environment:はコンテナに渡る値なのでビルドには渡りません。
なのでenvironment:にNOKOGIRI_USE_SYSTEM_LIBRARIES: 1とか書いてもビルドには使われません。(runならコンテナ経由なのでたぶん使われる)
ちなみにdocker-compose.ymlからDockerfileに値を渡したい場合はDockerfileのARG、docker-compose.ymlのbuild: args:を経由するという手段があるようです。
.envとenv_file:
- docker-composeは実行時にカレントディレクトリから.envを探し、その中の変数はdocker-compose.yml内で${}を使って参照することができます。
- env_file:に書かれたファイル内の変数は環境変数として直接コンテナに渡ります。
この2つの機能は似ているようで別ものなので、.env以外に書かれた変数はyml内では使えません。
参考:docker-compose doesn’t use env_file · Issue #4001 · docker/compose
最初自動的に探してくれるからと思って.envをコンテナの環境変数に使っていたら、.envはdocker-compose実行時のカレントディレクトリしか検索されないのでサブディレクトリから実行すると変数取得が失敗したりして不便だったので、env_file:に変えると共に名前も明示的に.envじゃなくしました。
ファイル整理
Dockerを練習していると、過去に作ったもう必要ないイメージやコンテナやvolumeが残ってストレージを圧迫していくので定期的にお掃除すると良いです。
Dockerの不要なコンテナ・イメージを一括削除する方法
ただコンテナが起動してない状態でvolume pruneすると本当はまた使いたいvolumeまで消えてしまう可能性があるので注意。
最後に
Web業界のデファクトスタンダードっぽいから軽い気持ちで勉強し始めたけどDocker難しいですね。
イメージ、コンテナ、volume、OverlayFS…色んなことを頭の中でちゃんと整理してないと意図通りのものを作るのが難しい!
というかこれでまだ開発環境ができただけなので、CIやデプロイの設定なども勉強しないとDockerを活かしたことにはならないと思うと先は長いです。
全般的に参考にさせてもらったページ
Rails 5.2 開発環境を Docker で構築する – Feedforce Developer Blog
Rails on Dockerプロダクションイメージの容量削減をしてみた
コメント