适用场景

这类问题常见于 Ubuntu、Debian 或启用了 systemd-resolved 的 Linux 主机:宿主机可以正常解析域名,但 Docker 容器内访问外部域名失败,表现为应用请求超时、包管理器无法安装依赖、容器启动后健康检查一直失败。

典型环境包括:

  • 宿主机使用 Docker Engine 运行业务容器;
  • 宿主机 /etc/resolv.conf 指向 127.0.0.53
  • 宿主机 DNS 由 systemd-resolved、NetworkManager、云厂商 DHCP 或内网 DNS 下发;
  • 容器内需要访问公网域名、内网域名、对象存储、镜像仓库或第三方 API。

现象描述

宿主机上解析正常:

getent hosts example.com
resolvectl query example.com
curl -I https://example.com

进入容器后解析失败:

docker exec -it app sh
getent hosts example.com
nslookup example.com
wget -S --spider https://example.com

常见报错包括:

Temporary failure in name resolution
bad address 'example.com'
Could not resolve host: example.com
lookup example.com on 127.0.0.11:53: server misbehaving
i/o timeout

如果应用是 Python、Java、Go 或 Node.js 服务,业务日志里可能只看到 HTTP 请求失败,例如 Name or service not knownUnknownHostExceptionno such host,容易被误判成目标服务不可用。

可能原因

容器 DNS 解析失败通常不是单一原因,排查时优先关注以下几类:

  1. Docker 给容器注入的 DNS 配置不可用;
  2. 宿主机 /etc/resolv.conf 指向 127.0.0.53,但容器无法访问宿主机本地 stub resolver;
  3. 内网 DNS 只能在宿主机网络命名空间访问,容器桥接网络无法访问;
  4. 防火墙或安全组阻断了容器到 DNS 服务器的 UDP/TCP 53;
  5. Docker daemon 的全局 DNS 配置被错误覆盖;
  6. 容器运行参数、Compose 文件或 Kubernetes 迁移残留配置指定了错误 DNS;
  7. 上游 DNS 对内网域名、搜索域或 split DNS 支持不一致。

排查思路

1. 先确认容器内实际 DNS 配置

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

常见输出如下:

nameserver 127.0.0.11
options ndots:0

127.0.0.11 是 Docker 内置 DNS 转发器,不是最终上游 DNS。它会把查询转发给 Docker daemon 从宿主机或 daemon 配置中选出的 DNS 服务器。

继续查看容器运行时配置:

docker inspect app --format '{{json .HostConfig.Dns}} {{json .HostConfig.DnsSearch}}'

关键字段说明:

  • .HostConfig.Dns:容器启动时显式指定的 DNS,空数组通常表示使用 Docker daemon 默认逻辑;
  • .HostConfig.DnsSearch:搜索域配置,内网短域名解析失败时要重点看它;
  • /etc/resolv.conf:容器最终看到的 DNS 配置。

2. 检查宿主机 resolver 状态

ls -l /etc/resolv.conf
cat /etc/resolv.conf
resolvectl status

如果看到类似内容:

nameserver 127.0.0.53
options edns0 trust-ad
search corp.example

说明宿主机本地应用把 DNS 请求发给 systemd-resolved 的 stub 地址。宿主机自己可以解析,不代表容器也能直接使用这个地址。容器里的 127.0.0.1127.0.0.53 指向的是容器自己的网络命名空间,不是宿主机。

resolvectl status 里重点看:

  • DNS Servers:真实上游 DNS,例如 10.0.0.2192.168.1.18.8.8.8
  • DNS Domain:搜索域或路由域;
  • 每块网卡是否有不同 DNS,特别是 VPN、内网专线、多网卡机器。

3. 在容器网络命名空间里直接测试上游 DNS

先从宿主机拿到真实 DNS:

resolvectl dns

假设真实 DNS 是 10.0.0.2,在容器内测试:

docker exec -it app sh -c 'nslookup example.com 10.0.0.2 || getent hosts example.com'

如果容器内没有 nslookup,可以临时用一个排查容器:

docker run --rm --network container:app busybox:1.36 nslookup example.com 10.0.0.2

这条命令的关键点是 --network container:app,它让临时 busybox 复用目标容器的网络命名空间,测试结果更接近业务容器真实状态。

4. 检查 UDP/TCP 53 是否被阻断

DNS 默认走 UDP 53,但响应过大、重试或某些策略下也可能走 TCP 53。可以分别验证:

docker run --rm busybox:1.36 sh -c 'nc -vz -u 10.0.0.2 53; nc -vz 10.0.0.2 53'

再看宿主机防火墙和 Docker 链:

iptables -S
iptables -S DOCKER-USER
iptables -t nat -S

重点检查 DOCKER-USER 链是否有拒绝容器网段访问 DNS 的规则。很多线上环境会在这条链里加统一出站策略,它优先于 Docker 自动生成的放行规则。

如果使用 firewalld:

firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-all
firewall-cmd --direct --get-all-rules

定位示例

一次线上排查中,现象是新部署的爬虫容器无法访问外部 API,日志持续出现:

httpx.ConnectError: [Errno -3] Temporary failure in name resolution

宿主机验证正常:

getent hosts api.example.com

容器内失败:

docker exec -it crawler getent hosts api.example.com

查看宿主机 /etc/resolv.conf

cat /etc/resolv.conf

输出:

nameserver 127.0.0.53
search internal.example

继续查看真实上游:

resolvectl dns

输出显示网卡 ens160 使用 10.20.0.1010.20.0.11。在容器网络里直连测试:

docker run --rm --network container:crawler busybox:1.36 nslookup api.example.com 10.20.0.10

解析成功,说明问题不在上游 DNS,而是 Docker 默认转发配置没有拿到可用的真实上游。最终通过给 Docker daemon 显式配置 DNS 解决。

修复方案

方案一:配置 Docker daemon 全局 DNS

编辑 /etc/docker/daemon.json

{
  "dns": ["10.20.0.10", "10.20.0.11"],
  "dns-search": ["internal.example"]
}

然后重启 Docker:

systemctl daemon-reload
systemctl restart docker

注意:重启 Docker 会影响当前容器,生产环境应先评估窗口。已运行容器通常需要重建或重启后才能拿到新的 DNS 配置:

docker compose up -d --force-recreate

验证:

docker run --rm busybox:1.36 cat /etc/resolv.conf
docker run --rm busybox:1.36 nslookup api.example.com

方案二:在 Compose 服务中显式指定 DNS

如果只想修复某个服务,可以在 docker-compose.yml 中配置:

services:
  crawler:
    image: example/crawler:latest
    dns:
      - 10.20.0.10
      - 10.20.0.11
    dns_search:
      - internal.example

重新创建容器:

docker compose up -d --force-recreate crawler

这种方式影响范围小,但如果多个服务都需要同一 DNS,长期维护成本会比 daemon 全局配置更高。

方案三:使用宿主机网络临时绕过

临时排查时可以使用:

docker run --rm --network host busybox:1.36 nslookup api.example.com

如果 host 网络模式下正常,而 bridge 网络模式下失败,说明问题集中在容器桥接网络、Docker DNS 转发或防火墙出站路径上。

不建议把业务长期改成 host 网络模式来规避 DNS 问题。它会改变端口暴露、网络隔离和安全边界,只适合短时间定位。

预防措施

  1. 生产 Docker 主机不要依赖不透明的 /etc/resolv.conf 自动推断,建议在 /etc/docker/daemon.json 显式配置可达的内网 DNS;
  2. 修改 DNS、VPN、NetworkManager、云厂商网卡配置后,补充容器内解析验证;
  3. 在镜像或发布流水线里增加启动前 DNS 检查,例如解析依赖的数据库域名、对象存储域名、API 网关域名;
  4. DOCKER-USER 链做变更时,同时验证容器到 DNS 的 UDP/TCP 53;
  5. 内网短域名依赖搜索域时,明确配置 dns-search,避免不同主机行为不一致;
  6. 应用侧对 DNS 失败和连接失败分别打日志,错误信息中保留目标域名,减少后续排查成本。

常用排查命令清单

# 宿主机 DNS 配置
cat /etc/resolv.conf
resolvectl status
resolvectl dns

# 容器 DNS 配置
docker exec -it app cat /etc/resolv.conf
docker inspect app --format '{{json .HostConfig.Dns}} {{json .HostConfig.DnsSearch}}'

# 复用目标容器网络命名空间测试解析
docker run --rm --network container:app busybox:1.36 nslookup example.com
docker run --rm --network container:app busybox:1.36 nslookup example.com 10.20.0.10

# 检查 Docker 防火墙链
iptables -S DOCKER-USER
iptables -t nat -S

# 验证新容器是否拿到 daemon DNS 配置
docker run --rm busybox:1.36 cat /etc/resolv.conf
docker run --rm busybox:1.36 nslookup example.com

总结

Docker 容器 DNS 解析失败时,不要只看宿主机能不能解析。宿主机、Docker daemon、容器网络命名空间和防火墙链路是四个不同层面。排查时先确认容器内 /etc/resolv.conf,再找到宿主机真实上游 DNS,接着在容器网络里直连上游 DNS 测试,最后检查 Docker daemon 配置和 DOCKER-USER 链。生产环境更推荐显式配置 Docker daemon 的 dnsdns-search,让容器解析路径稳定、可审计、可复现。