1. 方案简介
本方案采用 路由模式 (Routed Mode) 替代 Docker 默认的 NAT 模式。
原理:宿主机作为路由器,直接转发物理网络与容器网络之间的流量。
优势:
真实 IP:容器拥有独立的局域网 IP,便于管理和监控。
高性能:去除了 NAT 转换损耗,网络吞吐更高。
双向互通:局域网内的其他机器可以直接访问容器 IP。
2. 网络规划示例
假设物理局域网网段为 192.168.6.0/24,规划给 Docker 使用的网段为 172.30.1.0/24。
3. 配置步骤
第一步:物理网络侧配置(静态路由)
在局域网的核心交换机或上级路由器上添加一条静态路由,指明容器网段的去向。
目标网段:
172.30.1.0/24(掩码 255.255.255.0)下一跳 (Gateway):
192.168.6.157(宿主机的物理 IP)
作用:确保局域网内的其他机器发往
172.30.1.x的数据包能正确到达宿主机。
第二步:Docker 网络创建
在宿主机上创建一个关闭 NAT (IP Masquerade) 的自定义网桥。
docker network create \
--driver bridge \
--subnet=172.30.1.0/24 \
--gateway=172.30.1.1 \
--opt "com.docker.network.bridge.name=br-routed" \
--opt "com.docker.network.bridge.enable_ip_masquerade=false" \
routed-net关键参数:
enable_ip_masquerade=false禁止了 SNAT,从而保留了容器的真实源 IP。
iptables -I DOCKER-USER -i br-routed -j ACCEPT
iptables -I DOCKER-USER -o br-routed -j ACCEPT添加iptables路由,保证网络通畅
第三步:宿主机内核转发开启
宿主机必须开启 IP Forwarding 功能,才能在物理网卡和 Docker 网桥之间转发数据包。
临时生效:
sysctl -w net.ipv4.ip_forward=1永久生效:
编辑 /etc/sysctl.conf,添加或修改:
net.ipv4.ip_forward = 1然后执行 sysctl -p 生效。
第四步:Iptables 防火墙修正(核心关键)
Docker 在关闭 NAT 模式下,为了防止 IP 欺骗,默认会在 raw 表的 PREROUTING 链中添加一条 DROP 规则,拦截所有非 Docker 网桥进入的目标为容器网段的包。
现象:外部机器 Ping 容器 IP 时,包到达宿主机网卡后被直接丢弃。
修复命令(在 Docker 的 DROP 规则之前插入一条 ACCEPT 规则):
# 将允许规则插入到 raw 表 PREROUTING 链的第一位
iptables -t raw -I PREROUTING 1 -d 172.30.1.0/24 -j ACCEPT4. 自动化守护脚本(生产环境推荐)
由于 Docker 服务重启或网络重载可能会重新生成 iptables 规则,导致我们手动添加的规则位置被挤后(从而失效)。建议使用以下脚本进行自动维护。
脚本路径:/usr/local/bin/fix_docker_route.sh
#!/bin/bash
# ================= 全局配置区域 =================
# 1. RAW 表配置 (原有)
TARGET_NET="172.30.0.0/16"
RAW_TABLE="raw"
PREROUTING_CHAIN="PREROUTING"
RAW_RULE_SPEC="-d ${TARGET_NET} -j ACCEPT"
# 2. DOCKER-USER 表配置 (新增)
FILTER_TABLE="filter"
DOCKER_USER_CHAIN="DOCKER-USER"
BR_IFACE="br-routed"
# 规则1: 允许从网桥进入
DOCKER_IN_RULE_SPEC="-i ${BR_IFACE} -j ACCEPT"
# 规则2: 允许从网桥发出
DOCKER_OUT_RULE_SPEC="-o ${BR_IFACE} -j ACCEPT"
# ===========================================
# ==================================================================
# 函数: ensure_rule_at_top
# 功能: 检查指定规则是否存在,并确保其处于链的第 1 位。
# 参数:
# $1: 表名 (e.g., raw, filter)
# $2: 链名 (e.g., PREROUTING, DOCKER-USER)
# $3: 用于 grep 检查位置的关键标识符 (e.g., IP地址或接口名)
# $*: 规则的具体参数 (从 $4 开始)
# ==================================================================
function ensure_rule_at_top() {
local table=$1
local chain=$2
local check_ident=$3
shift 3
local rule_spec="$@"
echo "--------------------------------------------------------"
echo "[Check] 正在检查 iptables ${table} 表 ${chain} 链..."
echo " 规则详情: ${rule_spec}"
# 1. 检查规则是否存在 (使用 -C 参数)
iptables -t "${table}" -C "${chain}" ${rule_spec} 2>/dev/null
if [ $? -eq 0 ]; then
echo " -> [INFO] 规则已存在。"
# 2. 进一步检查:规则是否在第一位?
# 获取第一条规则的详细信息,并查找关键标识符
# 注意:iptables -L 的输出格式可能会有细微差别,使用关键标识符 grep 比较可靠
FIRST_RULE_CHECK=$(iptables -t "${table}" -nL "${chain}" --line-numbers | grep "^1 " | grep "${check_ident}")
if [ -n "${FIRST_RULE_CHECK}" ]; then
echo " -> [OK] 规则位置正确 (第 1 位)。无需操作。"
return 0
else
echo " -> [WARN] 规则存在,但不在第 1 位 (可能被其他规则覆盖)。"
echo " -> 正在修复位置..."
# 删除所有旧的该规则 (防止重复)
# 使用 while 循环确保删除所有匹配项
while iptables -t "${table}" -D "${chain}" ${rule_spec} 2>/dev/null; do
echo " 已删除一条旧规则..."
done
# 重新插入到第 1 位
iptables -t "${table}" -I "${chain}" 1 ${rule_spec}
if [ $? -eq 0 ]; then
echo " -> [FIXED] 规则已重新插入到第 1 位。"
else
echo " -> [ERROR] 规则重新插入失败,请检查权限或语法。"
exit 1
fi
fi
else
echo " -> [WARN] 规则不存在。"
echo " -> 正在添加规则..."
# 直接插入到第 1 位
iptables -t "${table}" -I "${chain}" 1 ${rule_spec}
if [ $? -eq 0 ]; then
echo " -> [FIXED] 规则已添加至第 1 位。"
else
echo " -> [ERROR] 规则添加失败,请检查权限或语法。"
exit 1
fi
fi
}
# ================= 主逻辑执行 =================
# --- 处理 RAW 表规则 ---
# 使用 TARGET_NET 作为 grep 检查的标识符
ensure_rule_at_top "${RAW_TABLE}" "${PREROUTING_CHAIN}" "${TARGET_NET}" ${RAW_RULE_SPEC}
# --- 处理 DOCKER-USER 表规则 ---
# 注意:我们连续调用两次插入到第1位。
# 最终结果是 DOCKER_OUT_RULE_SPEC 会在第 1 位,DOCKER_IN_RULE_SPEC 会被挤到第 2 位。
# 这两个都在最前面,满足需求。
# 处理入站规则 (-i br-routed),使用接口名作为 grep 标识符
ensure_rule_at_top "${FILTER_TABLE}" "${DOCKER_USER_CHAIN}" "${BR_IFACE}" ${DOCKER_IN_RULE_SPEC}
# 处理出站规则 (-o br-routed),使用接口名作为 grep 标识符
ensure_rule_at_top "${FILTER_TABLE}" "${DOCKER_USER_CHAIN}" "${BR_IFACE}" ${DOCKER_OUT_RULE_SPEC}
# ================= 最终验证 =================
echo ""
echo "==================== 最终状态验证 ===================="
echo "1. [${RAW_TABLE}] 表 [${PREROUTING_CHAIN}] 链前 3 条规则:"
iptables -t "${RAW_TABLE}" -nL "${PREROUTING_CHAIN}" --line-numbers | head -n 5
echo ""
echo "2. [${FILTER_TABLE}] 表 [${DOCKER_USER_CHAIN}] 链前 5 条规则:"
# 显示前5条,确保能看到我们插入的两条规则以及 Docker 可能添加的后续规则
iptables -t "${FILTER_TABLE}" -nL "${DOCKER_USER_CHAIN}" --line-numbers | head -n 7
echo "======================================================"部署建议:
赋予执行权限:
chmod +x /usr/local/bin/fix_docker_route.sh加入 Crontab 计划任务(每分钟检查一次,确保网络高可用):
* * * * * root /usr/local/bin/fix_docker_route.sh >> /var/log/docker_route_fix.log 2>&1
5. 验证测试
完成上述配置后,进行以下测试:
启动测试容器:
docker run -d --name test-app --network routed-net --ip 172.30.1.2 nginx连通性检查:
从局域网其他电脑 Ping 容器:
ping 172.30.1.2预期结果:通
从容器 Ping 外部网络:
docker exec -it test-app ping 192.168.6.1预期结果:通
从容器 Ping 公网(如 8.8.8.8):
预期结果:取决于上级路由器是否配置了对
172.30.1.0/24网段的 NAT (SNAT)。如果上级路由做了 NAT,则通;否则不通。