読者です 読者をやめる 読者になる 読者になる

今日はこのへんで

エンジニアです。雑記がメインですがたまにプログラミング、スタートアップなどについて書く予定です。

いまさら docker-compose

エンジニアリング

ここ数日、Railsアプリの開発環境をDockerに移行できないかなと思って触っていた。Dockerについては昔から横目でウォッチしていたものの、実際にRailsアプリの開発環境をDockerコンテナ上で作る機会がなかったので、いまさら試した。

触ろうと思って調べると、意外と日本語記事が古かったり、不満が多かったので、ここでは軽くTipsなんかをメモしておきたいと思う。

前提の環境としては

  • 既存Railsアプリの開発環境をDockerで作りたい
    • puma, webpack, postgres, redis (ActiveJob) のプロセスを起動する
  • docker -v : Docker version 1.13.1, build 092cba3
    • Docker for Mac 使ってる

今回試した結果のDockerfileとdocker-compose.ymlは以下に書き捨てた。まだ修正の余地はあると思うけど。。

https://gist.github.com/kechol/787429078865eff623e62b0ebbe78280

docker と docker-compose の関係

これは基本的なことをちゃんと理解していなかっただけなんだけど、docker-compose するときはほとんど dockerコマンドのお世話になることはない。docker-composeを使ってコンテナを作成する場合には、Dockerfileの一部(CMDとかEXPOSEとかENVとか)の内容を docker-compose.yml に移すことになり、Dockerfileが単体では動作しないものになるので、docker コマンドに対応した docker-composeコマンドを使う。

Overview of docker-compose CLI - Docker

buildだったら

$  docker-compose build SERVICE_NAME

runだったら

$  docker-compose run SERVICE_NAME COMMAND

になる。

Dockerfileのディレクトリ構成

docker-composeを使うときは複数のDockerfileを作成したり、開発環境と本番環境を分けたりするので、docker-compose.ymlをプロジェクトルートにおいて、あとは適当なディレクトリにまとめるのが良いと思う。

以下のような感じが良いんじゃないかと思う。

PROJECT_ROOT
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── .env
├── dockerfiles
│   ├── dev
│   │   ├── Dockerfile-rails
│   │   ├── Dockerfile-node
│   │   ├── Dockerfile-xxxxx
│   │   ├── entrypoint.sh
│   ├── prod
│   │   ├── Dockerfile-rails
│   │   ├── Dockerfile-node
│   │   ├── Dockerfile-xxxxx
│   │   ├── entrypoint.sh

最初 docker-compose.yml をdockerfilesサブディレクトリ以下に置こうとしたんだけど、セキュリティの都合で上の階層のディレクトリをVOLUMEとしてアタッチできないっぽかったのでルートに置かざるを得なかった。

Alpine Linuxを使う

特に開発用のDockerイメージのソースとしては、Alpine Linuxベースのものを使ったほうがいい。サイズが小さいから。いまはOfficialのソースもたいていalpineに対応しているしね。(-alpineとprefixがついていたり、タグがalpineになっている。)

$ docker images
ruby                2.3-alpine          c963a2466730        4 days ago          137 MB
node                6.10-alpine         340408be6110        5 days ago          50.3 MB
redis               alpine              0d39481626b2        12 days ago         20.5 MB
postgres            alpine              575c5e74b765        2 weeks ago         38.4 MB
busybox             latest              7968321274dc        6 weeks ago         1.11 MB

実際にイメージを見てみても、alpine ベースのイメージは100MB以下のものが多い。ubuntu とかのイメージだと、100MBくらいは余裕で使うから、ハードディスク容量を圧迫しなくて済む。

ひとつ注意点としては、パッケージ管理が apk というコマンドになっているので書き換える必要があるということだ。利用できるパッケージは Alpine packages で検索できる。

docker-compose.yml version 2 or 3 を使う

サンプルを見たいと思って色々なdocker-compose.ymlを見てみたけど、意外とver. 1の文法のままのものが多い。特に links がそのままなものが多かった気がする。よくわからずコピペしていると古い文法を使ってしまったりするので注意が必要。

なので結局、公式のリファレンスを読んで自分で組んでいくしかない。 Compose file version 2 reference - Docker

ちなみに公式にはもうversion 3のリファレンスが出ているんだけど、まだチュートリアルとかはver. 2のままだったので、今回はおとなしくver. 2を使った。

Shared Volumeを使う

DBのデータやインストールしたライブラリなどは永続化したり、コンテナ間で共有する必要があるが、その際の一般的なパターンがShared Volume、またはData-only Container Patternと呼ばれるもののようだった。

Manage data in containers - Docker

具体的にはデータを保持するためのコンテナを作っておき、そこにVolumeをアタッチするようにする。どうせVolume用のコンテナ内ではなにもしないので、イメージは小さいbusyboxを利用する。今回だと以下のような感じになった。

  datastore:
    image: busybox
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - redis_data:/data
  postgres:
    image: postgres:alpine
    volumes_from:
      - datastore
  redis:
    image: redis:alpine
    volumes_from:
      - datastore

volumes:
  postgres_data:
    driver: local
  redis_data:
    driver: local

なんだけど、実はDocker v.1.9.0から docker volume がサポートされるようになったので、実はわざわざコンテナを別で用意してvolumes_fromで参照する必要ないのかも(あんまりわかってない)。以下のドキュメントをもうちょっとよく読もうと思う。

Volume configuration reference | Compose file version 2 reference - Docker

ネットワークとデータベース接続

docker-compose でコンテナを作成すると、デフォルトで一つのネットワーク上に配置され、コンテナ間の通信がしやすいようになっている。

Networking in Compose - Docker

今回だと、データベースの接続に rails コンテナから postgres コンテナのDBを参照する必要があり、database.yml は以下のようになった。

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: postgres
  port: 5432
  username: postgres
  password:

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

host: postgres になっているのが一見奇妙だけど、コンテナ内でnslookupしてみるとちゃんとIPが返ってくる。

mac:~$ docker-compose run rails sh

rails:/app # nslookup postgres
Name:      postgres
Address 1: 172.18.0.3 app_postgres_1.app_default

いらないコンテナを削除する

これはDockerfileいじっている時には頻繁にやらないといけないので、エイリアス割り当てた。filter=で死んだイメージやコンテナだけを削除するのがポイント。

alias docker-clean-images='docker rmi $(docker images -a --filter=dangling=true -q)'
alias docker-clean-containers='docker rm $(docker ps --filter=status=exited --filter=status=created -q)'

deeeetさんのブログでもいろいろ紹介されている。 Dockerを便利に使うためのaliasをつくった | SOTA

Dockerコンテナをデバッグする

Dockerコンテナのプロセスが意図せず終了してしまう時、docker-compose logsでログみてもよくわかんないことがある。

そういうときはシンプルに docker-compose run SERVICE_NAME sh を実行して中に入り、コマンドを実行してみたりして動作を確認するのが良い。

Docker自体の挙動を追いたいときには、docker events & を実行したターミナルでdockerのコマンドを叩けば、コンテナが作成される様子などが逐一ログに流れてくるのでおすすめ。

ログがたくさん流れてくると動いていることがわかって安心するのは僕だけだろうか。

Entrykitについて

Dockerfileでは基本的に、CMDやENTRYPOINTで一つのコマンドしか流せないので、entrypoint.shstart.sh なんかを用意してコマンドをまとめて流す、というようなことをする。

それをより簡単にするEntrykit というツールがあるのだけど、今回は使うのをやめた。最初、コンテナを分けるのがむしろめんどくさいような気がして、nodeとrailsのプロセスを一つのコンテナに入れようかと思って検討していたのだけど、結局分けたほうが綺麗に収まるし、Entrykit自体の使いみちともずれるような気がした。

Entrykitを使った記事が英語だとそんなにでてこないので、実際どれくらい使われているかはわからないけど、テンプレート化したConfigファイルを配置したりするのに使えるのは確かに便利そうなので、本番のDocker環境構築なんかではまた検討するかもしれない。

余談:開発環境をDocker化するのはいいことか

んで、ここまでやっておいてなんだけど、ちょっと開発環境をDocker化するのはやめておこうかなぁとも思い始めた。

RailsとWebpackのログが一緒にでてきてしまうし、整形もされないのですごく見にくい。rails cなどのコマンドもdocker-compose run経由で叩かなければならず、遅いしコマンド打つのがなんとなく面倒に感じる。

(まだDockerに慣れておらず不安定だからだとも思うけど、)ローカルの開発環境のつもりなのに、Docker上で動かない原因は結局ローカル(Mac)上で確認しなければいけない状況なんかが生まれてきて、結局Macの開発環境を手放すわけにもいかない。なぜ僕はローカル環境のデバッグをローカルでしているのか、という気分になる。

あと単純に、Dockerで開発環境を作ると、コンテナやイメージで簡単に数GBを消費するのが辛い。複数のプロジェクトに関わったりしていて、それらをすべてDocker化したと思うとノートPCのHD容量がそれだけでパンクしてしまいそうだな、と思う。

Dockerを使っているというと、なんとなくクールだな、とか、本番環境もBlue-Green Deploymentでイケてる感じなんだろうな、とか思うし、今回手を動かして得た知見にも満足しているけど、どうしようかなぁ、というのが今回の結論だった。

ほんとはサービスをECSでデプロイしてお手軽に運用するところまでやりたかったのだけど、ちょっと触ってから置いておくのも手かもしれない。

なにか快適に開発するための良い知見があれば知りたいな。

その他参考にしたサイト(日本語記事とか)

Docker入門

Docker入門

f:id:Kechol:20140703080750j:plain