Docker 容器 DNS 变更后仍保留旧 nameserver 的排障记录

记录宿主机 DNS 迁移后,Docker 旧容器仍保留旧 nameserver,导致容器内域名解析失败的现象、根因、验证命令和修复方法。

这篇记录一个很容易踩的 Docker DNS 坑:宿主机 DNS 已经从旧地址切到新地址,但已经存在的 Docker 容器里仍然保留旧的 nameserver,或者 Docker internal DNS 仍然使用旧的上游 DNS,导致容器内域名解析失败。

这类问题经常表现为:宿主机上 curlnslookup 都正常,但容器内服务突然访问不了域名、WebHook 失败、推送通知失效、服务健康检查异常,或者 docker pull 走旧代理失败。

现象

一次 DNS / 网关迁移后,宿主机 /etc/resolv.conf 已经改为新的 DNS:

nameserver 192.0.2.12

宿主机解析和访问业务域名都正常:

nslookup notify.example.com
curl -kfsS --connect-timeout 5 -m 10 https://notify.example.com/v1/health

健康接口返回正常:

{"healthy":true}

但某些 Docker 容器里的业务仍然异常。进一步检查发现,容器内 DNS 还是旧地址。

普通容器或 network_mode: host 容器里可能直接看到旧 DNS:

nameserver 192.0.2.6

容器内解析失败:

;; connection timed out; no servers could be reached

bridge 网络容器里通常看到的是 Docker 内置 DNS:

nameserver 127.0.0.11
options ndots:0
# ExtServers: [host(192.0.2.6)]

也就是说,容器里虽然写的是 127.0.0.11,但 Docker 内置 DNS 的上游仍然是旧的 192.0.2.6

应用日志里可能出现类似错误:

gaierror [Errno -3] Temporary failure in name resolution

或者:

lookup api.example.com: no such host

如果 Docker daemon 也配置过旧代理,还可能导致 docker pull 失败,例如:

proxyconnect tcp: dial tcp 192.0.2.6:9666: connect: no route to host

根因

Docker 容器的 DNS 配置不是宿主机 /etc/resolv.conf 的实时映射。

Docker 在创建容器时,会根据当时宿主机的 DNS 配置生成容器自己的 /etc/resolv.conf

  • network_mode: host 的容器,常见表现是容器里直接写入当时的 nameserver
  • bridge 网络容器,容器里通常写 nameserver 127.0.0.11,这是 Docker internal DNS;但它背后的上游 DNS 仍然来自容器创建时 Docker 看到的宿主机 DNS。

所以当宿主机 DNS 从旧地址改成新地址后,已经存在的旧容器不会自动刷新 DNS。

这也是为什么:

  • 宿主机解析正常;
  • 新创建的临时容器解析正常;
  • 旧的业务容器仍然解析失败。

快速判断是不是这个问题

先看宿主机当前 DNS:

cat /etc/resolv.conf

再看目标容器内 DNS:

docker exec <container> cat /etc/resolv.conf

如果业务容器内仍出现旧 DNS 地址,或者 bridge 容器里出现类似:

# ExtServers: [host(192.0.2.6)]

就说明它还没刷新。

直接在容器内做解析测试:

docker exec <container> sh -c 'nslookup notify.example.com 2>&1 || true'

如果容器没有 nslookup,可以用 Python 测试:

docker exec <container> python - <<'PY'
import socket
try:
    print(socket.getaddrinfo('notify.example.com', 443)[0][4][0])
except Exception as e:
    print(type(e).__name__, e)
PY

也可以对比新创建的临时容器。下面用已存在的镜像举例,避免现场临时拉镜像时又被旧 DNS / 旧代理影响:

docker run --rm --network bridge --entrypoint sh <existing-image>:<tag> -c 'cat /etc/resolv.conf; nslookup notify.example.com 2>&1 | head -20'

如果新容器已经拿到新 DNS,而旧容器还是旧 DNS,基本就能确认是容器 DNS 没刷新。

修复方法

推荐:强制重建受影响容器

进入对应 compose 项目目录:

cd /path/to/compose-project
docker compose up -d --force-recreate <service>

例如:

docker compose up -d --force-recreate app

--force-recreate 会重新创建容器,Docker 会按当前宿主机 DNS 重新生成容器 /etc/resolv.conf

这个方式比直接 down 温和一些,通常不会先拆掉整个 compose 项目的网络。

也可以:down 后再 up

如果你确认 compose 项目内的数据都挂载在 volume 或 bind mount 上,也可以:

docker compose down
docker compose up -d

这同样会删除旧容器并重新创建,因此一般也会刷新 DNS。

注意:down 会停止并删除该 compose 项目的容器和默认 network。如果某个服务把数据放在容器内部而不是挂载目录里,删除容器会丢数据。因此生产服务优先用 up -d --force-recreate <service>

哪些命令不会可靠刷新 DNS

下面这些通常只是重启旧容器,不会重新生成容器 DNS:

docker restart <container>
docker compose restart <service>

重启 Docker 服务或重启整台主机也不建议当成可靠修复方法。它可能让部分容器重新启动,但不等价于重新创建容器,不能保证旧容器的 DNS 配置刷新。

修复后验证

检查容器 DNS 是否变成新地址:

docker exec <container> grep nameserver /etc/resolv.conf

bridge 网络容器还可以检查 Docker internal DNS 的上游提示:

docker exec <container> grep -A5 ExtServers /etc/resolv.conf || true

期望看到新 DNS,例如:

nameserver 192.0.2.12
# ExtServers: [host(192.0.2.12)]

检查域名解析:

docker exec <container> sh -c 'nslookup notify.example.com 2>&1 | head -20'

检查业务健康接口:

curl -kfsS --connect-timeout 5 -m 10 https://notify.example.com/v1/health

检查容器状态:

docker ps --format 'table {{.Names}}\t{{.Status}}' | grep -E 'NAMES|<service-name>'

如果是 Web/API 服务,也可以从宿主机请求本地监听端口:

curl -fsS --connect-timeout 5 -m 10 http://127.0.0.1:<port>/health

别忘了 Docker daemon 的旧代理

如果之前给 Docker daemon 配过代理,DNS / 网关迁移后还要检查代理配置是否仍指向旧地址。常见位置包括:

  • /etc/docker/daemon.json
  • /etc/systemd/system/docker.service.d/*.conf
  • /etc/default/docker
  • /etc/conf.d/docker
  • /etc/profile.d/*.sh
  • /root/.docker/config.json

排查命令:

grep -RInE 'HTTP_PROXY|HTTPS_PROXY|NO_PROXY|proxy|socks5|http://' /etc/docker /etc/systemd/system/docker.service.d /etc/default /etc/conf.d /etc/profile.d /root/.docker 2>/dev/null

如果确认新代理地址和端口,应把旧地址改掉;如果不再需要代理,则删除代理配置。改完后再重启 Docker 服务。

注意:重启 Docker 服务影响面比重建单个容器大,生产环境先确认有哪些容器会被影响。

经验总结

迁移 DNS / 网关后,Docker 环境建议按这个顺序检查:

  1. 宿主机 /etc/resolv.conf 是否已经变成新 DNS。
  2. 业务容器 /etc/resolv.conf 是否仍保留旧 DNS。
  3. bridge 容器的 127.0.0.11 上游 ExtServers 是否仍是旧 DNS。
  4. 容器内能否解析关键域名。
  5. 受影响服务是否需要 docker compose up -d --force-recreate <service>
  6. Docker daemon 是否还残留旧代理配置。

关键结论:改宿主机 DNS 后,旧容器不一定自动更新;要刷新容器 DNS,最可靠的是重建容器,而不是普通 restart。