複数の Docker Compose 環境でネットワークを共有する


2022年 06月 28日

こんにちは。SI部の r_maeda です。

先日、M1 チップの MacBook Pro を新しく会社から割り当ててもらいました。
これまで Rails アプリの開発には VirtualBox + Vagrant で作成した Linux VM を使うことが多かったのですが、現状 M1 Mac では VirtualBox を使うことができません。
これを機に、と Docker Compose を使って開発環境の仮想化を進めています。

そんな中で、具体的な設定の例や解説があまり出てこなかった、Docker Network に関する記事を書きました。
M1 Mac 以外でも利用できるものとなっていますので、ぜひ最後まで読んでいってください。

背景

今参画しているプロジェクトでは、1つの Web アプリケーションを構成する複数のサブシステムを取り扱っており、それぞれのサブシステムを個別の Git リポジトリで管理しています。

workdir/
   1st-system/
      .git/
      app/
      config/
      db/
      spec/
      Dockerfile.dev
      docker-compose.yaml
      ...
   2nd-system/
      .git/
      ...
   3rd-system/
      .git/
      ...
...

今回 Docker Compose で開発用の仮想環境を構築するにあたり、Dockerfile および docker-compose.yaml ファイルはリポジトリ単位で作成することにしました。
普段は「サブシステム単位でアプリコードを修正し、自動テストを実行する」事が多いため、「RDB/Redis など、自動テストを実行するために必要なサービス」が起動していれば十分であり、「全てのサブシステムの Docker コンテナを一度に立ち上げるような Compose 環境」を作っても、その起動待ちの時間のほうがネックとなるためです。

しかしながら、各サブシステムは別のサブシステムと HTTP 通信を行うことがあるため、時には複数サブシステムのコンテナを同時に起動して、実際に組み合わせて動作を確認したいこともあります。
そのための環境をローカルの開発環境でも用意できたら嬉しいな、というのが今回やりたいことです。

ユースケース

今回 Docker Compose を使って仮想化したいサブシステムは以下の2つです。

  • サブシステム1
    • SSO
    • CMS を利用できるコンテンツ管理者のログイン状態、コンテンツ管理者に付与されている CMS の操作権限等を管理する
  • サブシステム2
    • CMS
    • コンテンツ管理者が、アプリケーションデータの閲覧、操作を行う

これらの2つのサブシステムは、以下の通り組み合わせて利用します。

  • CMS を使いたいコンテンツ管理者はまず SSO にログインし、その後 CMS にアクセスする。
  • コンテンツ管理者は SSO からログアウトすれば、CMS の操作もできなくなる。

CMS は SSO に「コンテンツ管理者はログインしているか?」を問い合わせる必要があり、SSO は CMS に「コンテンツ管理者がログアウトした」ことを通知する必要があります。
つまり、SSO と CMS は相互にネットワーク通信を行う必要があります。

Docker Compose から Docker Network を利用する

デフォルトで作成される Docker Network

まずは何も考えずに、 SSO アプリを単体で起動するための docker-compose.yaml を書いてみましょう。

version: '3.8'

services:
  db:
    image: postgres:14
    environment:
      POSTGRES_USER: username
      POSTGRES_PASSWORD: password
    volumes:
      - pgdata:/var/lib/postgresql/data
  sso:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://username:password@db
    ports:
      - ${SSO_PORT:-3000}:3000

volumes:
  pgdata: {}

docker compose 環境のコンテナを起動します。

 % docker compose up -d
 ⠿ Network sso_default  Created                                                                                 0.0s
 ⠿ Container sso-db-1   Created                                                                                 0.0s
 ⠿ Container sso-sso-1  Created           

さて、ログを見てもらうと分かるように、sso_default という名前の Docker Network が作成されています。
(作成される Network, Container の prefix は、docker-compose.yaml が存在するディレクトリ名などによって変化します。)

そして、コンテナ起動後に Docker Network のメタデータを見ると、sso-db-1sso-sso-1 の 2つのコンテナが sso_default ネットワークに接続されていることが確認できます。

 % docker network inspect sso_default | jq '.[0].Containers'
{
  "106b4f5d41d939ea680f1e76a7512c784135cc0828c6ef40bf6f031e7b853284": {
    "Name": "sso-db-1",
    "EndpointID": "05cde234ffe13dc8621b7bb0ebe7d69eadfe57e18e1c3fde62f91ce2f26ad226",
    "MacAddress": "02:42:ac:19:00:02",
    "IPv4Address": "172.25.0.2/16",
    "IPv6Address": ""
  },
  "40f693f11f8a28d2c71c1040bd9752f53001c4049e21f6f9e4f16e660fe10df6": {
    "Name": "sso-sso-1",
    "EndpointID": "13ff389930dabdbf9724c403c03480258e86316108234350ca85d151fb781591",
    "MacAddress": "02:42:ac:19:00:03",
    "IPv4Address": "172.25.0.3/16",
    "IPv6Address": ""
  }
}

SSOコンテナと DBコンテナは同一の sso_default ネットワークに接続されているため、SSOコンテナから DBコンテナで起動している PostgreSQL にアクセスすることができます。

(補足)コンテナの FQDN

コンテナにアクセスするために必要な FQDN は、コンテナのメタデータから確認することができます。

 % docker container inspect sso-db-1 | jq '.[0].NetworkSettings.Networks.sso_default.Aliases'
[
  "sso-db-1",
  "db",
  "106b4f5d41d9"
]

デフォルトでは「コンテナID」「コンテナ名」「サービス名」が利用できますが、docker-compose.yaml 上で hostnamealiases を設定することで、これを変更することもできます。

Docker Network を明示的に作成する

さて、SSOコンテナから DBコンテナへのアクセスは、デフォルトで作成される Docker Network を利用していることが分かりました。

しかし、別の docker-compose.yaml で管理されている CMSコンテナから SSOコンテナへのアクセスを許可するためには、デフォルトで作成される Docker Network だけでは設定が十分ではありません。

そこで、明示的に Docker Network を作成しましょう。SSO 用の docker-compose.yaml を以下の通り変更します。

version: '3.8'

services:
  db:
    image: postgres:14
    environment:
      POSTGRES_USER: username
      POSTGRES_PASSWORD: password
    networks:
      - internal
    volumes:
      - pgdata:/var/lib/postgresql/data
  sso:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://username:password@db
    networks:
      - internal
      - external
    ports:
      - ${SSO_PORT:-3000}:3000

networks:
  internal:
    driver: bridge
    internal: true
  external:
    driver: bridge
    internal: false
volumes:
  pgdata: {}

追加箇所は以下の通りです。Docker Network を 2つ作成し、それぞれにコンテナを接続しています。

  • services.db.networks
  • services.sso.networks
  • networks.internal
  • networks.external

networks.internal

前述の sso_default ネットワークに代わり、SSOコンテナから DB コンテナにアクセスするために使うネットワークです。internal: true を設定することにより、外部との通信を制限しています。
(開発用途なのでそんなに神経質になる必要もありませんが、DB を無駄に公開ネットワークに置きたくないので設定しました)

networks.external

他の Docker コンテナを接続することで、SSO へのアクセスを許可するための公開ネットワークです。
現状このネットワークには SSO コンテナが接続しているのみですが、一旦はこれで大丈夫です。

補足: 作成される Docker Network の名前

作成されるネットワークの名前は {docker-compose.yaml があるディレクトリ名}_${docker-compose.yaml に書いた key} (今回の場合 sso_internal / sso_external)となりますが、name を指定することで任意の名前で作成することも可能です。

networks:
  external:
    driver: bridge
    internal: false
    name: custom_name_for_sso_external_network

なお、作成されたネットワークの名前は、docker compose up を実行した際のログや、docker network ls の実行結果等から確認できます。

外部で作成された Docker Network を参照する

続いて、CMS コンテナを sso_external ネットワークに接続しましょう。
CMS 用の docker-compose.yaml を以下の通り作成します。

version: '3.8'

services:
  db:
    image: postgres:14
    environment:
      POSTGRES_USER: username
      POSTGRES_PASSWORD: password
    networks:
      - internal
    volumes:
      - pgdata:/var/lib/postgresql/data
  cms:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://username:password@db
    networks:
      - internal
      - external
    ports:
      - ${CMS_PORT:-3001}:80

networks:
  internal:
    driver: bridge
    internal: true
  external:
    name: sso_external
    external: true
volumes:
  pgdata: {}

SSO と同じように

  • services.db.networks
  • services.cms.networks
  • networks.internal
  • networks.external

を設定しています。ここで注目してほしいのは、networks.external の設定です。

networks.external

networks:
  external:
    name: sso_external
    external: true

SSO では driverinternal: false を指定しましたが、CMS では nameexternal: true を指定しています。

違いは以下の通りです。

  • internal: false
    • 外部との通信が可能な Docker Network を作成する
  • external: true
    • すでに作成済みである Docker Network を利用する
    • 利用する Docker Network の name を、あわせて指定する必要がある

これで、sso_external ネットワークに SSO コンテナと CMS コンテナを接続することができました。

実際に動かしてみる

ダミーアプリ、docker-compose.yaml の作成

流石に業務アプリをここに書くわけにはいかないため、代わりに NGINX を使って簡単な Web アプリを作ります。
コンテナ間の通信の確認には、 curl を使います。

ダミーSSO

/ にアクセスすると Hello SSO! の文字列を返すだけのアプリケーションです。

sso/
   docker-compose.yaml
   public/
      index.html
version: '3.8'

services:
  sso:
    image: nginx:stable-alpine
    networks:
      - external
    ports:
      - ${SSO_PORT:-3000}:80
    volumes:
      - ./public:/usr/share/nginx/html:ro

networks:
  external:
    driver: bridge
    internal: false
Hello SSO!

ダミーCMS

こちらは / にアクセスすると Hello CMS! の文字列を返します。

cms/
   docker-compose.yaml
   public/
      index.html
version: '3.8'

services:
  cms:
    image: nginx:stable-alpine
    networks:
      - external
    ports:
      - ${CMS_PORT:-3001}:80
    volumes:
      - ./public:/usr/share/nginx/html:ro

networks:
  external:
    name: sso_external
    external: true
Hello CMS!

ダミーアプリの起動確認

ひとまずダミーの SSO を起動してみましょう。
問題なければ、ホストの Mac から http://localhost:3000/ にアクセスすることで Hello SSO! の文字列が表示されるはずです。

$ cd path/to/sso
$ docker compose up -d
$ curl http://localhost:3000/
Hello SSO!

次に、ダミーの CMS を起動します。
こちらは問題なければ、 http://localhost:3001/ から Hello CMS! の文字列が返ってくるはずです。

$ cd path/to/cms
$ docker compose up -d
$ curl http://localhost:3001/
Hello CMS!

Docker Container 間の通信

続いて Docker コンテナ間の通信ができることを見ていきましょう。
まずは CMS から SSO にアクセスできることを確認します。

$ cd path/to/sso
$ docker compose up -d sso # SSOコンテナの起動

$ cd path/to/cms
$ docker compose run --rm cms curl http://sso:80/ # CMSコンテナから SSOコンテナへのアクセス
Hello SSO!

今度は逆に、SSO から CMS にアクセスしてみます。

$ cd path/to/cms
$ docker compose up -d cms # CMSコンテナの起動

$ cd path/to/sso
$ docker compose run --rm sso curl http://cms:80/ # SSOコンテナから CMSコンテナへのアクセス
Hello CMS!

おめでとうございます!
別々の Docker Compose 環境に定義した、SSO と CMS の 2つのコンテナ間で相互に HTTP 通信ができました!

まとめ / 感想

  • Docker のネットワーク機能を使って、異なるリポジトリで管理している複数の Web アプリケーションを相互に通信させる方法について解説しました。
  • モノレポで複数アプリケーションを管理するのであれば、docker-compose.yaml をリポジトリルートに置けばいいので、こんな面倒なことはしなくていいですね。
    ただ、リポジトリの git log が膨大になるため、一長一短かなと思います。