Dockerネットワークとコンテナ名で実現する複数Docker Compose間の接続方法

インフラ

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

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

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

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

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

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

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

frontend/docker-compose.yml

version: '3'
services:
  web:
    tty: true
    stdin_open: true
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/app
      - gem_data:/usr/local/bundle
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
+   networks:
+     - external.group
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_USER: 'webuser'
      MYSQL_PASSWORD: 'webpass'
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'docker_rails7_web_mysql8_boilerplate_development'
volumes:
  mysql_data:
  gem_data:
+ networks:
+  external.group:
+   external: true

backend/docker-compose.yml

version: '3'
services:
  api:
    tty: true
    stdin_open: true
    build: .
    ports:
      - '3001:3000'
    volumes:
      - .:/app
      - gem_data:/usr/local/bundle
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    container_name: sample-app-api
+   networks:
+     - external.group
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_USER: 'webuser'
      MYSQL_PASSWORD: 'webpass'
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'docker_rails7_api_mysql8_boilerplate_development'
volumes:
  mysql_data:
  gem_data:
+ networks:
+  external.group:
+   external: true

Docker Compose内のコンテナにも接続する場合は内部通信用のDockerネットワークを別途設定する

Docker Compose外へ通信するコンテナについて、Docker Compose内のコンテナにも通信する場合は内部通信用のDockerネットワークを定義する必要があります。
今回の場合ですとフロントエンド環境のwebコンテナ、バックエンド環境のapiコンテナに対して各dbコンテナに接続するためのDockerネットワークを定義します。

defaultというDockerネットワークを定義することで内部通信ができます。defaultが定義されていないとdbコンテナにアクセスできず、Mysql2::Error::ConnectionError: Unknown MySQL server host 'db'のようなDB接続エラーがDocker Composeの起動時に発生します。

frontend/docker-compose.yml

version: '3'
services:
  web:
    tty: true
    stdin_open: true
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/app
      - gem_data:/usr/local/bundle
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    networks:
      - external-api
+     - default
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_USER: 'webuser'
      MYSQL_PASSWORD: 'webpass'
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'docker_rails7_web_mysql8_boilerplate_development'
volumes:
  mysql_data:
  gem_data:
networks:
  external-api:
    external: true

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

backend/docker-compose.yml

version: '3'
services:
  api:
    tty: true
    stdin_open: true
    build: .
    ports:
      - '3001:3000'
    volumes:
      - .:/app
      - gem_data:/usr/local/bundle
    depends_on:
      - db
    command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    container_name: sample-app-api
    networks:
      - external.group
+     - default
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_USER: 'webuser'
      MYSQL_PASSWORD: 'webpass'
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'docker_rails7_api_mysql8_boilerplate_development'
volumes:
  mysql_data:
  gem_data:
networks:
  external.group:
    external: true

Dockerネットワーク経由でコンテナに通信する方法

Dockerネットワーク経由でDocker Compose外のコンテナに接続する方法は以下の通りです。

  • コンテナに割り振られたIPアドレスを直接指定する
  • サービス名を指定する
  • コンテナ名を指定する
  • 『サービス名.Dockerネットワーク名』を指定する
  • 『コンテナ名.Dockerネットワーク名』を指定する

動作確認

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

Dockerネットワークの作成

外部連携用のDockerネットワークはdocker-compose upで自動作成されないため、Docker起動前に作成する必要があります。

### Dockerネットワーク作成
$ docker network create external.group

### Dockerネットワーク一覧確認
$ docker network ls
-> external.group があればOK

事前にDockerネットワークを作成をしていない場合、ERROR: Network external.group declared as external, but could not be found. Please create the network manually using docker network create external.group and try again.のようなエラーがDocker Compose起動時に発生します。

アプリケーションの起動

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

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

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

$ docker network inspect external.group

[
    {
        "Name": "external.group",
        (略)
        "Containers": {
            # バックエンドのapiコンテナ
            "38f1e8abd2f8d7e451182d0af4c4b1d5ad46bf91df4f6591b78d6ef43afacc13": {
                "Name": "sample-app-api",
                "EndpointID": "94055f806552a03b7630693b0180f3a1748be62276075924f08e52cc955f2a49",
                "MacAddress": "02:42:c0:a8:c0:02",
                "IPv4Address": "192.168.192.2/20",
                "IPv6Address": ""
            },
            # フロントエンドのwebコンテナ
            "b85a290dfdd99a2dfc57cdcf8cfa846f43f43713fd990331a90cf8f0d32acded": {
                "Name": "docker-rails7-web-mysql8-boilerplate_web_1",
                "EndpointID": "42240d67c540c9bae6a93c09e2abd2cabb56f4c8c5f809a1b907103da55dafbd",
                "MacAddress": "02:42:c0:a8:c0:03",
                "IPv4Address": "192.168.192.3/20",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

コンテナ間での疎通確認

Dockerネットワーク経由でコンテナと通信する方法を試した結果は以下のようになります。
結果を見て分かる通り、各方法は同じIPアドレスを指します。

$ cd /path/to/frontend
$ docker-compose exec web /bin/bash

### サービス名を指定する方法
$ dig api +short
# +short: IPアドレスのみを表示する
192.168.192.2

### コンテナ名を指定する方法
$ dig sample-app-api +short
192.168.128.2

### 『サービス名.Dockerネットワーク名』を指定する方法
$ dig api.external.group +short
192.168.128.2

### 『コンテナ名.Dockerネットワーク名』を指定する方法
$ dig sample-app-api.external.group +short
192.168.128.2

webコンテナからapiコンテナにpingを実行して反応があればOKです。

$ cd /path/to/frontend
$ docker-compose exec web /bin/bash

### コンテナに割り振られたIPアドレスを直接指定する方法
$ ping 192.168.192.2

PING 192.168.192.2 (192.168.192.2) 56(84) bytes of data.
64 bytes from 192.168.192.2: icmp_seq=1 ttl=64 time=1.16 ms

### サービス名を指定する方法
$ ping api

PING api (192.168.192.2) 56(84) bytes of data.
64 bytes from sample-app-api.external.group (192.168.192.2): icmp_seq=1 ttl=64 time=0.093 ms

### コンテナ名を指定する方法
$ ping sample-app-api

PING sample-app-api (192.168.192.2) 56(84) bytes of data.
64 bytes from sample-app-api.external.group (192.168.192.2): icmp_seq=1 ttl=64 time=0.149 ms

### 『サービス名.Dockerネットワーク名』を指定する方法
$ ping api.external.group

PING api.external.group (192.168.192.2) 56(84) bytes of data.
64 bytes from sample-app-api.external.group (192.168.192.2): icmp_seq=1 ttl=64 time=0.066 ms

### 『コンテナ名.Dockerネットワーク名』を指定する方法
$ ping sample-app-api.external.group

PING sample-app-api.external.group (192.168.192.2) 56(84) bytes of data.
64 bytes from sample-app-api.external.group (192.168.192.2): icmp_seq=1 ttl=64 time=0.077 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'

### hostは『api』, 『sample-app-api』, 『api.external.group』, 『sample-app-api.external.group』でも可
> host = "192.168.192.2"
> response = Net::HTTP.get_response(URI.parse("http://#{host}:3000/events"))
> response.body

=> "[{\"id\":1,\"title\":\"sample event\",\"created_at\":\"2022-02-07T12:26:16.685Z\",\"updated_at\":\"2022-02-07T12:26:16.685Z\"}]"

なお、バックエンドのRailsが6系以上ですとホスト名でアプリケーションにアクセスした際にBlocked host: xxxというエラーが発生し、HTTPリクエストのレスポンスが#<Net::HTTPForbidden 403 Forbidden readbody=true>になる可能性があります。
403エラーになった場合はlocalhost以外のホスト名でローカル環境のRailsに接続する方法を参考にホスト名でRailsアプリケーションにアクセスできるようにしてください。

Dockerネットワークの削除方法

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

$ docker network rm external-api

さいごに

ローカル環境の場合は、Dockerネットワークを利用せずローカル経由で異なるDocker環境に接続する方法もあります。詳細解説はローカル環境の異なるDocker Compose間の通信をhost.docker.internalで解決するで紹介しています。

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

参考資料