ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker容器网络排查:实时查看TCP连接与带宽的实用指南

Docker容器网络排查:实时查看TCP连接与带宽的实用指南 1. 这个需求从哪里来排查容器网络问题的真实场景先说个我自己的经历。上个月线上有个Java服务容器用户反馈接口时不时卡顿监控面板上CPU、内存都很正常但业务就是慢。最后查了半天问题出在容器里有个连接到了外部数据库的长连接异常堆积TCP连接数飙升到几万把整个宿主机的连接表都占满了。那时候我才意识到平时用docker stats看CPU和内存已经是习惯动作但对容器的TCP连接状态和带宽使用情况很多人其实是没有概念、也没有现成工具的。这个需求并不冷门。无论你是开发、运维还是SRE都在下面几个场景里遇到过同样的困惑线上容器变慢想确认是不是某个容器把带宽打满了容器内应用程序报“Too many open files”怀疑是TCP连接数超限排查容器之间的异常访问想知道某个容器到底在跟谁通信压测时想精确统计单个容器的实时流量而不是用宿主机总流量去猜。这篇教程就是来解决这些问题的。我会从最基础的命令讲起到nsenter、iftop、nethogs这些工具的组合用法再到把操作固化成脚本的完整方案。内容适合有一定Docker基础、但还没深入研究过网络监控的人同时也给老手一些排查思路上的补充。先说结论Docker本身没有一条命令能直接展示“某个容器的TCP连接明细”或“某个容器的实时带宽”。我们需要借助宿主机内核提供的网络命名空间机制再配合网络监控工具来实现。整个思路不复杂但需要你把几个环节串起来。2. 准备工作与工具选型搞清楚该装什么、为什么装2.1 核心原理容器网络与宿主机网络的关系要理解怎么监控容器的网络必须先搞清楚Docker的网络模型。每个Docker容器默认运行在独立的网络命名空间里你可以把它理解为每个容器都有一面看不见的墙墙内有一套自己的网络栈——自己的IP、自己的路由表、自己的防火墙规则、自己的TCP连接表。这和宿主机是隔离的。但容器总得跟外界通信所以Docker创建了一个虚拟网桥默认叫docker0每个容器通过一对虚拟网卡veth连到网桥上就像家里的电脑通过网线连到路由器一样。这就带来一个关键结论容器内的TCP连接和带宽数据在内核里其实是全局共享的只是通过命名空间做了逻辑隔离。所以我们可以用两种思路来查看直接进入容器的网络命名空间让查看命令“以为”自己运行在容器内部在宿主机上通过veth网卡、conntrack表等内核数据反向定位到具体容器。理解了这两条路后面的工具选择就顺理成章了。2.2 工具清单与选型理由先列一个我实际在用的工具清单后面会逐个说用法工具所属环境用途安装方式Ubuntu/Debianss宿主机/容器查看TCP连接状态、监听端口iproute2默认自带nsenter宿主机进入容器网络命名空间util-linux默认自带iftop宿主机按IP显示实时带宽apt install iftopnethogs宿主机按进程显示实时带宽apt install nethogsconntrack宿主机查看连接跟踪表apt install conntrack这里很多人会问为什么不直接在容器里装工具因为大部分生产环境的容器为了保持精简都是基于alpine或distroless镜像构建的里面连bash都没有更别说ss、netstat了。而且你也不应该为了排查问题去给生产容器安装额外软件——这会污染镜像、增加安全风险。所以我的思路是所有监控动作都在宿主机上完成。宿主机装工具、宿主机执行命令、通过nsenter“钻进”容器的命名空间去查看这是最干净、最安全、最通用的做法。2.3 一些辅助信息Docker Desktop环境要注意的事如果你用的是Windows或macOS上的Docker Desktop后面讲的nsenter方案会失效。原因是Docker Desktop本身跑在一个轻量级虚拟机里宿主机你的Windows/macOS和容器的网络命名空间之间隔了一层。这种情况下优先推荐用docker exec方式或者在容器里装好工具再进去也可以用Portainer这类图形化工具。这个差异我后面会在第5章的常见问题里详细展开。3. 查看容器TCP连接的三种姿势从最直接到最深入3.1 容器内直接执行 ss最简单但限制最多如果你只是临时看一下而且容器镜像里有ss或netstat那最直接的办法就是docker exec -it container_name ss -tnp-t表示只看TCP-n不做域名解析-p显示进程信息。输出大致长这样State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 0 172.17.0.2:5432 172.17.0.1:52314 postgres ESTAB 0 0 172.17.0.2:443 192.168.1.100:55120 nginx这个方法的好处是快、直观适合临时用一下。但它在生产环境中往往行不通精简容器里没有ss或netstat容器里没有ps命令时-p参数拿到的进程信息可能显示不出来部分容器以非root用户运行执行ss -p权限不够。如果遇到这种情况别在容器里折腾装包用下一节的nsenter方法。3.2 nsenter 进入容器网络命名空间最优解nsenter是Linux自带的一个工具作用是让一个进程进入另一个进程的命名空间。因为容器本质上就是一个进程我们可以拿到容器主进程的PID然后进入它的网络命名空间执行任意网络命令。执行两步# 获取容器主进程PID PID$(docker inspect -f {{.State.Pid}} container_name) # 进入容器的网络命名空间执行ss nsenter -t $PID -n ss -tnp先解释一下参数-t指定目标进程PID-n表示进入网络命名空间。执行完这条命令你看到的就是容器内部的TCP连接表但执行命令的是宿主机上的ss。这套方案有3个明显优势第一不依赖容器内任何工具镜像再精简都没关系第二看到的进程信息更完整因为ss运行在宿主机命名空间里能获取到宿主机视角的进程PID第三一条命令能连续执行多次配合watch命令做实时刷新很方便watch -n 1 nsenter -t $(docker inspect -f {{.State.Pid}} container_name) -n ss -tnp下面是我在排查一个Nginx容器时真实看到的输出State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 0 172.17.0.3:80 192.168.1.20:51876 nginx: worker ESTAB 0 0 172.17.0.3:80 192.168.1.30:51902 nginx: worker TIME_WAIT 0 0 172.17.0.3:80 192.168.1.40:52011 timer(nginx)一眼就能看出当前有多少活跃连接、来源IP分布、连接处于什么状态这对定位问题非常直接。3.3 查看连接状态分布一条命令汇总全貌排查TCP连接问题时光看明细列表还不够很多时候需要统计各类状态的数量比如有多少ESTABLISHED、多少TIME_WAIT、多少SYN_SENT。在ss输出上配合awk就能快速汇总nsenter -t $PID -n ss -tan | awk NR1 {print $1} | sort | uniq -c | sort -rn输出示例2843 ESTAB 156 TIME_WAIT 23 SYN_SENT 8 CLOSE_WAIT这个统计信息非常实用。TIME_WAIT多通常说明短连接频繁创建SYN_SENT多说明容器在尝试连接外部地址但对方没响应CLOSE_WAIT多则往往是应用代码里没有正确关闭连接——这些都是经典的网络排查入口。值得注意的是ss的-a参数表示显示所有状态的连接包括监听中的如果你只想看已建立的连接把-a去掉就行。3.4 从宿主机视角看容器连接与veth网卡的对应关系还有一种场景你在宿主机上用ss -tnp看到大量连接但不知道这些连接属于哪个容器。这时候可以从IP反查。Docker默认的桥接网络每个容器有一个独立的IP如172.17.0.x。先用ss拿连接列表再用收尾字段里最常用的IP跟docker inspect做匹配docker ps -q | xargs -I{} docker inspect -f {{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} {}这条命令会列出所有运行中容器的名称和IP。然后你以为某个IP上的连接多就反查是哪个容器再针对那个容器做深入排查。这个方法在现场排障时特别常用因为有时候问题容器不止一个得先锁定目标。4. 查看容器带宽使用从网卡计数器到实时流量视图4.1 先泼盆冷水docker stats 看不到带宽很多人习惯用docker stats看资源占用但请记住一个事实docker stats显示的是CPU、内存、网络I/O的累计值它只告诉你容器累计收发过多少字节没有办法看到当前实时的带宽速率也看不出流量是哪个连接产生的。所以要看带宽需要换工具。市面上工具很多我用了几年最后稳定在iftop和nethogs这两个组合上。4.2 iftop按IP维度看实时流量iftop是我最常用的带宽查看工具界面像增强版的top按连接显示实时上下行速度。安装很简单apt install iftop -y如果要看宿主机整体流量执行iftop -i eth0 -n -P参数说明-i指定网卡-n不解析域名-P显示端口。输出界面上每一行代表一条连接左右两列分别是源IP和目的IP中间的箭头方向表示数据流向右侧的数字是实时速率。屏幕顶部是总计、峰值和平均值。但这样看有一个问题它显示的是宿主机上所有流量你没法直接区分是哪个容器的流量。这就需要结合上一节的IP反查思路先通过iftop看到哪些IP在跑大量流量再通过docker inspect确认这些IP对应哪个容器。对于跑在桥接网络里的容器来说这个方案基本够用——桥接网络内的容器IP都在172.17.0.0/16这个网段扫一眼就能看出来。如果你管理的Docker主机上容器特别多建议在启动iftop时加上过滤条件iftop -i eth0 -n -P -F 172.17.0.0/16-F参数按源或目的IP的网段过滤界面会干净很多。这个命令在实际运维中帮我节省了不少时间尤其是当整个宿主机同时跑着几十个容器的时候。4.3 nethogs按进程维度看流量钉死元凶iftop告诉你哪两个IP之间有流量但没告诉你哪个进程在产生流量。如果容器里跑的是Java、Node这类多线程服务一个进程可能同时有上百条连接你需要知道到底是哪个进程在“跑路”。这时候用nethogs。它按进程名和PID显示实时带宽nethogs eth0运行后能看到类似这样的输出PID USER PROGRAM DEV SENT RECEIVED 31842 root /usr/lib/jvm/java-8-openjdk-amd64/bin/java eth0 12.3KB/s 156.4KB/s 28765 root /usr/bin/sshd eth0 0.2KB/s 0.4KB/s一眼就能看出哪个进程在大量上行或下行。很多人配nethogs的时候会用-d 1让刷新间隔缩短到1秒这个参数在带宽波动很快的场景下尤其有用。但nethogs有一个限制它以宿主机进程为单位显示。如果你在宿主机上看到的进程名是docker-proxy那说明这是Docker的端口映射代理进程实际流量还是对应某个容器。这时候需要再通过端口号反查容器docker ps --format {{.Names}}\t{{.Ports}}4.4 自己动手用 veth 网卡精确统计单容器流量如果你需要精确到单个容器的累计流量还有一种更“土”但完全可控的方法读容器对应veth网卡的计数器。每条veth网卡在宿主机上都有一个对应接口名字形如vethxxxxxxx。这些接口的rx_bytes和tx_bytes就是容器从外界收到的和发往外界的数据量。找到veth接口和容器的对应关系ip link show | grep veth # 或者在 /sys/class/net 下找 iflink 匹配 for i in /sys/class/net/veth*; do ifindex$(cat $i/iflink) veth$(basename $i) container$(docker ps -q | xargs -I{} sh -c docker inspect -f {{.Name}} {{.State.Pid}} {} | while read name pid; do if [ -d /proc/$pid/net ]; then ns_ifindex$(readlink /proc/$pid/ns/net) # 这里需要更精确的匹配实际上通过 /sys/class/net/.../iflink 可以直接映射 fi done) done上面的脚本其实太复杂我实际更推荐用ethtool来查看veth接口的统计信息或者在宿主机上每隔一段时间记录一次计数器算出差值就是这段时间内的平均带宽。一个简单版本# 第一次采样 RX1$(cat /sys/class/net/veth1234/statistics/rx_bytes) sleep 5 # 第二次采样 RX2$(cat /sys/class/net/veth1234/statistics/rx_bytes) # 带宽 (RX2 - RX1) / 5 echo 平均入向带宽: $(( (RX2 - RX1) / 5 )) bytes/s这个方法的好处是不依赖任何第三方工具适合在极端情况下比如连iftop都没装临时救急。但操作起来比较繁琐日常还是推荐iftop配合nethogs。5. 常见问题与排查技巧实录这些坑我都替你踩过5.1 容器里没有 ss/netstat 怎么办精简镜像容器里没有网络工具是常态。别急着在容器里apt install有两条路可以走第一条用nsenter这是我最推荐的方案前面已经详细说过。无论容器里有什么、没什么宿主机上的ss都能看到容器命名空间内的连接。第二条如果你确实需要在容器内直接执行命令可以把宿主机的ss二进制文件拷进容器里用。但要注意ss依赖动态链接库拷进去之后可能因为缺少.so文件而无法运行。需要先执行ldd $(which ss)查看依赖库再手动复制对应的.so文件。这个方案操作繁琐且容易出错不到万不得已不建议用。坦率地说最省心、最通用的还是nsenter方案。把它理解成“在宿主机上打开一扇通往容器的窗户”你不需要在容器里装任何东西就能看到容器网络栈的全貌。5.2 nsenter 报错Permission denied 或 no such filensenter执行时最常见的报错是Permission denied。原因很简单进入其他进程的网络命名空间需要root权限。解决办法在命令前加sudo或者确保当前用户在root组里。另一种报错是nsenter: cannot open /proc/xxx/ns/net: No such file or directory。这通常意味着你获取PID后容器已经退出了。容器停止、重启后PID会变化所以每次都重新获取PID才是王道。5.3 在 nsenter 里看到不到 sshd 等宿主机进程这是正常的。因为nsenter -n只是把网络命名空间切换到了容器但进程列表PID命名空间和文件系统还是宿主机的。所以你看到的进程信息其实是宿主机视角这反而是一件好事——你能拿到宿主机PID方便在宿主机上top、strace这些进程。5.4 Docker DesktopWindows/macOS环境怎么看在Docker Desktop里nsenter方案基本不可用因为Linux网络命名空间运行在虚拟机内部。这种情况我一般用三种替代方案一是用docker exec进入容器容器里能有ss就直接执行没有的话结合第5.1节的方法处理。二是如果你用的是Linux虚拟机方案比如在macOS上装了Docker Desktop后实际上它带了一个轻量级VM可以执行docker run -it --privileged --pidhost debian nsenter -t 1 -m -u -i -n来进入VM的命名空间但这对普通用户来说太复杂了。三是直接用图形化监控工具比如Portainer、Lens它们能展示容器的网络流量信息虽然精度不如命令行工具但胜在方便直观。如果你主要在Docker Desktop环境里做开发装一个Portainer是性价比很高的选择。5.5 多个容器流量混杂如何快速定位“罪魁祸首”宿主机上跑了几十个容器某个时刻带宽被打满要快速找到是哪个容器在“搞事情”我的操作顺序是这样的第一步在宿主机上执行iftop -i eth0 -n -P通过总流量界面看哪个IP在跑大流量第二步用docker ps -q | xargs -I{} docker inspect -f {{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} {}把IP和容器对应起来第三步锁定了容器之后用nsenter进入它的网络命名空间看具体是哪些TCP连接在跑流量第四步如果还需要更细粒度用nethogs按进程维度确认是容器里的哪个应用在占用带宽。这套组合拳下来基本能在1分钟内锁定元凶。我在实际维护的服务器上用这个方法解决过不少诡异问题——比如某个容器的日志采集Agent在深夜定时上传大量数据、某个服务的健康检查过于频繁导致连接数爆炸。5.6 关于 TIME_WAIT 和 ESTABLISHED 数量暴增的快速判断如果你发现某个容器的TCP连接数异常先看状态分布TIME_WAIT大量增加通常是短连接场景比如应用程序频繁连接外部数据库。一般是代码问题可以考虑开启连接复用或改用连接池。CLOSE_WAIT持续增加这是应用层没有正确关闭连接的表现属于代码bugNginx后端应用没有正确关闭上游连接时经常出现这个状态。SYN_SENT持续增加容器在尝试连接一个不可达的地址可能导致内核连接表堆积。每类问题对应的排查方向完全不同所以建议把状态统计做成日常巡检的一部分。我在服务器上写了一个简单的巡检脚本每天定时把各容器的连接状态统计写入日志有问题能提前发现而不是等用户反馈了才去查。6. 把临时操作固化成脚本一劳永逸的效率提升平时排查问题时手动执行命令没问题但如果你需要频繁查看或者要交给其他同事使用建议把常用操作固化成脚本。下面是我自己的两个脚本简单但实用。6.1 查看指定容器的 TCP 连接统计#!/bin/bash # 用法: ./tcp_status.sh container_name if [ $# -ne 1 ]; then echo 用法: $0 container_name exit 1 fi CONTAINER$1 PID$(docker inspect -f {{.State.Pid}} $CONTAINER 2/dev/null) if [ -z $PID ]; then echo 错误: 容器 $CONTAINER 不存在或未运行 exit 1 fi echo 容器 $CONTAINER (PID: $PID) TCP连接状态统计 nsenter -t $PID -n ss -tan | awk NR1 {print $1} | sort | uniq -c | sort -rn echo echo 连接数 TOP 10 的对端IP nsenter -t $PID -n ss -tn | awk NR1 {print $5} | awk -F: {print $1} | sort | uniq -c | sort -rn | head -10这个脚本会输出两部分信息所有TCP连接的状态分布以及连接数最多的10个对端IP。在排查“容器在跟谁通信”这类问题时第二部分非常有用。6.2 查看所有容器的实时带宽排名#!/bin/bash # 用法: ./container_bandwidth.sh echo 容器实时带宽排名每2秒刷新一次 for i in $(seq 1 5); do echo --- 第 $i 次采样 $(date %H:%M:%S) --- for CID in $(docker ps -q); do CNAME$(docker inspect -f {{.Name}} $CID | sed s/\///) # 获取容器网络命名空间内主网卡的字节计数 RX_BYTES$(nsenter -t $(docker inspect -f {{.State.Pid}} $CID) -n cat /sys/class/net/eth0/statistics/rx_bytes 2/dev/null) TX_BYTES$(nsenter -t $(docker inspect -f {{.State.Pid}} $CID) -n cat /sys/class/net/eth0/statistics/tx_bytes 2/dev/null) echo $CNAME RX_BYTES$RX_BYTES TX_BYTES$TX_BYTES done sleep 2 done注意这个脚本是示例性质的它展示的是“如何在不让容器装任何工具的情况下批量获取网络计数器”。实际你用的时候可以把第一次采样的值存起来第二次采样后相减除以时间间隔就算出了平均带宽。脚本的价值在于它把前面那些零散的操作串成了一条流水线。你不用再每次手动敲一长串命令也降低了同事误操作的概率。7. 最后再分享几个实战小技巧排查Docker网络问题时有几个细节值得单独拎出来说是我自己在生产环境里反复验证过的第一个技巧在任何长命令前都先输出容器名和PID。因为容器PID是变化的脚本里如果不重新获取很容易拿到过期PID然后报错。我的习惯是每次执行都先docker inspect重新获取PID绝不缓存。第二个技巧善用docker logs辅助判断网络问题。有时候TCP连接异常是应用层导致的比如超时配置不合理、连接池设置太小单纯看连接状态看不出根本原因。建议把网络排查和应用日志关联起来看避免在错误的层面浪费时间。第三个技巧给关键容器打标签。在多个容器的环境下docker ps输出的容器名可能不够直观。我习惯在启动容器时用--label打上业务标签排查时能更快定位。比如配置文件里加上labels: - app.nameorder-service - app.envproduction然后排查时用docker ps --filter labelapp.nameorder-service快速找出目标容器。第四个技巧别忽视DNS排查。容器的TCP连接异常有时候根本不是连接本身的问题而是域名解析超时导致连接无法建立。如果看到大量SYN_SENT而目标IP又正常记得进容器里看看/etc/resolv.conf的配置或者用getent hosts验证一下DNS是否正常。第五个技巧把命令包一层函数放进bashrc。我自己在宿主机上把这些常用排查命令写成了shell函数放到~/.bashrc里dockertcp() { local PID$(docker inspect -f {{.State.Pid}} $1) nsenter -t $PID -n ss -tnp } dockerconn() { local PID$(docker inspect -f {{.State.Pid}} $1) nsenter -t $PID -n ss -tan | awk NR1 {print $1} | sort | uniq -c | sort -rn }这样新建一个终端后直接输入dockertcp container_name就能执行省去记长命令的时间。我在实际使用中发现这套方法最大的价值不在于某个单一命令而在于搭建了一个完整的排查框架从连接明细到状态统计、从IP维度到进程维度、从一次性操作到脚本化巡检。框架搭好了后续不管遇到什么样的容器网络问题你都知道该从哪里下手、用什么工具、怎么一步步定位到根因。这也是这篇教程想真正分享给你的东西。
返回列表