前言
服务器能ping通,但业务就是跑不起来。
日志里没有报错,监控一切正常,但用户反馈时好时坏。
这种问题最折磨人------因为它不像代码bug那样有明确的错误信息,你甚至不知道该从哪查起。
过去几年,我在生产环境踩过不少网络配置的坑。挑5个印象最深的,每个都是"排查两小时,改一行配置就好了"的故事。
坑1:MTU不匹配------大包神秘失踪,小包完全正常
现象
公司把业务从自建机房迁到某云厂商,迁移完测试:小文件秒传,大文件必失败。
curl请求一个100KB的接口,正常。请求一个5MB的文件,超时。
排查过程
bash
# 用不同大小的包测试连通性
ping -s 1400 目标服务器IP # 正常
ping -s 1472 目标服务器IP # 正常
ping -s 1473 目标服务器IP # 超时,丢包100%
包大小到1473字节就挂了,加上IP头20字节+ICMP头8字节=1501字节,刚好超过1500。
MTU问题。
进一步确认:
bash
# 查看各网卡MTU
ip link show
# 结果
eth0: mtu 1500 # 云主机网卡,正常
tun0: mtu 1400 # VPN隧道网卡,小了!
公司有一条VPN隧道连通自建机房和云环境,VPN封装会额外增加包头开销(IPSec/GRE等),导致实际可用MTU变小。
但云主机的eth0 MTU还是1500,当发送一个1500字节的包时,VPN隧道无法承载,包被静默丢弃(没有ICMP Fragmentation Needed回包,因为中间设备禁了ICMP)。
根因
scss
应用层发出大包 → eth0(MTU 1500)放行 → 进入VPN隧道(MTU 1400) → 超过MTU → 要分片
→ 但DF位(Don't Fragment)被设置了 → 无法分片 → 静默丢弃
解决
bash
# 方案1:调小网卡MTU
ip link set eth0 mtu 1400
# 方案2:在iptables中对VPN流量做MSS钳制(更优雅)
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
教训
跨网络环境迁移,第一件事查MTU。 云环境、VPN、隧道、Docker overlay网络,都可能引入额外的包头开销。而且MTU问题的表现非常具有欺骗性------ping正常、小包正常、TCP握手正常,就是传大文件挂掉。
坑2:iptables规则顺序------"明明加了放行规则,为什么还是不通"
现象
新部署了一台MySQL从库,需要允许应用服务器 10.0.1.50 访问3306端口。
运维同事加了规则:
bash
iptables -A INPUT -s 10.0.1.50 -p tcp --dport 3306 -j ACCEPT
加完之后,还是连不上。
排查过程
bash
# 看完整规则链
iptables -L INPUT -n --line-numbers
输出:
sql
num target prot source destination
1 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
3 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
4 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpts:3306:3307
5 ACCEPT tcp -- 10.0.1.50 0.0.0.0/0 tcp dpt:3306
看到了吗?
第4条规则在第5条前面。 iptables是从上到下逐条匹配,匹配到就停止。
流量走到第4条------目的端口3306,命中,DROP。根本不会走到第5条。
-A(Append)是追加到末尾,而之前的规则里已经有一条拒绝3306的规则排在前面了。
解决
bash
# 用 -I 插入到指定位置(第4条前面)
iptables -I INPUT 4 -s 10.0.1.50 -p tcp --dport 3306 -j ACCEPT
修正后的规则链:
sql
1 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
3 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
4 ACCEPT tcp -- 10.0.1.50 0.0.0.0/0 tcp dpt:3306 ← 现在在前面了
5 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpts:3306:3307
教训
iptables的规则顺序就是优先级。 写防火墙规则时,必须清楚整条链的结构。建议用
--line-numbers查看后再操作,而不是盲目的-A追加。更稳妥的做法是用配置管理工具(Ansible等)管理整条规则链,而不是手动一条一条加。
坑3:内核参数没调------TIME_WAIT堆积到端口耗尽
现象
一个高并发的API服务,白天运行正常,每天下午4点左右开始出现大量连接超时。
重启服务后恢复,第二天同一时间又挂。
排查过程
bash
# 查看连接状态分布
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn
结果:
objectivec
28000 TIME_WAIT
3000 ESTABLISHED
500 CLOSE_WAIT
200 SYN_SENT
28000个TIME_WAIT连接。
TIME_WAIT是TCP四次挥手后主动关闭方的状态,会保持2MSL(默认60秒)。每个TIME_WAIT占一个端口。
bash
# 查看可用端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768 60999
可用端口约28000个,全被TIME_WAIT占满了。新的出站连接(比如调用下游服务)无端口可用,直接失败。
根因
这个API服务的特点是:高并发、短连接、频繁请求下游HTTP接口。
每次请求下游都新建TCP连接,请求完立刻关闭。每秒几千个短连接,TIME_WAIT快速堆积。
解决
bash
# /etc/sysctl.conf
# 允许TIME_WAIT状态的socket被快速回收
net.ipv4.tcp_tw_reuse = 1
# 扩大可用端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 缩短FIN_WAIT2超时时间
net.ipv4.tcp_fin_timeout = 15
# 加大最大连接跟踪数
net.core.somaxconn = 65535
net.ipv4.tcp_max_tw_buckets = 30000
bash
# 生效
sysctl -p
更根本的解决方案:改用连接池或长连接。
python
# 反面示例:每次请求都新建连接
import requests
for item in items:
requests.get(f"http://downstream/api/{item}") # 每次都建新TCP连接
# 正面示例:复用连接
session = requests.Session()
for item in items:
session.get(f"http://downstream/api/{item}") # 复用TCP连接
教训
短连接是TIME_WAIT堆积的元凶。 高并发场景下,必须用连接池或长连接。内核参数调优能缓解,但治标不治本。上线前压测时,一定要关注连接状态分布,不要只看QPS和响应时间。
坑4:安全组和iptables同时生效------"我已经放行了啊"
现象
新上了一台云服务器部署Redis,配置了iptables允许 10.0.1.0/24 网段访问6379端口。
bash
iptables -A INPUT -s 10.0.1.0/24 -p tcp --dport 6379 -j ACCEPT
但应用服务器 10.0.1.50 就是连不上。
排查过程
从Redis服务器本机:
bash
# 本机能不能连
redis-cli -h 127.0.0.1 -p 6379 ping
# PONG ✓
# iptables规则对不对
iptables -L INPUT -n | grep 6379
# ACCEPT tcp -- 10.0.1.0/24 0.0.0.0/0 tcp dpt:6379 ✓
# 监听有没有问题
ss -tlnp | grep 6379
# 0.0.0.0:6379 ✓
一切正常。但就是连不上。
突然想到------这是云服务器。
登录云厂商控制台一看:
markdown
安全组规则:
入站规则:
TCP 22 0.0.0.0/0 允许
TCP 80 0.0.0.0/0 允许
TCP 443 0.0.0.0/0 允许
(没有6379的规则)
根因
云服务器的网络流量路径:
客户端 → 云厂商安全组(第一道门) → iptables(第二道门) → 应用
两道门都要放行。 安全组没开6379,流量在云平台层面就被拦住了,根本到不了iptables。
解决
在云厂商控制台的安全组中添加入站规则:
yaml
TCP 6379 10.0.1.0/24 允许
教训
云环境下有两层防火墙:安全组 + iptables。 排查"连不上"的问题时,两层都要检查。建议把安全组当作粗粒度策略(按环境/角色开放大范围端口),iptables做细粒度控制(按具体IP和端口)。别两边重复配,容易漏。
坑5:网卡Bond模式选错------交换机重启后半数服务器断网
现象
机房交换机做了一次计划内重启,重启完之后,一半服务器网络正常,一半断了。
断网的服务器需要手动重启网络服务才能恢复。
排查过程
所有服务器的网卡都做了Bond(双网卡绑定,防止单网卡故障导致断网)。
检查网络正常的服务器:
bash
cat /proc/net/bonding/bond0
# Bonding Mode: IEEE 802.3ad Dynamic link aggregation
# 802.3ad info
# LACP rate: slow
检查断网的服务器:
bash
cat /proc/net/bonding/bond0
# Bonding Mode: fault-tolerance (active-backup)
Bond模式不一致。 一部分是 802.3ad(LACP),一部分是 active-backup。
根因
交换机侧配置了LACP协商。当交换机重启时:
- 802.3ad模式的服务器:交换机起来后LACP重新协商,自动恢复链路
- active-backup模式的服务器:不参与LACP协商,交换机重启后主链路断了,切到备链路,但备链路在交换机侧没有正确配置(access VLAN不对),所以断网
这批服务器是不同时间上架的,早期的用active-backup,后来统一要求改802.3ad,但有一批漏改了。
解决
统一所有服务器的Bond模式为 802.3ad:
bash
# /etc/sysconfig/network-scripts/ifcfg-bond0
BONDING_OPTS="mode=802.3ad miimon=100 lacp_rate=slow xmit_hash_policy=layer3+4"
同时写了一个Ansible playbook,确保所有服务器配置一致:
yaml
- name: Verify bond mode
command: cat /proc/net/bonding/bond0
register: bond_info
changed_when: false
- name: Alert if bond mode mismatch
debug:
msg: "WARNING: Bond mode is not 802.3ad on {{ inventory_hostname }}"
when: "'802.3ad' not in bond_info.stdout"
教训
基础设施配置不一致是定时炸弹。 平时看不出来,一遇到故障就暴露。必须用配置管理工具保证一致性,并定期巡检。Bond模式的选择不是随意的,要和交换机侧协商好。
总结
| 坑 | 根因 | 一句话教训 |
|---|---|---|
| MTU不匹配 | VPN/隧道降低可用MTU | 跨网环境迁移先查MTU |
| iptables顺序 | 规则从上到下匹配 | -A追加不如-I插入到指定位置 |
| TIME_WAIT堆积 | 短连接+高并发 | 长连接/连接池是正道,内核调参是辅助 |
| 安全组遗漏 | 云环境两层防火墙 | 安全组和iptables都要放行 |
| Bond模式不一致 | 配置漂移 | 用配置管理工具保持一致性 |
这五个坑有一个共同点:都不是代码bug,都是基础设施配置问题。 代码出了问题有日志、有报错、有堆栈,配置出了问题可能什么日志都没有,排查起来全靠经验和对网络原理的理解。
如果你也是做运维或者后端开发的,建议把这几条加到你的上线检查清单里。能省掉好几个凌晨两点的电话。
下一篇写 10个排查网络问题的命令行工具实战,每个工具配真实输出样例,收藏向。感兴趣可以关注一下。