V3以降はdocker-compose.ymlで外部ファイルを参照出来ない！

`YAML include`等と検索した所、
RubyやPython等の独自仕様が山ほど出てきて死んだので備忘録として書き残す。

# 結論

- V2まで: `extends`を利用する
- V3以降: `docker-compose.override.yml`を`docker-compose.yml`と同じディレクトリに配置するとマージしてくれる

どちらも後述で紹介しているので、
やり方だけ知りたい方はそちらを参照。

## 前提: そもそもYAMLで外部ファイルを参照する仕様はない

YAMLはデータをシリアライズ(文字列化)した文書ファイルでしかない。
外部ファイルを参照したければ、自前のプログラミング言語等で勝手に実装する必要がある。

例えばPHPの[Symfony](https://symfony.com/)というフレームワークでは、
YAMLファイルの上部に`imports`というおまじないを記載することで外部ファイルを読み込んでYAMLファイル同士をマージして扱う事が可能となる。
参考記事: [How to Import Configuration Files/Resources - Symfony Docs](https://symfony.com/doc/current/service_container/import.html)

これはYAMLファイルとしては`imports`という文字列の配列を定義しただけ。
これを読み取ったSymfonyが勝手に別のYAMLファイルも読み込みマージ、
巨大な連想配列を生成し直して、そこから自身のフレームワークの挙動を制御しているだけの話だ。

なので他の言語やライブラリでSymfonyと同じやり方を試してもうまくいかないだろう。
もしそのツールやライブラリの開発者が必要と判断したのなら、
独自の外部ファイル参照の仕組みが実装されることになる。
(その開発者が前例のSymfonyを元に同じような仕組みを採用するケースはあるかもしれないが期待薄)

例えばDockerのdocker-compose.ymlで外部ファイル参照がしたければ、
`YAML 外部ファイル`といったワードで検索しても時間の無駄。
`docker-compose.yml 外部ファイル`という風にツールやフレームワーク等を意識したワードとセットにして検索してみよう。

## docker-compose.ymlで外部ファイルを参照する方法(V2まで)

※以下の方法はV2までのファイルフォーマットであり、
V3からは「フラットな構築にしなさい」というありがたいお言葉と共に取り除かれてしまった。

公式サイト: [サービスの拡張 - Docker Compose](http://docs.docker.jp/compose/extends.html#extending-services)

> Docker Compose の `extends` （拡張）キーワードは、異なったファイル間で設定を共有できるだけでなく、異なったプロジェクトでも利用可能です。拡張サービスは複数のサービスを持っている場合、一般的な設定オプションの再利用に便利です。 `extends` を使えば、１箇所だけでなく、どこでも利用可能なサービス・オプションの共通セットを定義できます。

### `docker-compose.yml`

extendsキーで外部ファイルを指定し、サービス名をキーにまるごとテンプレートとしてまるごと持ってくる。
その後、コンテナの用途に合わせてプロパティを上書き指定していく形になる。

```yaml
web:
  extends:
    file: common-services.yml
    service: webapp
  ports:
    - "4000:8080"
```

### `common-services.yml`

```yaml
webapp:
  build: .
  ports:
    - "8000:8000"
  volumes:
    - "/data"
```

## 本題: V3でextendsが取り除かれた話

[アップグレードガイド](https://docs.docker.com/compose/compose-file/compose-versioning/#upgrading)を確認した所、こんな風に書いてある。

> `extends`: This option has been removed for `version: "3.x"` Compose files. (For more information, [see Extending services](https://docs.docker.com/compose/extends/#extending-services).)

和訳: このオプションは`バージョン: 3.x`では削除されています。

実際にV3で使おうと思ったら、こんなエラーが出まくりで動く気配がない。

```bash
$ docker-compose ps
ERROR: The Compose file './docker-compose.yml' is invalid because:
Unsupported config option for services.db: 'extends'
```

この機能を一時的にONにしようとdocker-compose.ymlの先頭行を`service: "2"`に変更しても、
参照先のファイルがV3準拠の場合は「お前V3のdocker-composeを参照しようとしてるだろ！小手先の事をしてもダメだぞ！」と叱られてしまう。

「V3で無くなったextendsに関しては復活の可能性を議論しています」みたいなIssueを見つけた。
[Add support for extends feature in Compose v3 / docker stack deploy #31101](https://github.com/moby/moby/issues/31101)

ざっとIssueを流し読みしたところ、
extendsはそもそもバギーだし、依存関係含めると中々対応が難しいという問題があった。
それでV3で削除してフラットなデータ構造にしてねという対応にしたらしい。

当然非難轟々で「受け皿がない」「コピペだらけになるだろ」「もうdocker-compose.ymlを自動生成するスクリプト作ったわ」という感じで荒れに荒れている。

因みにV3に於けるextendsを公式のドキュメントで調べると、
「削除されました」というワードにたどり着くだけで散々たらい回しにされてしまう。
これに関しても利用者はブチ切れており、下記のお叱りのコメントが付いている。

> Not only this, but the changelog says "this has been removed, see 'how to upgrade' for details". I look at "how to upgrade" for, you know, details on how to upgrade, and what that says is "see 'extending services' for details". I go to "extending services" figuring I'll finally see a way to extend my files, only to see "this has been removed, see the changelog for details".
> At this point, this seems like a cruel joke the documentation writer is playing.

意訳: V2からのアップデートに追従するにはAをご覧ください、Aに行くとBをご覧ください、そして最後にはAをご覧くださいに戻る。こんな下らん言葉遊びをドキュメントに書くな。

DockerComposeの[拡張フィールド](https://docs.docker.com/compose/compose-file/#extension-fields)を[使う案が2018/8](https://github.com/moby/moby/issues/31101#issuecomment-413323610)に出ているが、これは本格的な実装はまだまだ先っぽい？

完全にextendsと同じことがやりたいなら、docker-compose.ymlファイルを全てV2相当まで落とすか、
自分でスクリプトを作って自動的に生成する手段を取るしかなさそう。

## 追記: V3でもMultiple Compose filesを使う事で代用可能

extendsはなくても代用はできそうだ。
Multiple Compose filesに関しての記載がある記事を見つけたので追記。
参考記事: [docker-compose.ymlをDRYに書くテクニック2選 - @reireias](https://qiita.com/reireias/items/ecb81e248314253eb156)

この「Multiple Compose files」セクションがとても参考になる。

docker-composeは「docker-compose.override.yml」ファイルを用意したり、
`docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d`などと記述することで複数のYAMLファイルをマージしながら起動出来る仕組みが存在するようだ。

ベースとなるdocker-compose.ymlは上書き上等レベルのシンプルさで記述しておき、
Makefileやshell-script、package.jsonなんかに書いて管理するというのがベストプラクティスになるだろう。

ただしYAMLファイル同士全体のマージが前提となる。
V2が出来ていたextendsみたいに、あちこちのファイルから少しずつついばむような指定は出来ない。

そこが少々不便ではあるものの、
`docker-compose.yml`はシンプルにしましょうねって事を心がければ
その違いが大きな問題になることはほとんど無いと思う。