☰
Docker容器网络排查:TCP连接与带宽监控实用技巧
2026/10/21 1:07:25 网站建设 项目流程

排查容器网络问题的时候,最烦的就是“容器起来了,业务却不通”“外网连不上”“带宽突然被打满”,这时候你第一反应肯定是进容器看一眼连接状态。但真等你docker exec -it进去之后,往往发现容器里连netstat都没有,ss也不存在,甚至apt/yum都没法用,瞬间就卡住了。这篇东西就是专门解决这个问题的,核心是怎么查看 docker 容器的 TCP 连接和带宽使用情况,以及遇到工具缺失、权限不足、信息对不上号这些坑时怎么绕过去。内容更适合运维、后端开发、以及所有在用 Docker 跑业务但没系统梳理过网络排查手段的人。

先说一句总的思路:查看容器的网络状态分两条线,一条是连接维度,看的是 TCP 连接数、状态、对端地址、端口,解决的是“谁连着谁”“连接卡在哪儿”的问题;另一条是带宽维度,看的是实时速率和累计流量,解决的是“谁在占带宽”“跑得有多猛”的问题。两条线的排查手段不一样,先搞清楚要查什么,才能不走弯路。

1. 内容整体设计与思路拆解

1.1 先搞清楚要查什么:TCP连接与带宽是两个层面的问题

很多人在排查容器网络的时候习惯性先跑docker stats,看到网络那一栏有个数字就以为是带宽。其实docker stats里的NET I/O展示的是容器从启动以来累计的收发字节数,不是实时速率,更不能反映 TCP 连接细节。你要是靠这个判断“当前谁在跑满带宽”,基本等于看着里程表猜车速,误差大得离谱。

TCP 连接状态和带宽使用是两个维度:

  • TCP 连接维度:看的是 socket 级别的状态,比如ESTABLISHED、TIME_WAIT、SYN_SENT、CLOSE_WAIT等等,能定位到具体端口、对端 IP,能回答“这个容器目前有多少个连接”“为什么连接堆积”“哪个端口在被扫描”这类问题。
  • 带宽维度:看的是数据流动的速度,单位是 B/s、KB/s,能回答“某个容器现在下载速度多少”“哪个进程在上传大文件”“是不是有容器在跑 P2P”这类问题。

这两个维度在排查时经常需要交叉使用,比如你先发现带宽被占满,然后要顺着进程找到容器,再通过容器的 TCP 连接列表锁定对端地址,最后判断是正常业务流量还是异常外联。所以下面所有实操,我都是按照“先看连接、再看带宽、最后做关联定位”的思路来组织。

1.2 排查思路选型:容器内查、宿主机查、旁路抓包

围绕“查容器网络”这件事,实际上有三类思路,各有各的适用场景:

  1. 进入容器内查(docker exec+netstat/ss):直观,能直接看到容器内 socket 的视角,但最大的痛点是基础镜像为了保持精简,大多不带网络排查工具。另外容器内看到的是容器自己的网络命名空间,只能看到容器所属的地址空间,理解成本低。
  2. 在宿主机上查,再映射到容器(ss -tnp+PID反查容器):所见即所得,宿主机工具链丰富,能看到 veth 对、iptables 规则、宿主机整体带宽,适合跨容器对比,但需要做一层“PID -> 容器”的映射。
  3. 旁路抓包(tcpdump、iftop、nethogs):能看到最底层的流量真相,适合深度定位,比如抓docker0网桥的所有流量、分析握手失败原因、观察重传率等,也是最耗时的一种手段。

我的习惯是优先走宿主机视角,因为容器内工具缺失太常见了,临时装包又受网络限制,折腾一圈反而拖慢排查。遇到需要确认容器内进程视角的细节时,再用nsenter切进容器的网络命名空间,直接复用宿主机的工具链,这个方案比在容器里装 net-tools 靠谱得多。

2. 核心细节解析与实操要点

2.1 容器里工具缺失怎么办:两种务实出路

你进到一个精简容器里,敲netstat,系统提示bash: netstat: command not found,这时候不要慌,也不要立刻apt-get install net-tools,因为很多生产环境是不允许在容器里装东西的,而且即使能装,临时装包对排查黄金时间也是一种浪费。

第一种出路是在宿主机上用nsenter切进容器的网络命名空间,然后直接使用宿主机的ss或netstat,具体命令:

# 先拿到容器的主进程 PID docker inspect -f '{{.State.Pid}}' <容器名或容器ID> # 用 nsenter 进入该容器的网络命名空间 nsenter -t <PID> -n ss -tnp

其中-t指定目标进程 PID,-n表示进入进程的网络命名空间。执行完后,你看到的 socket 列表就是容器内部的视角,但工具是宿主机的,完全绕开了“容器里没有工具”的问题。这个方案我实测过很多次,稳定性很好,唯一要求是宿主机上要有nsenter、ss这些基础工具,大部分发行版默认就有。

第二种出路才是临时在容器里补充工具。如果你是 Debian/Ubuntu 系镜像,用的是apt,如果是 CentOS/RedHat 系,用yum。注意一点:容器里执行安装前,很可能 DNS 和镜像源都是不通的,装包失败概率不小。所以这个方案只建议在测试环境或者容器网络本身没问题的前提下使用。

2.2 连接状态与关键参数怎么看

当你成功拿到连接列表后,最关键的字段是State、Local Address:Port、Peer Address:Port,以及进程信息。以ss -tnp的输出为例:

State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 0 172.17.0.2:8080 10.0.0.5:52034 nginx TIME_WAIT 0 0 172.17.0.2:8080 10.0.0.8:33912 -
  • Recv-Q和Send-Q:接收队列和发送队列的字节数。如果Recv-Q长期非零且持续增长,说明对端在发数据但容器内应用没及时读取;如果Send-Q一直很大,通常表示本端写数据但对端不消费,可能是应用瓶颈,也可能是对端宕机导致窗口收缩。
  • State:连接状态。排障时重点关注异常状态,比如大量SYN_SENT说明出方向连接建立不了,可能被防火墙拦截;大量TIME_WAIT通常只是高并发短连接的自然现象,但如果超过几万,需要考虑开启tcp_tw_reuse之类的内核参数;最棘手的是CLOSE_WAIT堆积,一般是应用程序没调用 close 关闭 socket,属于代码层面的泄漏。
  • Peer Address:Port:对端地址。如果出现大量随机高端口连接一个固定 IP,或者连接一堆没见过的地址,要警惕是否被入侵外联了。

另外补充一个实用技巧:ss -tnp里的-n表示不做 DNS 反解,显示纯数字地址,排障时一定要带上,否则 DNS 反解会把命令拖得很慢,而且容易误事。-p表示显示进程信息,如果不加这个参数,你就看不到进程名和 PID,后面做 PID 与容器映射就无从谈起。

2.3 网络模式决定了你的排查方式

Docker 容器的网络模式对排查方式有直接影响,不同模式下“连接从哪儿看”是完全不一样的。常见的三种模式:

  • bridge 模式(默认):容器有自己的 IP,通常在172.17.0.0/16网段,NAT 到宿主机出去。排查时可以在宿主机上看到对应的 veth 网卡,流量经过docker0网桥。此时在宿主机上执行ss -tnp能看到进程对应的 socket,对端和本端地址都是 NAT 前的地址,配合容器 IP 就能对上号。
  • host 模式:容器直接共享宿主机的网络命名空间,没有独立 IP,端口直接绑在宿主机上。这种模式下在宿主机上ss -tnp看到的连接就是容器进程的连接,进程 PID 直接对应,排查反而最简单。难点在于无法通过 IP 区分容器本身,只能靠进程名。
  • macvlan/ipvlan 模式:容器有独立 IP,但是跟宿主机处于同一个物理网络二层。这种模式下流量不经过 docker0,宿主机的ss只能看到宿主机自己的 socket,容器内部的连接在宿主机上不一定全部可见。排查时通常得靠ps、PID -> 容器映射,或者直接进容器命名空间看。

判断容器网络模式的命令:

docker inspect -f '{{.HostConfig.NetworkMode}}' <容器名>

知道模式之后,你再决定用哪种排查手段,能少走很多弯路。

3. 实操过程与核心环节实现

3.1 完整流程:从进入容器到定位每个连接

我这里给一套可以直接复制的完整排查流程,适用于“业务异常,需要快速确认容器连接状态”的场景。

第一步,拿到容器列表,确认目标容器的名字或 ID:

docker ps | grep <关键词>

第二步,确认容器的主进程 PID:

docker inspect -f '{{.State.Pid}}' <容器名或容器ID>

第三步,直接查看该容器网络命名空间内的 TCP 连接列表:

nsenter -t <PID> -n ss -tnp

如果想看 UDP 连接,可以改成ss -unp;想看监听端口,可以加-l,即ss -tlnp。这一步完成之后,你面前就是一张清晰的连接表:哪些是LISTEN状态的监听端口,哪些是ESTABLISHED状态的活跃连接,哪些是积压的TIME_WAIT。

第四步,如果连接数非常多,想做一个统计分类,例如看各种状态的连接数量,可以直接在 nsenter 场景下用 awk 做统计:

nsenter -t <PID> -n ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

这样输出会类似于:

120 TIME_WAIT 85 ESTAB 10 SYN_SENT

看到大量SYN_SENT就要赶紧查防火墙规则、对端端口通不通;看到TIME_WAIT上万就要考虑是否需要调整内核参数来复用连接;看到ESTAB数量超过预期,就要警惕连接池是否配置过大或者受到连接耗尽攻击。

3.2 宿主机视角:通过PID反查容器并统计连接数

很多时候你不需要进容器,直接在宿主机上就能看到所有容器进程的 socket,然后按 PID 归类。比如你在宿主机上跑了ss -tnp,发现某个 PID 的进程有几百条TIME_WAIT,你想知道这个 PID 对应哪个容器,可以这样做:

# 方法1:从 PID 找出容器 ID(高版本 Docker 才有这个字段) cat /proc/<PID>/cgroup | grep docker # 输出类似: # 12:pids:/docker/5a4f9c2b1a0f...

/proc/<PID>/cgroup里会包含容器 ID,虽然路径在不同 Docker 版本里可能存在差异,但基本都有 docker 标识。拿到容器 ID 前缀之后,再docker ps --no-trunc | grep <容器 ID>就能得到完整容器信息。

更省事的做法是用脚本一次搞定:把宿主机的连接按 PID 分组,然后打印出容器名。下面这个脚本是我平时常用的,作用是在宿主机上把每个容器对应的连接数统计出来,适合容器很多、需要快速对比“谁的连接最多”的场景:

#!/bin/bash # 遍历宿主机上所有 docker 容器,统计每个容器当前 TCP 连接数 for cid in $(docker ps -q); do cname=$(docker inspect -f '{{.Name}}' "$cid" | sed 's/\///') pid=$(docker inspect -f '{{.State.Pid}}' "$cid") count=$(nsenter -t "$pid" -n ss -tan 2>/dev/null | wc -l) echo "$cname $count" done | sort -k2 -rn

这里有个细节要注意:ss -tan | wc -l会把表头那一行也统计进去,如果只想统计纯连接行,可以用awk 'NR>1'过滤。对排查来说,多一行表头影响不大,但写脚本做监控告警时,误差就不能忽视。

如果你想知道某个容器当前连接了哪些外部 IP 和端口,直接执行:

nsenter -t <PID> -n ss -tnp | awk 'NR>1 {print $5}' | sort | uniq -c | sort -rn

$5是对端地址端口列,统计后能一眼看出哪些外部 IP 与容器有大量连接,这对排查“到底谁在连接我的容器”非常有用。

3.3 带宽查看:docker stats与实时流量监控

带宽排查的第一步,你大概率会去看docker stats的NET I/O列,但前面已经说了,这是累计值,不是实时速率。假设容器已经运行了三天,docker stats显示NET I/O: 3.5GB / 2.1GB,这只能说明该容器收发了 3.5GB、发送了 2.1GB,你无法知道“就这一秒跑多快”。所以请看下面的实时监控方案。

方案一:宿主机上用iftop按网卡看实时流量。容器默认走docker0网桥(bridge 模式),按网卡名过滤后能看到所有容器的聚合流量:

iftop -i docker0 -n -N

-n不反解域名,-N不显示端口号(如果不想让端口占太多屏幕可以不加-N)。iftop界面右侧会展示两列速率,分别是每秒发送和接收的带宽,下部还会显示累计值。缺点是无法直接区分是哪个容器在跑流量,只能看到整体。要区分容器,可以按veth网卡逐个看,或者用方案三。

方案二:宿主机上用nethogs按进程看实时带宽:

nethogs -p docker0

nethogs会列出当前在docker0上产生流量的每个进程,并显示实时的 Sent 和 Received 速率。由于容器里的进程在宿主机上本身就是一个进程,你会看到进程名,比如nginx、java,配合 PID 反查容器就能定位到具体容器。这个工具是我排查“带宽突然被打满”时最常用的。

方案三:进入容器命名空间,用宿主机工具查容器内网卡速率:

nsenter -t <PID> -n iftop -n -t -s 5

这条命令会在容器的网络命名空间内运行 iftop,持续 5 秒后退出,适合只查当前容器的瞬时速率。也可以用ip -s link查看容器内所有网卡的收发累计字节和丢包数:

nsenter -t <PID> -n ip -s link

里面RX: bytes packets errors dropped这些字段能反映累计收发数据,重点看dropped和errors是不是在飙涨,如果dropped持续增加,说明容器网卡可能有丢包。

4. 常见问题与排查技巧实录

4.1 命令报错与权限问题速查

排查过程中最常见的 4 个坑,我逐个列一下,都是实测过的。

坑一:nsenter: failed to execute ss: No such file or directory

这个报错的意思是宿主机上就没有ss命令,一般是宿主机太精简,连iproute2都没装。解决办法直接装:

# Debian/Ubuntu apt install iproute2 # CentOS/RHEL yum install iproute

坑二:nsenter: cannot open /proc/<PID>/ns/net: No such file or directory

这个一般是因为 PID 写错了,或者容器已经被停止,进程不在了。排查时先确认容器还在运行,重新执行docker inspect -f '{{.State.Pid}}'拿新的 PID。

坑三:在容器内执行netstat提示权限拒绝(Operation not permitted)

部分容器在非 privileged 模式下,对某些 netlink 操作是受限的。解决方式是改用ss(如果容器里有),或者直接用nsenter切命名空间,不要在容器里折腾权限。还有一种情况是容器用了非 root 用户启动,此时在容器内直接netstat很可能看不到进程信息,这属于内核的限制,用nsenter带上宿主机的权限反而没问题。

坑四:docker stats显示 NET I/O 数值一直不变

这不一定是容器没流量,有可能是 Docker 的统计接口对某些网络驱动支持不完整。比如 macvlan 模式下,docker stats的 NET I/O 经常不更新,因为流量根本不走默认的 Docker 网络统计路径。遇到这种情况,不要纠结,直接用iftop/nethogs看实时流量,或者进容器看ip -s link。

4.2 实际案例:两个典型的网络异常排查

案例一:容器外连数暴涨,怀疑被入侵

有次我接到反馈,说某个业务容器好像连了特别多外部地址,一开始还以为是正常业务调用,跑了下统计命令:

docker inspect -f '{{.State.Pid}}' <容器名> nsenter -t <PID> -n ss -tnp | awk 'NR>1 {print $5}' | sort | uniq -c | sort -rn | head -20

结果发现排名第一的外部 IP 的端口是 6667,明显不是业务端口,再查一下这个地址的归属和连接状态,确认是异常外联。这种场景下,光看docker stats是发现不了任何问题的,但连接统计能很快暴露异常对端。所以我在日常巡检中,会给核心容器加一个简单的连接数告警,超过阈值就自动推送连接排名,这样即使不是被入侵,也能提前发现连接泄漏或调用方异常。

案例二:容器带宽被打满,定位占用进程

还有一次是宿主机带宽被打满,在线业务卡顿。我先用nethogs -p docker0 -t -c 8抓了 8 秒的进程流量,很快就看到一个 Java 进程的 Sent 速率飙到了 300Mbps,远超正常值。然后通过 PID 反查容器:

cat /proc/<PID>/cgroup | grep docker docker ps --no-trunc | grep <容器ID>

一查,是个跑数据同步任务的容器,定时任务没设置速率限制,把带宽吃满了。解决方案就是给该容器加--network侧的 QoS 策略(比如通过 tc 限制带宽),或者修改容器的同步并发参数。整个过程不超过 5 分钟。

4.3 我的长期建议:把排查命令固化成脚本

排查手段再熟练,每次临时敲命令还是容易漏步骤,尤其是容器数量多的时候。我建议把自己常用的排查命令整理成一个小脚本或者一组 alias,比如放到宿主机/usr/local/bin/dockernet,一行命令输出容器名、PID、TCP 连接数、实时速率前几名:

#!/bin/bash # 简单示例:列出所有容器及其TCP连接数 for cid in $(docker ps -q); do cname=$(docker inspect -f '{{.Name}}' "$cid" | sed 's/\///') pid=$(docker inspect -f '{{.State.Pid}}' "$cid") conn=$(nsenter -t "$pid" -n ss -tan 2>/dev/null | awk 'NR>1' | wc -l) echo "$cname $pid $conn" done | column -t | sort -k3rn

加了执行权限后,日常排查就不用再逐条敲命令了。脚本本身很简陋,但胜在直接,真正做到“一行命令看全量”。

最后再分享一个小技巧:当你需要频繁查看某个容器的网络命名空间时,设一个别名能省很多事:

alias cns='nsenter -t $(docker inspect -f "{{.State.Pid}}" $(docker ps -lq)) -n'

这样,cns ss -tnp就能直接查看最近启动容器的连接,而不用每次查 PID 再复制粘贴了。我个人用下来,这个别名在频繁切换容器排查时提升效率非常明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询