Docker网络学习第一篇-Linux虚拟网络
作者: 耳朵里有风
温馨提示,本篇内容较多,主要内容包括:
- namespace概念,及基本操作;
- 几种网络虚拟设备,分别介绍了原理以及演示创建过程;
Linux Namespace
Namespace 是Linux内核级别隔离系统资源的解决方案,将网络、进程等资源进行封装隔离,使得各资源彼此透明,互不干扰,这一机制为实现基于容器的虚拟化技术提供了很好的基础。我们可以在Linux一个Host中创建多个Namespace, 比如在一个主机中启动若干个Docker容器。
Namespace 可隔离的资源有:
- Mount: 隔离文件系统挂载点
- UTS: 隔离主机名和域名信息
- IPC: 隔离进程间通信
- PID: 隔离进程的ID
- Network: 隔离网络资源
- User: 隔离用户和用户组的ID
其中Network是实现网络虚拟化的重要功能,其会创建多个隔离的网络空间,各自拥有独自的网卡、路由表、iptables、网络协议栈等。下面演示一下namespace的简单命令行操作。
Namespace操作
命令 ip netns add testns
创建一个新的namespace, ip netns ls
命令查看是否创建成功(namespace会出现在/var/run/netns下)。
[root@2030-edu-01-no ~]# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
[root@2030-edu-01-no ~]# ip netns add testns
[root@2030-edu-01-no ~]# ip netns ls
testns
[root@2030-edu-01-no netns]# ll /var/run/netns/
total 0
-r--r--r-- 1 root root 0 Jul 3 13:49 testns
创建好的namespace后,使用ip netns exec
在对应的 network namespace 中执行命令,比如查看namespace中的IP地址即可使用 ip netns exec testns ip addr
[root@2030-edu-01-no netns]# ip netns exec testns ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@2030-edu-01-no netns]#
也可以使用ip netns exec testns /bin/bash
进入namespace窗口执行多个命令
[root@2030-edu-01-no netns]# ip netns exec testns bash
[root@2030-edu-01-no netns]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@2030-edu-01-no netns]# exit
exit
通过修改 bash 的前缀信息可以区分不同 shell,
图1ip netns exec testns /bin/bash --rcfile <(echo "PS1=\"namespace testns> \"")
通过ip addr可以看出,创建namespace时自动创建一个 lo 的 interface,但是并有其他网络接口,此时的namespace是无法和主机或者其他namespace通信的。那么namespace之间要如何通信呢,暂时按下不表,先来看一看Linux中虚拟网络设备。
虚拟网络设备
Linux 虚拟网络的背后其实是由一个个的虚拟设备所构成的,比如 tap、tun 和 veth-pair。对于一个网络设备来说,就像一个管道(pipe)一样,有两端,从其中任意一端收到的数据将从另一端发送出去。对于物理设备,如网卡eth0,它的两端分别是内核协议栈和外面的物理网络,从物理网络收到的数据,会转发给内核协议栈,而应用程序从协议栈发过来的数据将会通过物理网络发送出去。对于虚拟网络设备,作为一个网络设备,它也能配置IP,路由数据,不同的是虚拟网络设备从协议栈接收数据,但怎么发送,发送到哪些是由驱动自身决定的。
图2tun/tap
在linux下,要实现核心态和用户态数据的交互,有多种方式:可以通用socket创建特殊套接字,利用套接字实现数据交互;通过proc文件系统创建文件来进行数据交互;还可以使用设备文件的方式,访问设备文件会调用设备驱动相应的例程,设备驱动本身就是核心态和用户态的一个接口,Tun/tap驱动就是利用设备文件实现用户态和核心态的数据交互。
tun/tap设备的用处是将协议栈中的部分数据包转发给用户空间的应用程序,给用户空间的程序一个处理数据包的机会。设备最常用的场景是VPN。下图描述了tun/tap数据流程示意图,实际的原理比这个更加复杂。
图3
tun/tap设备实现VPN原理,vpn通过操作系统的接口直接虚拟出一张网卡,后续整个操作系统的网络通讯都将通过这张虚拟的网卡进行收发。这和任何一个代理的实现思路都差不多,应用层并不知道网卡是虚拟的,这样vpn虚拟网卡将以中间人的身份对数据进行加工,从而实现各种效果,而虚拟网卡就是tun或者tap。
2、网关2接收到数据包后,将其还原为包A(原理和上一步相同,也是利用了tun或tap将数据传至应用层再解封还原的过程),最终转到目标服务器, 图4
比如在家里要通过VPN访问内网的一个网站,而要访问的内网网址是172.31.130.23。通常情况下,VPN客户端拨入VPN服务器后,本机的默认网关会改为VPN的IP地址,当你访问网站时,具体流程如下:
1、数据包A到达网关1,网关1接收到请求后,构造新的数据包B, (数据包A根据路由规则指定到tun或tap设备(假设设备为tun0)来接收请求,tun0会进一步将数据传递给连接在另一端的VPN应用,应用接收到数据后做一系列处理,将原来数据包封装为新的包B),数据包B指向由原来的172.31.130.23变成了42.62.43.137,接着将数据包交给物理网卡发送到网关2。
下面演示了如何在centos7中创建一个tun设备
第一步,确认内核是否有tun模块 modinfo tun
, 如果有的话会输出tun相关描述信息;
第二步,确定内核模块是否已经加载,lsmod | grep tun
, 如果没有加载使用modprobe tun
命令加载;
[root@2030-edu-01-no ~]# lsmod | grep tun
tun 31740 0
0 表示tun设备数量
第三步,查看是否安装了 tunctl, 如果没有安装,参考下面的命令
cat << EOF > /etc/yum.repos.d/nux-misc.repo
> [nux-misc]
> name=Nux Misc
> baseurl=http://li.nux.ro/download/nux/misc/el7/x86_64/
> enabled=0
> gpgcheck=1
> gpgkey=http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
> EOF
yum -y --enablerepo=nux-misc install tunctl
第四步,创建虚拟网卡设备 tunctl -t tap0 -u root
, 此时再运行lsmod | grep tun
,数量由0变成了1,ifconfig -a
也可以查看到刚刚创建的设备
tap0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether fa:91:9d:65:af:13 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
第五步,设置虚拟网卡, 设置网关、ip等,如 ifconfig tap0 192.168.1.5 netmask 255.255.255.0 promisc
[root@2030-edu-01-no ~]# ifconfig tap0 192.168.1.5 netmask 255.255.255.0 promisc
[root@2030-edu-01-no ~]# ping 192.168.1.5
PING 192.168.1.5 (192.168.1.5) 56(84) bytes of data.
64 bytes from 192.168.1.5: icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from 192.168.1.5: icmp_seq=2 ttl=64 time=0.040 ms
veth-pair
veth-pair 是一对的虚拟设备接口,和 tap/tun 设备不同的是,它都是成对出现的。一端连着协议栈,一端彼此相连。
图5
当veth0接收到协议栈的数据发送请求后,会将数据发送到veth1上去, veth常常充当桥梁,连接各种虚拟网络设备,比如上文说的namespace与host以及namespace之间的通信就可以使用veth实现。下面演示一组host与namespace实现通信的过程。
- 使用
ip link add veth0 type veth peer name vethtestns
添加一组设备,直接使用ip link add type veth
则不指定名称,系统自动生成. (删除使用ip link delete veth0
, 会同时删除vethtestns) - 使用
ip l s vethtestns netns testns
将vethtestns设备加入testns namespace中 - 配置IP, 并启用
ip a a 10.1.1.2/24 dev veth0
ip l s veth0 up
ip netns exec testns ip a a 10.1.1.3/24 dev vethtestns
ip netns exec testns ip l s vethtestns up
- 在testns中ping host 的ip, 可以看到网络是联通的
[root@2030-edu-01-no /]# ip netns exec testns ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.059 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.055 ms
64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=0.047 ms
veth实现了两两通信,通信过程简单描述如下,左侧是刚刚演示的namespace与主机的通信模型,右侧是两个namespace之间的通信模型,但是如果需要多个接口互相通信,veth就无法胜任了。
图6
Bridge
同样Bridge也是虚拟网络设备之一,具有网络设备的通性,普通虚拟设备只有两个端口,一进一出,而Bridge 由多个端口,数据从任一端口进来,再根据mac地址从指定端口出去。可以看出Bridge是一套虚拟交换设备,和物理交换机的功能相同。
使用演示
通过命令ip link add name bridge0 type bridge && ip link set bridge0 up
创建并启动一个网桥
[root@2030-edu-01-no ~]# ifconfig bridge0
bridge0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::3cb6:45ff:fe15:94f2 prefixlen 64 scopeid 0x20<link>
ether 3e:b6:45:15:94:f2 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 656 (656.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
此时网桥一端连接协议栈,而另一端什么也没接。
图7
上文提到了veth设备只能实现两两连接,如果有多个接口需要连接,此时就可以借助Bridge来实现。我们假设现在有三个namespace 要互相通信,那么就需要创建三对veth设备,每对设备中的一个接到namespace中,另一个接入网桥中,具体的模型如下:
图8
具体步骤为
- 创建namespace、veth、和网桥资源(这里直接使用上一步创建好的bridge0)。
- 将veth分别接入namespace和网桥中;
[root@2030-edu-01-no ~]# ip netns add ns1
[root@2030-edu-01-no ~]# ip netns add ns2
[root@2030-edu-01-no ~]# ip netns add ns3
[root@2030-edu-01-no ~]# ip link add veth1 type veth peer name vethb1
[root@2030-edu-01-no ~]# ip link add veth2 type veth peer name vethb2
[root@2030-edu-01-no ~]# ip link add veth3 type veth peer name vethb3
[root@2030-edu-01-no ~]# ip link set veth1 netns ns1
[root@2030-edu-01-no ~]# ip link set veth2 netns ns2
[root@2030-edu-01-no ~]# ip link set veth3 netns ns3
[root@2030-edu-01-no ~]# ip link set vethb1 master bridge0
[root@2030-edu-01-no ~]# ip link set vethb2 master bridge0
[root@2030-edu-01-no ~]# ip link set vethb3 master bridge0
[root@2030-edu-01-no ~]# ip link set vethb1 up
[root@2030-edu-01-no ~]# ip link set vethb2 up
[root@2030-edu-01-no ~]# ip link set vethb3 up
[root@2030-edu-01-no ~]# ip netns exec ns1 ip addr add 10.1.1.2/24 dev veth1
[root@2030-edu-01-no ~]# ip netns exec ns2 ip addr add 10.1.1.3/24 dev veth2
[root@2030-edu-01-no ~]# ip netns exec ns3 ip addr add 10.1.1.4/24 dev veth3
[root@2030-edu-01-no ~]# ip netns exec ns1 ip link set veth1 up
[root@2030-edu-01-no ~]# ip netns exec ns2 ip link set veth2 up
[root@2030-edu-01-no ~]# ip netns exec ns3 ip link set veth3 up
- 测试是否已经联通,从ns3中分别ping ns1和ns2 ,从结果看已经成功啦!
[root@2030-edu-01-no ~]# ip netns exec ns3 ping 10.1.1.2 -c 1
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.098 ms
--- 10.1.1.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.098/0.098/0.098/0.000 ms
[root@2030-edu-01-no ~]# ip netns exec ns3 ping 10.1.1.3 -c 1
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.106 ms
--- 10.1.1.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.106/0.106/0.106/0.000 ms
[root@2030-edu-01-no ~]#
通常使用brctl 工具来管理网桥,使用yum install bridge-utils
命令安装,比如查看网桥信息.
[root@2030-edu-01-no ~]# brctl show bridge0
bridge name bridge id STP enabled interfaces
bridge0 8000.127eaae73032 no vethb1
vethb2
vethb3
上面演示的案例中,已经实现了三个namespace的互相联通,此时如果在和主机联通,只需要给网桥分配一个同网段的ip地址即可,ip addr add 10.1.1.1/24 dev bridge0
。
Bridge 应用
Bridge目前主要用在虚拟机和Docker中,Docker网络基础后续会专门介绍,先来看看网桥在虚拟机的应用模型。虚拟机通过tun/tap或者其它类似的虚拟网络设备,将虚拟机内的网卡同网桥连接起来,以此达到和真实交换机一样的效果,虚拟机发出去的数据包先到达网桥,然后由网桥交给物理网卡发送出去,数据包不需要经过host机器的协议栈,效率也比较高。
图9
借助 Linux Bridge 功能,同主机或跨主机的虚拟机之间能够轻松实现通信,也能够让虚拟机访问到外网,这就是我们所熟知的桥接模式,一般在装 VMware 虚拟机或者 VirtualBox 虚拟机的时候,都会提示我们要选择哪种模式,其中常用的一种模式是桥接。
OVS
Bridge 充当虚拟交换机已经能满足网络的通信,但是仍有一些不足的地方,比如网络管理和监控的便利性、数据包寻路和转发的高效性、隧道协议支持类型等。OVS(Open vSwitch)是流行的虚拟交换机之一,扩展了很多高级特性。
参考资料
- http://www.360doc.com/content/18/0829/07/44856983_782027344.shtml
- https://zhuanlan.zhihu.com/p/73248894
- https://www.jianshu.com/p/2a14fe583cdf
- https://blog.csdn.net/u012707739/article/details/78163354
- https://www.ibm.com/developerworks/cn/linux/l-tuntap/
- https://segmentfault.com/a/1190000009249039
- https://segmentfault.com/a/1190000009491002
- https://www.cnblogs.com/bakari/p/10613710.html
- https://www.cnblogs.com/bakari/p/8097439.html