局域网直通docker容器网络实践 1. 引言在生产环境中Docker 默认的bridge模式为容器提供了网络连通性同时借助MASQUERADEIP 伪装实现容器访问外网。但在某些场景下我们希望局域网内其他主机能够直接通过容器自身的 IP 地址访问服务例如ssh://git10.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.1Dockerdocker0网桥地址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外部访问容器不可达私网不可路由添加静态路由 允许转发端口映射-pDNAT 规则直接使用容器 IP:端口Docker iptables自动创建大量规则禁用 Docker 的iptables干预或手动清理我们不删除docker0网桥它依然是容器通信的必需组件但会阻止 Docker 添加 NAT 规则让宿主机变成真正的三层路由器。3. 前置准备3.1 系统环境Fedora ≥ 38Docker Engine 已安装确保iptables/firewalld可被脚本操作3.2 开启宿主机 IP 转发sudosysctl-wnet.ipv4.ip_forward1echonet.ipv4.ip_forward 1|sudotee/etc/sysctl.d/99-ipforward.confsudosysctl--system3.3 禁用 Docker 的 iptables 干涉关键Docker 默认会操作iptables增加 MASQUERADE 和 DNAT 规则这会导致容器出去的流量被 SNAT破坏我们的扁平目标。需要在 Docker 守护进程配置中关闭此行为sudomkdir-p/etc/dockersudotee/etc/docker/daemon.jsonEOF { log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, iptables: false } EOF# 重启 Dockersudosystemctl restartdocker⚠️警告将iptables设为false后Docker 将不再自动创建任何 iptables 规则包括容器间通信所需规则。因此容器之间通过docker0的互通性需要手动确保本文通过iptablesFORWARD 链保证后文有详细配置。3.4 创建 Docker bridge 网络并固定 IP 范围为了让容器 IP 固定我们使用自定义桥接网络避免默认bridge的 IP 分配不确定性sudodockernetwork create\--driverbridge\--subnet10.20.1.0/24\--gateway10.20.1.1\--ip-range10.20.1.192/27\-ocom.docker.network.bridge.namebr-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 规则允许从局域网到容器网络的流量sudoiptables-IFORWARD-ienp0s3-obr-flat-d10.20.1.0/24-jACCEPTsudoiptables-IFORWARD-ibr-flat-oenp0s3-s10.20.1.0/24-jACCEPT# 如果之前 Docker 残留了 lxcbr0 或 docker0 规则可予保留但确保没有 MASQUERADE 针对 10.20.1.0/24sudoiptables-tnat-LPOSTROUTING-n|grep10.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但系统可能仍有基础规则sudoiptables-IFORWARD-d10.20.1.200-ptcp--dport22-jACCEPT如果使用firewalld推荐通过direct规则或添加rich-rulesudofirewall-cmd--permanent--direct--add-rule ipv4 filter FORWARD0-d10.20.1.200-ptcp--dport22-jACCEPTsudofirewall-cmd--reload务必确认没有针对10.20.1.0/24的 REJECT/DROP 规则架在 FORWARD 链的前面。5. 启动 Gitea 容器并验证路由5.1 启动 Gitea 容器使用docker-compose.ymlversion:3.8services:gitea:image:gitea/gitea:latestcontainer_name:giteanetworks:flat_bridge:ipv4_address:10.20.1.200volumes:-./data:/dataenvironment:-USER_UID1000-USER_GID1000restart:alwaysnetworks:flat_bridge:external:true启动sudodockercompose up-d5.2 宿主机本地验证宿主机自身能否直接 ping 容器ping-c210.20.1.200由于宿主机上已有br-flat接口且 IP 转发开启直接可达。确认容器监听了 SSHsudodockerexecgitea ss-tlnp|grep225.3 从客户端验证本地路由缺失在客户端10.10.1.50上先 ping 容器地址很可能会失败因为客户端上并没有到10.20.1.0/24的路由。这正是我们需要手动添加静态路由的原因。6. 客户端一侧添加静态路由在任何需要直接访问容器的客户端或局域网网关/核心交换机上添加路由使得下一跳指向宿主机Linux 客户端sudoiprouteadd10.20.1.0/24 via10.10.1.100 dev eth0Windows 客户端管理员 PowerShellroute add 10.20.1.0 mask 255.255.255.0 10.10.1.100添加后客户端就能将发往10.20.1.0/24的报文送往10.10.1.100并由宿主机转发至容器。规模化建议如果局域网内有多台设备需要访问可在局域网默认网关路由器上添加这条静态路由这样全网所有设备都能直接访问容器网络无需逐台配置。验证连通性ping10.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-Tgit10.20.1.200# 克隆仓库替换 username 和 repogitclone ssh://git10.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 iptablesDocker 的容器间通信完全由内核的br-flat二层转发实现无需额外规则。但若需要容器访问公网宿主机仍需手动添加 MASQUERADE 规则此时应精确匹配源地址避免破坏扁平网络效果。安全性容器端口全部暴露于局域网相当于直接附加在物理网络中。务必在容器内部使用防火墙如 Gitea 的 SSH 白名单、或通过iptables在宿主机 FORWARD 链上限制来源。路由规划确保容器 IP 段10.20.1.0/24没有与任何其他网络冲突且 LAN 路由器能够接受此类路由注入。其他 Docker 网络如果还有其他容器使用默认bridgedocker0其流量规则不受影响因为 Docker 不再自动添加规则需要额外配置才能互访。10. 总结本文演示了在Fedora上通过禁用 Docker 自带的 NAT 规则、手工配置iptablesFORWARD 规则以及客户端静态路由成功将默认桥接网络下的容器 IP10.20.1.0/24暴露给物理网络10.10.1.0/24实现“扁平网络”效果。客户端可以直接使用ssh://git10.20.1.200:22克隆 Gitea 仓库体现了Linux 内核路由能力替代 Docker 内置 NAT 的强大灵活性。这种架构适用于需要固定容器 IP、且不希望大量端口映射的中小型生产环境。