Docker容器DNS解析失败怎么办?网络配置与名称解析排查

「容器里 apt update 报 Temporary failure resolving 'deb.debian.org'」「应用启动报 no such host」——这类报错十有八九是容器 DNS 解析出了问题。相比物理机,容器多了一层网络命名空间,DNS 配置的来源、优先级和生命周期都不一样,照搬宿主机的排查思路往往找不到根因。

Docker 的 DNS 解析链其实很清晰:容器内 /etc/resolv.conf 的内容,是由 Docker 守护进程在创建容器时根据宿主机配置和网络类型动态生成的。一旦这个链条上任何一环出问题——比如宿主机的 /etc/resolv.conf 被覆盖、自定义网络的 DNS 配置有误、或者 systemd-resolved 的 127.0.0.53 地址进了容器——解析就会失败。

下面按「先看现象、再定链路、逐层排查」的顺序,把这套方法讲透,最后给出常见故障的修复命令和一份可复用的排查清单。

一、先确认是 DNS 问题还是网络问题

排障第一原则:不要一上来就改 DNS。先分清是「域名解析不了」还是「网络根本不通」。

# 1) 直接 ping 公网 IP,验证基础连通性
docker exec -it mycontainer ping -c 2 114.114.114.114

# 2) 用域名排查解析
docker exec -it mycontainer ping -c 2 www.baidu.com

# 3) 看容器内 DNS 工具是否可用
docker exec -it mycontainer cat /etc/resolv.conf

判断逻辑很简单:
- IP 能通、域名不通 → 纯 DNS 问题,继续往下;
- IP 也不通 → 是路由/网关问题,先解决网络连通性,DNS 排查没有意义。

如果容器里连 ping、nslookup 都没装,可以用临时容器测试:

docker run --rm --network container:mycontainer nicolaka/netshoot \
  dig www.baidu.com

netshoot 是常用的排障镜像,内置 dig、nslookup、tcpdump、mtr 等工具,非常适合排查网络与 DNS。

二、看懂容器内的 /etc/resolv.conf

容器里的 DNS 配置长这样:

docker exec -it mycontainer cat /etc/resolv.conf

常见输出:

nameserver 127.0.0.11
options ndots:0

关键点:127.0.0.11 不是真实 DNS 服务器,而是 Docker 内置的嵌入式 DNS 解析器。它运行在每个自定义网络的命名空间里,负责两件事:
1. 解析同一网络内的容器名(返回容器 IP);
2. 把无法解析的外部域名转发给上游 DNS(即宿主机配置的 DNS)。

所以看到 127.0.0.11 是正常的,说明你在用自定义网络。如果用的是默认 bridge 网络,容器会直接继承宿主机的 /etc/resolv.conf 内容,看到的就是宿主机的真实 DNS 地址。

再看一个容易踩坑的情况:宿主机用了 systemd-resolved,/etc/resolv.conf 指向 127.0.0.53:

nameserver 127.0.0.53

这个 127.0.0.53 是宿主机本地的 stub resolver,只在宿主机命名空间里有效。如果它被原样写进容器,容器里根本没有这个监听,解析必然失败。解决办法见后文。

三、Docker 决定 DNS 的优先级

理解优先级能少走弯路。Docker 为容器设置 DNS 的顺序,从高到低大致是:

  1. 启动时用 --dns 参数显式指定;
  2. 自定义网络自身的 DNS 配置(建网时 --dns 指定);
  3. Docker 守护进程配置 /etc/docker/daemon.json 里的 dns 字段;
  4. 宿主机 /etc/resolv.conf 的内容。

对应命令:

# 方式一:单次运行时指定 DNS
docker run -d --dns 223.5.5.5 --dns 119.29.29.29 --name web nginx:alpine

# 方式二:建网时指定
docker network create --driver bridge --dns 223.5.5.5 mynet

# 方式三:全局配置,编辑 /etc/docker/daemon.json

/etc/docker/daemon.json 全局 DNS 配置示例:

{
  "dns": ["223.5.5.5", "119.29.29.29"]
}

改完重启守护进程生效:

# RHEL / CentOS / Rocky
sudo systemctl restart docker
# Debian / Ubuntu
sudo systemctl restart docker

风险提示:重启 Docker 会导致容器短暂中断(除非配置了 live-restore),生产环境请在维护窗口操作。回滚方式是恢复 daemon.json 原内容后重启。

四、六类典型 DNS 故障与修复

1. 容器继承了 127.0.0.53

现象:容器内 /etc/resolv.conf 是 nameserver 127.0.0.53,解析超时。

原因:宿主机启用 systemd-resolved,Docker 原样复制了 stub 地址。

修复:在 daemon.json 里指定公网 DNS,或在宿主机把 systemd-resolved 的 stub 关闭后让 /etc/resolv.conf 指向真实地址。

{ "dns": ["223.5.5.5", "8.8.8.8"] }

2. IPv6 解析挂了但 IPv4 正常

有些容器内只有 AAAA 记录解析失败,导致 go/rust 程序报错。可在 daemon.json 中禁用 IPv6,或让应用优先 IPv4。测试方法:

docker exec -it mycontainer getent hosts www.google.com

3. 自定义网络内容器名解析不了

现象:ping web 失败,日志 no such host。

排查:

docker network inspect mynet | grep -A5 Containers

确认两个容器确实在同一个网络里,且没有用默认 bridge。默认 bridge 不支持名称解析,这是最容易犯的错。

4. 上游 DNS 被防火墙拦截

宿主机能解析、容器不能,且容器用的是公网 DNS,很可能是出口防火墙只放行了内网 DNS 的 53 端口。验证:

docker run --rm --dns 8.8.8.8 alpine nslookup www.baidu.com
docker run --rm --dns 223.5.5.5 alpine nslookup www.baidu.com

如果两者表现不同,说明出口策略对某些 DNS 有限制,应统一使用公司指定的内网 DNS。

5. DNS 查询偶发超时

默认的 ndots:5 会让解析器对不含点的域名先尝试多次拼接,造成不必要的查询和超时。可在 daemon.json 调整:

{
  "dns-opts": ["ndots:1", "timeout:2", "attempts:2"]
}

timeout 是单次查询超时秒数,attempts 是重试次数,合理设置能显著降低「偶发慢」。

6. resolv.conf 被程序覆盖

某些镜像(尤其是老版本)启动脚本会重写 /etc/resolv.conf,把 Docker 写的内容冲掉。可改用 --mount 只读挂载,但要小心这会让容器名解析失效。

五、为什么容器比物理机更容易出 DNS 问题

理解了机制,才能理解为什么同样一套代码,装物理机上好好的,进了容器就开始报解析错误。原因主要有三点。

第一,容器的 /etc/resolv.conf 是「生成」出来的,不是「继承」来的。Docker 守护进程在创建容器时会读取宿主机的解析配置,再结合网络类型决定写什么进去。这个生成过程有它自己的优先级规则,一旦宿主机配置本身不规范(比如用了 stub resolver、或者 nameserver 写了多个其中有不可达的),生成的容器配置就会带上这个缺陷,而且每个新容器都会复制一遍。

第二,容器内有独立的网络命名空间,回环地址 127.0.0.1 在容器里指向容器自己,而不是宿主机。宿主机 /etc/resolv.conf 里常见的 127.0.0.53(systemd-resolved)进到容器后就指了个「没有监听的东西」,解析直接超时。这是最典型的「宿主机能用、容器不能用」场景。

第三,search domain 和 ndots 的放大效应。Kubernetes 和部分 Docker 配置会注入 search 域和 ndots 参数,让短域名先尝试拼接若干后缀再发查询。ndots:5 意味着任何少于 5 个点的域名都会先按 search 域拼一遍,产生多次无谓查询。域名越短、search 域越多,浪费的查询就越多,P99 延迟和解析失败率都会被放大。

理解了这三点,排查时就有了方向:优先怀疑 resolv.conf 的内容来源,其次怀疑上游 DNS 可达性,最后再优化 ndots 与 search 配置。这套思路对 Docker 和 Kubernetes 都适用,只是 Kubernetes 里多了一层 CoreDNS,需要在容器配置之外再看 CoreDNS 自身的健康状态。

六、DNS 排障对照表

现象 可能的根因 快速验证 修复方向
容器内是 127.0.0.53 systemd-resolved stub cat /etc/resolv.conf daemon.json 指定公网 DNS
容器名解析失败 用了默认 bridge docker network inspect 改用自定义网络
公网域名全失败 出口 53 端口被拦 换 DNS 对比测试 使用内网 DNS
偶发超时 ndots/timeout 不合适 dig +stats 看耗时 调整 dns-opts
仅 IPv6 失败 AAAA 记录不可达 getent hosts 测试 禁用 IPv6 或调优先
全部正常但程序报错 程序缓存了旧结果 重启容器验证 调整应用 DNS 缓存

七、一套可复用的排查流程

# 第 1 步:定位容器
docker ps --format '{{.Names}}\t{{.Networks}}'

# 第 2 步:看容器 DNS 配置
docker exec -it mycontainer cat /etc/resolv.conf

# 第 3 步:用 netshoot 做深度验证
docker run --rm --network container:mycontainer nicolaka/netshoot bash -c \
  "dig +short www.baidu.com; echo '---'; dig +short web"

# 第 4 步:抓包确认 DNS 请求有没有出去
docker run --rm --net=host nicolaka/netshoot tcpdump -i any port 53 -nn

经验补充:容器内 DNS 查询的 P99 延迟,在同机房同网段下通常在 1 到 10 毫秒量级;如果经常出现几百毫秒甚至秒级超时,优先怀疑上游 DNS 不稳定或首包丢包,而不是 Docker 本身。

常见问题

Q1:docker exec 进去能 ping 通 IP,但域名不行,跟容器配置有关吗?
A:基本可以断定是 DNS 配置问题。先看 /etc/resolv.conf,再看上游 DNS 是否被出口策略拦截。

Q2:为什么自定义网络里有个 127.0.0.11?会不会冲突?
A:这是 Docker 的内置 DNS,正常现象,不会和外网地址冲突。它只在本网络命名空间内生效。

Q3:改了 daemon.json 后已有容器会立即生效吗?
A:不会。DNS 配置在容器创建时写入,已有容器需要重建(docker rm 后重新 run)才会应用新配置。

Q4:Kubernetes 里的 DNS 排查思路一样吗?
A:类似但多一层。Pod 内 /etc/resolv.conf 通常指向 CoreDNS 的 Service IP(如 10.96.0.10),排查顺序是 CoreDNS Pod 是否正常、Service 是否可达、Pod 的 dnsConfig 是否正确。

Q5:容器里 nslookup 报 connection timed out 但 ping IP 正常,怎么快速定位?
A:用 --dns 临时指定一个已知可靠的 DNS,如果换 DNS 就好,说明原 DNS 不可达;如果换哪个都不行,检查 UDP 53 出方向是否被防火墙丢弃。

总结

  1. 排 DNS 前先分清「解析失败」还是「网络不通」,用 ping IP 这一步快速切分问题域。
  2. 记住 127.0.0.11 是 Docker 内置 DNS,属正常;容器名互访必须用自定义网络。
  3. 把 DNS 收敛到 daemon.json 统一管理,避免每个容器靠 --dns 打补丁,减少配置漂移。
  4. 遇到 127.0.0.53 进容器,基本可以确定是 systemd-resolved 的锅,直接指定公网或内网 DNS。
  5. 深度排查备好 netshoot 和 tcpdump,用 dig、getent hosts 交叉验证,比反复重启容器高效得多。