問題
だいたいどんなDBのイメージ使ってても、同じdocker-composeでサーバアプリケーションも一緒に動かそうとすると、いかのように叱られることがよくある。
Unknown MySQL server host 'localhost'
べつにMySQLに限らない。MongoDBでもRedisでも、ありがち。
調査
- mysql - Running migrations with Rails in a Docker container with multiple container instances - Stack Overflow
- A tutorial on how to use MySQL with Docker
- DockerでMySQL+永続コンテナを構築する - Qiita
- Linking django and mysql containers using docker-compose - Stack Overflow
- How would you go about hooking mysql into this set-up? · Issue #3 · denderello/symfony-docker-example · GitHub
- Adding new link does not update /etc/hosts with name specified in docker-compose.yml · Issue #2038 · docker/compose · GitHub
- docker-compose up did not create /etc/hosts entry for linked container · Issue #2172 · docker/compose · GitHub
原因その1: そもそもlocalhostとかじゃねえから
あるコンテナの中で動くサーバアプリケーションから見たlocalhostというのは、同コンテナのことであって、DBが動いているのは同マシーン上の別のコンテナであるため、localhostでは参照できない。docker-composeでは、これを解決するためにlinksというディレクティブがあって、また、アプリケーションからはdocker-compose.ymlに書かれたservicename(トップレベルに書かれた名前)あるいはcontainer_nameでhostのエイリアスとして参照できるようになる。
原因その2: コンテナ立たないと/etc/hostsの更新できねえから
linksで指定できるservicenameやcontainer_nameと、そのコンテナのIPアドレスは、具体的にはlinkするほうのコンテナの/etc/hosts
に書き込まれることで参照できるようになる。つまり、linkするほうのコンテナがビルド成功しrunされた状態になってはじめて/etc/hostsファイルの更新が可能なので、たとえばlinkするほうのDockerfileのRUNにmigrationなどが含まれていて、その中で参照しようとしても、まだこちらのコンテナがそもそも立っていないのだから/etc/hostsにlinksの書き込みは行われていない。
原因その3: DBのコンテナがまだ立ってねえから
docker-compose.ymlに則ってdocker-composeが複数のコンテナをホストマシーン上で起動させようとしたとき、その順番は保証されない。したがって、あるDBサーバのコンテナが立つのを待ってアプリケーションプロセスのコンテナが立つようにするするのは、綺麗にはできない。単純に両方同時にdocker-compose up
などで一緒に立てようとすると、DBが起動してないのにサーバアプリケーションがそれを参照しにいって、エラーでサーバアプリケーションが死ぬ、ということはよくある。
解決
まず、そもそもlocalhostじゃないので、docker-compose.ymlに書いてあるservicenameあるいはcontainer_nameをhostとして指定したほうがよい。
adapter: Ecto.Adapters.MySQL, username: "root", - password: "", + password: "xxxxx", database: "my_db_dev", - hostname: "localhost", + # hostname: "localhost", + hostname: "mysql", pool_size: 10
つぎに、サーバアプリケーションのDockerfile中のRUNでMigrationをするのは、composeしたときこちらのコンテナ内で/etc/hostsが更新されておらず、DBをservicenameあるいはcontainer_nameで参照できないので、プロセスの起動時、つまりENTRYPOINTで一気にやったほうがよい。
-RUN mix ecto.create -RUN mix ecto.migrate -ENTRYPOINT mix phoenix.server +ENTRYPOINT mix ecto.create && mix ecto.migrate && mix phoenix.server
また、起動時にmigrateするとしても、DBのコンテナのビルド時間の方が長く、まだDBサーバプロセスが立っていないともちろん失敗するので、docker-compose.ymlで次のようにする。
web:
container_name: web
build: .
dockerfile: Dockerfile
ports:
- "4000:4000"
links:
- mysql
+ restart: unless-stopped # to wait mysql up :(
雑感
Dockerはたのしいなあ。ローカルで開発するという意味でも、重めのミドルウェアをドカッと入れて、いらなくなったらバコッと消せるの、気持ちいいし、オープンソースとしてサーバソフトウェアを公開するとき、動作環境も一緒に提供できるのは、お互いにストレス無くてよいかなと思う。
DRYな備忘録として
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者: WINGSプロジェクト阿佐志保,山田祥寛
- 出版社/メーカー: 翔泳社
- 発売日: 2015/11/20
- メディア: 大型本
- この商品を含むブログ (1件) を見る