我正在参加「掘金·启航计划」
基本的知识点
正常情况下,我们在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 2"AAAA"NF}
NR>1 是去除第一行表头
AAAA是分隔符后面 如果用空格分割的话会把一行分成多列 可以替换成其他的
1:第一个参数 容器的id
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,3,5,$6}'
可以根据过滤条件单独输出外网的连接信息、单独输出内网的连接信息以及同时输出内外网的网络连接信息
效果图如下:
具体的脚本如下:
脚本使用方法 ./docker-util.sh -h 会显示使用帮助 用法: ./脚本名称 [-t showType] [-n containerName] [-i ip] [-p port] 描述: showType 0:同时显示内外网的连接状态信息(默认值) 1:显示外网 2:只显示内网 containerName 容器名 为空则处理所有的容器 ip 根据ip 过滤 port 根据端口过滤 -h 显示帮助
效果如下:
#!/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 OPTARG ;;
n) n=OPTARG ;;
p) p=OPTARG ;;
h) helpfunc ;;
?) echo "无效参数"; helpfunc ;;
esac
done
#if [[ ! -n t && ! -n n && ! -n i && ! -n $p ]]; then
helpfunc
#fi
#echo "t=n i=p"
showType=n
ip=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 line | awk -F 'AAAA' '{ printf "容器id:%-20s 容器镜像:%-60s 容器名字:%-30s \n", 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