My tech diary

ソフトウェアエンジニアをやっています。技術的内容を中心に調べたことを書いていきます。

Docker Compose V2 を理解する

この記事は 🌊 UMITRON 🐟 Advent Calendar 2021 20日 の記事です。 Advent Calendar も最終週ですね!

この記事は、以前から気になっていた Docker Compose V2 の経緯・現在地点について、自分なりに調べて、まとめておくものです。 あまり深堀り調査していないので、もし誤りがあれば、ご指摘ください。


(2022-03-10 一部追記しました)


結構以前だと思うのですが、いつものように docker-compose を使っていると、

Docker Compose is now in the Docker CLI, try docker compose up

みたいなメッセージが表示されるようになりました。

この時点では、そうか、これからは docker compose と打てばいいのか (後で調べよう...)、くらいの理解でとりあえず使っていました。

ところで、最近、 docker-compose を使っても、またこのメッセージ出現しなくなっております。 (後述の Use Docker Compose V2 のチェックを外したとしても)

この間、私を含む利用者はじわじわと、 Docker Compose の V1 から V2 と呼ばれる実装へ誘導されていたんですが、そもそも Compose V2 とはなんぞ、というところを先に書きます。

Docker Compose V1 vs V2

もともとの docker-compose の実装、つまり Compose V1 (と便宜的に呼びます) は Docker client とは全く別のツールとして Python で書かれていました。

https://github.com/docker/compose/tree/v1.25.2

そのため、従来 Linux において Docker Compose CLI は Docker CLI に追加して、別途インストールする必要がありました。 (現時点では、まだそうなってます)

Docker Compose を Go で書かれている Docker CLI に組み込むには、 Compose V1 が Python で書かれていることがネックになっており、実験的なプロジェクトとして、 Go 版の Docker Compose (Compose CLI) の開発が docker/compose-cliリポジトリで進められたようです。

やがて、こちらのリポジトリは Docker Compose "Cloud Integrations" として、 Docker Compose のクラウド連携機能に特化するようになり、 Compose V2 (従来の docker-compose が提供する機能の代替) 部分は、もともとの Compose V1 のリポジトリ (https://github.com/docker/compose) に移管されることになったようです。

https://github.com/docker/compose/tree/v2.2.2

Compose V2 は Go でフルスクラッチで書き直されていることが分かりますね。

Compose V1 と V2 は概ね互換性はあるものの、完全ではないようで、一部のコマンドについては差異があると言及されています。 実装の違いによる細かい動作の違いや、画面上の表示の変化については、自分も使っている中でいくつか感じられます。

(スムーズな移行のために、 docker-compose を実行したときに、内部的に docker compose (Compose V2) のコマンドに変換して実行してくれる Compose Switch というツールも提供されているようです)

さて、 Compose V2 は Compose V1 の後継ではあるのですが、もちろん機能を引き継いだだけではなく、いくつかの改良・新機能が含まれています。

また、 Compose V2 の開発にあたっては、 Compose V1 の挙動を元にした Compose Specification が platform 非依存な標準仕様として策定され、それに準拠する実装として Compose V2 を開発する、という体制が整えられました。

Docker Compose V2 への移行状況

およそここ1年間、 Docker Compose V2 への移行がどのように進められたかを、 macOS 版 Docker Desktop のみを対象に、漁ってみました。 (リアルタイムで追ってないので、詳しい状況はわからないけど)

  • Docker Desktop 3.0.0 (2020-12-10)
    • Go 版の Compose CLI が内包され、 docker のサブコマンド docker compose で実行できるようになった
  • Docker Desktop 3.4.0 (2021-06-09)
    • docker compose が Compose V2 beta として提供されるようになった
    • docker-compose を呼んだときに Compose V2 を使うかどうかを docker-compose disable-v2, docker-compose enable-v2 によって切り替えられるようになった
  • Docker Desktop 4.0.0 (2021-08-31)
    • (大企業向けに有料化)
  • Docker Desktop 4.0.1 (2021-09-13)
    • Compose V2 が github.com/docker/compose でホストされるように変更された
  • Docker Desktop 4.1.0 (2021-09-30)
    • docker-compose がデフォルトで Compose V2 を向くようになった
    • General settings (Use Docker Compose V2 という項目) で docker-compose を呼んだときに Compose V2 を使うかどうか選択できるようになった
  • Docker Desktop 4.3.1 (2021-12-11)
  • Docker Desktop 4.3.2 (2021-12-21)
  • Docker Desktop 4.4.2 (2022-01-13)
  • Docker Desktop 4.5.0 (2022-02-10)
    • (今ここ)

ということで、 macOS 版 (たぶん Windows 版も) Docker Desktop をきちんと最新版にアップデートしている場合、明示的に OFF しない限りは、既に Compose V2 への移行が完了しているということですね。

(明示的に Use Docker Compose V2 のチェックを外せば docker-compose コマンドで引き続き Compose V1 を利用することはできます)

(2022-03-10 追記: もしくは docker-compose-v1 コマンドを使うことで、引き続き Compose V1 を利用することができるようです)

Linux 版の状況もあって Compose V2 は 2021-12-20 現在ではまだ GA (Generally Available) とはなっていませんが、もう GA になるのは時間の問題と思われます。

試しに、今、手元に入っている macOS 版 Docker Desktop 4.3.1 で実験してみます。

Use Docker Compose V2 のチェックを外した状態では、 docker-compose では Compose V1, docker compose では Compose V2 がそれぞれ呼ばれます。

$ docker-compose version
docker-compose version 1.29.2, build 5becea4c
docker-py version: 5.0.0
CPython version: 3.9.0
OpenSSL version: OpenSSL 1.1.1h  22 Sep 2020

$ docker compose version
Docker Compose version v2.2.1

Use Docker Compose V2 のチェックを付けた状態では、いずれも Compose V2 が呼ばれます。

$ docker-compose version
Docker Compose version v2.2.1

$ docker compose version
Docker Compose version v2.2.1

Compose V2 のクラウド連携機能

ちょっと本筋と離れますが、もともとの "Compose CLI" であった、クラウド連携機能についても、個人的整理をしておきます。

Compose V2 のクラウド連携機能は docker-compose.yml で定義したマルチコンテナ環境を、簡単にクラウド環境にデプロイできるようにするもので、現時点で、以下のプラットフォームがサポートされています。

  • Amazon Elastic Container Service (ECS)
  • Microsoft Azure Container Instances (ACI)

Kubernetes についても対応が進められています。

こちらの機能については、元々 2020-07-09 に docker ecs というサブコマンドを追加する CLI Plugin がベータ版が公開されたものの流れを組むようです。 2020-11 には既に、今の "Compose CLI"、つまり docker compose として使えるものを公開したというアナウンスが出されています。

一方、上と同日の 2020-07-09 には AWS Copilot という、これまたコンテナ環境を ECS 環境にデプロイするためのツールが公開されていて、一体どっちを使えってこと?って混乱したのを覚えています。こちらのツールは元々は Amazon ECS CLI (ecs-cli) の流れを組むもののようです。

Compose V2 のクラウド連携機能は、当然 docker-compose.yml を中心に据えて、そこで定義したコンテナの集合体をクラウドで構築することを目標としているのに対し、 AWS Copilot の方は、個々の Dockerfile (コンテナ) をそれぞれ役割に応じて AWS のサービスにデプロイして、連携して動かすというアプローチの違いがあるようです。

ただ、似たツールなのは間違いないないですね。。 汎用 IaC ツールとして CDK や Terraform もありますし、正直、この手のツールは、今、乱立している感じはありますね。どれが覇権を取るのでしょう。。

さて、調査だけだと退屈なので、最後に簡単に動かしてみます。

コードはこちらに置いてあります。 (🐟漢字クイズです)

https://github.com/tearoom6/docker-ecs-tools-trials

Compose V2 と AWS Copilot を用いて AWS 環境にデプロイするのを試してみました。

まず Compose V2 の方を試してみます。

既存の docker-compose.yml がそのまま使えるかというと、全然そんなことはなく、いくつかの制約があるようです。 例えば、以下のようなエラーに遭遇しました。

  • published port can't be set to a distinct value than container port: incompatible attribute (ports の mapping はコンテナ内外で同じにしなくてはいけない)
  • ECS Fargate does not support bind mounts from host: incompatible attribute (Fargate の場合 volumes で host からのマウントができない)
  • services.build: unsupported attribute (Dockerfile を指定したイメージのビルドに対応していない)

image は ECR や DockerHub などから、 public で公開されているものか、カスタムビルドして push しておいたものを引っ張ってくるしかないようです。

今回はまず ECR で repository を作っておいて Dockerfile から作った Docker image を push します。

aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/a9b0d6v6
docker build -t fish_quiz .
docker tag fish_quiz:latest public.ecr.aws/a9b0d6v6/fish_quiz:latest
docker push public.ecr.aws/a9b0d6v6/fish_quiz:latest

その上で docker-compose.yml を用意しておけば、以下のようにしてデプロイ可能です。 デプロイの裏側では CloudFormation が動いています。

# Configure AWS context by answering questions.
docker context create ecs tearoom6
# ? Create a Docker context using: An existing AWS profile
# ? Select AWS Profile default
# Successfully created ecs context "tearoom6"

# Switch context to use AWS resources.
# `--context tearoom6` flag also can be used.
docker context use tearoom6

# Generate and review CFn template.
docker compose convert

# Deploy to AWS env.
docker compose up

# Check created LoadBalancer's DNS name to find endpoint.

# Delete all resources.
docker compose down

# Reset context to default!!
docker context use default

AWS Copilot の場合だと、 Dockerfile とソースコードを用意した上で、以下のようにして、とりあえずデプロイ可能です。 こちらも、デプロイの裏側では CloudFormation が動いています。

# Install copilot-cli.
brew install aws/tap/copilot-cli

# Create project and deploy by answering questions.
copilot init
# Application name: fish-quiz
# Workload type: Load Balanced Web Service
# Service name: front-end
# Dockerfile: ./Dockerfile
# Ok great, we'll set up a Load Balanced Web Service named front-end in application fish-quiz listening on port 80.
# ...
# All right, you're all set for local development.
# Deploy: Yes
# ...
# ✔ Deployed service front-end.
# Recommended follow-up action:
#   - You can access your service at http://fish-Publi-1QFXMQVITL58L-571977271.ap-northeast-1.elb.amazonaws.com over the internet.

# Delete all resources.
copilot app delete

References

Compose V2 での pts の stdout/stderr の向き先 (2022-03-10 追記)

V1 と V2 の動作の違いで、結構大きな違いを見つけたので、追記します。

Docker Compose V1 では、以下のように exec コマンドでコンテナに接続して、その中で標準出力に出力したメッセージを、リダイレクトによってホスト側のファイルに書き込むことができていました。

# Docker Compose V1
$ docker-compose-v1 exec app echo Hello > /tmp/test
$ cat /tmp/test
Hello

ところが、 Docker Compose V2 では、同様にしても、コマンド実行時にメッセージが表示され、ファイルには何も書き込まれません。

# Docker Compose V2
$ docker compose exec app echo Hello > /tmp/test
Hello
$ cat /tmp/test

ただし、 -T (--no-TTY) option をつけて実行をすることで従来どおり、ホスト側ファイルへリダイレクトすることは可能です。 このオプションは、 pts (pseudo-TTY, pseudo-terminal slave, 擬似端末) の割当を無効にするためのものとのことです。

# Docker Compose V2
$ docker compose exec -T app echo Hello > /tmp/test
$ cat /tmp/test
Hello

Docker Compose V1 の場合でも -T option をつけても同様の結果が得られました。

# Docker Compose V1
$ docker-compose-v1 exec -T app echo Hello > /tmp/test
$ cat /tmp/test
Hello

どういう理屈で、このような挙動になっているのか、という点については、私の Linux への理解がなさすぎて、下手に憶測を書いても不正確になりそうなので、控えます。 もし親切に教えていただける神の方がいたら嬉しいです。

Docker 雰囲気で扱えるくらいにはなっているけど、一歩奥に入れば分からないことだらけだなと思い知った次第です。

References