当主机使用 dnsmasq 并且 Google 的 DNS 服务器受到防火墙保护时,DNS 在 docker 容器中不工作?
DNS not working within docker containers when host uses dnsmasq and Google's DNS server are firewalled?
症状是:主机有正常的网络访问,但容器内的程序运行无法解析DNS名称(在进一步调查之前可能看起来是"can't access the network")。
$ 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的基础上加上ping
和host
的简单图片,网络不好放在这里很方便我们不能 apt install
这些工具)
问题是由于 docker 自动将 Google 的 public 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 的 public DNS 被某些防火墙规则过滤的网络上运行时显然不起作用。
发生这种情况的原因是:
- Docker 首先尝试在主机上和容器内配置相同的 DNS 服务器。
- 主机运行 dnsmasq,一项 DNS 缓存服务。 dnsmasq 充当 DNS 请求的代理,因此主机
/etc/resolve.conf
中明显的 DNS 服务器是 nameserver 127.0.1.1
,即 localhost.
- 主机的 dnsmasq 仅侦听来自本地主机的请求并阻止来自 docker 容器的请求。
- 由于在 docker 中使用
127.0.1.1
不起作用,docker 退回到 Google 的 public DNS,这也不起作用.
docker 容器中的 DNS 损坏可能有多种原因。这个问题(和答案)涵盖了以下情况:
- 使用了 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 的 public DNS 已过滤。 运行
host www.example.com 8.8.8.8
。如果失败或者超时,那么你就是这种情况。
在此配置中获得正确 DNS 配置的解决方案是什么?
一个残酷且不安全的解决方案是避免网络容器化,并在主机和容器上使用相同的网络。这是不安全的,因为这允许容器访问主机的所有网络资源,但如果您不需要这种隔离,这可能是可以接受的。
为此,只需将 --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
由于 dnsmasq
是问题所在,一种选择是在主机上禁用它。这可行,但会禁用主机上所有应用程序 运行ning 的 DNS 缓存,因此如果主机用于 docker.
以外的应用程序,这将是一个非常糟糕的主意
如果您确定要这样做,请卸载 dnsmasq
,例如在基于 Debian 的系统上,例如 Ubuntu、运行 apt remove dnsmasq
.
然后您可以检查容器中的 /etc/resolv.conf
是否指向主机使用的 DNS 服务器。
由于自动 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
选项在 docker run
时间设置 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,则可能会出现问题。
一个干净的解决方案是配置 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 的 public 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
一种方法是使用 user defined network for your container. In that case the container's /etc/resolv.conf
will have the nameserver 127.0.0.11
(a.k.a. the Docker's 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
, it will set up a custom network for your services automatically (with a file format v2+). Note, however, that while docker-compose
runs containers in a user-defined network, it still builds them in the default network. To use a custom network for builds you can specify the network
parameter in the build configuration (requires file format v3.4+).
我昨晚不得不处理这个问题,最终想起 docker 运行 有一组处理它的选项。我使用 --dns 来指定我希望容器使用的 DNS 服务器。像冠军一样工作,无需破解我的 docker 主机。域名和搜索后缀还有其他选项。
我的 docker 容器中的 DNS 解析器有问题。我尝试了很多不同的东西,最后,我发现 Hostgator 中的 VPS 默认没有安装 NetworkManager-tui (nmtui),我刚刚安装并重新启动它。
sudo yum install NetworkManager-tui
并使用默认 DNS 重新配置我的 resolv.conf
8.8.8.8
。
nano /etc/resolv.conf
症状是:主机有正常的网络访问,但容器内的程序运行无法解析DNS名称(在进一步调查之前可能看起来是"can't access the network")。
$ 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的基础上加上ping
和host
的简单图片,网络不好放在这里很方便我们不能 apt install
这些工具)
问题是由于 docker 自动将 Google 的 public 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 的 public DNS 被某些防火墙规则过滤的网络上运行时显然不起作用。
发生这种情况的原因是:
- Docker 首先尝试在主机上和容器内配置相同的 DNS 服务器。
- 主机运行 dnsmasq,一项 DNS 缓存服务。 dnsmasq 充当 DNS 请求的代理,因此主机
/etc/resolve.conf
中明显的 DNS 服务器是nameserver 127.0.1.1
,即 localhost. - 主机的 dnsmasq 仅侦听来自本地主机的请求并阻止来自 docker 容器的请求。
- 由于在 docker 中使用
127.0.1.1
不起作用,docker 退回到 Google 的 public DNS,这也不起作用.
docker 容器中的 DNS 损坏可能有多种原因。这个问题(和答案)涵盖了以下情况:
- 使用了 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 的 public DNS 已过滤。 运行
host www.example.com 8.8.8.8
。如果失败或者超时,那么你就是这种情况。
在此配置中获得正确 DNS 配置的解决方案是什么?
一个残酷且不安全的解决方案是避免网络容器化,并在主机和容器上使用相同的网络。这是不安全的,因为这允许容器访问主机的所有网络资源,但如果您不需要这种隔离,这可能是可以接受的。
为此,只需将 --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
由于 dnsmasq
是问题所在,一种选择是在主机上禁用它。这可行,但会禁用主机上所有应用程序 运行ning 的 DNS 缓存,因此如果主机用于 docker.
如果您确定要这样做,请卸载 dnsmasq
,例如在基于 Debian 的系统上,例如 Ubuntu、运行 apt remove dnsmasq
.
然后您可以检查容器中的 /etc/resolv.conf
是否指向主机使用的 DNS 服务器。
由于自动 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
选项在 docker run
时间设置 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,则可能会出现问题。
一个干净的解决方案是配置 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 的 public 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
一种方法是使用 user defined network for your container. In that case the container's /etc/resolv.conf
will have the nameserver 127.0.0.11
(a.k.a. the Docker's 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
, it will set up a custom network for your services automatically (with a file format v2+). Note, however, that while docker-compose
runs containers in a user-defined network, it still builds them in the default network. To use a custom network for builds you can specify the network
parameter in the build configuration (requires file format v3.4+).
我昨晚不得不处理这个问题,最终想起 docker 运行 有一组处理它的选项。我使用 --dns 来指定我希望容器使用的 DNS 服务器。像冠军一样工作,无需破解我的 docker 主机。域名和搜索后缀还有其他选项。
我的 docker 容器中的 DNS 解析器有问题。我尝试了很多不同的东西,最后,我发现 Hostgator 中的 VPS 默认没有安装 NetworkManager-tui (nmtui),我刚刚安装并重新启动它。
sudo yum install NetworkManager-tui
并使用默认 DNS 重新配置我的 resolv.conf
8.8.8.8
。
nano /etc/resolv.conf