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必須で利用する運用に統一しておいた方がハマらなくて良さそうだなと思います。