Docker 网络 DNS 解析原理与自定义 DNS 配置深度解析
在容器化微服务中,服务实例动态启停、IP 频繁变化。硬编码 IP 不可行,而手动维护 hosts 文件更不现实。Docker 通过内置 DNS 服务器实现了自动化的服务发现,让容器之间可以直接使用服务名或容器名进行通信,无需知晓底层 IP。理解这套 DNS 机制以及如何根据需求自定义 DNS 配置,是保障 Java 微服务稳定通信的基础。
一、Docker 网络 DNS 解析机制概览
不同网络驱动提供的 DNS 能力差异显著:
| 网络类型 | DNS 解析 | 解析目标 | 说明 |
|---|---|---|---|
| 默认 bridge (docker0) | 不支持内置 DNS | — | 容器只能通过 IP 通信;可借助--link在/etc/hosts中添加条目(已废弃) |
| 自定义 bridge | 内置 DNS 服务器 | 容器名 → 容器 IP | 同一网络下容器名自动解析;支持别名(--network-alias) |
| Overlay (Swarm) | 内置 DNS 服务器 | 服务名 → VIP(虚拟 IP),或任务名 → 副本 IP(DNSRR) | 与 Swarm 服务发现深度集成;VIP 模式提供负载均衡 |
| Host / None | 无特殊 DNS | 继承宿主机/etc/resolv.conf | 容器直接使用宿主机的 DNS 配置 |
结论:生产环境推荐始终使用自定义 bridge或overlay网络,以获得完整的 DNS 服务发现能力。
二、内置 DNS 服务器的工作原理
Docker 守护进程为每个自定义网络(bridge 或 overlay)都维护了一个嵌入式 DNS 服务器。该服务器监听在127.0.0.11,所有连接到此网络的容器都会自动将 DNS 请求发往该地址。
2.1 容器名解析流程(自定义 bridge)
当容器 A 尝试访问容器 B(通过容器名my-app)时:
关键特性:
- 动态更新:容器加入网络时,其名称和 IP 立即注册到 DNS 表;容器停止或断开网络时,记录同步移除。
- 多副本支持:若同一网络中有多个容器使用相同的别名(
--network-alias),DNS 会以轮询(Round-Robin)顺序返回多个 IP,实现简单的客户端负载均衡。 - FQDN 支持:容器可被解析为
<容器名>或<容器名>.<网络名>。
2.2 Swarm 服务名解析(VIP 与 DNSRR)
在 Swarm 集群中,创建 overlay 网络的服务时,Docker 会为该服务分配一个虚拟 IP (VIP)。容器内 DNS 解析服务名时:
- VIP 模式(默认):服务名解析为 VIP,VIP 通过内核 IPVS 负载均衡到各副本容器。客户端看到的是一个固定 IP,无需感知后端变化。
- DNS Round Robin (DNSRR):服务名直接解析为所有副本容器的 IP 列表,客户端自行轮询。适合需要自定义负载均衡策略的应用。
解析时序(VIP 模式):
三、默认 bridge 网络的 DNS 缺陷与--link
默认 bridge 网络(docker0)没有内置 DNS 服务器。容器只能通过/etc/hosts文件来解析其他容器的主机名,而该文件仅在容器启动时由 Docker 写入。要实现容器名解析,传统做法是使用--link参数,Docker 会在被链接容器的信息注入到当前容器的/etc/hosts和环境变量中。
问题:
- 单向性:link 是单向的,且被依赖容器重启后 IP 变化会导致 hosts 条目失效。
- 维护困难:在大规模场景下,手动管理 link 关系几乎不可行。
- 已废弃:Docker 官方已不推荐使用
--link,建议迁移至自定义网络。
四、容器 DNS 配置来源与resolv.conf生成
每个容器的/etc/resolv.conf文件决定了它使用的 DNS 服务器。Docker 按以下规则生成该文件:
- 默认行为:Docker 守护进程复制宿主机的
/etc/resolv.conf到容器,并过滤掉本地回环地址(127.0.0.*)等不适用条目。 - 加入自定义网络后:Docker 会在容器
resolv.conf的最前面插入内置 DNS 地址127.0.0.11。这样容器的 DNS 查询会优先发往内置 DNS,未命中(如外部域名)再转发给宿主机 DNS。 - Daemon 全局配置:可以通过
--dns、--dns-search、--dns-opt参数在docker run时覆盖,或在daemon.json中设置全局默认值。 - Docker Compose:
dns关键字可在服务级别配置。
生成逻辑架构图:
容器内实际看到的resolv.conf可能类似:
nameserver 127.0.0.11 # 内置 DNS(若有自定义网络) nameserver 8.8.8.8 # 自定义或宿主机 DNS五、如何自定义 DNS 配置
Docker 允许在多个层面灵活指定 DNS 配置:
| 配置方式 | 作用范围 | 说明 |
|---|---|---|
docker run --dns | 单个容器 | 指定 DNS 服务器地址;可多次使用以添加多个 |
docker run --dns-search | 单个容器 | 指定 DNS 搜索域 |
docker run --dns-opt | 单个容器 | 传递 DNS 选项(如ndots:5) |
daemon.json中的dns | 所有容器(全局默认) | 例如{"dns": ["8.8.8.8", "1.1.1.1"]} |
Docker Composedns | 服务级别的容器 | 定义在服务定义中 |
Docker Composeextra_hosts | 服务级别的容器 | 添加静态主机名映射到/etc/hosts |
自定义 DNS 的常见场景:
- 内部企业 DNS:需要解析企业内部域名(如数据库中间件地址)。
- 加速外部解析:使用更快的公共 DNS(如 DNSPod、Google DNS)。
- 测试环境域名劫持:将特定域名指向测试服务器。
- NDOTS 调整:优化短名称查询次数,适用于 Kubernetes 等分多级域名的环境。
六、Java 应用视角下的实践建议
Spring Boot 等 Java 微服务大量依赖 DNS 进行服务发现(例如数据库 URL 中使用服务名,或 Feign 调用其他微服务)。在 Docker 环境中应注意:
- 始终使用自定义网络,确保容器名或服务名可解析。
- 使用环境变量注入外部域名,避免硬编码。
- 若需要连接外部数据库(非容器化),可通过
--dns或配置extra_hosts来解析数据库主机名。 - JVM 的 DNS 缓存:Java 默认会永久缓存 DNS 解析结果(除非设置
networkaddress.cache.ttl),在频繁变动 IP 的环境中应设置较短的 TTL,以确保能快速感知容器 IP 变化。
七、思维导图总结
通过上述体系,您可以在面试中清晰阐述 Docker 的 DNS 解析机制、不同网络模式下的行为差异,以及如何根据实际需求定制 DNS 配置,体现对容器网络服务发现的深刻理解。