TT Lab
はじめる
学ぶ 学習パス コース

ボリューム・ネットワーク・Compose

スタックをファイル一つで宣言するということ

TT Labで続きを見る

一言でいうと

Composeファイルは、複数のコンテナの実行引数を宣言的に書いたものです。魔法ではなく、docker runのフラグと1対1で対応します。

なぜ必要なのか

コンテナ3つを手で起動しようとすると、ネットワークを作り、ボリュームを作り、それぞれに--network、-v、-e、-pを正しい順序で付ける必要があります。1人が1回やるだけなら問題ありませんが、2人目が同じスタックを再現しようとした瞬間から、地獄になります。どこにも書かれていないからです。

Composeが実際に解決する問題が、これです。スタックの形が1つのファイルに書かれていて、そのファイルがリポジトリにコミットされます。

どう動くのか

主な対応関係は次のとおりです。

Composeのキー docker runでの対応
image イメージの引数
command イメージの後ろのコマンド
ports -p
volumes -v / --mount
environment -e
networks --network
depends_on (対応なし。起動順序のみ)

ここで、2点を押さえる必要があります。

1つ目は、Composeはプロジェクトごとにユーザー定義ネットワークを自動で作り、サービス名をエイリアスとして登録することです。そのため、「Composeではうまく動いていたものが、docker runに移すと動かない」ことが起こります。原因はComposeの魔法ではなく、手で移すときにネットワークを作っていなかったからです。

2つ目は、depends_onは順序だけを保証し、準備状態は保証しないことです。DBコンテナが「起動」したことと、DBが「接続を受け付ける準備ができた」ことは違います。そのため、ヘルスチェックの条件を併せて付けるか、アプリケーションがリトライするようにする必要があります。この区別を見落とすと、デプロイのたびに最初の数秒間、コネクションエラーが大量に出ます。

現場での姿

実戦の構成でよく使うパターンは、ネットワークを2つに分けることです。フロントエンドのネットワークにはリバースプロキシだけを、バックエンドのネットワークにはDBとキャッシュを置き、アプリだけを両方に所属させます。すると、DBはプロキシ側から、名前すら解決できなくなります。

そして、exposeをportsと取り違えてはいけません。exposeはホストにポートを開きません。文書化のためのものです。コンテナ同士でだけ通信するサービスは、そもそもportsを使わないのが正しいです。

最後に、宣言と実際がずれる瞬間が、事故の始まりです。誰かが手でコンテナを変更すると、ファイルと現実が分かれ、そのずれは次のデプロイまで誰も気づきません。宣言と実際を照合する習慣が、そのため重要です。

依存順序を正しく設定する方法

depends_onだけでは不十分です。コンテナが起動したという意味であって、サービスの準備ができたという意味ではありません。DBコンテナは起動しているのに、PostgreSQLがまだ初期化中なら、アプリケーションが接続失敗で死にます。

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s          # 이 동안의 실패는 세지 않는다
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy    # ← 건강해질 때까지 기다린다

start_periodが重要です。これがないと、初期化中の失敗がリトライ回数を消費してしまい、遅いサービスがいつまでもunhealthyと判定されます。

それでも、アプリケーションは再接続ができる必要があります。運用ではcomposeがなく、DBが再起動することもあります。ヘルスチェックは開発の利便のためのもので、再接続ロジックの代わりにはなりません。

環境ごとにファイルを重ねる

composeファイルは、複数を重ねて使えます。

docker compose -f compose.yaml -f compose.dev.yaml up

後のファイルが前のファイルをオーバーライドします。共通部分はcompose.yamlに、開発用のマウントとデバッグ用のポートはcompose.dev.yamlに置きます。compose.override.yamlは、名前だけで自動的に適用されるため、開発者個人の設定に使います(.gitignoreに入れます)。

# compose.dev.yaml — 소스를 마운트해 즉시 반영
services:
  app:
    volumes: ["./src:/app/src"]
    environment: {DEBUG: "1"}
    command: ["python", "-m", "uvicorn", "app:app", "--reload"]

composeとKubernetesの境界

composeでうまく動くものが、Kubernetesでそのまま動くわけではありません。移すときに引っかかるものは、次のとおりです。

compose Kubernetes
depends_on なし。initContainerやリトライで対応します
サービス名でDNS 同じ(Serviceの名前)
volumes: ./src:/app hostPath。本番では避けます
restart: always デフォルトの動作(restartPolicy)
ports: 8080:80 Service + Ingress

komposeのような変換ツールがありますが、結果をそのまま使ってはいけません。リソースのrequestとlimit、プローブ、セキュリティコンテキストがすべて空だからです。変換は出発点であり、手で埋める必要があります。

次のラボですること

2つのサービスで構成されるスタックをComposeファイルで宣言し、同じスタックを手でも起動したうえで、宣言した値と実際に動いている値が一致しているかを自分で照合します。