当主机使用dnsmasq并且Google的DNS服务器被防火墙时,DNS无法在docker容器中工作?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当主机使用dnsmasq并且Google的DNS服务器被防火墙时,DNS无法在docker容器中工作?相关的知识,希望对你有一定的参考价值。

症状是:主机具有适当的网络访问权限,但在容器内运行的程序无法解析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

(码头图像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的DNS请求)。 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。
  • 谷歌的公共DNS已被过滤。运行host www.example.com 8.8.8.8。如果它失败或超时,那么你处于这种情况。

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

答案

一个干净的解决方案是配置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
另一答案

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

为此,只需将--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
另一答案

由于此处自动DNS发现有罪,您可以覆盖泊坞窗配置中的默认设置。

首先,获取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占位符。

您可以使用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配置。

或者,您可以在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,则可能会出现问题。

另一答案

一种方法是使用user defined network作为容器。在这种情况下,容器的/etc/resolv.conf将具有名称服务器127.0.0.11(a.k.a.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在用户定义的网络中运行容器,但它仍然在默认网络中构建它们。要为构建使用自定义网络,您可以在network中指定build configuration参数(需要文件格式v3.4+)。

另一答案

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

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

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

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

如何限制 dnsmasq 仅回复特定的客户端 DNS 请求?

centos7.3安装dnsmasq

搭建dnsmasq

DHCP实现

生产环境轻量级dns服务器dnsmasq搭建文档

Dnsmasq MacOS使用介绍