01 容器网络故障分析方法

一 了解整个通信流程

我们先来创建一个没有网络的容器,然后手动为容器构建出完整的网络通信流程,以此来了解整体的通信流程

第一步:创建一个容器,没有网络,容器里只有一个lo网卡,内部的数据包根本无法发出去

[root@test04 ~]# docker container run -d --network none --name test centos:7 tail -f /dev/null

[root@test04 ~]# docker inspect test |grep -i -w pid
            "Pid": 44951,

[root@test04 ~]# nsenter -t 44951 -n ip a # 只有一个本地lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

第二步:容器想对外发送数据包,必须先把数据包送到宿主机,然后由宿主机做转发才行,所以接下来我们需要让容器能够把包发送到宿主机

创建veth对

veth 就是一个虚拟的网络设备,

一般都是成对创建,而且这对设备是相互连接的。当每个设备在不同的 Network

Namespaces 的时候,Namespace 之间就可以用这对 veth 设备来进行网络通讯了。

# 一、说明:ip netns默认会去/var/run/netns/目录下找名称空间的pid号
你必须用ip netns创建过名称空间,才会生成目录/var/run/netns/
ip netns add xxx
ls /var/run/netns  # 可以看到有一个名为xxx的文件

# 所以我们要想借助ip nets操作我们的容器名称空间,必须把容器名称空间链接到指定目录下才行
pid=$(docker inspect test |grep -i -w pid| awk '{print $2}' |cut -d, -f1)
ln -s /proc/$pid/ns/net /var/run/netns/$pid

# 二、创建veth对
ip link add name veth_host type veth peer name veth_container

可以在宿主机上查看,已经创建好了一个veth对:ip link list |grep veth_

第三步:

veth对就想一根网线,如上图所示,我们把网线的一端接到了容器1上,接下来需要做的就把另一端接到一台交换机上,这台交换机就是宿主机上的docker0网桥

[root@test04 ~]# ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:81ff:fe51:48a  prefixlen 64  scopeid 0x20<link>
        ether 02:42:81:51:04:8a  txqueuelen 0  (Ethernet)
        RX packets 9081  bytes 446105 (435.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 16786  bytes 26134922 (24.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

处理veth对:容器端

# 把veth对中的一个放到容器的名称空间里
ip link set veth_container netns $pid

此时可以查看容器名称空间里,发现多了一个网卡,容器里没有ip命令的话,我们在宿主机用nsenter命令执行
[root@test04 ~]# nsenter -t 44951 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
21: veth_container@if22: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 12:6a:b6:19:d7:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0

# 接下来我们把容器里的那个网卡名字改的好听一些,并且设置一下ip地址
ip netns exec $pid ip link set veth_container name eth0 

# 为容器配置ip地址与默认路由,与网桥的ip地址吻合
ip netns exec $pid ip link set eth0 up 
ip netns exec $pid ip addr add 172.17.1.2/16 dev eth0  
ip netns exec $pid ip route add default via 172.17.0.1 

如果容器内没有ip命令,则可以用nsenter命令
nsenter -t $pid -n ip addr add 172.17.1.2/16 dev eth0 
nsenter -t $pid -n ip link set eth0 up 
nsenter -t $pid -n ip route add default via 172.17.0.1 # 网格指向docker0

nsenter -t $pid -n ip a
nsenter -t $pid -n route -n

处理veth对另外 一端:宿主机上的veth_host

# veth_host本身就是存在于宿主机上的,直接up就可以
ip link set veth_host up

测试:此时我们给veth_host加一个ip地址,就可以让容器与宿主机通信了,因为veth对的一端在容器内,另外一端也就是veth_host存在于宿主机上,相当于有一根网线将容器与宿主机直连了起来

ip addr add 172.17.1.1/16 dev veth_host

# 然后我们用容器去ping通宿主机的veth_host
[root@test04 ~]# nsenter -t $pid -n ping 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.
From 172.17.1.2 icmp_seq=1 Destination Host Unreachable

# 如果发现ping不通,可以打开另外一个终端,抓包分析,发现veth_host不响应arp
[root@test04 ~]# tcpdump -i veth_host -nnv
tcpdump: listening on veth_host, link-type EN10MB (Ethernet), capture size 262144 bytes
09:19:43.759305 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 172.17.1.1 tell 172.17.1.2, length 28

# 解决办法
如果你的veth对是非172.17.0.0/16段的,根本不会出现上述问题
上述问题是因为docker0相当于一个虚拟机交换机,一旦发送给宿主机、目标地址为172.17.0.0/16段的数据包,都会被docker0接收来进行arp广播,它广播的过程中会发现没有任何veth对接到它身上,当然会报无法响应

你要知道,虽然你的宿主机上由veth_host,但是它没有关联/插到虚拟机交换机上docker0上,而docker0是专门处理172.17.0.0/16数据包的,所有这个段的包都归它转发,当然它只会转发给关联到自己身上的接口

暂时解决该问题,可以这么做,临时暂时关掉docker0就可以了,这样目标地址为172.17.0.0/16段的数据包就不会被docker0截胡了
ifconfig docker0 down

# 然后测试你会发现直接可以ping通

第四步:把veth_host插到虚拟机交换机docker0上

ip link set veth_host master docker0

# 你也可以不用docker0,而用命令brctl addif br0 A自己创建一个

如下图

此时容器可以ping通docker0

[root@test04 ~]# nsenter -t $pid -n ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.045 ms

第五步:添加docker0到ense32的转发,让容器的包可以出去

# 宿主机开启路由转发功能
echo 1 > /proc/sys/net/ipv4/ip_forward

iptables -P FORWARD ACCEPT
# 防火墙转发规划不用加,如果你使用的是默认的docker0网桥,docker引擎默认都加了

# 测试
[root@test04 ~]# nsenter -t $pid -n ping www.baidu.com
PING www.baidu.com (36.152.44.96) 56(84) bytes of data.
64 bytes from 36.152.44.96 (36.152.44.96): icmp_seq=1 ttl=54 time=9.91 ms

二 网络故障排查步骤

容器的eth0

nsenter -t $pid -n tcpdump -i eth0 host www.baidu.com -nnv

宿主机上的veth_host

tcpdump -i veth_host host www.baidu.com -nnv

宿主机上的docker0

tcpdump -i docker0 host www.baidu.com -nnv

宿主机的物理网卡ens32

tcpdump -i ens32 host www.baidu.com -nnv

宿主机的转发规则

iptables -t nat -L -n

宿主机是否开启路由转发

cat /proc/sys/net/ipv4/ip_forward # 结果应该为1才对

补充:

1、同一宿主机上的多个容器通信,直接走二层

2、如果是跨节点的容器通信,就需要用到overlay网络

当然,如果仅仅只是为了让外部访问者能够访问到宿主机上的某一个容器,可以这么做

iptables -t nat -A PREROUTING -d 【宿主机ip】 -p tcp -m tcp --dport 【宿主机映射端口】 -j DNAT --to-destination 【容器ip】:【容器端口】

3、容器与外部通信需要通过veth对,到达虚拟交换机docker0,然后再通过nat或者route转发给物理机的eth0网卡,然后才能发到外部去

这比宿主机直接与外部通信增加了很多流程,必然带来一些网络延迟问题,可以采用macvlan或ipvlan来代替veth对的方案,对于 macvlan,每个虚拟网络接口都有自己独立的 mac 地址;而 ipvlan 的虚拟网络接口是和物理网络接口共享同一个 mac 地址,macvlan及ipvlan与veth对不同的是,容器的虚拟网络接口直接链接在宿主机的物理网络接口上,形成一个二层网络链接,减少了转发路径

虚拟网络介绍详见:https://egonlin.com/?p=7245

对于网络实时性要求较高的应用可以考虑采用macvlan或ipvlan,但由于它俩都是将网络接口直接挂到了物理网络接口上,你无法为对不同的容器定制单独的iptables规则,而这却是k8s中的svc赖以运行的根本。

可以用netperf:工具来进测试,它是一个测试网络性能的工具,提供单项吞吐量和端到端的延迟测试, 在unix系统中,可以直接运行可执行程序来启动netserver。测试的时候,必须在两台机器上同时安装netperf,当netserver在server端启动以后,就可以在client端运行netperf来测试网络的性能。

https://hewlettpackard.github.io/netperf/

https://centos.pkgs.org/7/lux/netperf-2.7.0-1.el7.lux.x86_64.rpm.html

安装

wget http://repo.iotti.biz/CentOS/7/x86_64/netperf-2.7.0-1.el7.lux.x86_64.rpm

rpm -ivh netperf-2.7.0-1.el7.lux.x86_64.rpm

命令介绍

根据作用范围的不同,netperf的命令行参数可以分为两大类:全局命令行参数、测试相关的局部参数,两者之间使用--分隔:
Netperf [global options] –-[test-specific options]
其中:
全局命令行参数包括如下选项:
-H host :指定远端运行netserver的server IP地址。
-l testlen:指定测试的时间长度(秒)
-t testname:指定进行的测试类型,包括TCP_STREAM,UDP_STREAM,TCP_RR,TCP_CRR,UDP_RR

测试相关的局部参数包括如下选项:
-s size 设置本地系统的socket发送与接收缓冲大小
-S size 设置远端系统的socket发送与接收缓冲大小
-m size 设置本地系统发送测试分组的大小
-M size 设置远端系统接收测试分组的大小
-D 对本地与远端系统的socket设置TCP_NODELAY选项

开启服务端

netserver -4 -L 0.0.0.0 -p 9991
# 或者
python -m SimpleHTTPServer 8080

容器里测试

联系管理员微信tutu19192010,注册账号

上一篇
下一篇
Copyright © 2022 Egon的知识星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术