当主机使用dnsmasq并且谷歌的DNS服务器被防火墙封锁时,Docker容器中的DNS无法工作?

43

症状是:主机可以正常访问网络,但在容器内运行的程序无法解析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,这也不起作用。

Docker容器中DNS无法正常工作可能有多种原因。这个问题(及答案)涉及以下情况:

在这种配置下获得适当的DNS配置的解决方案是什么?

7个回答

33

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

为此,您需要配置dnsmasq监听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

3
“172.17.0.1”是默认的Docker桥接网关吗?Docker安装应该会自动处理这个。 - dashesy
2
答案中写道:“172.17.0.1,即从docker内部访问主机的IP地址”。问题在于,默认情况下,172.17.0.1不会响应DNS请求(因为dnsmasq只监听本地接口),所以仅配置172.17.0.1是不够的,需要在dnsmasq方面进行一些配置。我猜测docker的开发人员和打包人员决定安装docker时不修改dnsmasq的配置。 - Matthieu Moy
1
“$ cat /etc/NetworkManager/dnsmasq.d/docker-bridge.conf” - 这里我不明白了。所谓“干净的解决方案”是指一个服务(dnsmasq)完全不知道其他服务(docker)将如何使用它。如果一个解决方案需要 dnsmasq 特别“知道” docker(正如一个文件名中带有“docker”的文件放在 dnsmasq.d/ 下),那么这就不是一个干净的解决方案。 - Szczepan Hołyszewski
如果您不喜欢名称包含“docker”的事实,可以将文件命名为以.conf结尾的任何其他名称。但无论如何,您都必须告诉dnsmasq在172.17.0.1上正确监听,因为来自docker的请求将被定向到此处。在这里,Docker不仅仅是在机器上运行的守护程序,它看起来像一个具有单独IP地址的独立机器,默认情况下,dnsmasq将拒绝其请求。 - Matthieu Moy
1
这个解决方案适用于哪个发行版?在Ubuntu上,/etc/NetworkManager目录不存在。 - Szczepan Hołyszewski
我已在Ubuntu上进行了测试。您可能没有安装NetworkManager,但我相信它在Ubuntu上是默认安装的。 - Matthieu Moy

4
一种残酷而不安全的解决方案是避免将网络容器化,并在主机和容器上使用相同的网络。这是不安全的,因为这会使容器访问主机的所有网络资源,但如果您不需要这种隔离,这可能是可接受的。
要这样做,只需在命令行中添加--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 - David R.
你尝试过这个选项吗:https://dev59.com/vFUL5IYBdhLWcg3wqpg7#50000642?在docker中你将无法获得DNS缓存,但是使用systemd-resolved应该可以解决问题(只需要更改查找真实DNS服务器的命令)。 - Matthieu Moy
nslookup 8.8.8.8 google.com => 我猜你的意思是 nslookup google.com 8.8.8.8?奇怪的是:如果这个命令可以运行,那么你可能不处于这个问题所描述的情况下。尝试在容器内外进行完全相同的 nslookuphost 查询,并查看它们之间的差异。 - Matthieu Moy
我遇到了这个问题,没有任何 DNS 配置解决它。但是你的解决方案很好用,非常感谢! - X99

3

一种方法是为您的容器使用 用户定义网络。 在这种情况下,容器的 /etc/resolv.conf 将具有名称服务器 127.0.0.11(也称为 Docker 的 内置 DNS 服务器),该服务器可以正确地将 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 在用户定义的网络中运行容器,但它仍会在默认网络中构建它们。要为构建使用自定义网络,您可以在 构建配置 中指定 network 参数(需要文件格式 v3.4+)。

2

昨晚我不得不处理这个问题,最终想起docker run有一组选项来处理它。我使用了--dns来指定容器要使用的DNS服务器。非常好用,无需修改我的docker主机。还有其他选项可以设置域名和搜索后缀。


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

你可以在 docker run 时使用 --dns选项设置 DNS:
$ 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,则可能会出现问题。

-1

我在我们的Docker容器中遇到了DNS解析器的问题。我尝试了很多不同的方法,最后发现我的Hostgator VPS默认没有安装NetworkManager-tui(nmtui),所以我只是安装并重新启动了它。

sudo yum install NetworkManager-tui

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

nano /etc/resolv.conf

-1

由于问题出在dnsmasq上,一种解决方案是在主机上禁用它。这样做可以解决问题,但会禁用主机上所有应用程序的DNS缓存,因此如果主机用于除docker之外的其他应用程序,则这是一个非常糟糕的想法。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接