
1. 双网卡场景的真实需求与常见误区一台Linux服务器上插两张网卡一张接内网交换机一张接外网路由这个需求在运维圈里太常见了。常见到什么程度几乎每个从机房托管转向自建机房的团队都会遇到每个做边缘计算、数据采集、视频转发的项目也绕不开。但真正动手配的时候很多人会发现一个尴尬的事实两张网卡都插上了ip addr也能看到两个接口都有IP但内网访问不了外网外网访问不了内网或者更诡异的是——两个都时通时不通。这个问题的本质不是网卡本身而是Linux内核的路由决策机制。默认情况下Linux只有一张路由表main表所有流量都走这张表。当你有两个出口的时候内核需要知道去10.0.0.0/8的包走哪张网卡去8.8.8.8的包走哪张网卡如果路由表里只有一条默认路由那所有流量都会挤到同一张网卡上另一张网卡等于白插。我见过太多人在这里翻车。有人直接把两张网卡的网关都设成默认网关结果内核随机选一条网络时断时续有人只给外网网卡配网关内网网卡不配结果内网机器访问不了这台服务器还有人用route add加了静态路由重启之后全丢了因为没写进配置文件。这篇文章要解决的就是这个问题。我会从路由表的基本原理讲起然后给出三种不同场景下的配置方案最后分享几个我实际踩过的坑。不管你是刚接触Linux网络的新手还是配过几次但总觉得没配明白的老手应该都能从里面找到有用的东西。提示本文所有操作基于主流的Linux发行版CentOS 7/Ubuntu 18.04使用iproute2工具集。如果你还在用ifconfig和route命令建议尽快切换到ip和ip route前者已经停止维护多年了。2. 路由表、网关与策略路由的底层逻辑2.1 一张路由表为什么不够用Linux内核默认维护三张路由表local表、main表和default表。ip route show看到的其实是main表的内容。当你执行ip route add default via 192.168.1.1的时候这条默认路由就写进了main表。问题来了main表里只能有一条默认路由。如果你给两张网卡都配了网关后配的那条会覆盖先配的或者内核直接报错说File exists。这就是为什么很多人配完发现只有一张网卡在工作。那能不能在main表里加两条默认路由让内核自己选理论上可以但内核的选择逻辑是基于metric值的metric小的优先。如果两条路由metric相同内核会选第一条另一条永远不会被用到。这显然不是我们想要的效果。真正的解决方案是策略路由Policy Routing。策略路由的核心思想是不只看目的地址还要看源地址、入接口、TOS字段等信息然后决定用哪张路由表。Linux最多支持255张路由表每张表里都可以有自己的默认路由和静态路由。2.2 网关、路由与ARP的关系在深入配置之前有必要把几个基础概念理清楚。很多人配双网卡出问题根源是对这几个概念的理解有偏差。网关的本质是下一跳。当内核发现目的地址不在本地子网时它不会直接ARP请求目的地址而是把包发给网关让网关去转发。所以网关必须和本机某张网卡在同一个子网内否则内核根本发不出去。路由是目的网络 - 下一跳 - 出接口的映射。一条完整的路由包含三个要素目的网络、下一跳地址、出接口。当你写ip route add 10.0.0.0/8 via 192.168.1.1 dev eth0的时候意思是去10.0.0.0/8的包从eth0出去下一跳是192.168.1.1。ARP是IP地址 - MAC地址的解析。当内核决定从某张网卡发包时它需要知道下一跳的MAC地址。如果ARP表里没有它会发ARP请求。这里有个关键点ARP请求是从哪张网卡出去的取决于路由决策。如果路由决策错了ARP请求就会从错误的网卡出去导致解析失败。2.3 策略路由的规则匹配顺序策略路由的核心是ip rule命令。内核在处理每个包的时候会从上到下匹配规则列表找到第一条匹配的规则然后查对应的路由表。默认的规则列表是这样的ip rule show 0: from all lookup local 32766: from all lookup main 32767: from all lookup default优先级数字越小越先匹配。local表是内核自动维护的包含本机所有IP的本地路由不要动它。main表就是我们平时操作的那张表。default表是空的基本用不到。我们要做的就是在32766之前插入自己的规则。比如ip rule add from 192.168.1.100 lookup 100 ip rule add from 10.0.0.100 lookup 200这两条规则的意思是源地址是192.168.1.100的包查100号表源地址是10.0.0.100的包查200号表。然后在100号表和200号表里分别配置默认路由。这样配置之后内核会根据包的源地址选择不同的路由表从而实现双网卡分流。注意策略路由的规则匹配是首次匹配即停止所以规则的顺序非常重要。如果你把from all lookup main放在前面后面的规则永远不会被匹配到。3. 三种典型场景的配置方案与实操3.1 场景一内网网卡只负责内网通信这是最简单的场景。外网网卡假设是eth0IP 203.0.113.10/24网关203.0.113.1负责所有对外通信内网网卡假设是eth1IP 10.0.0.10/24无网关只负责和内网其他机器通信。这种场景下其实不需要策略路由。你只需要给eth0配IP和默认网关给eth1配IP不配网关在main表里加一条到内网网段的静态路由具体操作# 配置eth0 ip addr add 203.0.113.10/24 dev eth0 ip link set eth0 up ip route add default via 203.0.113.1 dev eth0 # 配置eth1 ip addr add 10.0.0.10/24 dev eth1 ip link set eth1 up # 添加内网静态路由 ip route add 10.0.0.0/8 via 10.0.0.1 dev eth1等等这里有个问题eth1的IP是10.0.0.10/24它本身就在10.0.0.0/24网段内。内核会自动添加一条10.0.0.0/24 dev eth1的路由所以同网段的机器可以直接通信不需要额外配置。但如果你要访问10.1.0.0/16或者10.2.0.0/16这些其他内网网段就需要加静态路由指向内网的网关比如10.0.0.1。这种配置的优点是简单缺点是所有外网流量都走eth0如果eth0挂了内网通信也会受影响因为默认路由没了但内网通信其实不需要默认路由所以影响有限。3.2 场景二两张网卡都需要默认网关这是最复杂的场景。外网网卡有外网网关内网网卡有内网网关而且两张网卡都需要能主动发起连接。比如这台服务器既要从外网拉取数据又要向内网推送数据。这种场景必须用策略路由。配置思路是创建两张自定义路由表比如100和200在100号表里配置外网默认路由在200号表里配置内网默认路由添加策略规则根据源地址选择路由表配置ARP过滤防止ARP冲突具体操作# 创建路由表在/etc/iproute2/rt_tables里添加 echo 100 external /etc/iproute2/rt_tables echo 200 internal /etc/iproute2/rt_tables # 配置eth0外网 ip addr add 203.0.113.10/24 dev eth0 ip link set eth0 up ip route add 203.0.113.0/24 dev eth0 src 203.0.113.10 table external ip route add default via 203.0.113.1 dev eth0 table external # 配置eth1内网 ip addr add 10.0.0.10/24 dev eth1 ip link set eth1 up ip route add 10.0.0.0/24 dev eth1 src 10.0.0.10 table internal ip route add default via 10.0.0.1 dev eth1 table internal # 添加策略规则 ip rule add from 203.0.113.10 lookup external ip rule add from 10.0.0.10 lookup internal # 配置ARP过滤 echo 1 /proc/sys/net/ipv4/conf/eth0/arp_filter echo 1 /proc/sys/net/ipv4/conf/eth1/arp_filter这里有几个关键点需要解释为什么要在自定义表里加本地网段的路由因为策略规则匹配后内核只查指定的表。如果external表里没有203.0.113.0/24的路由内核就不知道这个网段是直连的会尝试走默认网关导致通信失败。为什么要配置arp_filter默认情况下Linux的ARP响应是弱主机模式任何网卡收到ARP请求只要本机有对应的IP就会响应。这会导致一个问题外网网卡可能会响应内网IP的ARP请求内网网卡可能会响应外网IP的ARP请求。如果交换机做了ARP绑定或者有安全策略就会出问题。arp_filter1的意思是只有当ARP请求的入接口和路由表中该IP的出接口一致时才响应。为什么不用ip route add default直接加两条默认路由因为main表里只能有一条默认路由第二条会报错。即使你用metric区分内核也只会用metric小的那条另一条永远不会被选中。3.3 场景三基于目的地址的分流还有一种场景默认走外网但访问特定内网网段时走内网网卡。这种场景其实不需要策略路由只需要在main表里加静态路由即可。# 默认走外网 ip route add default via 203.0.113.1 dev eth0 # 特定内网网段走内网 ip route add 10.0.0.0/8 via 10.0.0.1 dev eth1 ip route add 172.16.0.0/12 via 10.0.0.1 dev eth1 ip route add 192.168.0.0/16 via 10.0.0.1 dev eth1这种配置的优点是简单不需要策略路由。缺点是只支持主动访问内网如果内网机器要主动访问这台服务器回包的时候会查main表发现源地址是内网IP但默认路由是外网网关就会把回包发到外网去导致连接失败。要解决这个问题还是需要策略路由给内网网卡也配一张路由表添加from 10.0.0.10 lookup internal的规则。3.4 三种场景的对比与选型建议场景是否需要策略路由配置复杂度适用情况内网网卡只负责内网通信否低服务器只需要被动接受内网访问两张网卡都需要默认网关是高服务器需要主动向内网和外网发起连接基于目的地址分流视情况中默认走外网特定网段走内网我的建议是如果你的服务器不需要主动向内网发起连接用场景一的方案就够了。如果确实需要双向通信老老实实上策略路由别想着用metric或者别的偏方绕过。4. 配置持久化与重启后的自动生效4.1 为什么命令行配置重启后会丢失用ip addr add和ip route add做的配置都是临时的重启之后全部丢失。这是因为这些命令直接操作内核的网络栈没有写入任何配置文件。Linux的网络配置持久化机制因发行版而异这也是很多人觉得Linux网络配置很乱的原因。CentOS/RHEL系列用/etc/sysconfig/network-scripts/ifcfg-*文件Ubuntu/Debian系列用/etc/netplan/*.yaml或者/etc/network/interfaces。策略路由的规则和自定义路由表则需要额外的机制来持久化。4.2 CentOS 7的持久化方案在CentOS 7及以上版本中网卡配置文件在/etc/sysconfig/network-scripts/目录下。假设外网网卡是eth0内网网卡是eth1# /etc/sysconfig/network-scripts/ifcfg-eth0 TYPEEthernet BOOTPROTOstatic DEVICEeth0 ONBOOTyes IPADDR203.0.113.10 PREFIX24 GATEWAY203.0.113.1 DEFROUTEyes # /etc/sysconfig/network-scripts/ifcfg-eth1 TYPEEthernet BOOTPROTOstatic DEVICEeth1 ONBOOTyes IPADDR10.0.0.10 PREFIX24 DEFROUTEno关键点是DEFROUTEno这告诉系统不要为eth1创建默认路由。如果不加这一行系统会尝试为eth1也创建默认路由导致冲突。策略路由的持久化需要用到/etc/sysconfig/network-scripts/rule-eth1和/etc/sysconfig/network-scripts/route-eth1文件# /etc/sysconfig/network-scripts/rule-eth1 from 10.0.0.10 lookup internal # /etc/sysconfig/network-scripts/route-eth1 10.0.0.0/24 dev eth1 src 10.0.0.10 table internal default via 10.0.0.1 dev eth1 table internal同时需要在/etc/iproute2/rt_tables里添加自定义表的定义echo 200 internal /etc/iproute2/rt_tables4.3 Ubuntu的持久化方案Ubuntu 18.04及以上版本默认用netplan。配置文件在/etc/netplan/目录下通常是01-netcfg.yaml或者类似的名字network: version: 2 renderer: networkd ethernets: eth0: addresses: - 203.0.113.10/24 routes: - to: 0.0.0.0/0 via: 203.0.113.1 metric: 100 routing-policy: - from: 203.0.113.10 table: 100 eth1: addresses: - 10.0.0.10/24 routes: - to: 10.0.0.0/24 via: 10.0.0.1 table: 200 - to: 0.0.0.0/0 via: 10.0.0.1 table: 200 routing-policy: - from: 10.0.0.10 table: 200netplan的优点是语法清晰支持策略路由。缺点是不同版本的netplan对routing-policy的支持程度不一样有些老版本可能不支持。如果遇到问题可以回退到/etc/network/interfaces配合up和down脚本的方式。4.4 用systemd服务做兜底如果发行版的持久化机制不好用或者你需要更灵活的控制可以写一个systemd服务在开机时执行配置脚本# /etc/systemd/system/dual-nic.service [Unit] DescriptionDual NIC routing configuration Afternetwork.target [Service] Typeoneshot ExecStart/usr/local/bin/dual-nic.sh RemainAfterExityes [Install] WantedBymulti-user.target# /usr/local/bin/dual-nic.sh #!/bin/bash ip rule add from 203.0.113.10 lookup external ip rule add from 10.0.0.10 lookup internal ip route add default via 203.0.113.1 dev eth0 table external ip route add default via 10.0.0.1 dev eth1 table internal这种方式的好处是灵活坏处是需要自己处理幂等性重复执行不能报错。可以在脚本开头加ip rule del和ip route flush来清理旧配置。提示不管用哪种持久化方案配置完之后一定要重启一次验证。我见过太多次命令行配通了重启就失联的情况尤其是远程操作的时候重启前一定要确认有带外管理或者物理接触的条件。5. 排查双网卡不通的完整思路5.1 从现象到根因的排查链路双网卡配置出问题的时候现象通常有三种内网不通、外网不通、或者两个都不通。排查的时候不要瞎试按照下面的链路一步步来。第一步确认网卡和IP状态ip link show ip addr show确认两张网卡都是UP状态IP地址配置正确。如果网卡是DOWN的用ip link set eth0 up启动。第二步确认路由表ip route show ip route show table all看看main表里有没有默认路由自定义表里有没有对应的路由。特别注意如果自定义表是空的策略规则匹配后会查不到路由包会被丢弃。第三步确认策略规则ip rule show确认规则列表里有你添加的规则而且优先级在32766之前。如果规则在32766之后永远不会被匹配到。第四步测试连通性ping -I eth0 203.0.113.1 ping -I eth1 10.0.0.1用-I参数指定源接口可以测试特定网卡的连通性。如果指定接口能通不指定就不通说明路由决策有问题。第五步抓包分析tcpdump -i eth0 -n icmp tcpdump -i eth1 -n icmp如果ping不通抓包看看包有没有发出去有没有收到回包。如果包发出去了但没有回包可能是对端的问题如果包根本没发出去说明路由决策有问题。5.2 几个容易忽略的细节rp_filter反向路径过滤Linux默认开启rp_filter它会检查入接口是否和路由表中该源地址的出接口一致。如果两张网卡配置了相同的网段或者路由表配置不当rp_filter会导致包被丢弃。可以用sysctl net.ipv4.conf.all.rp_filter查看当前值临时关闭用sysctl -w net.ipv4.conf.all.rp_filter0。ARP flux前面提到的arp_filter只是解决方案之一。还有arp_ignore和arp_announce两个参数分别控制ARP请求的响应和ARP请求的发送。在双网卡场景下通常需要设置echo 1 /proc/sys/net/ipv4/conf/all/arp_ignore echo 2 /proc/sys/net/ipv4/conf/all/arp_announce路由缓存Linux有路由缓存机制修改路由表后已建立的连接可能还在用旧的路由。可以用ip route flush cache清空缓存。NetworkManager的干扰如果系统装了NetworkManager它可能会覆盖你的手动配置。可以用systemctl stop NetworkManager临时关闭或者配置NetworkManager忽略特定网卡。5.3 一个真实的排查案例之前遇到过一个案例服务器配了双网卡外网能通内网ping不通。排查过程如下ip addr确认两张网卡都有IP状态UPip route show发现main表里只有一条默认路由走外网网卡ip rule show发现没有内网的策略规则添加策略规则后内网能ping通了但外网又断了检查发现策略规则的优先级有问题内网规则把外网规则覆盖了调整优先级后两个都通了这个案例的教训是策略规则的顺序很重要添加规则的时候一定要用ip rule show确认顺序。6. 进阶优化与长期维护建议6.1 用metric实现简单的故障切换如果两张网卡都连到同一个网络比如双上联可以用metric实现主备切换ip route add default via 203.0.113.1 dev eth0 metric 100 ip route add default via 203.0.113.2 dev eth1 metric 200metric小的优先当eth0的网关不可达时内核会自动切换到eth1。这种方式的缺点是切换不是即时的需要等内核检测到网关不可达通常需要几十秒。6.2 用keepalived做高可用如果对可用性要求高可以用keepalived配合VRRP协议做高可用。两台服务器各配双网卡通过VRRP选举主备主服务器故障时自动切换到备服务器。这种方案配置复杂但可靠性最高。6.3 监控与告警双网卡配置好之后建议加监控分别ping两个网关确认连通性监控两张网卡的流量确认分流符合预期监控路由表和策略规则确认没有被意外修改可以用简单的shell脚本配合cron实现#!/bin/bash ping -c 1 -W 2 -I eth0 203.0.113.1 /dev/null 21 if [ $? -ne 0 ]; then echo External gateway unreachable | mail -s Network Alert adminexample.com fi6.4 文档化配置最后一点也是最重要的一点把配置文档化。包括网卡的IP、网关、路由表、策略规则、持久化文件的位置。我见过太多团队配置是某个人配的人走了之后没人知道怎么改出了问题只能重装系统。文档不需要很复杂一个Markdown文件就够了# 双网卡配置说明 ## 网卡 - eth0: 203.0.113.10/24, 网关203.0.113.1, 外网 - eth1: 10.0.0.10/24, 网关10.0.0.1, 内网 ## 路由表 - external (100): 外网默认路由 - internal (200): 内网默认路由 ## 策略规则 - from 203.0.113.10 lookup external - from 10.0.0.10 lookup internal ## 持久化 - CentOS: /etc/sysconfig/network-scripts/ - 自定义表: /etc/iproute2/rt_tables我在实际运维中的体会是双网卡配置本身不难难的是持久化和排查。命令行配通只是第一步重启后还能通、出问题能快速定位才是真正考验功力的地方。建议每次配置完都重启验证一次并且把排查命令整理成脚本下次遇到问题直接跑脚本比凭记忆敲命令靠谱得多。