局域网直通docker容器网络实践
1. 引言
在生产环境中,Docker 默认的 bridge 模式为容器提供了网络连通性,同时借助 MASQUERADE(IP 伪装)实现容器访问外网。但在某些场景下,我们希望局域网内其他主机能够 直接通过容器自身的 IP 地址 访问服务(例如 ssh://git@10.20.1.200:22),而不必依赖 -p 端口映射或反向代理。这便是“扁平网络”所追求的效果:虽然容器处于独立私网(10.20.1.0/24),但整个局域网都能通过路由直达,感觉就像和宿主机处于同一网段。
本文将基于 Fedora,演示如何在不依赖 Docker 桥接的 NAT 功能、仅使用 Linux 内核路由表和 iptables 的条件下,让物理主机网段 10.10.1.0/24 中的客户端能够直连 Docker 容器网段 10.20.1.0/24,并以 Gitea 的 22 端口(SSH) 拉取代码作为完整验证场景。宿主机将扮演 路由器 角色,将发往容器网络的报文正确转发。
核心思想:保留 Docker 默认的
bridge网络作为容器二层隔离手段,但 剥离其 snat(源地址转换) 规则,转而由宿主机的net.ipv4.ip_forward和显式路由完成数据包转发,使容器网络对外完全“透明”,不再由 Docker 包揽一切。
2. 环境信息与设计思路
2.1 网络拓扑
| 角色 | IP 地址 | 说明 |
|---|---|---|
| 宿主机 (Fedora) | 10.10.1.100 | 物理接口 enp0s3,连接局域网 |
| 宿主机 bridge 网关 | 10.20.1.1 | Docker docker0 网桥地址 |
| Gitea 容器 | 10.20.1.200 | 运行于 Docker bridge,分配固定 IP |
| 客户端 | 10.10.1.50 | 需要直连容器拉取代码 |
简化拓扑(需加静态路由):
[客户端 10.10.1.50] ──── 局域网 ──── [宿主机 10.10.1.100] ──── [docker0 10.20.1.1] ──── [Gitea 容器 10.20.1.200]
↑ ↑ ↑
客户端发往 10.20.1.200 的报文必须先送到宿主机,宿主机再根据自身路由表转发至 docker0 网桥,最终到达容器。
2.2 与默认 Docker 桥接的区别
| 项目 | Docker 默认行为 | 本文目标 |
|---|---|---|
| 容器出网 | MASQUERADE 成宿主机 IP | 保留容器源 IP(无 NAT) |
| 外部访问容器 | 不可达(私网不可路由) | 添加静态路由 + 允许转发 |
| 端口映射 | -p DNAT 规则 | 直接使用容器 IP:端口 |
| Docker iptables | 自动创建大量规则 | 禁用 Docker 的 iptables 干预(或手动清理) |
我们不删除 docker0 网桥(它依然是容器通信的必需组件),但会 阻止 Docker 添加 NAT 规则,让宿主机变成真正的三层路由器。
3. 前置准备
3.1 系统环境
- Fedora ≥ 38
- Docker Engine 已安装
- 确保
iptables/firewalld可被脚本操作
3.2 开启宿主机 IP 转发
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-ipforward.conf
sudo sysctl --system
3.3 禁用 Docker 的 iptables 干涉(关键)
Docker 默认会操作 iptables 增加 MASQUERADE 和 DNAT 规则,这会导致容器出去的流量被 SNAT,破坏我们的扁平目标。需要在 Docker 守护进程配置中关闭此行为:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<EOF
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"iptables": false
}
EOF
# 重启 Docker
sudo systemctl restart docker
⚠️ 警告:将
iptables设为false后,Docker 将不再自动创建任何 iptables 规则,包括容器间通信所需规则。因此容器之间通过docker0的互通性需要手动确保(本文通过iptablesFORWARD 链保证,后文有详细配置)。
3.4 创建 Docker bridge 网络并固定 IP 范围
为了让容器 IP 固定,我们使用自定义桥接网络(避免默认 bridge 的 IP 分配不确定性):
sudo docker network create \
--driver bridge \
--subnet=10.20.1.0/24 \
--gateway=10.20.1.1 \
--ip-range=10.20.1.192/27 \
-o "com.docker.network.bridge.name"="br-flat" \
flat_bridge
--driver bridge:使用默认的桥接驱动(所以是“一般模式”)。--subnet/--gateway:容器私网 IP 网段,宿主机侧以10.20.1.1作为网关。--ip-range:控制 DHCP 分配范围,避免冲突。-o ...bridge.name:指定网桥名称为br-flat,方便管理。
之后我们所有的容器都连接到
flat_bridge网络,并通过ipv4_address参数固定 IP。
4. 防火墙(iptables / firewalld)配置
4.1 核心转发规则
由于 Docker 的 iptables 已禁用,我们需要手工添加转发规则,确保 docker0 / br-flat 与外部网卡 enp0s3 之间的流量能够双向转发,且 不进行源地址转换。
创建自定义 iptables 规则(临时,重启不丢失则需写成 systemd 脚本或 rc.local):
# 添加 FORWARD 规则:允许从局域网到容器网络的流量
sudo iptables -I FORWARD -i enp0s3 -o br-flat -d 10.20.1.0/24 -j ACCEPT
sudo iptables -I FORWARD -i br-flat -o enp0s3 -s 10.20.1.0/24 -j ACCEPT
# 如果之前 Docker 残留了 lxcbr0 或 docker0 规则,可予保留;但确保没有 MASQUERADE 针对 10.20.1.0/24
sudo iptables -t nat -L POSTROUTING -n | grep 10.20.1
# 如果有输出,应将其删除
# sudo iptables -t nat -D POSTROUTING <规则行号>
以上规则保证
enp0s3<->br-flat的双向流量被允许,且不发生 NAT。回程报文源地址仍然是10.20.1.200。
4.2 允许 SSH 流量(端口 22)
若宿主机自身开启了防火墙(firewalld 或 iptables),需要显式放行目标为容器 10.20.1.200:22 的流量。对于 iptables(已关闭 Docker iptables,但系统可能仍有基础规则):
sudo iptables -I FORWARD -d 10.20.1.200 -p tcp --dport 22 -j ACCEPT
如果使用 firewalld,推荐通过 direct 规则或添加 rich-rule:
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -d 10.20.1.200 -p tcp --dport 22 -j ACCEPT
sudo firewall-cmd --reload
务必确认 没有针对
10.20.1.0/24的 REJECT/DROP 规则 架在 FORWARD 链的前面。
5. 启动 Gitea 容器并验证路由
5.1 启动 Gitea 容器
使用 docker-compose.yml:
version: "3.8"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
networks:
flat_bridge:
ipv4_address: 10.20.1.200
volumes:
- ./data:/data
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
networks:
flat_bridge:
external: true
启动:
sudo docker compose up -d
5.2 宿主机本地验证
宿主机自身能否直接 ping 容器?
ping -c 2 10.20.1.200
由于宿主机上已有 br-flat 接口且 IP 转发开启,直接可达。
确认容器监听了 SSH:
sudo docker exec gitea ss -tlnp | grep 22
5.3 从客户端验证本地路由缺失
在客户端 10.10.1.50 上先 ping 容器地址,很可能会失败,因为客户端上并没有到 10.20.1.0/24 的路由。这正是我们需要手动添加静态路由的原因。
6. 客户端一侧:添加静态路由
在任何需要直接访问容器的客户端(或局域网网关/核心交换机)上添加路由,使得下一跳指向宿主机:
Linux 客户端:
sudo ip route add 10.20.1.0/24 via 10.10.1.100 dev eth0
Windows 客户端(管理员 PowerShell):
route add 10.20.1.0 mask 255.255.255.0 10.10.1.100
添加后,客户端就能将发往 10.20.1.0/24 的报文送往 10.10.1.100,并由宿主机转发至容器。
💡 规模化建议:如果局域网内有多台设备需要访问,可在局域网默认网关(路由器)上添加这条静态路由,这样全网所有设备都能直接访问容器网络,无需逐台配置。
验证连通性:
ping 10.20.1.200 # 应成功
6.1 确保回程路径
当容器回复客户端的报文到达容器网关(br-flat,即宿主机 10.20.1.1)后,宿主机内核查找路由表,发现目标 10.10.1.50 属于直连的 enp0s3 所在网段,因此直接通过 enp0s3 发出。由于我们在 iptables 中 未做 SNAT,回复报文的源 IP 依然是 10.20.1.200,客户端收到后完成握手。
7. 验证 Gitea SSH 拉取代码
首先创建测试仓库,并确保 Gitea 配置允许 SSH 连接(默认即已开启)。
在客户端上执行:
# 测试 SSH 认证(按提示输入密钥,如果已配置)
ssh -T git@10.20.1.200
# 克隆仓库(替换 username 和 repo)
git clone ssh://git@10.20.1.200:22/username/repo.git
如果一切顺利,Git 将会通过直连容器 IP 的 SSH 服务拉取数据,整个过程 没有任何 NAT 或者端口映射,真正展现了“扁平网络”下的容器直连。
8. 持久化与故障恢复
以上 iptables 规则和路由添加都是临时的,重启后失效。为了生产环境,建议:
- 宿主机 iptables:将规则写入
/etc/sysconfig/iptables或通过firewalld的direct永久配置,也可以使用iptables-persistent或systemd服务。 - 静态路由持久化:在 Fedora 宿主机上,可通过 NetworkManager 的连接配置文件添加静态路由;在客户端上同样采用对应系统的持久化方式。
- Docker 守护进程:
daemon.json中的"iptables": false已为持久配置,重启不影响。
9. 注意事项
- 不可见性:由于关闭 Docker iptables,Docker 的容器间通信完全由内核的
br-flat二层转发实现,无需额外规则。但若需要容器访问公网,宿主机仍需手动添加 MASQUERADE 规则,此时应精确匹配源地址,避免破坏扁平网络效果。 - 安全性:容器端口全部暴露于局域网,相当于直接附加在物理网络中。务必在容器内部使用防火墙(如 Gitea 的 SSH 白名单)、或通过
iptables在宿主机 FORWARD 链上限制来源。 - 路由规划:确保容器 IP 段
10.20.1.0/24没有与任何其他网络冲突,且 LAN 路由器能够接受此类路由注入。 - 其他 Docker 网络:如果还有其他容器使用默认
bridge(docker0),其流量规则不受影响(因为 Docker 不再自动添加规则),需要额外配置才能互访。
10. 总结
本文演示了在 Fedora 上,通过禁用 Docker 自带的 NAT 规则、手工配置 iptables FORWARD 规则以及客户端静态路由,成功将默认桥接网络下的容器 IP(10.20.1.0/24)暴露给物理网络(10.10.1.0/24),实现“扁平网络”效果。客户端可以直接使用 ssh://git@10.20.1.200:22 克隆 Gitea 仓库,体现了 Linux 内核路由能力 替代 Docker 内置 NAT 的强大灵活性。这种架构适用于需要固定容器 IP、且不希望大量端口映射的中小型生产环境。
更多推荐

所有评论(0)