当前位置: 首页 > 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.zskr.cn/news/1418625.html

相关文章:

  • Arduino旋转电位器应用:从模拟信号读取到Processing数据可视化
  • 北斗导航“指路”申通西安转运中心让特产寄递跑出“加速度”
  • Arduino电子钢琴DIY:从电路设计到C++编程的嵌入式音乐项目实践
  • 别只盯着地图!深度解析ArcGIS Pro内容窗格的5个隐藏选项卡(选择、编辑、捕捉…)
  • 0104摩尔定律死亡终审:性能提升唯一路径——放弃几何微缩,转向场域升维+时间重构
  • 新手也能搞定的TPS5430电源设计:从24V到15V,手把手教你选对每个元器件(附完整BOM清单)
  • ArcMap新手必看:三种要素选择方法(按属性、位置、图形)的保姆级图文教程
  • Arm CoreLink NIC-400与NI/NoC动态调频技术详解
  • 从实验室到产线:Imatest枯叶图在摄像头批量质检中的实战应用与自动化脚本思路
  • 告别死板教程!用ShaderGraph复刻《和平精英》动态海面,这5个参数调好了效果直接翻倍
  • C语言在嵌入式Linux系统开发中的实战应用
  • PriLLM: 为LLM服务实时定价的 Stackelberg Game 建模 【School of CS and Eng,Southeast University】
  • 别再只会拖Button了!用Python脚本+Unity UGUI EventSystem,5分钟自动化测试你的UI交互
  • OpenCV 4.x时代,如何用ORB替代SIFT搞定Python图像拼接(附完整代码)
  • 避坑指南:Unity ShaderGraph制作透明火焰效果时,Alpha混合和Surface设置的那些坑
  • 别再死记硬背了!用Python实战模拟四种循环(简单/嵌套/连锁/非结构)的测试用例设计
  • 亚控组态报表数据导出Excel后,如何用VBA实现自动汇总与图表生成?
  • 技术美术进阶:三方向映射纹理的“坑”与优化技巧(从UE4到Unity的避坑指南)
  • 保姆级教程:理光喷头UV打印机白墨与光油通道设置实战(以1H2C_4C+2WV为例)
  • Oracle数据清洗实战:用正则表达式搞定脏数据,附赠常用SQL模板
  • Yolov8全系列模型C#推理性能优化:TensorRT vs. OpenVINO C# API对比实测
  • 工业网关实战:基于神州龙芯GSC3290双网口与YT8521S的稳定网络方案设计与调试心得
  • RuoYi-Vue + PostgreSQL实战:除了改驱动和URL,这些配置细节你调对了吗?
  • 手把手教你用Vivado 2019.1配置Tri Mode Ethernet MAC,搞定FPGA与RTL8211E的千兆UDP通信
  • 别再手动折腾了!用Composer和PECL一键搞定PHPStudy的imagick扩展(附PHP7.3/7.4版本适配指南)
  • 告别偏色!手把手教你用i1Profiler 3.5为打印机制作精准ICC曲线(附D50/D65光源选择指南)
  • AI搜索变天后,最先掉队的不是小网站,而是还没搞懂向量引擎的人
  • 从Photoshop到Word:拆解那些‘小而美’的工具栏按钮,用Qt的QToolButton轻松复现
  • 告别网页登录!用OpenWrt路由器+sdusrun脚本自动搞定深澜校园网认证(保姆级教程)
  • 为AI编程助手构建自动化工作流:规则、命令与钩子实践