手元 resolver と Docker  Embedded DNS Server を共存する

# 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](https://github.com/jwilder/nginx-proxy) という label を書くだけで手軽に proxy をしてくれる便利な image があります。

```yaml:docker-compose.yml
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](https://github.com/jwilder/nginx-proxy/blob/master/README.md) より)

上記の `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 を立てて、

```yaml:/etc/unbound/unbound.conf
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` に書く必要がありますが、

```:/etc/resolv.conf
nameserver 127.0.0.1
```
とすると、 docker は デフォルトで ホストの `/etc/resolv.conf` を、コンテナの `/etc/resolv.conf` にマウントするため、コンテナ自身が参照されてしまいコンテナ内でうまく名前解決ができません。

# 無理やりな解決方法(間違い)

そこで、私は `/etc/docker/daemon.json` で、コンテナのサブネットを固定し、 dns を default gateway に向けることで無理やりホスト側 unbound を使っていました。

https://docs.docker.com/v17.09/engine/userguide/networking/default_network/custom-docker0/

```json:/etc/docker/daemon.json
{
  "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` には

1. `non-loopback` な自分のアドレスを書く
1. `127.0.0.1` の loopback アドレス + それ以外の resolver アドレスを書く

の どちらかをする必要があります。






# 参考
- [Unboundを使って内向きの名前解決を行う](https://www.usagi1975.com/12may161926/)
- [Customize the docker0 bridge](httnps://docs.docker.com/v17.09/engine/userguide/networking/default_network/custom-docker0/)
- [Embedded DNS server in user-defined networks](https://docs.docker.com/v17.09/engine/userguide/networking/configure-dns/)