docker-compose で複数環境を構築するときの設定をなるべく DRY に書く
概要
docker-composeの-f, --fileオプションを複数使って、共通の Composeファイル と環境ごとの Compose ファイルを読み込むようにします- こうすることで、共通の設定を DRY に書けます
-p, --project-nameオプションとnetworksで環境を分離します- 以下のリファレンスの内容が理解できていればこの記事を読まなくても大丈夫です
背景
docker-compose, 便利ですよね。 Docker 完全に理解した *1 くらいのレベルで複数コンテナの環境を作るなら手軽でよいです。
その docker-compose が開発環境だけならまだいいんですが、 テスト用の環境も同じ仕組みで作るようになると、 環境差分をどうするかが課題になります。 例えば、以下のような環境差分が考えられます。
まだまだありそうです。 実際に差分が発生するかはアプリケーションの設計や環境にもよるんですが、 いったんこういう差分があり得るという前提で話を進めます。
アンチパターン
1. Compose ファイルは 1 つで、環境ごとに branch を切る
- 修羅の道です
- Compose ファイルがブランチごとに成長して、目も当てられなくなります
- 唯一のメリットは、「環境ごとの起動コマンドが一緒」
- でも各環境にそれぞれ 1 step で起動できるジョブを用意しておけばいいですよね?
2. 環境ごとに Compose ファイルを作る
- 修羅の道パート 2 です
- やっぱり Compose ファイルがファイルごとに成長していきます
- プロジェクト名が同じだから同じサービス名が使えなくなり、かなりつらいです
解決策
$ docker-compose -f docker-compose.yml -f <your_env>.yml -p <your_env> up -d
実装例はこちら。
解説
-f, --file オプション
共通の Compose ファイル ( docker-compose.yml ) と環境依存の Compose ファイル ( <your_env>.yml ) を読み込みます
共通
$ cat docker-compose.yml version: "3.5" services: web: build: context: ./web volumes: - ./web/proxy-to-back.conf:/etc/nginx/conf.d/proxy-to-back.conf:ro environment: VIRTUAL_HOST: "*.web.local,*.back.local" networks: - default - front back: build: context: ./back depends_on: - web networks: front: external: true
環境依存
$ cat env1.yml version: "3.5" services: web: environment: VIRTUAL_HOST: "env1.web.local,env1.back.local" APP_ENV: env1 volumes: - ./web/env1.html:/usr/share/nginx/html/index.html:ro back: volumes: - ./back/env1.html:/usr/share/nginx/html/index.html:ro networks: default: name: env1 $ diff env1.yml env2.yml 5c5 < VIRTUAL_HOST: "env1.web.local,env1.back.local" --- > VIRTUAL_HOST: "env2.web.local,env2.back.local" 7c7 < - ./web/env1.html:/usr/share/nginx/html/index.html:ro --- > - ./web/env2.html:/usr/share/nginx/html/index.html:ro 10c10 < - ./back/env1.html:/usr/share/nginx/html/index.html:ro --- > - ./back/env2.html:/usr/share/nginx/html/index.html:ro 14c14 < name: env1 --- > name: env2
- 前のファイルで定義した同じフィールドの項目が後のファイルにあれば、それを上書きします。
- 新しい値があれば追加します。
例えば、
environmentの同じキー (VIRTUAL_HOST) は上書きされますenvironmentの異なるキー (APP_ENV) は追加されますvolumesは追加されます
最終的にどんな設定になるのかは、 docker-compose config コマンドを使うと見れます。
$ docker-compose -f docker-compose.yml -f env1.yml -p env1 config networks: default: name: env1 front: external: true name: front services: back: build: context: /home/ikasamak/work/dc-multi-env/back depends_on: - web volumes: - /home/ikasamak/work/dc-multi-env/back/env1.html:/usr/share/nginx/html/index.html:ro web: build: context: /home/ikasamak/work/dc-multi-env/web environment: APP_ENV: env1 VIRTUAL_HOST: env1.web.local,env1.back.local networks: default: null front: null volumes: - /home/ikasamak/work/dc-multi-env/web/proxy-to-back.conf:/etc/nginx/conf.d/proxy-to-backi.conf:ro - /home/ikasamak/work/dc-multi-env/web/env1.html:/usr/share/nginx/html/index.html:ro version: '3.5'
-p, --project-name オプション
プロジェクト名を指定します。 デフォルトは compose ファイルのあるディレクトリ名です。
~/work/dc-multi-env master* $ docker-compose up -d Creating network "dc-multi-env_default" with the default driver
プロジェクト名を指定せずに同じディレクトリで別環境を立ち上げると、 同プロジェクトの同サービスと見なされ、既存のコンテナがかき消されてしまいます。
$ docker-compose -f docker-compose.yml -f env1.yml up -d Creating network "env1" with the default driver Creating dc-multi-env_web_1 ... done Creating dc-multi-env_back_1 ... done $ docker-compose -f docker-compose.yml -f env2.yml up -d Creating network "env2" with the default driver Recreating dc-multi-env_web_1 ... done Recreating dc-multi-env_back_1 ... done $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ffd5b9f636be dc-multi-env_back "nginx -g 'daemon of…" 5 minutes ago Up 5 minutes 80/tcp dc-multi-env_back_1 6dcafba12120 dc-multi-env_web "nginx -g 'daemon of…" 5 minutes ago Up 43 seconds 80/tcp dc-multi-env_web_1
-p でプロジェクト名を指定し、別環境として立ち上げます。
$ docker-compose -f docker-compose.yml -f env1.yml -p env1 up -d Creating network "env1" with the default driver Creating env1_back_1 ... done Creating env1_web_1 ... done $ docker-compose -f docker-compose.yml -f env2.yml -p env2 up -d Creating network "env2" with the default driver Creating env2_back_1 ... done Creating env2_web_1 ... done $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2496c6858e0c env2_web "nginx -g 'daemon of…" 4 seconds ago Up 3 seconds 80/tcp env2_web_1 fa5bcec301ad env2_back "nginx -g 'daemon of…" 5 seconds ago Up 4 seconds 80/tcp env2_back_1 0aca011671e7 env1_web "nginx -g 'daemon of…" 12 seconds ago Up 11 seconds 80/tcp env1_web_1 ec885d01fa73 env1_back "nginx -g 'daemon of…" 13 seconds ago Up 12 seconds 80/tcp env1_back_1
プロジェクト名を指定する方法
-p, --project-nameオプションを使うCOMPOSE_PROJECT_NAMEを使う- 環境変数を
.envファイルで指定する
- 環境変数を
Compose ファイルにプロジェクト名を指定できれば楽なんですけど、そういう仕様にはならなかったようです。
まあ普通は環境ごとにディレクトリ分けるから、 .env で何とかしなさいということなんでしょう。
networks
適切にネットワークを設定しないと、コンテナ名で名前解決していると別環境にトラフィックが飛んでしまうことがあります。
例えば、面倒なんで全部 front のプロキシのいるネットワークにつないでしまえ! ということをすると
$ cat docker-compose.yml version: "3.5" services: web: build: context: ./web volumes: - ./web/proxy-to-back.conf:/etc/nginx/conf.d/proxy-to-back.conf:ro environment: VIRTUAL_HOST: "*.web.local,*.back.local" networks: - front depends_on: - back back: build: context: ./back networks: - front networks: front: external: true
env1 の web が env1, env2 両方の back とつながるので、
back へのアクセスがロードバランシングされてしまいます。
$ curl -H "Host: env1.back.local" localhost here is env2.back! $ curl -H "Host: env1.back.local" localhost here is env1.back! $ curl -H "Host: env1.back.local" localhost here is env2.back! $ curl -H "Host: env1.back.local" localhost here is env1.back!
なので、適切にネットワークを設定しましょう。
プロジェクト名を分けているのであれば、 default で通信するようにしましょう。
*2
おまけ
extends を使えば、設定をモジュール化して再利用できるようです。
2019/03/08 追記
extends は compose file format v3 で使えなくなってました。
まとめ
- 共通部分、環境依存部分に分けることで Compose ファイルを DRY に書けます
-pオプションでプロジェクト名を分け、同じサービス名を別環境で同時に動かせるようにしますnetworksを適切に設定して別環境にトラフィックが迷い込まないようにします
参考
- Overview of docker-compose CLI | Docker Documentation
- Compose file version 3 reference | Docker Documentation
- Networking in Compose | Docker Documentation
*1:https://twitter.com/ito_yusaku/status/1042604780718157824
*2:サービス名だけの名前解決 ( http://back とか ) は、default ネットワークから行われるようです