Docker 路由模式(No NAT)内网互通配置文档

作者:Administrator 发布时间: 2026-02-12 阅读量:6 评论数:0

1. 方案简介

本方案采用 路由模式 (Routed Mode) 替代 Docker 默认的 NAT 模式。

  • 原理:宿主机作为路由器,直接转发物理网络与容器网络之间的流量。

  • 优势

    • 真实 IP:容器拥有独立的局域网 IP,便于管理和监控。

    • 高性能:去除了 NAT 转换损耗,网络吞吐更高。

    • 双向互通:局域网内的其他机器可以直接访问容器 IP。

2. 网络规划示例

假设物理局域网网段为 192.168.6.0/24,规划给 Docker 使用的网段为 172.30.1.0/24

节点

角色

IP 地址

说明

物理交换机/路由器

核心网关

192.168.6.1

需配置静态路由

宿主机 (Linux)

容器宿主

192.168.6.157

需开启转发

Docker 容器

业务节点

172.30.1.x

网关指向 172.30.1.1


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 ACCEPT

4. 自动化守护脚本(生产环境推荐)

由于 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 "======================================================"

部署建议

  1. 赋予执行权限:chmod +x /usr/local/bin/fix_docker_route.sh

  2. 加入 Crontab 计划任务(每分钟检查一次,确保网络高可用):

    * * * * * root /usr/local/bin/fix_docker_route.sh >> /var/log/docker_route_fix.log 2>&1

5. 验证测试

完成上述配置后,进行以下测试:

  1. 启动测试容器

    docker run -d --name test-app --network routed-net --ip 172.30.1.2 nginx
  2. 连通性检查

    • 从局域网其他电脑 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,则通;否则不通。

评论