容器里 ping 域名或 curl 域名时跳出 Temporary failure in name resolution,多半就是 DNS 这一层出了问题。容器内的 DNS 并不是直接读宿主机的 /etc/resolv.conf,而是指向 Docker 自带的内置 DNS 代理 127.0.0.11,由这个代理再向上游 DNS 转交查询。所以同一个报错,根因散落在好几个层面:默认网络里上游拿到 127.0.0.53 这类本机地址、Ubuntu 24.04 的 systemd-resolved 抢占了 /etc/resolv.conf、自定义网络下 iptables 没生成 MASQUERADE、Compose 启动参数里被 K8s 残留的 --dns 污染、DOCKER-USER 链挡住了 TCP。要先分清到底是哪一类,再对症处理。
最省事的第一步是分清”是 DNS 还是路由”。在容器里执行 ping -c 2 223.5.5.5,通就是 DNS,不通就是路由或防火墙——后者本文不展开。确认是 DNS 之后,看一眼容器内的 DNS 配置是怎么写的:
docker exec <容器名> cat /etc/resolv.conf
默认情况你会看到 nameserver 127.0.0.11,这是 Docker 内置 DNS 代理,它再去问宿主机传下来的上游 DNS 。如果上游是 127.0.0.53(systemd-resolved 的本机地址)这种只能在宿主机内访问的地址,在容器里就完全没用了。
最通用的改法是编辑宿主机的 /etc/docker/daemon.json,给所有新建容器指定公共 DNS :
{
"dns": ["223.5.5.5", "114.114.114.114"]
}
如果文件里已经有 registry-mirrors 等其它键,记得把整段合并成合法的 JSON 对象,注意逗号和括号。改完之后一定要重启 Docker:
sudo systemctl restart docker
这一步有两个坑。第一,daemon.json 只对新建容器生效,已经在跑的容器不会跟着变,需要用 docker compose up -d --force-recreate 之类的方式重建。第二,JSON 写错一处,Docker 启动就会失败,现象是 docker 命令全部报连不上 daemon。所以改完先用 sudo docker info 或 systemctl status docker 看一眼服务是不是真起来了。临时验证可以用一个一次性容器直接指定 DNS:
docker run --rm --dns 223.5.5.5 alpine nslookup baidu.com
另一种常见思路是直接在容器里编辑 /etc/resolv.conf 把 nameserver 改成公共 DNS,这样立刻生效,但容器重启或重建后会被 Docker 覆盖回去,只能临时用。
如果是 Ubuntu 24.04,会发现改 /etc/resolv.conf 根本没用,怎么改都被抢回 127.0.0.53。原因是这个文件被软链到了 /run/systemd/resolve/stub-resolv.conf,由 systemd-resolved 统一接管,改原文件是改不到点子上的。就连直接改 /run/systemd/resolve/stub-resolv.conf 也不行,systemd 会按自己的配置定期回写。
这一类要动的是 /etc/systemd/resolved.conf,把里面的 DNS= 改成你想要的公共 DNS:
DNS=1.1.1.1 233.6.6.6 114.114.114.114 8.8.8.8
然后把 /etc/resolv.conf 重新软链到不带 stub 的那份:
mv /etc/resolv.conf /etc/resolv.conf.bak
ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
systemctl restart systemd-resolved
改完用 cat /run/systemd/resolve/resolv.conf 验一下,里面应该已经写上了你指定的 nameserver 。主机 DNS 修好以后,容器通过 127.0.0.11 拿到的上游也就跟着对了。
上面两种都默认”容器 IP 能通”,但有时候你会遇到更麻烦的情形:容器内 ping 8.8.8.8 也 100% 丢包,--network host 模式却完全正常。这说明不是 DNS,是整个出站网络不通。看一眼容器内 /etc/resolv.conf 里的注释行,如果写着 ExtServers: [...],就能确认 daemon.json 的 DNS 是被读到了的,只是代理发不出包。
这种情况多见于曾经装过 K8s/CNI 后又退役的节点,或者是 19.03 这类老版本 Docker 升级不彻底。残留的 CNI 插件、sysctl 参数、甚至旧的 daemon.json 里带着 <> 角括号导致整个文件被忽略,都可能让 Docker daemon 在初始化用户自定义桥接网络时行为异常,跳过自动创建 NAT 规则的步骤。
排查时先看 NAT 表里到底有没有规则:
iptables -t nat -L POSTROUTING -n -v
正常情况下 Docker 会为每个桥接网络在 POSTROUTING 链自动加一条 MASQUERADE,让容器源 IP(通常是 172.18.x.x 这种)能被伪装出去。如果对应子网下没有这条规则,就是这里的问题。临时补一条:
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -j MASQUERADE
子网可以用 docker network inspect <网络名> 查到。补完之后记得用 iptables-save 落到文件里持久化,重启才不会丢。同时把 CNI 残留、/etc/docker/daemon.json 里的脏配置一并清理干净。
Docker Compose 里服务之间按容器名解析失败,报 lookup redis-wanxu: i/o timeout,根因常常不是 Compose 本身,而是 Docker daemon 的启动参数被污染。先查一下:
systemctl show docker | grep -i dns
如果输出里出现 --dns 10.233.0.3、--dns-search default.svc.cluster.local 这种 K8s 风格的配置,基本可以确认是 K8s 退役时残留了 drop-in 文件。它们通常位于 /etc/systemd/system/docker.service.d/ 下,逐个 cat 就能看到谁在塞 --dns。
处理方法是把这些 drop-in 清掉或重写,然后重新加载:
systemctl daemon-reload
systemctl restart docker
docker compose up -d
还有一种更基础、同样容易踩的情况:docker-compose.yml 里忘了声明 networks:,或者服务没有都 join 同一个网络段,现象也是按容器名解析不到。补上正确的网络段即可。
还有一种容易混淆的现象:自定义网络里 nslookup 通、ping 也通,但 curl http://other:8080 就是超时,可是 curl localhost:8080 在容器内又能正常返回。这通常是宿主机 iptables 的 DOCKER-USER 链挡住了容器间的 TCP 流量——ICMP 没被过滤,所以 ping 看着正常。
排查这一层:
iptables -L DOCKER-USER -n -v
看是否对应子网(172.23.0.0/16 这种)到目标端口有放行规则。临时补一条:
sudo iptables -I DOCKER-USER 1 -s 172.23.0.0/16 -p tcp -m multiport --dports 80,8080,3306,6379 -j ACCEPT
-I DOCKER-USER 1 表示插到第一条,优先级最高。持久化需要装 netfilter-persistent,保存一次规则:
sudo apt install netfilter-persistent
sudo netfilter-persistent save
改完之后可以用一次性容器直接验证:
docker run --rm alpine sh -c 'cat /etc/resolv.conf && ping -c 2 www.baidu.com'
nameserver 是你指定的公共 DNS、ping 有响应,就说明 daemon.json 的改动被容器接到了。如果 host 模式正常而 bridge 模式不正常,回到 iptables/NAT 那一节继续查;如果 host 模式也不正常,问题在主机网络层,去看路由、nmcli、防火墙规则。










暂无评论内容