extendsについて
Compose Specの機能として extends というものがあり、この機能を使うとComposeファイル内の別サービスを拡張し、オプションで設定を上書きするこができます。
例えば下記のような compose.yaml
があったとして、 web
と admin
の差分が ports
しかない
という場合に extends
をうまく活用することで、 compose.yaml
の見た目をスッキリさせることができます。
extends 活用前
# compose.yaml
services:
web:
image: php:8.2.3-apache
volumes:
- ./index.php:/var/www/html/index.php
ports:
- 8080:80
user: root
admin:
image: php:8.2.3-apache
volumes:
- ./index.php:/var/www/html/index.php
ports:
- 8888:80
user: root
extends 活用後
# compose.yaml
services:
web:
image: php:8.2.3-apache
volumes:
- ./index.php:/var/www/html/index.php
ports:
- 8080:80
user: root
admin:
extends: web
ports:
- 8888:80
実際にやってみるとかなり見通しが良くなったと思います。
ただ、この状態だと正常に動作しません。
実際に extends活用後
の compose.yaml
でdocker compose up -d
を行うと8080ポートが競合を起こした旨のエラーが発生します。
$ docker compose up -d
[+] Running 2/3
⠿ Network private_default Created 0.0s
⠿ Container private-admin-1 Started 0.5s
⠿ Container private-web-1 Starting 0.5s
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:8080 -> 0.0.0.0:0: listen tcp 0.0.0.0:8080: bind: address already in use
直感的にextendsした側でports
を設定すればextends元のports
設定は上書きされてくれそうな感じがしますが、実際はextends元のports
設定がマージされます。
docker compose convert
を実行して確認すると下記のような結果が得られます(web
に8080:80
と8888:80
の設定がされていること確認できます)。
# docker compose convert の結果
name: private
services:
admin:
extends:
service: web
image: php:8.2.3-apache
networks:
default: null
ports:
- mode: ingress
target: 80
published: "8080"
protocol: tcp
- mode: ingress
target: 80
published: "8888"
protocol: tcp
~~~~~ 以下結果省略 ~~~~~
spec.md を確認すると、
以下のキーはシーケンシャルな値として扱う必要があり、マージの結果生じた重複はすべて削除され、ユニークな要素のみが含まれるようになります。
cap_add, cap_drop, configs, deploy.placement.constraints, deploy.placement.preferences, deploy.reservations.generic_resources, device_cgroup_rules, expose, external_links, ports, secrets, security_opt
と書かれていました。
つまり、これらのキーでextends元にある設定は必ず受け継がれるということになります。
fileオプションを使う
それでは今回のようなports競合を起こしてしまうケースの場合にはextendsは使えないのかというと、そうではありません。
extends
にはfile
というオプションがあり、これを活用することでportsの競合を回避することが可能です。
それでは実際に先ほどの例に適用してみます。
まず、 compose.yaml
を下記のように変更します。
extends
にfile
オプションが追加して compose.base.yaml
を読み込むようにしています。
また、 base
サービスを拡張元に設定しています。
# compose.yaml
services:
web:
extends:
file: compose.base.yaml
service: base
ports:
- 8080:80
admin:
extends:
file: compose.base.yaml
service: base
ports:
- 8888:80
次にcompose.base.yaml
を作成します(compose.yaml
と同じディレクトリに配置してください)。
compose.base.yaml
services:
base:
image: php:8.2.3-apache
volumes:
- ./index.php:/var/www/html/index.php
user: root
docker compose up -d && docker compose ps
を実行して動作確認。
無事起動することが確認できました。
$ docker compose up -d && docker compose ps
[+] Running 2/0
⠿ Container private-web-1 Running 0.0s
⠿ Container private-admin-1 Running 0.0s
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
private-admin-1 php:8.2.3-apache "docker-php-entrypoi…" admin 4 minutes ago Up 4 minutes 0.0.0.0:8888->80/tcp
private-web-1 php:8.2.3-apache "docker-php-entrypoi…" web 4 minutes ago Up 4 minutes 0.0.0.0:8080->80/tcp
所感
最初からfile
を使っていればports
競合を踏むこともなかったのですが、composeファイルが1つ増えるのが微妙かなと思い見事にハマりました。
仮にfile
を使わなくてもうまくいくパターンだとしても、チームでの運用を考えるとextends
を使うときはfile
必須で利用する運用に統一しておいた方がハマらなくて良さそうだなと思います。