当主机使用 dnsmasq 并且 Google 的 DNS 服务器有防火墙时,DNS 在 docker 容器中不起作用?

Posted

技术标签:

【中文标题】当主机使用 dnsmasq 并且 Google 的 DNS 服务器有防火墙时,DNS 在 docker 容器中不起作用?【英文标题】:DNS not working within docker containers when host uses dnsmasq and Google's DNS server are firewalled? 【发布时间】:2018-10-04 12:05:13 【问题描述】:

症状是:宿主机有正常的网络访问权限,但容器内运行的程序无法解析 DNS 名称(在进一步调查之前可能看起来是“无法访问网络”)。

$ sudo docker run -ti mmoy/ubuntu-netutils /bin/bash
root@082bd4ead733:/# ping www.example.com
... nothing happens (timeout) ... ^C
root@082bd4ead733:/# host www.example.com
... nothing happens (timeout) ... ^C

(docker镜像mmoy/ubuntu-netutils是一个基于Ubuntu的简单镜像,包含pinghost,这里很方便,因为网络坏了我们不能apt install这些工具)

问题在于 docker 自动将 Google 的公共 DNS 配置为容器内的 DNS 服务器:

root@082bd4ead733:/# cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN

nameserver 8.8.8.8
nameserver 8.8.4.4

这仅适用于许多配置,但当主机运行在 Google 的公共 DNS 被某些防火墙规则过滤的网络上时显然不起作用。

发生这种情况的原因是:

Docker 首先尝试在主机和容器内配置相同的 DNS 服务器。 主机运行dnsmasq,一个DNS缓存服务。 dnsmasq 充当 DNS 请求的代理,因此主机 /etc/resolve.conf 中的明显 DNS 服务器是 nameserver 127.0.1.1,即 localhost。 主机的 dnsmasq 仅侦听来自 localhost 的请求并阻止来自 docker 容器的请求。 由于在 docker 中使用 127.0.1.1 不起作用,docker 退回到 Google 的公共 DNS,这也不起作用。

DNS 在 docker 容器中损坏可能有多种原因。这个问题(和答案)涵盖了以下情况:

使用了 dnsmasq。要检查是否是这种情况: 在主机上运行ps -e | grep dnsmasq。如果输出为空,则说明您没有运行 dnsmasq。 检查主机的resolv.conf,它可能包含类似nameserver 127.0.1.1 的条目。如果它包含nameserver 127.0.0.53,您可能正在运行systemd-resolved 而不是dnsmasq。如果是这样,您将无法使用将 DNS 请求转发到 dnsmasq 的解决方案(使用 listen-address=172.17.0.1 的解决方案)。 systemd-resolved hardcodes the fact that it listens only on the 'lo' interface hence there's no easy way to adapt this solution。以下其他答案将适用于 systemd-resolved。 Google 的公共 DNS 已被过滤。运行host www.example.com 8.8.8.8。如果它失败或超时,那么您就处于这种情况。

在此配置中获得正确 DNS 配置的解决方案是什么?

【问题讨论】:

【参考方案1】:

由于自动 DNS 发现在这里是有罪的,你可以覆盖 docker 配置中的默认设置。

首先,获取 dnsmasq 正在使用的 DNS 服务器的 IP,例如:

$ sudo kill -USR1 `pidof dnsmasq`
$ sudo tail /var/log/syslog 
[...]
Apr 24 13:20:19 host dnsmasq[2537]: server xx.yy.zz.tt1#53: queries sent 0, retried or failed 0
Apr 24 13:20:19 host dnsmasq[2537]: server xx.yy.zz.tt2#53: queries sent 0, retried or failed 0

IP 地址对应于上面的xx.yy.zz.tt 占位符。

或者,如果您的系统使用 systemd-resolve 而不是 dnsmasq,请运行:

$ resolvectl status | grep 'Current DNS'
Current DNS Server: xx.yy.zz.tt

您可以使用 --dns 选项将 DNS 设置为 docker run 时间:

$ sudo docker run --dns xx.yy.zz.tt1 --dns xx.yy.zz.tt2 -ti mmoy/ubuntu-netutils bash
root@6c5d08df5dfd:/# ping www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=54 time=86.6 ms
64 bytes from 93.184.216.34: icmp_seq=2 ttl=54 time=86.6 ms

此解决方案的一个优点是不涉及配置文件,因此不会因特定配置而忘记配置并在以后遇到麻烦:当且仅当您输入此 DNS 配置时--dns 选项。

一个缺点是您不会在容器中获得任何 DNS 缓存,因此 DNS 解析会更慢。

或者,您可以在 Docker 的配置文件 /etc/docker/daemon.json 中永久设置它(如果不存在,则在主机上创建它):

$ cat /etc/docker/daemon.json

    "dns": ["xx.yy.zz.tt1", "xx.yy.zz.tt2"]

您需要重新启动 docker 守护程序以将 daemon.json 文件考虑在内:

sudo service docker restart

然后你可以检查配置:

$ sudo docker run -ti mmoy/ubuntu-netutils bash
root@56c74d3bd94b:/# cat /etc/resolv.conf 
nameserver xx.yy.zz.tt1
nameserver xx.yy.zz.tt2
root@56c74d3bd94b:/# ping www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=54 time=86.5 ms

请注意,这会将 DNS IP 硬编码到您的配置文件中。如果您的计算机是连接到不同网络的笔记本电脑,则强烈建议不要这样做,如果您的互联网服务提供商更改 DNS 服务器的 IP,则可能会出现问题。

【讨论】:

【参考方案2】:

我的 docker 容器中的 DNS 解析器出现问题。我尝试了很多不同的东西,最后,我发现我在 Hostgator 中的 VPS 没有默认安装 NetworkManager-tui (nmtui),我刚刚安装并重新启动它

sudo yum install NetworkManager-tui

并将我的 resolv.conf 重新配置为默认 DNS 为 8.8.8.8

nano /etc/resolv.conf

【讨论】:

【参考方案3】:

我昨晚刚刚处理了这个问题,最终想起了 docker run 有一组处理它的选项。我使用 --dns 来指定我希望容器使用的 DNS 服务器。像冠军一样工作,无需破解我的 docker 主机。域名和搜索后缀还有其他选项。

【讨论】:

【参考方案4】:

一种方法是为您的容器使用user defined network。在这种情况下,容器的 /etc/resolv.conf 将拥有名称服务器 127.0.0.11(也称为 Docker 的 embedded DNS server),它可以正确地将 DNS 请求转发到主机的环回地址。

$ cat /etc/resolv.conf
nameserver 127.0.0.1
$ docker run --rm alpine cat /etc/resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4
$ docker network create demo
557079c79ddf6be7d6def935fa0c1c3c8290a0db4649c4679b84f6363e3dd9a0
$ docker run --rm --net demo alpine cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0    

如果您使用docker-compose,它将自动为您的服务设置自定义网络(文件格式为v2+)。但是请注意,虽然docker-compose 在用户定义的网络中运行容器,但它仍然在默认网络 中构建它们。要使用自定义网络进行构建,您可以在build configuration 中指定network 参数(需要文件格式v3.4+)。

【讨论】:

【参考方案5】:

一个干净的解决方案是配置 docker+dnsmasq,以便将来自 docker 容器的 DNS 请求转发到主机上运行的 dnsmasq 守护进程。

为此,您需要configure dnsmasq to listen to the network interface used by docker,通过添加文件/etc/NetworkManager/dnsmasq.d/docker-bridge.conf

$ cat /etc/NetworkManager/dnsmasq.d/docker-bridge.conf
listen-address=172.17.0.1

然后重新启动网络管理器以考虑配置文件:

sudo service network-manager restart

完成此操作后,您可以将 172.17.0.1(即 docker 中的主机 IP 地址)添加到 DNS 服务器列表中。这可以使用命令行来完成:

$ sudo docker run -ti --dns 172.17.0.1 mmoy/ubuntu-netutils bash
root@7805c7d153cc:/# ping www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=54 time=86.6 ms

...或者通过docker的配置文件/etc/docker/daemon.json(不存在就创建):

$ cat /etc/docker/daemon.json                      

  "dns": [
    "172.17.0.1",
        "8.8.8.8",
        "8.8.4.4"
  ]

(如果 dnsmasq 失败,这将退回到 Google 的公共 DNS)

您需要重新启动 docker 以考虑配置文件:

sudo service docker restart

然后就可以照常使用docker了:

$ sudo docker run -ti mmoy/ubuntu-netutils bash
root@344a983908cb:/# ping www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=54 time=86.3 ms

【讨论】:

什么是“172.17.0.1”?默认码头桥网关? docker install 应该自动处理这个 答案中写着:“172.17.0.1,即来自 docker 的主机 IP 地址”。问题是172.17.0.1默认不回复DNS请求(因为dnsmasq只监听本地接口),所以仅仅配置172.17.0.1是不够的,需要在dnsmasq端进行一些配置。我猜 docker 的开发者和打包者决定安装 docker 不应该修改 dnsmasq 的配置。 $ cat /etc/NetworkManager/dnsmasq.d/docker-bridge.conf - 你把我弄丢了。 “干净的解决方案”是一种服务(dnsmasq)完全不知道其他服务(docker)将使用它。如果解决方案要求 dnsmasq 专门“了解” docker(如名称中带有“docker”的文件在 dnsmasq.d/ 下所证明的那样),那么它不是一个干净的解决方案。 如果您不喜欢名称中包含“docker”这一事实,您可以为文件指定以.conf 结尾的任何其他名称。但无论如何,你必须告诉 dnsmasq 在 172.17.0.1 上正确监听,因为这是来自 docker 的请求将被定向的地方。这里的 Docker 不仅仅是一个运行在机器上的守护进程,它看起来像是一台具有单独 IP 地址的独立机器,默认情况下 dnsmasq 会拒绝它的请求。 此解决方案适用于什么分布?在 Ubuntu 上,目录 /etc/NetworkManager 不存在。【参考方案6】:

由于dnsmasq 是问题所在,一种选择是在主机上禁用它。这可行,但会禁用主机上运行的所有应用程序的 DNS 缓存,因此如果主机用于 docker 以外的应用程序,这是一个非常糟糕的主意。

如果您确定要这样做,请卸载 dnsmasq,例如在 Ubuntu 等基于 Debian 的系统上,运行 apt remove dnsmasq

然后您可以检查容器内的/etc/resolv.conf 是否指向主机使用的 DNS 服务器。

【讨论】:

【参考方案7】:

一个残酷且不安全的解决方案是避免网络容器化,并在主机和容器上使用相同的网络。这是不安全的,因为这使容器可以访问主机的所有网络资源,但如果您不需要这种隔离,这可能是可以接受的。

为此,只需在命令行中添加--network host,例如

$ sudo docker run -ti --network host mmoy/ubuntu-netutils /bin/bash
root@ubuntu1604:/# ping www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34: icmp_seq=1 ttl=55 time=86.5 ms
64 bytes from 93.184.216.34: icmp_seq=2 ttl=55 time=86.5 ms

【讨论】:

有没有比这更好的方法来解决使用 systemd-resolved 的系统上的问题?我在 Ubuntu 18.04 上。我确实尝试了自定义桥接网络,但它没有用。 docker network create -d bridge testnet 然后 docker run -it --network=testnet debian:stretch bash 然后 apt update 失败。哦对了,iptables 也被刷新了,我的本地机器可以nslookup 8.8.8.8 google.com 就好了。 您是否尝试过此选项:***.com/questions/49998099/…?您不会在 docker 中获得 DNS 缓存,但这应该与 systemd-resolved 一起使用(只有查找真正 DNS 服务器的命令应该更改)。 nslookup 8.8.8.8 google.com => 我猜你的意思是nslookup google.com 8.8.8.8?那是奇怪的部分:如果这有效,那么您可能不在此问题所描述的情况下。在容器内外尝试完全相同的nslookuphost 查询,看看有什么区别。 我遇到了这个问题,没有任何 DNS 配置可以解决它。但是您的解决方案运行良好,非常感谢!

以上是关于当主机使用 dnsmasq 并且 Google 的 DNS 服务器有防火墙时,DNS 在 docker 容器中不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

无法使用nginx-proxy和dnsmasq在带有docker-compose的容器之间访问虚拟主机

dnsmasq 设置自动获取主机名

Dnsmasq简单应用

dnsmasq+nginx实现地址转发

DHCP实现

DNSmasq介绍