一 了解整个通信流程
我们先来创建一个没有网络的容器,然后手动为容器构建出完整的网络通信流程,以此来了解整体的通信流程
第一步:创建一个容器,没有网络,此时容器里只有一个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 # 用ifconfig命令看不到,需要用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的ip地址清理掉
ip link addr del 172.17.1.1/16 dev veth_host
第四步:把veth_host插到虚拟机交换机docker0上
ip link set veth_host master docker0
# 你也可以不用docker0,而用命令brctl addif br0 A自己创建一个,想用brctl命令需要yum install bridge-utils -y
如下图
此时容器可以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
容器里测试