我们先来创建一个没有网络的容器,然后手动为容器构建出完整的网络通信流程,以此来了解整体的通信流程
第一步:创建一个容器,没有网络,此时容器里只有一个lo网卡,内部的数据包根本无法发出去
| [root@test04 ~] |
| |
| [root@test04 ~] |
| "Pid": 44951, |
| |
| [root@test04 ~] |
| 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/ |
| ip netns add xxx |
| ls /var/run/netns |
| |
| |
| pid=$(docker inspect test |grep -i -w pid| awk '{print $2}' |cut -d, -f1) |
| ln -s /proc/$pid/ns/net /var/run/netns/$pid |
| |
| |
| ip link add name veth_host type veth peer name veth_container |
| |
| 可以在宿主机上查看,已经创建好了一个veth对:ip link list |grep veth_ |
第三步:
veth对就想一根网线,如上图所示,我们把网线的一端接到了容器1上,接下来需要做的就把另一端接到一台交换机上,这台交换机就是宿主机上的docker0网桥
| [root@test04 ~] |
| 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对:容器端
| |
| ip link set veth_container netns $pid |
| |
| 此时可以查看容器名称空间里,发现多了一个网卡,容器里没有ip命令的话,我们在宿主机用nsenter命令执行 |
| [root@test04 ~] |
| 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 netns exec $pid ip link set veth_container name eth0 |
| |
| |
| 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 |
| |
| nsenter -t $pid -n ip a |
| nsenter -t $pid -n route -n |
处理veth对另外 一端:宿主机上的veth_host
测试:此时我们给veth_host加一个ip地址,就可以让容器与宿主机通信了,因为veth对的一端在容器内,另外一端也就是veth_host存在于宿主机上,相当于有一根网线将容器与宿主机直连了起来
| ip addr add 172.17.1.1/16 dev veth_host |
| |
| |
| [root@test04 ~] |
| 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 |
| |
| |
| [root@test04 ~] |
| 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 |
| |
| |
| ip link addr del 172.17.1.1/16 dev veth_host |
第四步:把veth_host插到虚拟机交换机docker0上
| ip link set veth_host master docker0 |
| |
| |
如下图
此时容器可以ping通docker0
| [root@test04 ~] |
| 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 |
| |
| |
| [root@test04 ~] |
| 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 |
宿主机的转发规则
宿主机是否开启路由转发
| cat /proc/sys/net/ipv4/ip_forward |
补充:
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 |
容器里测试