2615 字
约 8 分钟
13
S15-X12 · 进程与端口排查:ss、lsof 与抓包

"服务连不上"这句话里藏着四层不同的事实:进程在不在、端口听没听、连接能不能建立、数据有没有回来。绝大多数排障时间浪费在跳步——一上来就抓包,抓了半天才想起看进程是不是根本没起来。这篇把四层拆开,每层配一组命令。


1. 排查顺序:先把四层分开

层 问题 一条命令 否证后的方向
1 进程 进程活着吗 ss -lntp 查 8080 启动失败,去看应用日志
2 端口 在本机听了吗 ss -lnt 查 8080 监听地址错,或启动没完成
3 连接 能建立 TCP 吗 curl -v telnet://127.0.0.1:8080 防火墙、安全组、监听地址
4 数据 有响应回来吗 curl -v http://TARGET/PATH 上游处理、超时、反代配置

顺序不能反。第 2 层已经否了还去抓包,抓到的只会是空的。第 1 与第 2 层的区别也很重要:进程活着不代表在监听,监听也不代表进程是你以为的那个。


2. ss:替换 netstat 的主力

netstat 在多数发行版已不默认安装,ss 直接读内核,更快也有更多过滤能力。

$ ss -lntp                 # TCP 监听 + 端口 + 进程
$ ss -tnp state established # 只看已建立连接
$ ss -tnp state time-wait | wc -l
$ ss -s                    # 全局统计:各状态计数
$ ss -tnp dst 10.0.0.5:3306 # 按对端过滤
组合 含义 用在哪一层
-lntp 监听、数字端口、TCP、带进程 第 1、2 层
-tnp 已建立/所有 TCP 连接带进程 第 3 层
state <NAME> 按状态过滤 判断堆积类型
-s 汇总统计 一眼看全机状态分布
-i 接口与队列信息 判断 backlog 是否溢出

-p 需要 root 或对应进程属主,非 root 时进程列是空的——这不是"没有进程",是权限不足。-i 输出里的 Send-Q 在监听 socket 上是 backlog 队列上限,在连接 socket 上是未确认字节数,两个含义别混。


3. 谁占着端口:lsof 与 fuser

$ lsof -i :8080 -nP
$ fuser -n tcp 8080
$ fuser -k -n tcp 8080     # 强杀占用者,慎用
工具 输出 适合
ss -lntp 监听 socket 与进程 常规确认
lsof -i :PORT 进程、fd、协议、状态 查具体是哪个进程/可执行文件
fuser -n tcp PORT 仅 PID 脚本里快速取 PID
lsof -p PID 该进程打开的全部 fd 判断是否持有连接/文件

-nP 关掉反解,避免 DNS 卡住命令(这一点在 DNS 本身有问题时特别明显)。


4. 连接状态:四种堆积各有病根

状态 含义 大量出现说明什么
SYN-SENT 客户端已发 SYN,等 SYN-ACK 对端不响应:网络不通、被丢包、端口没开
SYN-RECV 服务端收到 SYN,已回 SYN-ACK 客户端没收到回包,或 SYN Flood / backlog 满
CLOSE_WAIT 对端已 FIN,本端还没 close 应用没关连接:连接泄漏、没关流、没设读超时
TIME_WAIT 主动关闭方的等待态 短连接高频,正常现象,通常不该调
ESTABLISHED 正常 看数量是否远超预期并发

CLOSE_WAIT 是最有诊断价值的一个:它由本地应用造成,不是网络问题。堆积意味着代码里拿到了连接却没在异常路径关闭——典型是 try 里开了流、finally 里没关,或者用了连接池但没配空闲回收。

TIME_WAIT 相反,是主动关闭方的正常状态,等 2×MSL 自然消失。只有确认短连接量确实极高、端口耗尽时才考虑 net.ipv4.tcp_tw_reuse=1;靠改内核参数掩盖短连接设计,收益有限且容易踩坑。真正常用的两个观察口径是 ss -s 的总量,以及 netstat -s(或 nstat)里的重传、丢包计数。


5. 监听地址:127.0.0.1 与 0.0.0.0 的区别

LISTEN 0 511 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 511 0.0.0.0:8080   0.0.0.0:*

第一行只能被本机回环访问。容器里跑的服务如果绑 127.0.0.1,端口映射出去也连不上——因为容器的回环不是宿主的回环。这是"curl localhost 通、外部不通"的第一嫌疑,第二嫌疑才是防火墙。

三种检查一起做,避免只查一层:

$ ss -lnt | grep :8080                       # 监听地址
$ sudo nft list ruleset | grep -i 8080       # 主机防火墙
$ sudo iptables -S | grep -i 8080            # 旧栈
# 云主机还有一层安全组,只能在控制台/API 侧确认

主机防火墙与云安全组是两层独立策略,两层都放行才通。


6. 抓包:最小可用的一组命令

$ sudo tcpdump -i any -nn -c 200 'port 8080'
$ sudo tcpdump -i eth0 -nn -s 0 -w /tmp/cap.pcap 'host 10.0.0.5 and port 3306'
$ sudo tcpdump -i any -nn -A 'port 8080' | grep -E '^(GET|POST|HTTP)'

-nn 关掉反解、-c 限制包数、-s 0 抓完整包、-w 落盘。不设 -c 的抓包在故障期间会长到几个 G,写盘写满反而制造新故障。

抓不到包只有三类原因:

现象 原因 验证
完全无输出 接口选错 用 -i any 或 ip -br a 确认接口
有包但无目标流量 过滤器写错(端口写反、少了 host) 先去掉过滤器只抓 20 个包看有没有流量
有出无回 请求根本没到,或被中间设备丢弃 内核计数里的 drop:nstat -az

容器内抓包是另一个视角。docker exec 进容器抓,看到的是容器 namespace 的流量;在宿主机抓 -i docker0(或对应 veth),看到的是经过网桥的流量。NAT 之后源地址会变,所以"宿主机抓到的是容器 IP、应用日志里是网关 IP"完全正常,不需要修。


7. curl 分段定位与 DNS

$ curl -v http://TARGET/api/health
$ curl -o /dev/null -s -w 'dns=%{time_namelookup} conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://TARGET/
阶段 变量 变大说明什么
DNS 解析 time_namelookup 解析慢或走了错误的 resolver
TCP 建连 time_connect 网络/防火墙/对端 backlog
TLS 握手 time_appconnect 证书链、OCSP、CPU 不足
首字节 time_starttransfer 上游处理慢,与应用层有关
总计 time_total 与上面几项对比看差在哪

time_connect 大而 time_starttransfer 不大,问题在网络;反过来问题在应用。这一对比能省掉很多无效抓包。

DNS 侧只看一个文件:

$ cat /etc/resolv.conf
$ getent hosts TARGET          # 走系统解析(含 nsswitch)
$ dig +short TARGET @127.0.0.53

getent 与 dig 结果不一致,说明差异出在 nsswitch.conf 的顺序(files 在前会先读 /etc/hosts)而不是 DNS 本身。容器内 /etc/resolv.conf 由 daemon 生成,search 域与 options ndots 都可能与宿主机不同,跨集群访问短域名时优先写全限定名。


8. 进程层面:ps、pmap 与 /proc

$ ps -eo pid,ppid,rss,vsz,etime,cmd --sort=-rss | head
$ pmap -x <PID> | tail -1
$ ls -l /proc/<PID>/fd | wc -l
$ cat /proc/<PID>/status | grep -E 'Threads|VmRSS|VmSwap'
位置 看什么 异常信号
ps -o rss 常驻内存 单调增长不回落
pmap -x 内存映射明细 大量匿名映射 → 堆或直接内存
/proc/<PID>/fd 打开的文件与 socket 数量接近上限 → 泄漏
/proc/<PID>/status 线程数、RSS、Swap Threads 持续上升 → 池泄漏
/proc/<PID>/limits open files 上限 与 fd 数量对照

/proc/<PID>/exe 指向进程实际运行的可执行文件,比 which 更可信——which 只反映当前 shell 的 PATH。


9. 排错

现象 原因 处理
服务重启了但端口还是旧的 旧进程没退,新进程绑不上端口静默失败 ss -lntp 找到 PID,确认后 kill,再重启
本机 curl 通、外部不通 只听 127.0.0.1,或防火墙/安全组未放行 改监听 0.0.0.0;两层策略一起查
ss 看不到容器里的进程 容器在独立 network namespace docker exec 进容器执行,或 nsenter -t PID -n
抓到的包源地址是容器 IP 抓点在 NAT 之前 在宿主机网桥侧或容器内对比抓
抓包文件几十 GB 没设 -c 或没设 -w 轮转 先 -c 限量,再落盘分析
CLOSE_WAIT 上千 应用未关闭连接 查流关闭与连接池空闲回收,不是调内核
ss 的进程列是空的 非 root / 非属主 用对应权限执行
lsof 卡住不返回 反解 DNS 慢 加 -nP
排查命令本身超时 resolver 不可用 临时用 curl --resolve 指定 IP 验证链路

10. 边界

场景 更合适的工具 理由
需要连接历史与聚合 eBPF / ss 定时采样 单次快照看不到趋势
需要应用层协议解析 tcpdump 存 pcap + Wireshark/tshark 手工看裸包效率低
需要跟踪系统调用 strace -f -p PID 网络层无法说明阻塞点
需要观测队列溢出 ss -i 的 Send-Q、nstat 的 ListenOverflows 只看状态计数会漏
多节点拓扑内的连通性 分布式探针 / mesh 侧指标 单机命令无法区分中间节点

11. 自测

  1. ss -lntp 里看不到进程名,最先怀疑什么?
  2. 大量 CLOSE_WAIT 与大量 TIME_WAIT,哪个是本地应用的锅,为什么?
  3. listen 0 511 127.0.0.1:8080 这行里 511 代表什么?
  4. tcpdump 完全没输出,可能的三类原因是什么?
  5. curl -w 里 time_connect 大而 time_starttransfer 小,问题在哪一层?
  6. 容器里 curl 127.0.0.1:PORT 通、宿主机连不上,为什么?
S15-X12 · 进程与端口排查:ss、lsof 与抓包
https://www.clxhxhhr.top/posts/5869/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。