netstat查看Docker容器的网络连接状态 

570 阅读4分钟

我正在参加「掘金·启航计划」

基本的知识点

正常情况下,我们在linux 系统中可以通过 netstat 命令查看linux系统中的网络连接状态,但是在查看容器中的网络连接状态时,使用netstat命令确无法直接查看容器的网络连接状态。 原因是:pid的命名空间, 我们知道内核的许多部分包含在命名空间中. 这可以建立系统的多个虚拟视图, 并彼此分隔开来. 每个实例看起来像是一台运行Linux的独立机器,但在一台物理机器上,可以同时运行许多这样的实例。在内核版本2.6.24开发期间,内核也开始对网络子系统采用命名空间. 这对该子系统增加了一些额外的复杂性,因为该子系统的所有属性在此前的版本中都是"全局"的,而现在需要按命名空间来管理, 例如, 可用网卡的数量. 对特定的网络设备来说,如果它在一个命名空间中可见,在另一个命名空间中就不一定是可见的。 netstat失效原因:常用的获取主机网络连接命令netstat为什么不能扫描出docker网络连接,分析源码,netstat的实现原理,主要包括如下三个步骤: 1)遍历进程fd(/proc/pid/fd)目录获取文件类型为socket的inode号(inode->pid) 2)遍历/proc/net/tcp和/proc/net/udp 网络状态文件,按行解析网络连接信息(inode->ip port四元组、连接状态) 3)通过网络连接中的inode信息与第一步获取的inode关联,将进程与网络连接对应起来 该原理与获取linux进程级网络流量统计实现一致

总的来说就是宿主机的网络命名空间和 容器的命名空间不是同一个命名空间,导致netstat失效

查看docker容器的tcp连接

一文读懂Linux网络命名空间 nsenter 工具的使用 以及一些sh 基础命令的使用

查看docker容器网络连接的意义 我们在排查服务的连接状态、连接数量、是否是长链接、是否请求了外网时,通常会使用netstat 命令查看,而该命令不能直接在宿主机上查看docker的网络连接,直接进入容器执行netstat,一般还需要在容器中安装相应的工具,比较不方便,因此这里开发了一个小sh脚本便于查看容器的网络连接状态,方便我们排查某些问题。

实现 netstat工具获取容器的网络连接

大致的流程如下 获取容器的信息 docker ps | awk 'NR>1{print 1"AAAA"1"AAAA"2"AAAA"NF} NR>1 是去除第一行表头 AAAA是分隔符后面 如果用空格分割的话会把一行分成多列 可以替换成其他的 1:第一个参数 容器的id 2第二个参数容器的镜像2 第二个参数 容器的镜像 NF 最后一个参数 一般是容器的名字 这里可以根据条件过滤是处理一个容器或者所有的容器 对第一步获取的容器信息列表进行遍历 输出容器信息 result=echo $line | awk -F 'AAAA' '{ printf "容器id:%-20s 容器镜像:%-60s 容器名字:%-30s \n", $1,$2,$3}'"\n" 'AAAA' :分隔符 %-20s: 格式化参数 效果如下: 容器id:85ab5f7e7c90 容器镜像:mysql:5.7.32 容器名字:mysql57

获取容器的网络连接信息

tcps=echo $line | awk -F 'AAAA' '{ print $1}' | xargs docker inspect -f {{.State.Pid}} | xargs -i sh -c "sudo nsenter -t {} -n netstat -tn" | awk 'NR>2{printf "%sAAAA%sAAAA%sAAAA%sAAAA%sAAAA%s\n",$1,$2,$3,$4,$5,$6}' 这里主要用到了 1) docker inspect 用于获取容器的pid 2)nsenter -t pid(容器的pid) -n netstat -tn :通过nsenter 进入 指定pid的容器的网络空间,并且执行 netstat -tn 获取 改pid对应的容器的网络连接信息 Awk :用于重新组装信息,便于后续输出 格式化输出 网络连接信息 echo tcp | awk -F 'AAAA' '{printf "%-15s %-20s %-20s %-20s %-20s %-20s \n",1,2,2,3,4,4,5,$6}' 可以根据过滤条件单独输出外网的连接信息、单独输出内网的连接信息以及同时输出内外网的网络连接信息

效果图如下:

image.png 具体的脚本如下: 

脚本使用方法 ./docker-util.sh -h 会显示使用帮助 用法: ./脚本名称 [-t showType] [-n containerName] [-i ip] [-p port] 描述: showType 0:同时显示内外网的连接状态信息(默认值) 1:显示外网 2:只显示内网 containerName 容器名 为空则处理所有的容器 ip 根据ip 过滤 port 根据端口过滤 -h 显示帮助

效果如下:

image.png #!/bin/sh helpfunc() { echo "用法:" echo "./脚本名称 [-t showType] [-n containerName] [-i ip] [-p port]" echo "描述:" echo "showType 0:同时显示内外网的连接信息(默认值) 1:显示外网 2:只显示内网" echo "containerName 容器名 为空则处理所有的容器" echo "ip 根据ip 过滤" echo "port 根据端口过滤" echo "-h 显示帮助" exit 0 } while getopts "t:n:i:p:h" opt do case optint)t=opt in t) t=OPTARG ;; n) n=OPTARG;;i)i=OPTARG ;; i) i=OPTARG ;; p) p=OPTARG ;; h) helpfunc ;; ?) echo "无效参数"; helpfunc ;; esac done #if [[ ! -n t && ! -n n && ! -n i && ! -n $p ]]; then

helpfunc

#fi #echo "t=tn=t n=n i=ip=i p=p" showType=tcontainerName=t containerName=n ip=iport=i port=p if [ ! -n "showType" ]; then showType=0 fi #echo "showType=showType" #docker ps | awk 'NR>1{ print 1}' | xargs docker inspect -f {{.State.Pid}} | xargs -i sh -c "sudo nsenter -t {} -n netstat -tn" containers="" if [ -n "containerName" ]; then containers=docker ps | grep $containerName | awk '{print $1"AAAA"$2"AAAA"$NF}' else containers=docker ps | awk 'NR>1{print $1"AAAA"$2"AAAA"$NF}' fi count=0 for line in containersdoresult=echocontainers do result=`echo line | awk -F 'AAAA' '{ printf "容器id:%-20s 容器镜像:%-60s 容器名字:%-30s \n", 1,1,2,$3}'`"\n"

tcps=`echo $line | awk -F 'AAAA'  '{ print $1}' | xargs docker inspect -f {{.State.Pid}}  | xargs -i  sh -c "sudo  nsenter -t {} -n netstat -tn" | awk 'NR>2{printf "%sAAAA%sAAAA%sAAAA%sAAAA%sAAAA%s\n",$1,$2,$3,$4,$5,$6}'`
result=$result"Proto(协议) Recv-Q(网络接收队列) Send-Q(网络发送队列) Local Address(本地地址)           Foreign Address(外部地址)         State(状态)\n"
#echo $tcps
hasContent=0
for tcp in $tcps
do
  isInNet=`echo $tcp | awk -F 'AAAA' '{print $5}'`
  tempStr=""
  if [ $showType -eq "1" ];then
    if [[ ! $isInNet =~ "192.168" && ! $isInNet =~ "172." && ! $isInNet =~ "127.0.0.1"  ]]; then
        tempStr=`echo $tcp | awk -F 'AAAA' '{printf "%-15s %-20s %-20s %-20s %-20s %-20s \n",$1,$2,$3,$4,$5,$6}'`"\n"
    fi
  fi
  if [ $showType -eq "2" ];then
      if [[  $isInNet =~ "192.168" ||  $isInNet =~ "172." || $isInNet =~ "127.0.0.1"  ]]; then
         tempStr=`echo $tcp | awk -F 'AAAA' '{printf "%-15s %-20s %-20s %-20s %-20s %-20s \n",$1,$2,$3,$4,$5,$6}'`"\n"
      fi
  fi
  if [ $showType -eq "0" ];then
      tempStr=`echo $tcp | awk -F 'AAAA' '{printf "%-15s %-20s %-20s %-20s %-20s %-20s \n",$1,$2,$3,$4,$5,$6}'`"\n"
  fi      
 if [ -n "$tempStr" ]; then
        if [[  $tempStr =~ "$ip" &&  $tempStr =~ "$port"  ]]; then
           hasContent=1
           result=$result$tempStr
        fi   
 fi      
done
result=$result"------------------华丽的分割线---------------"
if [ $hasContent -eq "1" ]; then
  echo -e $result
fi

done