Docker

Compose で複数サービスをつなぐ

7

Compose で複数サービスをつなぐ — Docker Tips

Compose はサービスを複数並べるための道具です。web(nginx)と api(小さな Python HTTP)の 2 つだけで、名前解決と depends_on の限界、既定ネットワークを確かめます。DB やクラウドは使いません。

参考: Services · Networking in Compose · Startup order · Networks


目次

  1. 作る構成の概要
  2. compose.yaml を用意する
  3. 起動してポートを確認する
  4. サービス名で名前解決する
  5. depends_on が保証すること・しないこと
  6. service_healthy でもう一歩確実にする
  7. 既定のネットワーク(bridge)を覗く
  8. 片付け
  9. よくある失敗

作る構成の概要

サービス イメージ 役割
web nginx:alpine ホストにポートを公開する「入口」役
api python:3.12-alpine python -m http.server で待ち受ける簡易サーバー

web から api に対して、コンテナの IP アドレスを知らなくても サービス名 api で直接アクセスできる ことを確認するのが目的です。

flowchart LR  Host["ホスト<br/>localhost:8080"] --> Web["web (nginx)"]  Web -- "http://api:8000" --> Api["api (python http.server)"]  subgraph net["Compose の既定ネットワーク(bridge)"]    Web    Api  end

compose.yaml を用意する

任意の作業フォルダに compose.yaml を作成します(docker-compose.yml でも動作は同じですが、Compose Specification に合わせて compose.yaml を使います。version: キーは Compose Specification では不要かつ obsolete のため書きません)。

services:  api:    image: python:3.12-alpine    working_dir: /tmp    command: ["sh", "-c", "sleep 8 && python -m http.server 8000"]  web:    image: nginx:alpine    depends_on:      - api    ports:      - "8080:80"
設定 意味
api.command 起動直後に わざと 8 秒待ってから HTTP サーバーを立てる(後述の検証用)
web.depends_on: [api] api を先に起動してから web を起動する
web.ports ホストの 8080web(nginx)の 80 に公開

api にはポート公開を設定していません。これは コンテナ間だけで通信できれば十分 で、ホストから直接 api を叩く必要がないケースを想定しているためです。ホストの外に出ないサービス(内部 API・ワーカーなど)はこのように ports を省略できます。


起動してポートを確認する

docker compose up -ddocker compose ps
見るポイント
NAME <フォルダ名>-web-1 / <フォルダ名>-api-1 のような名前になる
STATUS 両方が Up になっているか
PORTS web0.0.0.0:8080->80/tcp が付いているか

ブラウザで http://localhost:8080 を開けば nginx の Welcome ページが表示されます。単体の docker run -p で nginx を動かしたときと同じ確認です。


サービス名で名前解決する

Compose は起動時に プロジェクト専用のネットワーク を自動で作り、各サービスをそのネットワークに参加させます。参加したコンテナは、IP アドレスを調べなくても サービス名がそのままホスト名になる ため、名前で直接通信できます。

api の 8 秒スリープが終わったあとに、web コンテナの中から api へアクセスしてみます。

docker compose exec web wget -qO- http://api:8000

nginx:alpine は Alpine ベースで、busybox 由来の wget が同梱されているため追加インストールなしで確認できます。しばらくすると次のような一覧がテキストで返ってきます(python -m http.server がカレントディレクトリの一覧を返すため)。

<!DOCTYPE html><html><head><title>Directory listing for /</title></head>...

このとき webapi の IP アドレスを一切知らずに、サービス名 api だけで到達 しています。これが Compose の既定ネットワークが提供する DNS 解決です。


depends_on が保証すること・しないこと

depends_on は便利ですが、保証する範囲は狭いです。

保証する 保証しない
依存先コンテナを 先に起動開始する 依存先の中の アプリが接続を受け付けられる状態 になっていること
docker compose down 時に 依存元を先に止める(逆順で停止) ポートが実際に listen しているか

先ほどの compose.yaml では apisleep 8 してから HTTP サーバーを立てています。もし docker compose up -d直後(8 秒以内)に web から api へアクセスすると、次のように失敗します。

docker compose up -ddocker compose exec web wget -qO- http://api:8000
wget: can't connect to remote host (172.x.x.x:8000): Connection refused

depends_on: [api] によって api コンテナ自体は先に 起動開始 していますが、その中の python -m http.server はまだ sleep 8 の最中で listen していません。「コンテナが起動した」と「アプリが準備完了した」は別物 であることを、この短い例で確認できます。


service_healthy でもう一歩確実にする

「準備完了を待ちたい」場合は、healthcheckdepends_oncondition: service_healthy を組み合わせます。

services:  api:    image: python:3.12-alpine    working_dir: /tmp    command: ["sh", "-c", "sleep 8 && python -m http.server 8000"]    healthcheck:      test: ["CMD", "wget", "-qO-", "http://localhost:8000"]      interval: 2s      timeout: 2s      retries: 10      start_period: 15s  web:    image: nginx:alpine    depends_on:      api:        condition: service_healthy    ports:      - "8080:80"
設定 意味
healthcheck.test コンテナ内で定期的に実行し、成功すれば healthy とみなすコマンド
depends_on.api.condition: service_healthy api が healthy になるまで web の起動を待つ

これで docker compose up -dapi が healthy になるまで web を作成しません。condition: service_healthy は対象サービスの healthcheck 結果に依存するため、healthcheck を定義していないサービスに付けると、多くの場合 docker compose up 時点でエラーになり起動が止まります。healthy 待ちを使うなら、先に対象サービスへ healthcheck を書いてください。


既定のネットワーク(bridge)を覗く

明示的に networks: を書かなくても、Compose は <プロジェクト名>_default という名前のブリッジネットワークを自動生成し、全サービスをそこに参加させます。

docker network lsdocker network inspect <フォルダ名>_default

inspect の出力の Containers セクションに webapi の両方が載っていれば、同じネットワークに参加していることが確認できます。サービスを増やしたいときは compose.yamlservices: 配下へ新しいキーを追記するだけで、そのサービスも自動的に同じ既定ネットワークへ参加します。


片付け

docker compose down

イメージまで削除したい場合は次を使います。この例では image: を明示しているため、--rmi local ではなく --rmi all を使います。

docker compose down --rmi all -v

-v は名前付きボリュームも削除します(この例ではボリュームを使っていないため付けても付けなくても差はありません)。--rmi localimage: 未指定で自動生成されたイメージ向けです。


よくある失敗

症状 原因 対処
wget: can't connect ... Connection refused api がまだ listen していない(depends_on は起動順のみ保証) 数秒待つ、または healthcheck + condition: service_healthy を使う
wget: bad address 'api' 別ネットワークに乗っていて名前解決できない、または typo サービス名の綴りを確認、docker network inspect で参加コンテナを確認
docker compose exec web wget: not found 使っているベースイメージに wget が入っていない nginx:alpine を使う、または curl が入ったイメージに変える
depends_on を書いたのに接続エラーが出る depends_on は起動順序のみで、readiness は保証しない service_healthy 条件か、アプリ側にリトライ処理を入れる
ポート 8080 が使われている 別コンテナや別アプリが使用中 docker ps --filter "publish=8080" で確認し停止、または別ポートに変更
docker compose down 後も <フォルダ名>_default ネットワークが残る 他のプロジェクトや停止していないコンテナが参加している docker network ls で確認し、不要なら docker network rm

depends_on は「順番」の制御であり、「準備完了」の制御ではない——この一点を理解しておくと、複数サービスを組んだときの接続エラーの大半に見当がつくようになります。

シェア