複数のdocker-compose間で通信する

異なるdocker-composeで動く環境に接続したくなったので調べてみました。

準備

まず、以下のようなdocker-compose.ymlで定義された2つの環境があるとします。

.  
├── app_a  
│   ├── app/...  
│   └── docker-compose.yml  
└── app_b  
    ├── app/...  
    └── docker-compose.yml
version: '3.7'
services:
app_a:
image: golang:latest
tty: true
db_a:
image: postgres:latest
version: '3.7'
services:
app_b:
image: golang:latest
tty: true
db_b:
image: postgres:latest

この状態で app_a のコンテナと app_b のコンテナで通信することを考えます。

そもそもdocker-composeのネットワークってどうなってるの?

先ほどのapp_aのdocker-composeを起動すると以下のようなログが出ます。

~/app_a$ docker-compose up -d  
Creating network "app_a_default" with the default driver  
Creating app_a_db_a_1  ... done  
Creating app_a_app_a_1 ... done

コンテナを立ち上げる前にapp_a_default というネットワークが作られています。このネットワークの詳細を見ると、以下のようになっています。

~/app_a$ docker network inspect app_a_default   
[  
    {  
        "Name": "app_a_default",  
        "Id": "2c3cc9e7edaa5f54371d12299eaa6e26e710c45d5baca8196eeae67054e5fc53",  
        "Created": "2020-02-01T11:53:37.0174152Z",  
        "Scope": "local",  
        "Driver": "bridge",  
        "EnableIPv6": false,  
        "IPAM": {  
            "Driver": "default",  
            "Options": null,  
            "Config": [  
                {  
                    "Subnet": "192.168.80.0/20",  
                    "Gateway": "192.168.80.1"  
                }  
            ]  
        },  
        "Internal": false,  
        "Attachable": true,  
        "Ingress": false,  
        "ConfigFrom": {  
            "Network": ""  
        },  
        "ConfigOnly": false,  
        "Containers": {  
            "0688a9b74b6dab1b2b85113f0bc535c585cb7bd48b659f0f00081f3d183ee310": {  
                "Name": "app_a_db_a_1",  
                "EndpointID": "5442fa856c1c3e6c81bb2e10fcb424ae10c22287c43a198c3bc7b8fdf2dd2fa6",  
                "MacAddress": "02:42:c0:a8:50:03",  
                "IPv4Address": "192.168.80.3/20",  
                "IPv6Address": ""  
            },  
            "a7d712117e7c243437269eb29f83f0ea453c5ba9201639218d9993f45c06e1bb": {  
                "Name": "app_a_app_a_1",  
                "EndpointID": "8cb0d595d775ed385bd9a6ff30d988631261cd0f3e634f98b38ace7432af0afd",  
                "MacAddress": "02:42:c0:a8:50:02",  
                "IPv4Address": "192.168.80.2/20",  
                "IPv6Address": ""  
            }  
        },  
        "Options": {},  
        "Labels": {  
            "com.docker.compose.network": "default",  
            "com.docker.compose.project": "app_a",  
            "com.docker.compose.version": "1.24.1"  
        }  
    }  
]

"Containers" という項目を見ると、2つのコンテナがネットワーク上にあるのが分かります。

このように、docker-compose up で起動すると、ネットワークを自動で作り、そのネットワークにコンテナがアタッチされた状態になります。
そのため、docker-composeを使用していると、自分で何も設定しなくてもコンテナ間で通信ができます。

Dockerのネットワークに関しては以下の記事が参考になりました。

参考訳:Docker コンテナ・ネットワークの理解 - Qiita
Docker container networkinghttp://docs.docker.com/engine/userguide/networking/dockernetworks/ Docker コンテナ・ネットワーク...

異なるdocker-compose間での通信

別のdocker-composeであろうと、同一のネットワーク上に存在するコンテナどうしなら通信ができます。

以下の記事を参考に設定をしてみました。

docker-compose で別の docker-compose.yml で作ったコンテナとリンクする (ネットワークを繋げる) - Qiita
バーチャルホストのように、1つのサーバーに複数のWebアプリを置いておきたい場合があります。その際に、nginxと各アプリにはそれぞれの docker-compose.yml を作って別々に管理したいと思います。 nginx (...

以下のようにdocker-compose.ymlを修正します。

version: '3.7'
services:
app_a:
image: golang:latest
tty: true
networks:
- default
- shared-network
db_a:
image: postgres:latest
networks:
shared-network:
external: true
version: '3.7'
services:
app_b:
image: golang:latest
tty: true
networks:
- default
- shared-network
db_b:
image: postgres:latest
networks:
shared-network:
external: true

それぞれのdocker-composeのapp_a,app_bの設定にnetworks という項目を設定しています。これは各コンテナに接続するネットワークを指定できます。この項目を何も設定しなければ、自動でdefaultのネットワークにセットされます。
app_a,app_bのnetworks に共通のネットワーク(shared-network)を追加することで、コンテナ間の通信が可能になります。
shared-network はdocker-composeで管理されていないので、networks の設定が必要です(services と同じ階層)。
external: true はdocker-composeで管理されていない外部のネットワーク、という意味です。

そして、docker-compose起動前に以下のコマンドを実行し、ネットワークを作成しておきます。

docker network create shared-network

これでshared-netowork というネットワークが作成されます。このネットワークを作成した状態で上の2つのdocker-composeを起動します。

~/app_a$ docker network create shared-network  
1eb174ca4efc48e20e8210a443d7b9c371fedb7b18bb6fa20ce34234da1bf7df

~/app_a$ docker-compose up -d  
Creating network "app_a_default" with the default driver  
Creating app_a_app_a_1 ... done  
Creating app_a_db_a_1  ... done~/app_a$ cd ../app_b/  

~/app_b$ docker-compose up -d  
Creating network "app_b_default" with the default driver  
Creating app_b_app_b_1 ... done  
Creating app_b_db_b_1  ... done

app_bのコンテナに入り、app_aに対してpingを打ってみます。

~/app_b$ docker-compose exec app_b bash

root@ec898649299c:/go# ping app_a  
PING app_a (192.168.96.2) 56(84) bytes of data.  
64 bytes from app_a_app_a_1.shared-network (192.168.96.2): icmp_seq=1 ttl=64 time=0.508 ms  
64 bytes from app_a_app_a_1.shared-network (192.168.96.2): icmp_seq=2 ttl=64 time=0.209 ms

きちんと通信できています。

ちなみに、app_bとdb_aは別のネットワークなので通信はできません。

root@ec898649299c:/go# ping db_a   
ping: db_a: Name or service not known

docker-compose外のネットワークの管理が面倒

これで通信自体はできますが、shared-network を作成せずにdocker-compose up するとエラーになったり、docker-compose downしてもshared-network は消えないので残ったままになったりと、色々と面倒です。
そこで、Makefileにそのあたりの設定を追加することにしました。
(たまたま自分が今のプロジェクトでMakefileを使っていたのでMakefileでやりましたが、普通のシェルスクリプトでも良いと思います)

Makefileに以下を追加します。

NETWORK_NAME = shared-network
up:
@if [ -z "`docker network ls | grep $(NETWORK_NAME)`" ]; then docker network create $(NETWORK_NAME); fi
docker-compose up -d
down:
docker-compose down
@if [ -n "`docker network inspect $(NETWORK_NAME) | grep \"\\"Containers\\": {}\"`" ]; then docker network rm $(NETWORK_NAME); fi

これで make up を実行すると、shared-network が作成されていなければ作成してからdocker-compose up されます。
また、make down を実行すると、docker-compose down した後にshared-network 上のコンテナを確認し、1つもなければshared-network を削除します。

`docker network inspect $(NETWORK\_NAME) | grep \"\\"Containers\\": {}\"`

のあたりが若干強引なので、何かいい方法があると良いのですが…

まとめ

以下のような構成だった場合、

.  
├── app_a  
│   ├── app/...  
│   ├── Makefile  
│   └── docker-compose.yml  
└── app_b  
    ├── app/...  
    ├── Makefile  
    └── docker-compose.yml

以下のように設定すると、異なるdocker-composeのコンテナ間で通信ができます。

version: '3.7'
services:
app_a:
image: golang:latest
tty: true
networks:
- default
- shared-network
db_a:
image: postgres:latest
networks:
shared-network:
external: true
version: '3.7'
services:
app_b:
image: golang:latest
tty: true
networks:
- default
- shared-network
db_b:
image: postgres:latest
networks:
shared-network:
external: true
NETWORK_NAME = shared-network
up:
@if [ -z "`docker network ls | grep $(NETWORK_NAME)`" ]; then docker network create $(NETWORK_NAME); fi
docker-compose up -d
down:
docker-compose down
@if [ -n "`docker network inspect $(NETWORK_NAME) | grep \"\\"Containers\\": {}\"`" ]; then docker network rm $(NETWORK_NAME); fi