当前位置: 首页 > news >正文

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中。

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

# 先启动两个容器在自定义网络 my-net 中dockernetwork create my-netdockerrun-d--nameweb--networkmy-net nginx:alpinedockerrun-d--nameapp--networkmy-net alpinesleep3600# 查看 app 容器的 DNS 配置dockerexecappcat/etc/resolv.conf

输出会类似:

nameserver127.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 容器名解析

# 创建自定义网络dockernetwork create test-dns# 启动两个容器dockerrun-d--nameserver--networktest-dns nginx:alpinedockerrun-d--nameclient--networktest-dns alpinesleep3600# 从 client 解析 server 的 IPdockerexecclientnslookupserver

输出示例:

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别名:

# 启动 Redis 容器,并指定一个网络别名 --network-aliasdockerrun-d--namemy-redis--networktest-dns --network-alias cache redis:alpine# 从 client 容器用别名访问dockerexecclientnslookupcache

输出:

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,实现基本的客户端负载均衡:

dockerrun-d--namemy-redis2--networktest-dns --network-alias cache redis:alpine# 多次查询 cache,观察返回的 IP 交替dockerexecclientnslookupcache# 第一次可能返回 172.18.0.3dockerexecclientnslookupcache# 第二次可能返回 172.18.0.4

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

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

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

# 创建两个独立的网络dockernetwork create net1dockernetwork create net2# 在 net1 中启动一个容器dockerrun-d--nameapp-in-net1--networknet1 alpinesleep3600# 在 net2 中启动另一个容器dockerrun-d--nameapp-in-net2--networknet2 alpinesleep3600# 让 app-in-net1 也加入 net2dockernetwork connect net2 app-in-net1# 现在 app-in-net1 可以解析 app-in-net2dockerexecapp-in-net1ping-c2app-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来访问宿主机:

dockerrun--rmalpineping-c2host.docker.internal

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

dockerrun--rm--add-host host.docker.internal:host-gateway alpineping-c2host.docker.internal

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

4.2 容器访问外网

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

sudoiptables-tnat-LPOSTROUTING|grepMASQUERADE

输出示例:

MASQUERADE all --172.17.0.0/160.0.0.0/0 MASQUERADE all --172.18.0.0/160.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镜像。

# 创建网络dockernetwork create counter-net# 启动 Redis,并给一个网络别名dockerrun-d--nameredis--networkcounter-net --network-alias cache redis:alpine# 启动 Flask 应用(注意连接同一网络)dockerrun-d--nameflask-app--networkcounter-net-p5000:5000 flask-redis-counter:2.0# 查看容器状态dockerps--format"table {{.Names}}\t{{.Status}}\t{{.Ports}}"

输出:

NAMES STATUS PORTS flask-app Up30seconds(healthy)0.0.0.0:5000->5000/tcp redis Up30seconds6379/tcp

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

# 1. 查看 Flask 容器的 DNS 配置dockerexecflask-appcat/etc/resolv.conf# nameserver 127.0.0.11# options ndots:0# 2. 使用 nslookup 验证 Redis 的名称解析dockerexecflask-appnslookupredis# 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)dockerexecflask-appnslookupcache# Name: cache# Address: 172.18.0.2# 4. 测试应用功能curlhttp://localhost:5000# Hello World! I have been seen 1 times.

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

增加副本:验证 DNS 轮询

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

# 启动第二个 Redis 容器,给予相同的网络别名dockerrun-d--nameredis-2--networkcounter-net --network-alias cache redis:alpine# 从 Flask 容器内多次解析 cachedockerexecflask-appnslookupcache# 可能返回两个 IP 地址(轮询)

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

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

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

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

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

# 检查容器的网络信息dockerinspect flask-app--format='{{json .NetworkSettings.Networks}}'|python3-mjson.tool

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

6.2 检查 DNS 服务器地址

dockerexecflask-appcat/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 规则、网桥状态、宿主机防火墙。

# 先查 Redis 的 IPdockerinspect redis--format='{{.NetworkSettings.Networks.counter-net.IPAddress}}'# 假设输出 172.18.0.2# 从 Flask 容器 ping IPdockerexecflask-appping-c2172.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 思维 !

http://www.jsqmd.com/news/907326/

相关文章:

  • 百度网盘提取码智能查询:3步告别资源获取烦恼的终极指南
  • 别再只关RST了!深入聊聊Intel快速存储技术(RAID)与Ubuntu/Linux的‘爱恨情仇’
  • Arduino旋转电位器应用:从模拟信号读取到Processing数据可视化
  • 不是所有 AI 产品都适合出海,真需求和全球化幻觉差在哪? | 嗨点小圆桌
  • 从压电传感器到示波器:手把手教你搭建电荷放大器与低通滤波器(含Multisim仿真与PCB焊接避坑指南)
  • Jetson Orin Nano + DeepStream 6.2 实战:将YOLOv5模型集成到生产级视觉流水线
  • Python爬虫实战:批量下载校园风光图
  • 10427条密码产品证书全部收集到,我发现几个数据跟认知完全对不上
  • 如何查物种的12S基因片段是否存在于NCBI公共数据库?
  • 别再傻傻用软件SPI了!实测STM32硬件SPI驱动GC9A01屏幕,速度提升10倍(附完整代码)
  • 打破大模型 KV Cache 魔咒:一种让跨模型 Agent 缓存 99% 命中的动态工具注入方案
  • 从音响制造到AI家庭娱乐生态:不见不散AI智能K歌音响亮相第二十届深圳国际金融博览会
  • 百年名校焕新光智底座,华为“领航”光智共融
  • Windows电脑也能玩转AI大模型!6G显存就能本地部署,免费无限用!
  • 北斗导航“指路”申通西安转运中心让特产寄递跑出“加速度”
  • 3D点云处理新思路:ParSeNet如何用“聚类+拟合”两阶段网络搞定复杂曲面重建?
  • Arduino电子钢琴DIY:从电路设计到C++编程的嵌入式音乐项目实践
  • 用鼠标单击我的电脑桌面图标或单击文件夹会自动变成重命名状态
  • Unity 2019.3+ 项目从内置管线迁移到URP的保姆级避坑指南(含材质修复)
  • 别只盯着地图!深度解析ArcGIS Pro内容窗格的5个隐藏选项卡(选择、编辑、捕捉…)
  • 手把手教你用阿里云服务器本地部署AWS DeepRacer训练环境(避坑指南)
  • 量子采样经典算法:突破NISQ时代组合优化瓶颈
  • 0104摩尔定律死亡终审:性能提升唯一路径——放弃几何微缩,转向场域升维+时间重构
  • 亚控组态数据导出踩坑实录:报表保存为Excel时文件名乱码、数据错位的解决办法
  • docker 实战:将一个多组件应用完整容器化
  • 新手也能搞定的TPS5430电源设计:从24V到15V,手把手教你选对每个元器件(附完整BOM清单)
  • 别再只用欧氏距离了!用Python实战Hausdorff距离,搞定图像匹配与异常检测
  • Unity游戏特效实战:用LineRenderer复刻红警磁暴闪电(附完整C#源码)
  • ArcMap新手必看:三种要素选择方法(按属性、位置、图形)的保姆级图文教程
  • 不只是安装:用ArcSWAT做水文分析前,你最好先调整好这3个界面设置