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 アドレスを書く
の どちらかをする必要があります。