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

DRYな備忘録

Don't Repeat Yourself.

docker-composeで Unknown MySQL server host 'localhost' とかなる現象

docker docker-compose docker-machine mysql

問題

だいたいどんなDBのイメージ使ってても、同じdocker-composeでサーバアプリケーションも一緒に動かそうとすると、いかのように叱られることがよくある。

Unknown MySQL server host 'localhost'

べつにMySQLに限らない。MongoDBでもRedisでも、ありがち。

f:id:otiai10:20160204160309j:plain

調査

原因その1: そもそもlocalhostとかじゃねえから

あるコンテナの中で動くサーバアプリケーションから見たlocalhostというのは、同コンテナのことであって、DBが動いているのは同マシーン上の別のコンテナであるため、localhostでは参照できない。docker-composeでは、これを解決するためにlinksというディレクティブがあって、また、アプリケーションからはdocker-compose.ymlに書かれたservicename(トップレベルに書かれた名前)あるいはcontainer_nameでhostのエイリアスとして参照できるようになる。

otiai10.hatenablog.com

原因その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が起動してないのにサーバアプリケーションがそれを参照しにいって、エラーでサーバアプリケーションが死ぬ、ということはよくある。

stackoverflow.com

解決

まず、そもそも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教科書 インフラの基礎知識&コードによる環境構築の自動化

プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化