
👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Docker这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
- Docker – 基础环境下的容器网络初识 🌐
-
- 一、为什么容器需要自己的网络?——从进程到网络隔离的演进逻辑 🧩
- 二、Docker 默认网络驱动:`bridge` 模式深度解析 🏗️
- 三、Java 实战:构建双容器通信的 REST 微服务链 🐘→☕
-
- 3.1 编写 user-service(Spring Boot)
- 3.2 编写 order-service(调用 user-service)
- 3.3 构建并运行双容器(同 bridge 网络)
- 3.4 验证容器间 DNS 与 HTTP 通信
- 四、深入 iptables:Docker 如何实现端口映射与防火墙? 🔒
- 五、host 模式 vs bridge 模式:何时选择? 🤔
-
- Java 示例:在 host 模式下运行一个暴露 JMX 的 Spring Boot 应用
- 六、none 模式与 container 模式:极简与复用哲学 🧘♂️
-
- 6.1 `–network none`:彻底隔离的“哑容器”
- 6.2 `–network container:<name>`:网络复用(Network Reuse)
- 七、Docker 网络排障黄金法则 🔍
-
- ✅ Step 1:确认容器是否在运行且网络就绪
- ✅ Step 2:进入容器,检查自身网络栈
- ✅ Step 3:检查宿主机 iptables 与路由
- ✅ Step 4:检查 Docker daemon 日志(终极线索)
- 八、进阶思考:Docker 网络与云原生生态的衔接 🌍
- 九、总结:一张图掌握 Docker 网络核心 🧭
- 十、结语:网络不是黑盒,而是可触摸的乐高积木 🧱
Docker – 基础环境下的容器网络初识 🌐
在现代云原生开发与部署实践中,Docker 已成为事实上的标准容器运行时。而理解其网络模型,是掌握容器化应用通信、服务发现、安全隔离与可观测性的基石。本文将从零出发,系统性地剖析 Docker 在基础环境(即单机、无 Kubernetes、无 Swarm)下的容器网络机制——不堆砌概念,不跳过细节,辅以可运行的 Java 示例、实时验证的网络行为观察、以及清晰的 Mermaid 可视化图解,助你真正“看见”数据包如何在 docker0、veth 对、iptables 与容器命名空间之间流动 🚀。
💡 核心前提:本文所有实验均基于 Docker Desktop for Mac/Windows(启用 WSL2 或 Hyper-V 后端)或原生 Linux(如 Ubuntu 22.04+),Docker Engine 版本 ≥ 24.0。所有命令与代码在标准安装下开箱即用,无需额外插件或配置。
一、为什么容器需要自己的网络?——从进程到网络隔离的演进逻辑 🧩
传统 Linux 进程共享宿主机的网络协议栈(Network Namespace),这意味着:
- 所有进程都使用 127.0.0.1 指向同一回环接口;
- 端口冲突频繁(如两个 Spring Boot 应用都想监听 8080);
- 无法实现服务级防火墙策略;
- 难以模拟真实微服务拓扑(如 A 服务调用 B,B 调用 C,C 访问数据库)。
Docker 的破局之道,是为每个容器创建独立的网络命名空间(Network Namespace) ——这是 Linux 内核提供的轻量级隔离机制。它让容器拥有:
- 自己的 lo 回环设备;
- 自己的 IP 地址(通常来自私有网段);
- 自己的路由表、iptables 规则、ARP 表;
- 自己的 socket 接口集合。
但这带来一个新问题:隔离之后,容器之间、容器与宿主机之间、容器与外部世界之间,如何通信? 答案不是“打破隔离”,而是通过受控的桥梁(bridge)、路由(routing)与地址转换(NAT)重建连接。这就是 Docker 网络模型的精髓所在 🔗。
二、Docker 默认网络驱动:bridge 模式深度解析 🏗️
当你执行 docker run -d nginx,Docker 并未显式指定 –network,但它默认使用了名为 bridge 的内置网络驱动。这并非简单的“桥接”,而是一套精密协同的组件组合:
| docker0 | Linux 网桥(Bridge Device) | 宿主机网络命名空间中 |
| vethXXXXXX | 虚拟以太网设备对(veth pair) | 一端在宿主机(连 docker0),一端在容器命名空间(连 eth0) |
| iptables 规则 | 实现 NAT(SNAT/DNAT)、端口映射、转发控制 | 宿主机 filter 和 nat 表 |
| 容器内 eth0 | 容器视角的默认网卡,IP 由 Docker 分配 | 容器 Network Namespace |
让我们用一组命令直观感受这个结构:
# 启动一个基础容器
docker run -d –name web1 -p 8080:80 nginx
# 查看宿主机网桥状态
ip link show docker0
# 输出示例:
# 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 …
# inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
# 查看 veth 设备(名称因环境而异)
ip link show | grep veth
# 输出示例:12: vethabc123@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> …
# 查看容器内部网络配置(需进入容器)
docker exec web1 ip addr show eth0
# 输出示例:
# 23: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 …
# inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
🔍 关键洞察:
- docker0 的 IP 是 172.17.0.1/16,它是整个 bridge 网络的“网关”;
- 容器 web1 的 IP 是 172.17.0.2/16,与 docker0 处于同一子网;
- vethabc123 的 @if12 表示其另一端(容器内)的索引是 12,形成一对绑定关系;
- 宿主机上 iptables -t nat -L POSTROUTING 可见 SNAT 规则,确保容器出向流量源地址被替换为宿主机 IP。
下面这张 Mermaid 图表,精准刻画了 bridge 模式下一次典型 HTTP 请求(宿主机浏览器访问 http://localhost:8080)的数据路径:
#mermaid-svg-IVKTuEyjoV9Wxjwi{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IVKTuEyjoV9Wxjwi .error-icon{fill:#552222;}#mermaid-svg-IVKTuEyjoV9Wxjwi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IVKTuEyjoV9Wxjwi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .marker.cross{stroke:#333333;}#mermaid-svg-IVKTuEyjoV9Wxjwi svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IVKTuEyjoV9Wxjwi p{margin:0;}#mermaid-svg-IVKTuEyjoV9Wxjwi .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .cluster-label text{fill:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .cluster-label span{color:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .cluster-label span p{background-color:transparent;}#mermaid-svg-IVKTuEyjoV9Wxjwi .label text,#mermaid-svg-IVKTuEyjoV9Wxjwi span{fill:#333;color:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .node rect,#mermaid-svg-IVKTuEyjoV9Wxjwi .node circle,#mermaid-svg-IVKTuEyjoV9Wxjwi .node ellipse,#mermaid-svg-IVKTuEyjoV9Wxjwi .node polygon,#mermaid-svg-IVKTuEyjoV9Wxjwi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .rough-node .label text,#mermaid-svg-IVKTuEyjoV9Wxjwi .node .label text,#mermaid-svg-IVKTuEyjoV9Wxjwi .image-shape .label,#mermaid-svg-IVKTuEyjoV9Wxjwi .icon-shape .label{text-anchor:middle;}#mermaid-svg-IVKTuEyjoV9Wxjwi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .rough-node .label,#mermaid-svg-IVKTuEyjoV9Wxjwi .node .label,#mermaid-svg-IVKTuEyjoV9Wxjwi .image-shape .label,#mermaid-svg-IVKTuEyjoV9Wxjwi .icon-shape .label{text-align:center;}#mermaid-svg-IVKTuEyjoV9Wxjwi .node.clickable{cursor:pointer;}#mermaid-svg-IVKTuEyjoV9Wxjwi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .arrowheadPath{fill:#333333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IVKTuEyjoV9Wxjwi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IVKTuEyjoV9Wxjwi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IVKTuEyjoV9Wxjwi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IVKTuEyjoV9Wxjwi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .cluster text{fill:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi .cluster span{color:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IVKTuEyjoV9Wxjwi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IVKTuEyjoV9Wxjwi rect.text{fill:none;stroke-width:0;}#mermaid-svg-IVKTuEyjoV9Wxjwi .icon-shape,#mermaid-svg-IVKTuEyjoV9Wxjwi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IVKTuEyjoV9Wxjwi .icon-shape p,#mermaid-svg-IVKTuEyjoV9Wxjwi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IVKTuEyjoV9Wxjwi .icon-shape .label rect,#mermaid-svg-IVKTuEyjoV9Wxjwi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IVKTuEyjoV9Wxjwi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IVKTuEyjoV9Wxjwi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IVKTuEyjoV9Wxjwi :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
HTTP GET /
DNAT via iptables
L2 转发
veth pair
内核协议栈
HTTP Response
DNAT 回写
宿主机浏览器
宿主机端口 8080
docker0 网桥
vethabc123
容器 web1 eth0
nginx 进程监听 80
✅ 此图已通过 Mermaid Live Editor(https://mermaid.live)验证可渲染,你可直接复制粘贴至支持 Mermaid 的 Markdown 预览器中查看交互式流程。
三、Java 实战:构建双容器通信的 REST 微服务链 🐘→☕
理论终须落地。我们用 Java 构建两个轻量服务:
- user-service:提供 /users/{id} 接口,返回用户 JSON;
- order-service:调用 user-service 获取用户信息,再生成订单。
二者将运行在同一 Docker bridge 网络中,通过容器名自动 DNS 解析通信(Docker 内置 DNS 服务器 127.0.0.11 实现),而非硬编码 IP —— 这是生产就绪的关键实践 ✅。
3.1 编写 user-service(Spring Boot)
User.java:
public class User {
private Long id;
private String name;
private String email;
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// getters & setters omitted for brevity
}
UserController.java:
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 模拟数据库查询
if (id == 1L) {
return new User(1L, "Alice Johnson", "alice@example.com");
} else if (id == 2L) {
return new User(2L, "Bob Smith", "bob@example.com");
}
throw new RuntimeException("User not found: " + id);
}
}
application.yml(关键:绑定到 0.0.0.0,非 127.0.0.1):
server:
port: 8080
address: 0.0.0.0 # ⚠️ 必须!否则容器内其他进程无法访问
spring:
application:
name: user–service
3.2 编写 order-service(调用 user-service)
OrderRequest.java:
public class OrderRequest {
private Long userId;
private String product;
// constructor, getters, setters
}
OrderController.java:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/orders")
public class OrderController {
// 使用容器名作为 host(Docker DNS 自动解析)
@Value("${user.service.url:http://user-service:8080}")
private String userServiceUrl;
private final RestTemplate restTemplate = new RestTemplate();
@PostMapping
public String createOrder(@RequestBody OrderRequest request) {
try {
// ✅ 关键:调用 user-service 容器名,非 localhost 或 IP
String userUrl = userServiceUrl + "/users/" + request.getUserId();
ResponseEntity<User> response = restTemplate.getForEntity(userUrl, User.class);
User user = response.getBody();
String orderDesc = String.format(
"Order for %s (%s): %s",
user.getName(), user.getEmail(), request.getProduct()
);
System.out.println("[ORDER-SERVICE] Generated: " + orderDesc);
return orderDesc;
} catch (Exception e) {
System.err.println("[ORDER-SERVICE] Failed to call user-service: " + e.getMessage());
return "ERROR: User service unavailable";
}
}
}
application.yml:
server:
port: 8081
address: 0.0.0.0
spring:
application:
name: order–service
# 自定义属性,供 Java 代码注入
user:
service:
url: http://user–service:8080
3.3 构建并运行双容器(同 bridge 网络)
首先,为两个服务分别编写 Dockerfile(使用官方 OpenJDK 基础镜像):
Dockerfile(user-service):
FROM openjdk:17-jdk-slim
COPY target/user-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
Dockerfile(order-service):
FROM openjdk:17-jdk-slim
COPY target/order-service.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","/app.jar"]
构建镜像:
# 在 user-service 目录下
mvn clean package -DskipTests
docker build -t user-service .
# 在 order-service 目录下
mvn clean package -DskipTests
docker build -t order-service .
🚀 关键一步:创建自定义 bridge 网络(推荐替代默认 bridge)
docker network create –driver bridge \\
–subnet=172.20.0.0/16 \\
–gateway=172.20.0.1 \\
my-network
启动容器(指定同一网络):
# 启动 user-service(不暴露端口到宿主机,仅容器间通信)
docker run -d \\
–name user-service \\
–network my-network \\
–restart unless-stopped \\
user-service
# 启动 order-service(暴露 8081 到宿主机,便于测试)
docker run -d \\
–name order-service \\
–network my-network \\
-p 8081:8081 \\
–restart unless-stopped \\
order-service
3.4 验证容器间 DNS 与 HTTP 通信
进入 order-service 容器,手动测试 DNS 解析与 curl:
docker exec -it order-service sh
# 查看 /etc/resolv.conf(Docker 自动注入)
cat /etc/resolv.conf
# 输出应包含:nameserver 127.0.0.11
# 解析 user-service 容器名
nslookup user-service
# 输出应显示 A 记录:172.20.0.2(或类似)
# 发起 HTTP 请求(使用容器内 IP 或名称均可)
curl -v http://user-service:8080/users/1
# ✅ 成功返回:{"id":1,"name":"Alice Johnson","email":"alice@example.com"}
# 退出容器
exit
现在,从宿主机发起请求:
curl -X POST http://localhost:8081/orders \\
-H "Content-Type: application/json" \\
-d '{"userId":1,"product":"Laptop"}'
预期输出:
Order for Alice Johnson (alice@example.com): Laptop
🎉 成功! order-service 通过容器名 user-service 成功调用了 user-service,全程无需硬编码 IP,且流量完全在 my-network 内部流转,不经过宿主机 docker0(因为我们没用默认 bridge),更安全、更可控。
四、深入 iptables:Docker 如何实现端口映射与防火墙? 🔒
前文提到 -p 8081:8081 将宿主机端口映射到容器端口。这背后是 iptables 的 DNAT(目标地址转换)规则在工作。
执行以下命令查看关键规则:
# 查看 nat 表中的 DOCKER 链(Docker 创建的专用链)
sudo iptables -t nat -L DOCKER -n
# 典型输出(简化):
# Chain DOCKER (2 references)
# target prot opt source destination
# DNAT tcp — 0.0.0.0/0 0.0.0.0/0 tcp dpt:8081 to:172.20.0.3:8081
这条规则含义是:所有发往宿主机任意 IP 的 8081 端口的 TCP 包,目标地址被重写为 172.20.0.3:8081(即 order-service 容器 IP)。
再看 filter 表的转发规则:
sudo iptables -L FORWARD -n | grep '172.20.0.3'
# 输出可能包含:
# ACCEPT all — 0.0.0.0/0 172.20.0.3 ctstate RELATED,ESTABLISHED
# ACCEPT all — 172.20.0.3 0.0.0.0/0
这确保了:
- 容器 172.20.0.3 的入向连接被允许(第二行);
- 容器 172.20.0.3 的出向响应包被允许(第一行,RELATED,ESTABLISHED)。
💡 安全提示:Docker 默认设置 FORWARD 链策略为 ACCEPT,这在生产环境存在风险。建议在宿主机防火墙(如 ufw)中显式限制 FORWARD 流量,例如:
# 仅允许 my-network 子网内的容器间通信
sudo ufw allow from 172.20.0.0/16 to any port 8080,8081 proto tcp
五、host 模式 vs bridge 模式:何时选择? 🤔
Docker 提供多种网络模式,bridge 是默认,但 host 模式在特定场景下不可替代。
| 网络命名空间 | ✅ 独立(隔离) | ❌ 共享宿主机 |
| 端口映射 | ✅ 支持 -p | ❌ 不支持(直接使用宿主机端口) |
| 性能 | ⚡ 中等(经 veth + netfilter) | 🚀 最高(零拷贝,无 NAT 开销) |
| DNS 解析 | ✅ 容器名自动解析 | ❌ 无内置 DNS,需用 –add-host |
| 适用场景 | 通用 Web 服务、微服务通信 | 高吞吐监控代理(如 Prometheus Node Exporter)、性能敏感中间件 |
Java 示例:在 host 模式下运行一个暴露 JMX 的 Spring Boot 应用
假设你需要从宿主机 jconsole 连接容器内 JVM 的 JMX 端口(通常 9010):
# 启动容器(host 模式)
docker run -d \\
–name jmx-app \\
–network host \\
–env JAVA_OPTS="-Dcom.sun.management.jmxremote \\
-Dcom.sun.management.jmxremote.port=9010 \\
-Dcom.sun.management.jmxremote.rmi.port=9010 \\
-Dcom.sun.management.jmxremote.authenticate=false \\
-Dcom.sun.management.jmxremote.ssl=false \\
-Djava.rmi.server.hostname=localhost" \\
-p 9010:9010 \\
your-jmx-app
⚠️ 注意:–network host 时 -p 参数无效(端口已天然暴露),-p 9010:9010 此处仅为示意,实际应删除。正确做法是确保 JVM 的 hostname 设置为 localhost,这样宿主机 jconsole 可直连 localhost:9010。
六、none 模式与 container 模式:极简与复用哲学 🧘♂️
除了 bridge 和 host,Docker 还提供两种“非典型”模式:
6.1 –network none:彻底隔离的“哑容器”
容器只有 lo 回环接口,无任何外部网络能力。适用于:
- 运行纯计算任务(如批处理、加密);
- 安全沙箱(禁止任何外联);
- 作为其他容器的“网络模板”。
docker run –rm -it –network none alpine ip addr
# 输出仅含 lo 接口
6.2 –network container:<name>:网络复用(Network Reuse)
新容器完全共享指定容器的网络命名空间(包括 IP、端口、socket、iptables)。适用于:
- Sidecar 模式(如日志收集器与主应用共用网络);
- 调试工具容器(如 nicolaka/netshoot 进入目标容器网络)。
# 启动主容器
docker run -d –name main-app -p 8080:8080 nginx
# 启动调试容器,复用 main-app 网络
docker run -it –rm –network container:main-app nicolaka/netshoot \\
ss -tuln # 查看 main-app 的所有监听端口
此模式下,ss -tuln 输出与在 main-app 容器内执行结果完全一致,因为它们共享同一个网络栈。
七、Docker 网络排障黄金法则 🔍
遇到“容器 ping 不通”、“服务调用超时”,按此顺序排查:
✅ Step 1:确认容器是否在运行且网络就绪
docker ps -a | grep your-container
docker inspect your-container | grep -A 10 "NetworkSettings"
# 检查 "IPAddress" 是否非空,"Gateway" 是否正确
✅ Step 2:进入容器,检查自身网络栈
docker exec -it your-container sh
ip addr show eth0 # 确认 IP 分配
ping -c 3 172.20.0.1 # ping 网关(docker0 或自定义网关)
ping -c 3 google.com # 测试外网连通性(需 DNS)
nslookup your-other-service # 测试 DNS 解析
curl -v http://other:8080/health # 测试目标服务可达性
✅ Step 3:检查宿主机 iptables 与路由
# 查看 NAT 规则是否生效
sudo iptables -t nat -L DOCKER -n | grep your-port
# 查看 FORWARD 链是否放行
sudo iptables -L FORWARD -n | grep your-container-ip
# 查看路由表(确认子网可达)
ip route | grep 172.20.0.0
✅ Step 4:检查 Docker daemon 日志(终极线索)
# Linux
sudo journalctl -u docker.service -n 100 –no-pager
# macOS/Windows(Docker Desktop)
# 查看 Docker Desktop 应用内 “Troubleshoot” → “View logs”
八、进阶思考:Docker 网络与云原生生态的衔接 🌍
Docker 的 bridge 模式是学习网络的绝佳起点,但它止步于单机。当业务增长,你将自然走向:
- Kubernetes CNI(Container Network Interface):如 Calico、Cilium,提供跨节点 Pod 网络、NetworkPolicy 等企业级能力;
- 服务网格(Service Mesh):如 Istio、Linkerd,在应用层抽象流量管理、mTLS、可观测性;
- eBPF 加速:Cilium 等利用 eBPF 替代 iptables,实现零延迟、高精度网络策略。
但请牢记:所有这些高级能力,都建立在对基础网络(Namespace、veth、bridge、iptables)深刻理解之上。没有扎实的地基,云原生大厦只是空中楼阁 🏗️➡️☁️。
正如 CNCF(Cloud Native Computing Foundation)官网 所倡导:“云原生技术使组织能够在现代动态环境(如公有云、私有云和混合云)中构建和运行可扩展的应用程序。”而网络,正是这一动态环境的血脉。
九、总结:一张图掌握 Docker 网络核心 🧭
最后,用一张 Mermaid 图表整合全文核心组件与数据流向,巩固认知:
#mermaid-svg-JDpzEF7kd00LYj4i{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JDpzEF7kd00LYj4i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JDpzEF7kd00LYj4i .error-icon{fill:#552222;}#mermaid-svg-JDpzEF7kd00LYj4i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JDpzEF7kd00LYj4i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JDpzEF7kd00LYj4i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JDpzEF7kd00LYj4i .marker.cross{stroke:#333333;}#mermaid-svg-JDpzEF7kd00LYj4i svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JDpzEF7kd00LYj4i p{margin:0;}#mermaid-svg-JDpzEF7kd00LYj4i .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-JDpzEF7kd00LYj4i .cluster-label text{fill:#333;}#mermaid-svg-JDpzEF7kd00LYj4i .cluster-label span{color:#333;}#mermaid-svg-JDpzEF7kd00LYj4i .cluster-label span p{background-color:transparent;}#mermaid-svg-JDpzEF7kd00LYj4i .label text,#mermaid-svg-JDpzEF7kd00LYj4i span{fill:#333;color:#333;}#mermaid-svg-JDpzEF7kd00LYj4i .node rect,#mermaid-svg-JDpzEF7kd00LYj4i .node circle,#mermaid-svg-JDpzEF7kd00LYj4i .node ellipse,#mermaid-svg-JDpzEF7kd00LYj4i .node polygon,#mermaid-svg-JDpzEF7kd00LYj4i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JDpzEF7kd00LYj4i .rough-node .label text,#mermaid-svg-JDpzEF7kd00LYj4i .node .label text,#mermaid-svg-JDpzEF7kd00LYj4i .image-shape .label,#mermaid-svg-JDpzEF7kd00LYj4i .icon-shape .label{text-anchor:middle;}#mermaid-svg-JDpzEF7kd00LYj4i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JDpzEF7kd00LYj4i .rough-node .label,#mermaid-svg-JDpzEF7kd00LYj4i .node .label,#mermaid-svg-JDpzEF7kd00LYj4i .image-shape .label,#mermaid-svg-JDpzEF7kd00LYj4i .icon-shape .label{text-align:center;}#mermaid-svg-JDpzEF7kd00LYj4i .node.clickable{cursor:pointer;}#mermaid-svg-JDpzEF7kd00LYj4i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JDpzEF7kd00LYj4i .arrowheadPath{fill:#333333;}#mermaid-svg-JDpzEF7kd00LYj4i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JDpzEF7kd00LYj4i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JDpzEF7kd00LYj4i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JDpzEF7kd00LYj4i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JDpzEF7kd00LYj4i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JDpzEF7kd00LYj4i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JDpzEF7kd00LYj4i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JDpzEF7kd00LYj4i .cluster text{fill:#333;}#mermaid-svg-JDpzEF7kd00LYj4i .cluster span{color:#333;}#mermaid-svg-JDpzEF7kd00LYj4i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JDpzEF7kd00LYj4i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JDpzEF7kd00LYj4i rect.text{fill:none;stroke-width:0;}#mermaid-svg-JDpzEF7kd00LYj4i .icon-shape,#mermaid-svg-JDpzEF7kd00LYj4i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JDpzEF7kd00LYj4i .icon-shape p,#mermaid-svg-JDpzEF7kd00LYj4i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JDpzEF7kd00LYj4i .icon-shape .label rect,#mermaid-svg-JDpzEF7kd00LYj4i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JDpzEF7kd00LYj4i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JDpzEF7kd00LYj4i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JDpzEF7kd00LYj4i :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Container2_Namespace
Container1_Namespace
Host_Namespace
TCP/IP
ARP + L2
DNAT
SNAT
to internet
宿主机进程e.g. browser, curl
宿主机网卡eth0
docker0 bridge172.17.0.1/16
iptables nat/forward
容器1 vethA
容器2 vethB
容器1 eth0172.17.0.2/16
容器1 应用e.g. nginx:80
容器2 eth0172.17.0.3/16
容器2 应用e.g. java:8080
这张图清晰展示了:
- 三层命名空间隔离(Host / Container1 / Container2);
- veth 对是跨命名空间的唯一通道;
- docker0 是 bridge 模式的中心枢纽;
- iptables 是流量调度与转换的总控开关;
- 容器间通信走 L2(无需 NAT),高效直接。
十、结语:网络不是黑盒,而是可触摸的乐高积木 🧱
Docker 网络常被初学者视为晦涩的黑盒,但本文试图证明:它不过是由 Linux 内核提供的几个稳定、成熟、文档完备的原语(Namespace、veth、bridge、iptables)拼装而成的乐高模型 🧱。每一个 docker run 命令,都在后台精确地创建、连接、配置这些积木。
当你下次看到 docker network inspect my-network 的 JSON 输出,不再只关注 "IPAM" 字段,而是能脑中浮现 docker0 的 MAC 地址表、veth 的 peer 关系、iptables 的跳转链——那一刻,你就真正“拥有”了容器网络。
继续深入?推荐阅读:
- Linux Networking Documentation(权威内核网络文档)
- Docker Networking Official Guide(Docker 官方网络指南)
- The Illustrated TLS Connection(虽非 Docker 专属,但对理解 HTTPS + 容器网络结合极有启发)
愿你在云原生的海洋中,始终手握网络罗盘,乘风破浪,稳舵前行 🌊⛵。
🙌 感谢你读到这里! 🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。 💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友! 💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿 🔔 关注我,不错过下一篇干货!我们下期再见!✨



