如何让Docker容器在主机上访问dnsmasq本地DNS解析器?

24
有很多种方法可以让Docker容器在DNS设置方面出现混乱(只需搜索SO或更广泛的互联网即可了解),其中常见的解决方法之一是:
  1. 在主机系统上设置dnsmasq作为本地DNS解析器
  2. 将其绑定到docker0网络接口上
  3. 配置Docker使用docker0 IP地址进行DNS解析
然而,在许多现代Linux系统上天真地尝试应用此解决方法会使您陷入Linux网络和进程管理复杂性的兔子洞中,因为systemd向您保证dnsmasq未运行,但netstat告诉您它正在运行,并且实际尝试启动dnsmasq时失败并抱怨端口53已经被占用。
那么,如何可靠地让容器访问在主机上运行的本地解析器,即使系统默认已经有一个正在运行呢?

这听起来像是一种陈述,甚至是抱怨,而不是一个问题。你想让人们告诉你什么? - mc0e
1
我并不是在寻求他们告诉我什么 - 我已经回答了自己的问题,并想分享那些结果,就像 https://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/ 中所描述的那样。虽然我没有给这个标题加上Jeopardy的处理,但现在我会这样做 :) - ncoghlan
2个回答

28
这里的问题是许多现代Linux系统隐式运行dnsmasq,因此您现在的目标是设置一个第二个实例,专门供Docker使用。实际上需要3个设置才能正确完成这个操作:
  • --interface=docker0 监听默认的Docker网络接口
  • --except-interface=lo 跳过环回接口的隐式添加
  • --bind-interfaces 关闭dnsmasq功能,即使它只处理其中一个接口的流量,它仍会默认监听所有接口

设置专用dnsmasq实例

与更改默认系统范围的dnsmasq实例的设置不同,这些说明展示了如何在已定义默认dnsmasq服务的系统上使用systemd设置专用dnsmasq实例:

$ sudo cp /usr/lib/systemd/system/dnsmasq.service /etc/systemd/system/dnsmasq-docker.service
$ sudoedit /etc/systemd/system/dnsmasq-docker.service

首先,我们将默认服务设置复制到专用的服务文件中。然后编辑该服务文件,查找服务定义部分,应该类似于以下内容:

[Service]
ExecStart=/usr/sbin/dnsmasq -k

我们编辑该部分以定义我们的附加选项:
[Service]
ExecStart=/usr/sbin/dnsmasq -k --interface=docker0 --except-interface=lo --bind-interfaces

整个文件实际上非常简短,完整的文件如下:
[Unit]
Description=DNS caching server.
After=network.target
After=docker.service
Wants=docker.service

[Service]
ExecStart=/usr/sbin/dnsmasq -k --interface=docker0 --except-interface=lo --bind-interfaces

[Install]
WantedBy=multi-user.target

[Unit] 部分指示 systemd 在网络堆栈和主要的 Docker 守护程序都可用之后才启动此服务,而 [Install] 则指示在启用服务时将其添加到哪个系统状态目标中。

然后我们配置新服务以在系统启动时启动,并且明确启动它以供立即使用:

$ sudo systemctl enable dnsmasq-docker
$ sudo systemctl start dnsmasq-docker

作为使服务运行的最后一步,我们检查它是否按预期启动:
$ sudo systemctl status dnsmasq-docker

在输出中,我们需要寻找的两行关键信息是:
Loaded: loaded (/etc/systemd/system/dnsmasq-docker.service; enabled; vendor preset: disabled)
Active: active (running) since <date & time>

在第一行注意“已启用”的状态,而在第二行注意“活动(正在运行)”的状态。如果服务没有正确启动,则额外的诊断信息将希望解释原因(尽管有时可能会很难理解,因此需要这篇文章)。
注意:此配置可能无法在系统重新启动时启动dnsmasq-docker,并显示有关未定义docker0接口的错误。虽然等待docker.service似乎非常可靠地避免该问题,但如果系统重新启动后docker容器中的名称解析不起作用,请尝试运行:
$ sudo systemctl start dnsmasq-docker

配置主机防火墙

为了能够在本地Docker容器中使用解析器,我们还需要关闭主机和运行在容器中的系统之间的网络防火墙:

sudo firewall-cmd --permanent --zone=trusted --change-interface=docker0
sudo firewall-cmd --reload

(这在生产容器主机上绝对是一个非常糟糕的想法,但在开发者工作站上可以是一种有益的风险与便利的权衡)

使用systemd环境文件配置Docker

现在我们已经运行了本地解析器,我们需要默认配置Docker来使用它。 Docker需要docker0接口的IP地址而不是接口名称,因此我们使用ifconfig来检索它:

$ ifconfig docker0 | grep inet
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0

因此,对于我的系统来说,默认的docker0桥接口上的主机接口可通过172.17.0.1访问(将| cut -f 10 -d ' '附加到该命令中可过滤输出以获取仅 IP 地址)

由于我假设使用基于 systemd 的 Linux 系统和系统提供的 Docker 包,我们将查询系统包的服务文件以了解服务是如何启动的:

$ cat /usr/lib/systemd/system/docker.service

我们首先要找的是启动守护进程的确切命令,应该看起来像这样:

ExecStart=/usr/bin/docker daemon \
          $OPTIONS \
          $DOCKER_STORAGE_OPTIONS \
          $DOCKER_NETWORK_OPTIONS \
          $INSECURE_REGISTRY

我们需要查找的第二部分是服务是否配置了使用环境文件,这可以通过类似于以下一行或多行标识来确定:
EnvironmentFile=-/etc/sysconfig/docker

当环境文件被使用(如在Fedora 23上),则更改Docker守护程序设置的方法是编辑该文件并更新相关的环境变量:

$ sudoedit /etc/sysconfig/docker

现有的 Fedora 23 上的 OPTIONS 入口如下所示:
OPTIONS='--selinux-enabled --log-driver=journald'

为了更改默认的DNS解析设置,我们需要修改它,使其像这样:

OPTIONS='--selinux-enabled --log-driver=journald --dns=172.17.0.1'

然后重新启动Docker守护进程:

$ sudo systemctl restart docker

实施这些更改后,Docker容器现在应该能够可靠地访问主机系统可以访问的任何系统(包括通过VPN隧道访问的系统,这是我需要解决的原因)

您可以在容器内运行curl以检查名称解析是否正常工作:

docker run -it centos curl google.com

google.com 替换为你遇到问题的主机名(如果您在 Docker 容器中运行进程时遇到名称解析问题,您应该只会找到此答案)

使用 systemd drop-in 文件配置 Docker

(注意:由于我的系统使用环境文件,我无法测试下面基于 drop-in 文件的方法,但它应该可以工作 - 我已经包含了它,因为 Docker 文档似乎表明他们现在更喜欢使用 systemd drop-in 文件而不是环境文件)

如果系统服务文件使用EnvironmentFile,则可以通过使用 drop-in 配置文件来替换整个ExecStart条目:

$ sudo mkdir -p /etc/systemd/system/docker.service.d
$ sudoedit /etc/systemd/system/docker.service.d/daemon.conf

我们接着告诉Docker清除现有的ExecStart条目,并用我们的新条目替换它,附加了额外的设置:
[Service]
ExecStart=
ExecStart=/usr/bin/docker daemon \
          $OPTIONS \
          --dns 172.17.0.1 \
          $DOCKER_STORAGE_OPTIONS \
          $DOCKER_NETWORK_OPTIONS \
          $INSECURE_REGISTRY

我们随后告诉systemd加载该配置更改并重新启动Docker:
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

参考资料:


我想避免将docker0接口放入受信任的firewalld区域,但我最初尝试将其添加到内部区域并将dns服务添加到该区域,结果是在容器内“ping”工作,但“curl”不起作用的配置。 - ncoghlan
越来越好奇 - 这个设置在 Fedora 和 CentOS 容器上运行良好,但是 Debian 和 Ubuntu 容器却无法看到外部世界。 - ncoghlan
啊,看起来当前的指令中有一个错误,dnsmasq-docker在系统重启后可能无法启动(抱怨“docker0”丢失)。今天我运行的镜像恰好是基于Debian的,所以我误诊了问题的根源。 - ncoghlan
等待Docker守护进程启动似乎可以在docker0接口已经可用后可靠地启动dnsmasq实例,因此我已相应地更新了建议的服务配置。 - ncoghlan
1
现在ifconfig已经被弃用,如果有人遇到这个问题并想知道使用ip的相应命令是什么,那么这里就是:ip -c addr show docker0(-c用于着色)。 - swenzel
显示剩余3条评论

8
如果您的Docker容器位于自定义网络上,您可以从主机的本地DNS解析器(例如dnsmasq)中使用它们。在这种情况下,容器的/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在用户定义的网络中运行容器,但它仍然在默认的bridge网络中构建容器。要为构建指定自定义网络,可以在构建配置中指定network参数(需要文件格式v3.4+)。

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