ちょっと話題の記事

Dockerのコンテナ間の名前解決方法が気になったので確認してみた

2021.09.04

AWS Fargateを利用することが最近多く、コンテナ間の名前解決にはECS Service Discoveryをよく利用しています。ECS Service Discoveryは平たく言えばRoute53を利用してコンテナ間の名前解決できる仕組みです。

ふと手元に見るとローカルでコンテナ起動しているときはコンテナ間の名前解決をどこで行っているのか?を今まで気にしたことがありませんでした。気にしたことがなかったことに気づけたことは幸いです。手を動かして確認してみましょう。

まとめ

  • Dockerはコンテナ間名前解決に利用できるService Discovery機能がある
  • コンテナが指定するDNSサーバはループバック用のアドレス範囲にある127.0.0.11
    • ユーザ定義のネットワークを使用している場合に限り利用できる機能
    • デフォルトのネットワーク(bridge)はService Discoveryをサポートしていない
  • コンテナ内にDNSサーバがあるわけではない
    • 実態はDockerデーモン内にDNSサーバがあり、レコードを管理をしている

コンテナからDockerデーモンにあるDocker DNS Serverへ問い合わせしている様子の図

下調べ

まず公式ドキュメントを確認します。大切なことはすべて書いてありました。

Docker デーモンは内蔵 DNS サーバを動かし、ユーザ定義ネットワーク上でコンテナがサービス・ディスカバリを自動的に行えるようにします。 コンテナから名前解決のリクエストがあれば、内部 DNS サーバを第一に使います。リクエストがあっても内部 DNS サーバが名前解決できなければ、外部の DNS サーバにコンテナからのリクエストを転送します。 割り当てできるのはコンテナの作成時だけです。内部 DNS サーバが到達可能なのは 127.0.0.11 のみであり、コンテナの resolv.conf に書かれます。 ユーザ定義ネットワーク上の内部 DNS サーバに関しては ユーザ定義ネットワーク用の内部 DNS サーバ をご覧ください。

デフォルトのネットワーク・ブリッジ上では、Docker は自動的なサービス・ディスカバリをサポートしていません

引用: Docker コンテナ・ネットワークの理解 — Docker-docs-ja 19.03 ドキュメント

Docker 1.10 では、docker デーモンに内蔵 DNS サーバを実装しました。これはコンテナ作成時の有効な 名前(name) もしくは ネット・エイリアス(net-alias) または リンク(link) の別名を元にしたサービス・ディスカバリを提供します。

引用: ユーザ定義ネットワーク用の内部 DNS サーバ

ここまでまとめるとコンテナが問い合わせするDNSサーバは127.0.0.11である。ループバックアドレスの範囲なのでコンテナ自体にDNSサーバを持たせるのかと思いきや、DNSサーバの実態はDockerデーモン上にある。

画像引用: Docker 概要 — Docker-docs-ja 1.12.RC2 ドキュメント

16-20ページのService Discoveryの説明は図で動作を確認できた数少ない資料でした。

確認してみた

項目 バージョン
Docker 20.10.8
docker-compose 1.29.2
macOS 10.15.7

準備

検証に利用したファイル類は以下に置いてあります。

bigmuramura/study-docker-local-network

AmazonLinux2コンテナにはdigやnslookupなどのツールのインストールを、Nginxコンテナはデフォルトのindex.htmlを差し替えただけです。

$ docker-compose up -d --build

AmazonLinux2コンテナ(inspect)と、Nginxコンテナ(web)が起動しました。

$ docker-compose ps
                Name                              Command               State   Ports
--------------------------------------------------------------------------------------
study-docker-local-network_inspect_1   /bin/bash                        Up
study-docker-local-network_web_1       /docker-entrypoint.sh ngin ...   Up      80/tcp

AmazonLinux2コンテナ

名前解決の確認ためAmazonLinux2コンテナへログインします。

$ docker-compose exec inspect bash

curlでNginxコンテナ(web)にアクセスしてみます。webのホスト名を名前解決してアクセスできています。

bash-4.2# curl http://web
<!DOCTYPE html>
<html lang="ja">

<head>
	<meta charset="utf-8">
	<title>Welcome to Abashiri</title>
</head>

<body>
	<h1>こんにちは、網走</h1>
</body>

</html>

pingwebのホスト名に対して通りました。

bash-4.2# ping -c 4 web
PING web (172.18.0.3) 56(84) bytes of data.
64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=1 ttl=64 time=0.180 ms
64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=2 ttl=64 time=0.167 ms
64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=3 ttl=64 time=0.210 ms
64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=4 ttl=64 time=0.134 ms

--- web ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3037ms
rtt min/avg/max/mdev = 0.134/0.172/0.210/0.031 ms

調査

引き続きAmazonLinux2コンテナ(inspect)上で進めます。

DNSサーバはドキュメント通り127.0.0.11でした。また、プライベートIPを引けています。

bash-4.2# nslookup web
Server:		127.0.0.11
Address:	127.0.0.11#53

Non-authoritative answer:
Name:	web
Address: 172.18.0.3

resolv.confの内容を確認すると、たしかに127.0.0.11が指定されています。

/etc/resolv.conf

bash-4.2# cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0

Dockerのネットワークを確認します。Service Discorveryがサポートされていないデフォルトネットワークのbridgeではなく、新規に作成されたstudy-docker-local-network_defaultがあります。

$ docker network ls
NETWORK ID     NAME                                 DRIVER    SCOPE
a6edd8127d60   bridge                               bridge    local
e0c54af7d81a   host                                 host      local
5cecf693c5a1   none                                 null      local
9e7f00b6fffe   study-docker-local-network_default   bridge    local

Note:
Dockerのネットワークの説明は以下のリンクがわかりやすかったです。

study-docker-local-network_defaultネットワークの詳細確認します。

$ docker network inspect study-docker-local-network_default

起動中のAmazonLinux2コンテナ(inspect)と、Nginxコンテナ(web)コンテナの情報を確認できました。

実行結果

[
    {
        "Name": "study-docker-local-network_default",
        "Id": "9e7f00b6fffe23950583b659af213a328738a6b16b6d2cdf3b27026df7ac47a0",
        "Created": "2021-09-04T01:45:00.780707704Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "1fc682bbcf689ce76feefdbbd033e0a294b0ad73ec409a90cf4621effad2c932": {
                "Name": "study-docker-local-network_web_1",
                "EndpointID": "c434855b3ad53be5cd6b57c9e64ffefb6431f2c66dc05001eea9205ca454db13",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "31f665f961bab7e6c4f1561172a0bca11f22e282781c229fc504194b2cfde96b": {
                "Name": "study-docker-local-network_inspect_1",
                "EndpointID": "58951e17713c75980251a45b5a8355ec33563ddfbb9aa081d09834f8ad3d5f30",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "study-docker-local-network",
            "com.docker.compose.version": "1.29.2"
        }
    }
]

ここまでは下調べの情報どうりです。

  • デフォルトではないネットワークの使用している
    • よって、名前解決できている
  • コンテナのDNSサーバ指定は127.0.0.11

レコード登録・削除

DockerデーモンにあるDNSサーバへのレコード登録、削除の挙動がわからないのでいろいろ試してみます。

Webコンテナを10台に増やします。Aレコードが追加されるはずです。

$ docker-compose up -d --scale web=10

web_*シリーズが増えました。名前解決するとどのような回答が返ってくるのでしょうか。

$ docker-compose ps
                Name                              Command               State   Ports
--------------------------------------------------------------------------------------
study-docker-local-network_inspect_1   /bin/bash                        Up
study-docker-local-network_web_1       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_10      /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_2       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_3       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_4       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_5       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_6       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_7       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_8       /docker-entrypoint.sh ngin ...   Up      80/tcp
study-docker-local-network_web_9       /docker-entrypoint.sh ngin ...   Up      80/tcp

複数値回答してくれました。10台すべてのプライベートIPが返ってきてます。コンテナ起動時にAレコードが追加されていることがわかりました。

# AmazonLinux2コンテナより
bash-4.2# dig web

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> web
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44132
;; flags: qr rd ra; QUERY: 1, ANSWER: 10, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;web.				IN	A

;; ANSWER SECTION:
web.			600	IN	A	172.18.0.12
web.			600	IN	A	172.18.0.4
web.			600	IN	A	172.18.0.9
web.			600	IN	A	172.18.0.7
web.			600	IN	A	172.18.0.11
web.			600	IN	A	172.18.0.5
web.			600	IN	A	172.18.0.10
web.			600	IN	A	172.18.0.3
web.			600	IN	A	172.18.0.6
web.			600	IN	A	172.18.0.8

;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Sat Sep 04 03:21:52 UTC 2021
;; MSG SIZE  rcvd: 211

Webコンテナにアクセスした時、リクエストは分散されるのか確認してみます。

curlを連打します。

# AmazonLinux2コンテナより
bash-4.2# curl http://web

Nginx(web)コンテナのアクセスログを確認します。

$ docker-compose logs -f

多少偏りは見られるものの1台に偏るということはありませんでした。curlコマンドからのアクセスだとランダムのように見えます。サンプル数が10件と少ないためあまり適切ではありませんが、web_3, 4, 8, 10は出現しませんでした。

出力結果

web_7      | 172.18.0.2 - - [04/Sep/2021:03:29:18 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_5      | 172.18.0.2 - - [04/Sep/2021:03:29:19 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_7      | 172.18.0.2 - - [04/Sep/2021:03:29:25 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_1      | 172.18.0.2 - - [04/Sep/2021:03:29:31 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_1      | 172.18.0.2 - - [04/Sep/2021:03:29:31 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_1      | 172.18.0.2 - - [04/Sep/2021:03:29:32 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_9      | 172.18.0.2 - - [04/Sep/2021:03:29:32 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_7      | 172.18.0.2 - - [04/Sep/2021:03:29:33 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_6      | 172.18.0.2 - - [04/Sep/2021:03:29:33 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
web_2      | 172.18.0.2 - - [04/Sep/2021:03:29:33 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"

次はコンテナ起動に追加されたAレコードはどのタイミング削除されるのか確認します。

Nginx(web)コンテナを次々と止めてみます。

$ docker stop study-docker-local-network_web_1

途中経過、返ってくるプライベートIPが減っています。

# AmazonLinux2(inspect)コンテナより
bash-4.2# dig web

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> web
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9234
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;web.				IN	A

;; ANSWER SECTION:
web.			600	IN	A	172.18.0.6
web.			600	IN	A	172.18.0.11
web.			600	IN	A	172.18.0.10
web.			600	IN	A	172.18.0.9

;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Sat Sep 04 03:36:51 UTC 2021
;; MSG SIZE  rcvd: 97

Nginx(web)コンテナをすべて落としました。

$ docker-compose ps
                Name                              Command               State    Ports
--------------------------------------------------------------------------------------
study-docker-local-network_inspect_1   /bin/bash                        Up
study-docker-local-network_web_1       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_10      /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_2       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_3       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_4       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_5       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_6       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_7       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_8       /docker-entrypoint.sh ngin ...   Exit 0
study-docker-local-network_web_9       /docker-entrypoint.sh ngin ...   Exit 0

プライベートIPは一切引けなくなりました。ANSWER SECTION:が存在していないですね。コンテナ停止と共に動的にAレコードが削除されていることがわかりました。

# AmazonLinux2(inspect)コンテナより
bash-4.2# dig web

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> web
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 21717
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;web.				IN	A

;; Query time: 49 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Sat Sep 04 03:38:24 UTC 2021
;; MSG SIZE  rcvd: 21

ネットワークの違い

冒頭の以下の引用文が気になったので違いを調べます。

デフォルトのネットワーク・ブリッジ上では、Docker は自動的なサービス・ディスカバリをサポートしていません

まずデフォルトのbridgeネットワークでAmazonLinux2コンテナ(inspect)を起動します。

$ docker run --rm -it study-docker-local-network_inspect bash

ループバック用のアドレス範囲にあるアドレスのDNSサーバ127.0.0.11が指定されていません。DNSサーバの指定に違いがあるんですね。

/etc/resolv.conf

bash-4.2# cat /etc/resolv.conf
# DNS requests are forwarded to the host. DHCP DNS options are ignored.
nameserver 192.168.65.5

次にユーザ定義のネットワーク(study-docker-local-network_default)で起動してみます。このネットワークは今までdocker-composeで使っていたものです。

$ docker run --rm -it --net=study-docker-local-network_default study-docker-local-network_inspect bash

127.0.0.11のDNSサーバが指定されています。

/etc/resolv.conf

bash-4.2# cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0

/etc/resove.confの内容に違いが見られることを確認できました。コンテナを起動するネットワークがデフォルト(bridge)か、ユーザ定義のネットワークかの違いでDNSサーバの指定が異なる。

おわりに

Dockerのコンテナ間通信のときのホスト名の名前解決はService Discoveryという仕組みを持っていた。ちなみにAWSのECSで使われるコンテナ間の名前解決の仕組みはECS Service Discoveryで名前が似ています。 ECS Service Discoveryはオートスケールやタスク数設定によるコンテナ増減に連動して、Route 53のレコードが自動的に書き換えてくれる。DockerのService Discoveryもコンテナ数の増減に連動して、DockerデーモンにあるDNSサーバのレコードを自動的に書き換えていた。 柔軟にコンテナの名前解決できる仕組みという意味では同じ印象です。

ECS Service Discoveryは今後も検証する機会がありそうだけど、ローカルネットワークをまた検証する機会はあまりなさそうです。気になったときにまた調べたいと思います。

細かい違いとしては複数値の回答数が異なる点には気がついたのですが、ECS Service Discoveryの仕様を把握できていなかったので改めてサービス検出を読み直す必要がありました。巡り巡ってECS Service Discoveryの勉強し直せて良い機会でした。

クライアントが複数値回答ルーティングを使用して DNS リクエストを行うと、Route 53 は DNS クエリに応答して特定のドメイン名の正常なレコードを最大 8 個ランダムに選択して返します。

複数値ルーティングポリシーとシンプルルーティングポリシーを理解する

残った疑問

127.0.0.11に問い合わせた際になぜ再帰的に名前解決する経路がわからなかったのだろうか。

bash-4.2# dig +trace google.com

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> +trace google.com
;; global options: +cmd
;; Received 17 bytes from 127.0.0.11#53(127.0.0.11) in 5 ms

参考