tl;dr
-
/etc/docker/daemon.jsonには DNS 設定("dns": ['192.168.1.1']など) を書かない -
/etc/resolv.confには non-localhost な自分を指すアドレスを指定する- あるいは、
127.0.0.1と、自分以外の resolver も指定する
- あるいは、
解決したいこと
手元に resolver(unbound) を立てている状況で、docker build あるいは docker run で起動したコンテナ内では、外部の名前解決が可能でしたが、docker-compose up で起動したコンテナでは、外部の名前解決ができない状況でした。
この記事の前提
- Linux 環境
- Docker 1.10 以降
- docker-compose
手元 resolver を建てたい経緯
かなり有名ですが、nginx-proxy という label を書くだけで手軽に proxy をしてくれる便利な image があります。
services:
nginx-proxy:
image: jwilder/nginx-proxy
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
whoami:
image: jwilder/whoami
environment:
- VIRTUAL_HOST=whoami.local
(README より)
上記の docker-compose.yml の場合、 http://whoami.local でアクセスした時に nginx-proxy が HTTP Host Header を見て whoami コンテナに proxy してくれます。
これを local 環境で実行しようとする場合、 whoami.local が 127.0.0.1 で解決できる必要があります。
別解としては、ターミナルから curl --header 'HOST: whoami.local' 127.0.0.1 とするとアクセスだけなら可能ですが、ブラウザから見ようとすると Chrome の場合、Chrome Extension を入れたりしないといけません。
whoami.local を local 環境で 127.0.0.1 に解決するには、/etc/hosts に書くのが手っ取り早いですが、.local なサブドメインが今後大量に増えるかもしれない、あるいは別のドメインを使いたい場合、その都度 /etc/hosts に足すのは面倒です。
そこで .local をワイルドカードドメインとして 127.0.0.1 に解決したいという欲求が生まれます。
ちなみに /etc/hosts にはワイルドカードは書けないようです(自分が見つけれなかっただけかもしれません)。
手元 resolver で .local を解決する
例として手元に unbound を立てて、
server:
use-syslog: yes
username: "unbound"
directory: "/etc/unbound"
trust-anchor-file: trusted-key.key
interface: 0.0.0.0
interface: ::0
access-control: 0.0.0.0/0 allow
local-zone: "local" redirect
local-data: "local. IN A 127.0.0.1"
とすると、*.local が 127.0.0.1 で解決できるようになります。
手元環境でこの unbound を使う場合、ホスト側の /etc/resolv.conf に書く必要がありますが、
nameserver 127.0.0.1
とすると、 docker は デフォルトで ホストの /etc/resolv.conf を、コンテナの /etc/resolv.conf にマウントするため、コンテナ自身が参照されてしまいコンテナ内でうまく名前解決ができません。
無理やりな解決方法(間違い)
そこで、私は /etc/docker/daemon.json で、コンテナのサブネットを固定し、 dns を default gateway に向けることで無理やりホスト側 unbound を使っていました。
{
"bip": "192.168.1.5/24",
"fixed-cidr": "192.168.1.5/25",
"dns": ["192.168.1.1"]
}
docker build や docker-compose build は docker network の default bridge を使うため、この仕組みでうまく行ってしまいますが、docker-compose up で上げたコンテナ内部から外部にアクセスしようとすると名前解決ができません。
docker-compose だと名前解決が出来ない理由
https://docs.docker.com/v17.09/engine/userguide/networking/configure-dns/
Docker 1.10 以降、container optionとして name, net-alias, link などを使うと、コンテナ内の resolver は 127.0.0.11 に立ち上がる Docker Embedded DNS Server が内部的に使われるようになりました。
この際、/etc/docker/daemon.json に書いたfixed-ciderサブネットは使われず、ネットワークは別に払い出されるもの (user-defined brige network など) が割り当てられます。(手元では 172.17.0.0/8)
また、コンテナ内の /etc/resolv.conf には、ホストの /etc/resolv.conf は使われず 127.0.0.11 が書かることになります。
docker-compsoe を使う場合、(おそらく)内部的に docker コマンドとしての link や name が使われているため、docker-compose up で起動したコンテナとネットワークは、resolver として Docker Embedded DNS が使われ fixed-cider とは別のサブネットに配置されます。
ここで問題になるのが、/etc/docker/daemon.json に書いた dns: ["192.168.1.1"] の記述です。
この場合、Docker Embedded DNS Server は、自身と 192.168.1.1 の resolver が指定されることとなり、内部向けの名前解決は自分で出来ますが、外部の名前解決は 192.168.1.1 を使うため名前解決ができない状況が発生します。
また、/etc/docker/daemon.json で dns: ["192.168.1.1"] を指定しなかったとしても、/etc/resolv.conf には nameserver 127.0.0.1 と書かれているため、この場合でも外部の名前解決は出来ません。
In the absence of the --dns=IP_ADDRESS..., --dns-search=DOMAIN..., or --dns-opt=OPTION... options, Docker uses the /etc/resolv.conf of the host machine (where the docker daemon runs). While doing so the daemon filters out all localhost IP address nameserver entries from the host’s original file.
公式Doc に書かれていますが、
Note: If you need access to a host’s localhost resolver, you must modify your DNS service on the host to listen on a non-localhost address that is reachable from within the container.
localhost の resolver を使う場合は non-localhost なアドレスを指定する必要が有ります。
正しい解決方法
Docker Embedded DNS Server に、ホストの /etc/resolv.conf を読んでもらうために、dns, dns-search, dns-opt 相当のオプションは、/etc/docker/daemon.json から削除します。
その上で、/etc/resolv.conf の nameserver には
-
non-loopbackな自分のアドレスを書く -
127.0.0.1の loopback アドレス + それ以外の resolver アドレスを書く
の どちらかをする必要があります。