異なるDocker環境どうしをDockerネットワークで連携する方法

インフラ

フロントエンドとバックエンドが別々のDocker環境で管理されている場合など、異なるDocker環境どうしでアクセスをしたい時があります。

Dockerのネットワーク機能を利用することでコンテナ間の通信をカスタマイズできます。
今回はDockerネットワークを利用した、異なるDocker環境のコンテナ間通信の実装方法について紹介します。

(追記)
本記事で紹介する方法でも異なるDocker環境のコンテナ間通信は実現できますが、もう少しシンプルな実装方法を別記事で書きました。
詳細についてはDockerネットワークとコンテナ名で実現する複数Docker Compose間の接続方法をご覧になってください。

今回実現したいDocker環境について

異なるリポジトリで管理されたフロントエンドとバックエンドのDocker環境があります。

上記のDocker環境を連携させ、コンテナ間でAPI通信をできるようにすることが今回実現したいことです。

Docker Compose外へ通信するコンテナに対してexternalなDockerネットワークを設定する

フロントエンド環境のwebコンテナとバックエンド環境のapiコンテナを連携する場合は以下のようにします。
webコンテナとapiコンテナはexternal-apiというDockerネットワーク経由で通信することになります。

frontend/docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails6_mysql8
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
+    networks:
+      - external-api
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data_web:/var/lib/mysql
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_USER: 'webuser'
      MYSQL_PASSWORD: 'webpass'
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'rails6_mysql8_development'
 volumes:
   mysql_data_web:
+ networks:
+  external-api:
+    external: true

backend/docker-compose.yml

version: '3'
services:
  api: # Ruby on Railsが起動するコンテナ
    build: .
    ports:
      - '3001:3000' # localhostの3000ポートでアクセスできるようにする
    volumes:
      - .:/rails6_api_mysql8 # アプリケーションファイルの同期
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
+   networks:
+     - external-api
  db: # MySQLが起動するコンテナ
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql # データの永続化
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする。
    environment:
      MYSQL_USER: 'webuser'
      MYSQL_PASSWORD: 'webpass'
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'rails6_api_mysql8_development'
 volumes:
   mysql_data: # データボリュームの登録
+ networks:
+  external-api:
+    external: true

外部連携が不要なコンテナに対して独自のネットワークを設定する

Docker Compose内で通信をするDockerネットワークを作成します。

以下のようにすると、フロントエンド環境のwebコンテナとdbコンテナはinternal-dbというDockerネットワークで通信することになります。

frontend/docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails6_mysql8
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    networks:
      - external-api
+     - internal-db
  db:
    image: mysql:8.0.21
    (略)
+   networks:
+     - internal-db
volumes:
  mysql_data_web:
networks:
  external-api:
    external: true
+ internal-db:

同様にバックエンドのDocker Composeも修正します。

backend/docker-compose.yml

version: '3'
services:
  api: # Ruby on Railsが起動するコンテナ
    build: .
    ports:
      - '3001:3000' # localhostの3000ポートでアクセスできるようにする
    volumes:
      - .:/rails6_api_mysql8 # アプリケーションファイルの同期
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    networks:
      - external-api
+     - internal-db
  db: # MySQLが起動するコンテナ
    image: mysql:8.0.21
    (略)
+   networks:
+     - internal-db
volumes:
  mysql_data: # データボリュームの登録
networks:
  external-api:
    external: true
+ internal-db:

動作確認

異なるDocker環境どうしで通信ができるか確認をします。

Dockerネットワークの作成

externalなDockerネットワークはdocker-compose upで自動作成されないため、Docker起動前に作成しておく必要があります。

$ docker network create external-api

$ docker network ls
-> external-api があればOK

アプリケーションの起動

$ cd /path/to/frontend
$ docker-compose up

$ cd /path/to/backend
$ docker-compose up

docker network inspectでDockerネットワークの詳細がわかります。
external-apiネットワークにwebコンテナとapiコンテナが存在していればOKです。

$ docker network inspect external-api

[
    {
        "Name": "external-api",
        (略)
        "Containers": {
            // フロントエンドのwebコンテナ
            "06a39d2c3fb39f9807bb60c3d6181fa37e2a7c53b16c486d906da966e3bbf023": {
                "Name": "docker-rails6-mysql8_web_1",
                "EndpointID": "4124f872a642f3f08c4b01a99414570e87a5b1beb81fb2b3547bd688b3cce594",
                "MacAddress": "02:42:c0:a8:d0:02",
                "IPv4Address": "192.168.192.2/20",
                "IPv6Address": ""
            },
            // バックエンドのapiコンテナ
            "26ae6153a07325c9409fed41195c2aca10634a37ab781123d12ddf8adfcf511c": {
                "Name": "docker-rails6-api-mysql8_api_1",
                "EndpointID": "df0234c0cc59bc9a2e42d686d4506bd2c79e1c251f8b4416d0299c6aedeb5123",
                "MacAddress": "02:42:c0:a8:d0:03",
                "IPv4Address": "192.168.192.3/20",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

コンテナ間での疎通確認

docker inspectに記載されていたコンテナのIPアドレスに対してpingを実行して反応があればOKです。

### webコンテナからapiコンテナ(192.168.192.3)に対してpingを実行
$ cd /path/to/frontend

$ docker-compose exec web ping 192.168.192.3

PING 192.168.192.3 (192.168.192.3) 56(84) bytes of data.
64 bytes from 192.168.192.3: icmp_seq=1 ttl=64 time=0.087 ms
64 bytes from 192.168.192.3: icmp_seq=2 ttl=64 time=0.065 ms
64 bytes from 192.168.192.3: icmp_seq=3 ttl=64 time=0.061 ms

バックエンドで作成したWeb APIをフロントエンドから呼んでみる

バックエンドで実装したWeb APIをフロントエンドから呼び出せるのか確認してみます。サンプルコードはRails・Rubyです。

まずはバックエンド環境でWeb APIを実装します。

$ cd /path/to/backend

### Eventを操作する機能(モデル、ビュー、コントローラー)を一括作成
$ docker-compose exec api rails g scaffold event title:string

### eventsテーブルを作成
$ docker-compose exec api rails db:migrate

### rails consoleでeventsのレコードを作成
$ docker-compose exec api rails c
> event = Event.new(title: 'sample event')
> event.save

次にフロントエンド環境からバックエンドのAPIを呼びます。レスポンスが返ってくればOKです。

$ cd /path/to/frontend

### irbでバックエンドに対してHTTPリクエストを行う
$ docker-compose exec web irb
> require 'net/http'
> response = Net::HTTP.get_response(URI.parse("http://192.168.192.3:3000/events"))
> response.body

=> "[{\"id\":1,\"title\":\"sample event\",\"created_at\":\"2021-04-04T09:06:17.348Z\",\"updated_at\":\"2021-04-04T09:06:17.348Z\"}]"

docker networkの削除方法

externalなDockerネットワークの削除はdocker network rmで行います。

$ docker network rm external-api

参考: コンテナに割り振られるIPアドレスを固定する方法

docker network inspectで調べたIPアドレスを利用して通信する方法でもよいのですが、毎回IPアドレスを調べるのは面倒です。

コンテナに割り振られるIPアドレスを固定する場合は、以下のようにします。

### subnet, gatewayを指定してexternalなDockerネットワークを作成
$ docker network create external-api --subnet=192.168.192.0/20 --gateway=192.168.192.1

frontend/docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails6_mysql8
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    networks:
      # Dockerネットワークのサブネットに属する任意のIPを指定
+     external-api:
+       ipv4_address: 192.168.192.2
      internal-db:
  db:
    image: mysql:8.0.21
    (略)
    networks:
      - internal-db
volumes:
  mysql_data_web:
networks:
  external-api:
    external: true
  internal-db:

backend/docker-compose.yml

version: '3'
services:
  api: # Ruby on Railsが起動するコンテナ
    build: .
    ports:
      - '3001:3000' # localhostの3000ポートでアクセスできるようにする
    volumes:
      - .:/rails6_api_mysql8 # アプリケーションファイルの同期
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    networks:
      # Dockerネットワークのサブネットに属する任意のIPを指定
+     external-api:
+       ipv4_address: 192.168.192.3
      internal-db:
  db: # MySQLが起動するコンテナ
    image: mysql:8.0.21
    (略)
    networks:
      - internal-db
volumes:
  mysql_data: # データボリュームの登録
networks:
  external-api:
    external: true
  internal-db:

docker network inspectで確認すると、指定したIPアドレスがコンテナに割り当てられていることがわかります。

$ docker network inspect external-api

[
    {
        "Name": "external-api",
        (略)
        "Containers": {
            "4b30e4985e56a317a751fef74cabeb68e0c1be07a1af5ea2b7431f845ff817e4": {
                "Name": "docker-rails6-mysql8_web_1",
                "EndpointID": "29c156b1021dbfea785ce088848a247b2b1685be8403392a94d7fba6e54676f1",
                "MacAddress": "02:42:c0:a8:c0:03",
                // ↓ docker-composeで指定したIPアドレスになっている
                "IPv4Address": "192.168.192.2/20",
                "IPv6Address": ""
            },
            "6b7296dbf4636b3b287a9cd0302e4207fd9ddcbbd3e9ab7e039be82c162c0e05": {
                "Name": "docker-rails6-api-mysql8_api_1",
                "EndpointID": "025f62c11dbc00521e09d2e691d388399ffcb3bd6db90b95247b1af6431d54a6",
                "MacAddress": "02:42:c0:a8:c0:02",
                // ↓ docker-composeで指定したIPアドレスになっている
                "IPv4Address": "192.168.192.3/20",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

まとめ

Dockerネットワークを利用したDocker環境の連携方法
  1. Docker環境を連携するExternalなDockerネットワークを作成
  2. 作成したDockerネットワークを各docker-compose.ymlで定義
  3. IPアドレスを固定する場合はDockerネットワークのサブネットに属するIPを指定
  4. Docker環境内で連携するコンテナどうしにはExternalとは別のDockerネットワークを指定

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考