Docker 网络进阶:容器间通信与 DNS 解析

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。


第 8 篇我们打开了 Docker 网络的大门,介绍了桥接网络、端口映射和 host 模式。你可能会好奇:多个容器在自定义网络中能够通过容器名 互相访问,这背后到底发生了什么?app.py 里写的 host='redis' 为什么能神奇地定位到 Redis 容器的 IP?如果你创建了多个副本(比如三个 Flask 实例),它们之间如何发现彼此?

答案的关键,就是 Docker 内嵌的 DNS 服务器。这一篇,我们将深入探讨容器间通信的完整链路,把网络背后的"翻译官"彻底剖析清楚。

一、容器间通信的核心:DNS 服务发现

在第 8 篇中,我们看到了默认桥接网络和自定义桥接网络的本质区别:后者内置了 DNS 解析功能。这个 DNS 服务器运行在 127.0.0.11 地址上,并且被注入到每个连接到自定义网络的容器的 /etc/resolv.conf 中。

你可以进入任何一个连接到自定义网络的容器,验证这一点:

bash 复制代码
# 先启动两个容器在自定义网络 my-net 中
docker network create my-net
docker run -d --name web --network my-net nginx:alpine
docker run -d --name app --network my-net alpine sleep 3600

# 查看 app 容器的 DNS 配置
docker exec app cat /etc/resolv.conf

输出会类似:

bash 复制代码
nameserver 127.0.0.11
options ndots:0

nameserver 127.0.0.11 告诉容器:所有 DNS 查询都发给本地的嵌入式 DNS 服务器。这个 DNS 服务器由 Docker 守护进程维护,它会根据容器名、网络别名、服务名(在 Docker Compose 或 Swarm 模式下)动态返回对应的 IP 地址。

二、DNS 解析的工作流程

当一个容器尝试解析另一个容器的名字时,数据包会经历以下步骤:

  1. 发起查询 :容器内应用调用 getaddrinfo("redis") 或执行 ping redis

  2. 本地 DNS 请求 :容器的 DNS 客户端根据 /etc/resolv.conf127.0.0.11:53 发送 UDP 查询。

  3. 嵌入式 DNS 处理 :Docker 守护进程在宿主机的网络命名空间中捕获到这个 DNS 请求(通过 iptables 规则将流量转发到 Docker 引擎)。Docker 在内部维护了一张映射表,记录了 <容器名> → <IP> 的对应关系。

  4. 返回 IP:如果查询的容器名在当前网络中,Docker 直接返回该容器的 IP;如果不是,Docker 会将查询转发到宿主机的 DNS(或你自定义的外部 DNS)。

  5. 缓存:解析结果会被缓存(默认 TTL 通常较短),提高后续查询速度。

整个过程对于容器内的应用完全透明------它只需要知道目标服务的名称,剩下的都由 Docker 网络栈接管。这种设计,正是 Kubernetes Service 抽象的前身。在 K8s 中,你访问 my-service,CoreDNS 返回的是 Service 的 ClusterIP,而非单个 Pod IP,但基于名称的稳定访问这一理念完全一致。

三、实验:深入理解 DNS 行为

现在,我们动手验证 DNS 解析的三种典型场景。

3.1 容器名解析

bash 复制代码
# 创建自定义网络
docker network create test-dns

# 启动两个容器
docker run -d --name server --network test-dns nginx:alpine
docker run -d --name client --network test-dns alpine sleep 3600

# 从 client 解析 server 的 IP
docker exec client nslookup server

输出示例:

bash 复制代码
Server:    127.0.0.11
Address:   127.0.0.11:53

Non-authoritative answer:
Name:   server
Address: 172.18.0.2

nslookup 明确显示 DNS 服务器就是 127.0.0.11,并且返回了 server 容器的 IP(这里是 172.18.0.2,实际 IP 取决于网络子网分配)。

3.2 网络别名:一个容器多个名字

Docker 允许你为一个容器指定多个别名,这在服务发现中极为实用。例如,一个 Redis 容器除了自己的容器名外,还可以有一个统一的 cache 别名:

bash 复制代码
# 启动 Redis 容器,并指定一个网络别名 --network-alias
docker run -d --name my-redis --network test-dns --network-alias cache redis:alpine

# 从 client 容器用别名访问
docker exec client nslookup cache

输出:

bash 复制代码
Server:    127.0.0.11
Address:   127.0.0.11:53

Non-authoritative answer:
Name:   cache
Address: 172.18.0.3

这就意味着,你可以通过 cache 这个名字来访问 Redis,而不管实际的容器名是 my-redis 还是 redis-prod-v2。Kubernetes 的 Service 正是这个模式的延伸:Service 名称作为稳定的 DNS 记录,后端 Pod 的 IP 可以随时变化。

如果你再启动一个同样有 cache 别名的容器,DNS 会以**轮询(round-robin)**方式返回多个 IP,实现基本的客户端负载均衡:

bash 复制代码
docker run -d --name my-redis2 --network test-dns --network-alias cache redis:alpine

# 多次查询 cache,观察返回的 IP 交替
docker exec client nslookup cache
# 第一次可能返回 172.18.0.3
docker exec client nslookup cache
# 第二次可能返回 172.18.0.4

这就是 Docker 层面的"服务发现"和"负载均衡"雏形。当你进入 Kubernetes 时,Service 的 ClusterIP 配合 kube-proxy 实现的正是同一套思想,只不过规模更大、机制更完善。

3.3 跨网络通信:容器连接多个网络

默认情况下,不同网络的容器是相互隔离的。但你可以让一个容器加入多个网络,充当桥梁。

bash 复制代码
# 创建两个独立的网络
docker network create net1
docker network create net2

# 在 net1 中启动一个容器
docker run -d --name app-in-net1 --network net1 alpine sleep 3600

# 在 net2 中启动另一个容器
docker run -d --name app-in-net2 --network net2 alpine sleep 3600

# 让 app-in-net1 也加入 net2
docker network connect net2 app-in-net1

# 现在 app-in-net1 可以解析 app-in-net2
docker exec app-in-net1 ping -c 2 app-in-net2
# 输出:64 bytes from app-in-net2.net2 (172.19.0.2): ...

这种方式常用于实现"前端-后端"的分层隔离,我们在第 8 篇已演示过。一个更符合生产实践的用法是:反向代理容器(如 Nginx)连接前端网络和后端网络,成为两个网络之间的唯一入口------这和 K8s Ingress Controller 的架构角色完全一致。

四、容器与宿主机通信:数据包的完整旅程

容器不仅需要与其他容器通信,还需要访问宿主机上的服务,或通过宿主机访问外网。理解这些场景的通信链路,可以帮你快速定位网络故障。

4.1 容器访问宿主机

在 Docker for Mac/Windows 中,可以使用特殊域名 host.docker.internal 来访问宿主机:

bash 复制代码
docker run --rm alpine ping -c 2 host.docker.internal

在 Linux 下,这个域名默认不可用(Docker Desktop for Linux 除外),但你可以使用 --add-host 参数手动添加,或直接使用 docker0 网桥的 IP(通常是 172.17.0.1):

bash 复制代码
docker run --rm --add-host host.docker.internal:host-gateway alpine ping -c 2 host.docker.internal

host-gateway 是 Docker 20.10+ 引入的特殊值,会自动解析为宿主机的网关 IP。如果你的宿主机上运行着数据库或 API 服务,容器通过这个地址就能访问到。

4.2 容器访问外网

容器访问外网依赖宿主机的 IP 伪装(MASQUERADE)。数据包路径为:容器 → veth → docker0 网桥 → 宿主机 NAT(修改源 IP)→ 物理网卡 → 外网。你可以用 iptables 验证 NAT 规则的存在:

bash 复制代码
sudo iptables -t nat -L POSTROUTING | grep MASQUERADE

输出示例:

bash 复制代码
MASQUERADE  all  --  172.17.0.0/16      0.0.0.0/0
MASQUERADE  all  --  172.18.0.0/16      0.0.0.0/0

这些规则确保容器的私有 IP 被替换为宿主机的真实 IP,从而使外部网络能够正确回包。

4.3 外部访问容器

外部请求到达宿主机指定端口后,由 iptables DNAT 规则将目标 IP 重写为容器 IP,然后通过 docker0 网桥送达容器。这一整条路径我们在第 8 篇已经分析过,但值得再次强调:所有端口映射的可靠性都建立在 iptables 规则的正确性之上。当容器"莫名其妙连不通"时,检查 iptables 规则是否被意外修改是一个有效排查方向。

五、实战:Flask + Redis 计数器应用的网络通信验证

现在,我们回到贯穿本系列的 Flask + Redis 计数器应用,专门验证它的 DNS 解析和网络通信流程。在进入本篇实验前,请确保你已经按照前几篇的步骤构建了 flask-redis-counter:2.0 镜像。

bash 复制代码
# 创建网络
docker network create counter-net

# 启动 Redis,并给一个网络别名
docker run -d --name redis --network counter-net --network-alias cache redis:alpine

# 启动 Flask 应用(注意连接同一网络)
docker run -d --name flask-app --network counter-net -p 5000:5000 flask-redis-counter:2.0

# 查看容器状态
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

输出:

bash 复制代码
NAMES       STATUS                    PORTS
flask-app   Up 30 seconds (healthy)   0.0.0.0:5000->5000/tcp
redis       Up 30 seconds             6379/tcp

现在,让我们深入 flask-app 容器内部,验证它的 DNS 配置和 Redis 发现过程:

bash 复制代码
# 1. 查看 Flask 容器的 DNS 配置
docker exec flask-app cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0

# 2. 使用 nslookup 验证 Redis 的名称解析
docker exec flask-app nslookup redis
# Server:    127.0.0.11
# Address:   127.0.0.11:53
# Non-authoritative answer:
# Name:   redis
# Address: 172.18.0.2

# 3. 使用别名 cache 解析(与 redis 相同 IP)
docker exec flask-app nslookup cache
# Name:   cache
# Address: 172.18.0.2

# 4. 测试应用功能
curl http://localhost:5000
# Hello World! I have been seen 1 times.

Flask 应用里的 redis.Redis(host='redis', port=6379) 之所以能工作,正是因为容器的 DNS 解析将 redis 翻译成了 Redis 容器的 IP 172.18.0.2。整个过程无需手动配置 IP,无需硬编码,完全是 Docker 网络栈在背后默默工作。

增加副本:验证 DNS 轮询

如果我们启动另一个 Redis 实例(模拟集群或读写分离),并且给它相同的别名 cache

bash 复制代码
# 启动第二个 Redis 容器,给予相同的网络别名
docker run -d --name redis-2 --network counter-net --network-alias cache redis:alpine

# 从 Flask 容器内多次解析 cache
docker exec flask-app nslookup cache
# 可能返回两个 IP 地址(轮询)

你可以观察到,nslookup cache 会交替返回 redisredis-2 的 IP。这展示了 Docker 内置的 DNS 负载均衡能力。当你的应用只需要一个逻辑服务名(如 cache)而不关心有多少个后端实例时,网络的灵活性就体现出来了。

六、故障排查:DNS 解析失败怎么办?

在实际使用中,你可能会遇到"容器名解析不了"的情况。以下是常见的排查步骤。

6.1 检查容器是否在同一网络

这是最常见的原因。两个容器必须连接到同一个自定义网络,才能通过容器名互相解析。

bash 复制代码
# 检查容器的网络信息
docker inspect flask-app --format='{{json .NetworkSettings.Networks}}' | python3 -m json.tool

查看输出中是否包含 counter-net。如果 Redis 容器不在这个网络里,nslookup redis 会失败。

6.2 检查 DNS 服务器地址

bash 复制代码
docker exec flask-app cat /etc/resolv.conf

如果 nameserver 不是 127.0.0.11,说明这个容器可能连接的是默认 bridge 网络(默认 bridge 没有内嵌 DNS),或者 /etc/resolv.conf 被错误修改。

6.3 使用 ping 和 nslookup 定位问题

  • ping IP 通,但 ping 容器名不通:DNS 解析问题。检查网络和 DNS 配置。

  • ping IP 不通:网络层问题。检查 iptables 规则、网桥状态、宿主机防火墙。

bash 复制代码
# 先查 Redis 的 IP
docker inspect redis --format='{{.NetworkSettings.Networks.counter-net.IPAddress}}'
# 假设输出 172.18.0.2

# 从 Flask 容器 ping IP
docker exec flask-app ping -c 2 172.18.0.2
# 如果通,说明网络层正常,问题在 DNS
# 如果不通,检查网络安全组、iptables 规则或宿主机路由

6.4 清空 DNS 缓存(必要时)

Docker 内嵌 DNS 有短缓存,但很少需要手动干预。如果是 Docker Compose 环境,重启服务(docker compose restart)会让容器重新获得 DNS 记录。

七、本篇总结

这一篇我们深入到了 Docker 网络的神经中枢------DNS 服务发现。

  • DNS 的核心作用:将容器名、网络别名、服务名翻译为动态的 IP 地址,实现服务发现。

  • Docker 内嵌 DNS :运行在 127.0.0.11,由 Docker 守护进程维护,自动同步网络中容器的增删。

  • 网络别名--network-alias 允许多个容器共享一个逻辑名称,实现简单的 DNS 轮询负载均衡。

  • 多网络连接docker network connect 让容器充当网络间的桥梁,实现分层架构。

  • 全链路通信:我们追踪了容器到容器、容器到宿主机、外部到容器的完整数据包路径。

  • 故障排查三板斧:查网络归属 → 查 DNS 配置 → 查 IP 连通性。

从下一篇文章开始,我们将进入本系列第一个重要的里程碑------第 10 篇:实战:将一个多组件应用完整容器化。我们将不再零散地操作单个容器,而是以项目化的方式,把 Flask + Redis 计数器应用连同它的依赖、配置、网络、数据卷一次性打包,写成正式的容器化交付物,并最终推送到 Docker Hub。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
饮品爱好者9 小时前
[ 网络 ] NO.1 TCP/IP网络模型
网络·网络协议·tcp/ip
小陶来咯10 小时前
agent × 豆包:端到端语音实时交互
网络·ai·机器人·bug·交互
艾莉丝努力练剑10 小时前
【Linux:文件】库的制作与原理进阶
linux·运维·服务器·网络·数据库·c++·人工智能
z2023050810 小时前
RDMA之RDMA 的发展原因和软件架构基础(10)
linux·服务器·网络·人工智能·ai
云边云科技_云网融合10 小时前
企业级网络智能运维体系构建:从被动响应到主动预判
大数据·网络·人工智能
VOOHU-沃虎10 小时前
音频变压器选型指南:阻抗匹配、隔离耐压与低失真设计的工程实践
网络·音频
努力成为AK大王10 小时前
网络层核心(四):路由协议与移动IP全解析
网络·智能路由器·路由选择协议
潜创微科技10 小时前
IT68051+IT6615:4K@60Hz HDMI+USB Over IP 网线延长方案|低延时 100 米无损传输
网络·网络协议·tcp/ip
AI科技星10 小时前
维度原本——基于超复数谱系的全域维度统一理论
c语言·前端·javascript·网络·electron