CLOVER🍀

That was when it all began.

Docker Composeを使った時に、コンテナ間で名前解決したい

これは、なにをしたくて書いたもの?

ふだんDocker Composeをそんなに使っていないのですが、たまに使うとコンテナ間で名前解決するのにどうすればいいのかよくわからなく
なるので、ちゃんと見ておくことにしました。

参照ページ

主に、以下のページを見ています。

Networking in Compose | Docker Documentation

Compose specification | Docker Documentation

環境

今回の環境は、こちら。

Docker Engine。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.23
 API version:       1.41
 Go version:        go1.18.10
 Git commit:        7155243
 Built:             Thu Jan 19 17:45:08 2023
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.23
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.10
  Git commit:       6051f14
  Built:            Thu Jan 19 17:42:57 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.15
  GitCommit:        5b842e528e99d4d4c1686467debf2bd4b88ecd86
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker Compose。

$ docker compose version
Docker Compose version v2.15.1

確認する構成

今回確認を行うための構成は、以下から始めることにしました。

compose.yaml

services:
  web:
    image: nginx:1.23.3

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null

  cache:
    image: redis:7.0.8

nginxとRedisがあり、間にあるUbuntu LinuxからnginxやRedisにアクセスする、というシナリオで考えてみたいと思います。

以降は、docker compose upで各コンテナを起動した後に

$ docker compose up

Ubuntu Linuxにログインして、動作確認に必要なパッケージをインストールしたものとして書いていきます。

$ docker compose exec -it task bash
# apt update && apt install -y curl redis-tools bind9-dnsutils

もっともシンプルなパターン

実は、なにもせずとも各コンテナにアクセスできます。

Each container can now look up the hostname web or db and get back the appropriate container’s IP address.

Networking in Compose | Docker Documentation

ネットワークを作成して、servicesに定義した名前でアクセスできるようになるみたいです。

When you run docker compose up, the following happens:

  1. A network called myapp_default is created.
  2. A container is created using web’s configuration. It joins the network myapp_default under the name web.
  3. A container is created using db’s configuration. It joins the network myapp_default under the name db.

起動。

$ docker compose up

確認。

# curl web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


# redis-cli -h cache
cache:6379>

確かにアクセスできます。

# dig +short web
172.18.0.3


# dig +short cache
172.18.0.4

なお、/etc/hostsに書かれているというわけではなさそうです。

# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2      619f085a21ac

サービスあたりのコンテナ数を増やしてみる

サービスあたりのコンテナ数を増やしてみると、どうなるのでしょう?

今度は、nginxおよびRedisを3つにしてみます。

$ docker compose up --scale web=3 --scale cache=3

確認してみます。

# curl web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


# redis-cli -h cache
cache:6379>

依然としてアクセスできますね、どうなっているんでしょう?

# dig +short web
172.18.0.5
172.18.0.7
172.18.0.2


# dig +short cache
172.18.0.6
172.18.0.8
172.18.0.4

コンテナ数だけIPアドレスが返ってくるようになっていますね。

networks+aliasesでエイリアスを定義する

networksとaliasesを使うことで、エイリアスも定義できます。

Compose specification / Services top-level element / networks

たとえば、nginxとredisというエイリアスを定義してみます。ネットワークは、デフォルトネットワークとしました。

compose.yaml

services:
  web:
    image: nginx:1.23.3
    networks:
      default:
        aliases:
          - nginx

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null

  cache:
    image: redis:7.0.8
    networks:
      default:
        aliases:
          - redis

コンテナ数は増やしたまま起動しましょう。

$ docker compose up --scale web=3 --scale cache=3

サービス名でも、エイリアスでも名前解決できますね。

# dig +short web
172.18.0.5
172.18.0.3
172.18.0.7


# dig +short nginx
172.18.0.5
172.18.0.3
172.18.0.7


# dig +short cache
172.18.0.8
172.18.0.6
172.18.0.4


# dig +short redis
172.18.0.4
172.18.0.6
172.18.0.8

linksも使うことができます。

Compose specification / Services top-level element / links

以下のように、linksに[サービス名]:[エイリアス]または[サービス名]で定義します。サービス名のみを指定した場合は、サービス名が
そのままエイリアスになるようです。

compose.yaml

services:
  web:
    image: nginx:1.23.3

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    links:
      - "web:nginx"
      - "cache"

  cache:
    image: redis:7.0.8

コンテナ数は増やして起動。

$ docker compose up --scale web=3 --scale cache=3

確認。

# dig +short web
172.18.0.7
172.18.0.5
172.18.0.3


# dig +short nginx
172.18.0.7


# dig +short cache
172.18.0.6

動きが変わりましたね。エイリアスでアクセスすると、複数のコンテナがある場合はひとつのIPアドレスに収束するようです。

そもそも、linksの説明にサービス間の通信をしたいのであれば必要ない、と書かれていますからね。

Links are not required to enable services to communicate - when no specific network configuration is set, any service MUST be able to reach any other service at that service’s name on the default network.

Compose specification / Services top-level element / links

名前解決の目的で、linksは使わない方が良さそうです。

また、linksにはコンテナ間に依存関係を持ち込む効果もあるようですが、これはdepends_onでよいでしょう。

Links also express implicit dependency between services in the same way as depends_on, so they determine the order of service startup.

複数のネットワークを定義して、通信を区分けする

最後は、このパターンを試してみたいと思います。

Networking in Compose / Specify custom networks

ここまではデフォルトネットワークを使用していましたが、独自にネットワークを構成して、サービスがどのネットワークに属するかで
通信を区切ることもできるようです。

たとえば、2つのネットワークを定義してみます。

compose.yaml

services:
  web:
    image: nginx:1.23.3
    networks:
      - frontend

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    networks:
      - frontend
      - backend

  cache:
    image: redis:7.0.8
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

Ubuntu Linuxは、どちらのネットワークにも属しています。

起動。

$ docker compose up --scale web=3 --scale cache=3

確認。

# dig +short web
172.20.0.3
172.20.0.5
172.20.0.4


# dig +short cache
172.19.0.4
172.19.0.5
172.19.0.2

どちらのサービスも参照できていますね。

では、次はUbuntu LinuxはRedisが属するネットワークには含めないようにしてみます。

compose.yaml

services:
  web:
    image: nginx:1.23.3
    networks:
      - frontend

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    networks:
      - frontend

  cache:
    image: redis:7.0.8
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

この部分ですね。

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    networks:
      - frontend

すると、Redisへの名前解決ができなくなります。

# dig +short web
172.20.0.3
172.20.0.4
172.20.0.5


# dig +short store

こんなところでしょうか。

まとめ

Docker Composeを使った時に、コンテナ間で名前解決するバリエーションについて見てみました。

このあたりはほとんどわかっていなかったのでで、どういうバリエーションがあるか、その挙動について確認できたので良かったかなと
思います。

デフォルトだと、なにもせずにサービス名で名前解決できるんですね。