适用场景
这类问题常见于 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 known、UnknownHostException、no such host,容易被误判成目标服务不可用。
可能原因
容器 DNS 解析失败通常不是单一原因,排查时优先关注以下几类:
- Docker 给容器注入的 DNS 配置不可用;
- 宿主机
/etc/resolv.conf指向127.0.0.53,但容器无法访问宿主机本地 stub resolver; - 内网 DNS 只能在宿主机网络命名空间访问,容器桥接网络无法访问;
- 防火墙或安全组阻断了容器到 DNS 服务器的 UDP/TCP 53;
- Docker daemon 的全局 DNS 配置被错误覆盖;
- 容器运行参数、Compose 文件或 Kubernetes 迁移残留配置指定了错误 DNS;
- 上游 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.1 或 127.0.0.53 指向的是容器自己的网络命名空间,不是宿主机。
resolvectl status 里重点看:
DNS Servers:真实上游 DNS,例如10.0.0.2、192.168.1.1、8.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.10 和 10.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 问题。它会改变端口暴露、网络隔离和安全边界,只适合短时间定位。
预防措施
- 生产 Docker 主机不要依赖不透明的
/etc/resolv.conf自动推断,建议在/etc/docker/daemon.json显式配置可达的内网 DNS; - 修改 DNS、VPN、NetworkManager、云厂商网卡配置后,补充容器内解析验证;
- 在镜像或发布流水线里增加启动前 DNS 检查,例如解析依赖的数据库域名、对象存储域名、API 网关域名;
- 对
DOCKER-USER链做变更时,同时验证容器到 DNS 的 UDP/TCP 53; - 内网短域名依赖搜索域时,明确配置
dns-search,避免不同主机行为不一致; - 应用侧对 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 的 dns 和 dns-search,让容器解析路径稳定、可审计、可复现。
Discussion
评论