跳到主要内容
预计阅读 19 分钟

容器之间怎么”串门” —— Docker网络模型与容器间通信机制

一个容器独自运行是孤岛,容器之间能够互相通信才组成了大陆。理解Docker的网络模型,是构建多容器应用的基础。

📋 开篇自测:你已经知道多少?

  1. Docker默认有哪几种网络驱动模式?
  2. 在同一个自定义网络中的容器如何通过名称互相访问?
  3. -p 8080:80 这个端口映射中,哪个是宿主机端口,哪个是容器端口?

一、为什么需要关注容器网络

假设你要开一家餐厅。光有厨房(应用容器)还不够,你还需要:前台接待(Nginx反向代理容器)、收银系统(数据库容器)、仓库管理(Redis缓存容器)。这些角色分布在不同的”房间”里,但它们必须能互相沟通——厨房要知道客人点了什么菜,收银要知道客人结了没有。

在容器化的世界里,“不同的房间”就是不同的容器,“互相沟通”就是网络通信。如果你不理解Docker的网络机制,你的多容器应用就是一群各自为政的孤岛。

容器网络要回答的核心问题有三个:

  1. 容器怎么访问外部网络(互联网)?
  2. 外部网络怎么访问容器内的服务?
  3. 容器之间怎么互相访问?

Docker针对这三个问题,设计了一套灵活的网络模型。


二、Docker的网络模式

Docker内置了三种网络驱动(bridge、host、none),其中bridge又分为默认bridge和自定义bridge两种用法,共四种常见的网络配置方式。让我用一栋办公楼来打比方:

Bridge模式(桥接网络)—— 默认模式

这是Docker安装后自带的默认网络模式。每个容器都连接到一个虚拟的”桥接网络”上,通过这个桥与外界通信。

想象一下:办公楼里每个公司(容器)都有自己的内线电话(容器IP),公司之间可以通过内线互打。要打外线(访问互联网),需要通过总机(Docker的NAT网关)转接。外面的人想打进来,也需要总机帮忙转接(端口映射)。

# 查看Docker网络列表
docker network ls

# 你会看到一个名为 "bridge" 的默认网络
# NETWORK ID     NAME      DRIVER    SCOPE
# xxxxxxxxxxxx   bridge    bridge    local

在默认bridge网络中:

  • 每个容器获得一个172.17.0.x的IP地址
  • 容器可以通过IP互相访问
  • 容器可以访问外部网络
  • 外部访问容器需要端口映射(-p
# 启动两个容器
docker run -d --name web nginx
docker run -d --name db redis

# 查看容器的IP地址
docker inspect web --format '{{.NetworkSettings.IPAddress}}'
# 输出类似:172.17.0.2

docker inspect db --format '{{.NetworkSettings.IPAddress}}'
# 输出类似:172.17.0.3

# 从web容器ping db容器的IP(先安装ping工具)
docker exec web bash -c "apt-get update > /dev/null 2>&1 && apt-get install -y iputils-ping > /dev/null 2>&1 && ping -c 3 172.17.0.3"

但注意:默认bridge网络不支持通过容器名称来访问。你不能在web容器里用 ping db 来找到db容器。这是默认bridge网络的一个重要局限。

自定义Bridge网络 —— 推荐做法

Docker允许你创建自定义的bridge网络,而且自定义网络相比默认bridge有几个重要优势:

自动DNS解析:同一个自定义网络中的容器可以通过容器名称互相访问,无需记IP地址。 更好的隔离性:不同网络中的容器默认无法通信。 可以随时连接/断开:容器可以在运行时动态加入或离开某个网络。

# 创建自定义网络
docker network create my-network

# 在自定义网络中启动容器
docker run -d --name web --network my-network nginx
docker run -d --name cache --network my-network redis

# 现在可以用容器名称互相访问了
docker exec web bash -c "apt-get update > /dev/null 2>&1 && apt-get install -y iputils-ping > /dev/null 2>&1 && ping -c 3 cache"
# 能ping通!Docker自动帮你做了DNS解析

这就像给几家公司租了同一层楼——它们不仅有内线电话,还有一本楼层通讯录。只要说”找Cache公司”,总机就知道帮你转到哪里。

生产环境中,永远使用自定义网络而不是默认的bridge网络。

Host模式(主机网络)

容器直接使用宿主机的网络栈,没有任何网络隔离。就像公司不租办公室了,直接在楼大厅办公——共用一切网络设施。

docker run -d --name web --network host nginx

这种模式下:

  • 容器的端口直接就是宿主机的端口,不需要 -p 映射
  • 网络性能最好(没有NAT转发的开销)
  • 但牺牲了网络隔离性
  • 在macOS和Windows的Docker Desktop上不可用(因为容器实际跑在虚拟机里)

适用场景:对网络性能要求极高的场景,比如网络监控工具、负载均衡代理等。

None模式(无网络)

容器完全没有网络接口(除了回环接口lo)。就像一间完全隔音的密室——与世隔绝。

docker run -d --name isolated --network none alpine sleep 3600

适用场景:运行不需要任何网络访问的离线计算任务,或者出于安全考虑需要彻底断网的场景。

网络模式对比

模式隔离性性能DNS解析适用场景
默认bridge不支持容器名单机简单测试
自定义bridge支持容器名大多数场景(推荐)
host最佳N/A高性能需求
none最高N/AN/A安全隔离场景

🤔 想一想 如果你有两个项目,项目A包含web和db两个容器,项目B也包含web和db两个容器。如果它们都在默认bridge网络中,会不会产生命名冲突?如果用自定义网络呢?


三、端口映射:打开通向外界的窗户

容器内部的服务默认只能被同网络的其他容器访问。如果你想让宿主机或外部网络也能访问,就需要端口映射。

端口映射的语法是 -p 宿主机端口:容器端口

# 把容器的80端口映射到宿主机的8080端口
docker run -d -p 8080:80 nginx

# 只绑定到本地回环地址(外部网络无法访问,更安全)
docker run -d -p 127.0.0.1:8080:80 nginx

# 映射多个端口
docker run -d -p 8080:80 -p 8443:443 nginx

# 让Docker自动分配宿主机端口
docker run -d -p 80 nginx
# 用 docker port 查看分配的端口
docker port <容器>

# 映射UDP端口
docker run -d -p 53:53/udp my-dns-server

一个常见的困惑是:为什么要映射端口?容器不是在本机上跑的吗?

这就要回到bridge网络的工作原理了。Docker创建了一个虚拟网络(通常是172.17.0.0/16),容器在这个虚拟网络中有自己的IP。宿主机和这个虚拟网络之间有一个”桥”(docker0网络接口)。端口映射的本质是在这个桥上设置了一条转发规则:把发送到宿主机某端口的流量转发给容器的某端口。

打个比方:容器是一栋大楼里的住户,端口映射就是在大楼门口设置了一个转接柜台——外面的人对柜台说”找8080号”,柜台就把电话转到里面的”80号房间”。


四、容器互联实战

让我们通过一个完整的例子来演示容器之间的网络通信。我们要搭建一个简单的Web应用,包含三个容器:

  • Nginx:反向代理,对外暴露80端口
  • Node.js App:业务逻辑
  • Redis:缓存

第一步:创建自定义网络

docker network create webapp-net

第二步:启动Redis容器

docker run -d \
  --name redis \
  --network webapp-net \
  redis:7.2-alpine

Redis不需要暴露端口给外部,只需要被同网络的其他容器访问即可。

第三步:启动Node.js应用容器

假设你的应用代码中这样连接Redis:

const redis = require('redis');
// 注意这里用的是容器名 "redis" 作为主机名
const client = redis.createClient({ url: 'redis://redis:6379' });
docker run -d \
  --name nodeapp \
  --network webapp-net \
  -e REDIS_HOST=redis \
  my-node-app

因为nodeapp和redis在同一个自定义网络中,所以nodeapp可以直接用”redis”这个名字来访问Redis服务。Docker内置的DNS服务器会把”redis”解析为Redis容器的IP地址。

第四步:启动Nginx容器

docker run -d \
  --name nginx \
  --network webapp-net \
  -p 80:80 \
  -v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro \
  nginx:1.28-alpine

Nginx配置中这样转发请求:

server {
    listen 80;
    location / {
        proxy_pass http://nodeapp:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

同样,用容器名”nodeapp”来指向Node.js应用。

现在访问 http://localhost,流量会经过:

浏览器 → 宿主机:80 → Nginx容器:80 → Node.js容器:3000 → Redis容器:6379

整个链路畅通无阻。

网络诊断技巧

当容器之间通信出现问题时,以下命令能帮你排查:

# 查看容器的网络配置
docker inspect redis --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool

# 查看网络中有哪些容器
docker network inspect webapp-net

# 在容器内测试DNS解析
docker exec nodeapp nslookup redis

# 在容器内测试端口连通性
docker exec nodeapp sh -c "nc -zv redis 6379"

# 查看容器的端口映射情况
docker port nginx

⚠️ 常见误区

  • 误区一:“容器的IP是固定的”。不是。每次重建容器,IP可能会变。所以永远不要在代码或配置里硬编码容器IP,应该使用容器名称(在自定义网络中有DNS支持)。
  • 误区二:“所有容器默认能互相通信”。只有在同一个网络中的容器才能互相通信。不同网络中的容器默认是隔离的。
  • 误区三:“-p 暴露端口就安全了”。端口映射默认绑定到0.0.0.0(所有网络接口),意味着外部网络也能访问。如果只是本地开发,建议绑定到127.0.0.1。

五、进阶:跨网络通信与网络别名

容器连接多个网络

一个容器可以同时连接到多个网络:

# 创建两个网络
docker network create frontend
docker network create backend

# API容器同时连接两个网络
docker run -d --name api --network frontend my-api
docker network connect backend api

# 前端容器只在frontend网络
docker run -d --name web --network frontend nginx

# 数据库容器只在backend网络
docker run -d --name db --network backend postgres

# 结果:
# web ←→ api(通过frontend网络)
# api ←→ db(通过backend网络)
# web ✗ db(不在同一网络,无法直接通信)

这种架构就像办公室的门禁系统:前台接待(web)和产品经理(api)在同一个区域可以自由交流,产品经理和研发人员(db)在另一个区域也可以交流,但前台不能直接闯进研发区域。这提供了额外的安全层。

网络别名

你可以给容器在某个网络中设置别名,让其他容器可以用不同的名字来访问它:

docker run -d \
  --name postgres-primary \
  --network backend \
  --network-alias db \
  --network-alias database \
  postgres

# 其他容器可以用 "postgres-primary"、"db"、"database" 中的任何一个来访问

这在升级数据库、切换服务实现等场景中特别有用——改个别名指向就行,不需要修改所有客户端的配置。


六、网络管理命令总结

# 列出所有网络
docker network ls

# 创建网络
docker network create my-net

# 创建网络(指定子网和网关)
docker network create --subnet=192.168.1.0/24 --gateway=192.168.1.1 my-net

# 查看网络详情
docker network inspect my-net

# 将运行中的容器连接到网络
docker network connect my-net my-container

# 将容器从网络断开
docker network disconnect my-net my-container

# 删除网络
docker network rm my-net

# 清理所有未使用的网络
docker network prune

🤔 想一想 在微服务架构中,服务之间的调用通常要经过”服务发现”机制。Docker的DNS解析其实就是一种最基础的服务发现。你觉得它在什么场景下足够用,什么场景下需要更专业的服务发现工具(如Consul、etcd)?


📝 掌握度自测

  1. Docker的默认bridge网络和自定义bridge网络在DNS解析方面有什么区别?为什么推荐使用自定义网络?

  2. 解释 -p 127.0.0.1:3000:80-p 3000:80 的区别,以及各自适用的场景。

  3. 如果容器A在network-x中,容器B在network-y中,它们能直接通信吗?如果需要通信,应该怎么做?

  4. 画出(或描述)一个包含前端(Nginx)、后端(Node.js)、数据库(PostgreSQL)的三容器架构的网络拓扑,说明哪些端口需要映射到宿主机,哪些不需要。

  5. 为什么不建议在代码中硬编码容器的IP地址?应该用什么替代方案?

💡 自我评估

  • 全部答对:你已经掌握了Docker网络的核心概念,可以设计合理的多容器网络架构了。
  • 答对3-4题:网络基础很扎实。建议动手搭建本章的三容器示例,在实践中加深理解。
  • 答对1-2题:Docker网络确实是较难理解的部分。建议先记住”使用自定义网络+容器名称通信”这个核心用法,其他细节随着实践慢慢掌握。

购买课程解锁全部内容

告别「在我电脑上能跑」:Docker 容器化实战

¥29.90