2019-06-24-Linux原生网络虚拟化实践

本篇是虚拟化技术系列最后一片文章(容器技术作为专题单独介绍),介于后续OpenStack的Neutron在利用libvirt构建虚拟化网络服务时,利用了很多Linux虚拟网络功能(Linux内核中的虚拟网络设备以及其他网络功能)。因此,在网络虚拟化的收尾部分,特意安排一篇Linux虚拟网络的基础实践,一方面巩固大家对网络虚拟化的认知,另一方面也为后续学习OpenStack打好基础。下面,就来带大家了解一下Linux系统原生的与Neutron密切相关的虚拟网络设备。

Tap

Tap这个概念大家应该不陌生,Tap交换机总听说过吧,信令平台采集探针数据收敛必备设备,是个纯二层设备。Linux中的tap属于虚拟网络设备,也是个二层虚拟设备。而在linux中所指的“设备”并不是我们实际生产或生活中常见路由器或交换机这类设备,它其实本质上往往是一个数据结构、内核模块或设备驱动这样含义。

在linux中,tap和tun往往是会被并列讨论,tap位于二层,tun位于三层。为了说明这一点我们先看看下面linux用于描述tap和tun的数据结构内容:

1
2
3
4
5
6
7
8
9
Struct  tun_strruct  {
Char name [8]; //设备名称
Unsigned long flags; //区分tun和tap的标志位;
Struct fasync_struct *fasync; //文件异步调用通知接口
Wait_queue_head_t read_wait; //消息队列读等待标志
Struct net_device dev; //定义一个抽象网络设备
Struct sk_buff_head txq; //定义一个缓存区
Struct net_device_stats stats; //定义一个网卡状态信息结构
};

从上面的数据结构可以看出,tap和tun的数据结构其实一样的,用来区别两者的其实就是flags。Tap从功能上说,位于二层也就是数据链路层,而二层的主要网络协议包括:

  • 点对点协议(Point-to-Point Protocol)
  • 以太网协议(Ethernet)
  • 高级数据帧链路协议(High-Level Data Link Protocol)
  • 帧中继(Frame Relay)
  • 异步传输模式(Asynchronous Transfer Mode)

而tap只是与以太网协议(Ethernet)对应,所以tap常被称为“虚拟以太网设备” 。要想使用Linux命令行操作一个tap,首先Linux得有tun模块(Linux使用tun模块实现了tun/tap),检查方法如下所示,输入modinfo tun命令如果有如下输出,就表示Linux内核具备tun模块。

接下来,还要看看Linux内核是否加载tun模块。检查办法就是输入指令lsmod|grep tun,查看是否有如下图所示信息输出。如果有,就表示已加载,否则输入modprobe tun指令进行加载,然后再次输入lsmod|grep tun指令进行确认(这种加载-确认的操作方式是一种好的操作习惯,要养成!!!

当我们确认了Linux加载了tun模块后,我们还需要确认Linux是否有操作tun/tap的命令行工具tunctl。在Linux命令行输入tunctl help进行确认。

如上图所示,如果提示command not found,在CentOS 6.x系统下直接输入yum install -y tunctl安装即可。但是,在CentOS 7.x系统下,需要先指定一个特定仓库,然后按照指定的仓库进行安装,否则会提示找不到tunctl rpm包。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 首先创建一个指定仓库
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

# 按照指定仓库进行安装
[root@C7-Server01 ~]# yum -y --enablerepo=nux-misc install tunctl

安装完后,再次执行tunctl help命令后,有如下信息输出,表示安装成功。

具备了tun和tunctl后,我们就亦可以创建一个tap设备了,创建命令使用-t选项指定创建的tap设备名称。

此时,我们输入ip link list或ifconfig命令可以查看到刚才创建的tap设备llb_tap1。

通过上面的命令输出,我们发现这个tap设备还没有绑定ip,可以通过执行ip addr或ifconfig命令为该tap设备绑定一个ip地址,我们给它规划一个192.168.1.1/24的地址。

1
2
3
4
5
# 通过ip addr命令绑定ip地址
[root@C7-Server01 ~]# ip addr local 192.168.1.1/24 dev llb_tap1

# 或者使用ifconfig命令绑定ip地址
[root@C7-Server01 ~]# ifconfig llb_tap1 192.168.1.1/24

注意:上述操作方式,个人推荐第一种,毕竟我们要与时俱进。。。

到此为止,一个tap设备就创建完毕了,在OpenStack的Neutron中创建的虚拟网络,虚拟机的vNIC与虚拟交换机之间就是通过一个tap桥接,而这个tap设备就是俗称的端口组,所以也就有了同一个端口组内的port归属同一个network的说法。后续通过测试用例,我们会再次讲述tap的用法。

Namespace

namespace是Linux虚拟网络的一个重要概念。传统的Linux的许多资源是全局的,比如进程ID资源。而namespace的目的首先就是将这些资源进行隔离。容器技术的两大实现基石之一就是namespace,它主要负责容器虚拟化中资源隔离,另一个就是cgroups,主要负责容器虚拟化中资源的管理。

在Linux中,可以在一个Host内创建许多namespace,于是那些原本是Linux全局的资源,就变成了namespace范围内的“全局”资源,而且不同namespace的资源互相不可见、彼此透明。Linux中会被namespace隔离的全局资源包括:

  • uts_ns:UTS是Unix Timesharing System的简称,包含内核名称、版本、底层体系结构等信息。
  • ipc_ns:所有与进程通信(IPC)有关的信息。
  • mnt_ns:当前装载的文件系统;
  • pid_ns:有关进程的id信息。
  • user_ns:资源配额信息。
  • net_ns:网络信息。

至于为什么这些全局资源会被隔离,是由Linux全局资源的数据结构定义决定的,如下所示,Linux的全局资源数据结构定在在文件nsproxy.h中。

1
2
3
4
5
6
7
8
9
Struct nsproxy {
Atomic_t count;
Struct uts_namespace *uts_ns;
Struct ipc_namespace *ipc_ns;
Struct mnt_namespace *mnt_ns;
Struct pid_namespace *pid_ns;
Struct user_namespace *user_ns;
Struct net *net_ns;
};

从资源的隔离的角度来说,Linux namespace的示意图如下所示。

上图表明,每个namespace里面将原本是全局资源的进行了隔离,彼此互相不可见。同时在Linux的Host或者每一个VM中,都各自有一套相关的全局资源。借助虚拟化的概念,我们可以将Linux Host的namespace称为root namespace,其它虚拟机namespace称为guest namespace。

单纯从网络的视角来看,一个namespace提供了一份独立的网络协议栈。包括:网络设备接口、IPv4、IPv6、IP路由、防火墙规则、sockets等。一个Linux Device,无论是虚拟设备还是物理设备,只能位于一个namespace中,不同namespace的设备之间通过veth pair设备进行连接,veth pair设备也是一个虚拟网络设备,可以暂时将其理解为虚拟网线,后面会详细介绍。

Linux中namespace的操作命令是ip netns,这个命令的帮助如下所示:

首先,我们通过ip netns list命令查看一下当前系统的namespace列表信息,由于当前系统没有创建namespace,所以没有任何信息返回。然后,我们通过ip netns add命令添加一个namespace(llb_ns1)。最后,再通过ip netns list命令进行查看,结果如下图所示。

接下来,我们可以通过ip link set名ing把刚才创建的虚拟tap设备迁移到llb_ns1中去。这时候,我们在root namespace下执行ifconfig命令就找不到llb_tap1设备了。

我们可以通过ip netns exec [NAME] CMD的方式操作,namespace中的设备,其中NAME参数为namespace名称,CMD是要执行的指令。

在namespace中给llb_tap1重新绑定ip地址。因为,llb_tap1在root namesapce中绑定的ip归属root namesapce的资源,当llb_tap1更改归属namespace时,原来的资源自然无法使用,必须重新配置。

到此为止,namespace的创建就讲述完了,后续还会根据实例继续讲解其它用法。

Veth pair

veth pair不是一个设备,而是一对设备,以连接两个虚拟以太端口,类似网线。操作veth pair,需要跟namespace一起配合,不然就没有意义。其实,veth pair设备本质上有三个接口,一端连接linux内核,另两端连接两个tap设备,是一个Y型结构,实现上更像一个HUB。如下图所示:

两个namespace llb_ns1/llb_ns2中各有一个tap设备,组成veth pair,两者的ip如上图所示,测试两个ip进行互ping。配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建veth pair设备
[root@C7-Server01 ~]# ip link add llb_tap1 type veth peer name llb_tap2

# 创建两个namespace:llb_ns1,llb_ns2
[root@C7-Server01 ~]# ip netns add llb_ns1
[root@C7-Server01 ~]# ip netns add llb_ns2

# 将veth pair设备两端的两个tap设备移动到对应的namespace中
[root@C7-Server01 ~]# ip link set llb_tap1 netns llb_ns1
[root@C7-Server01 ~]# ip link set llb_tap2 netns llb_ns2

# 设置两个tap设备的ip地址,并启动
[root@C7-Server01 ~]# ip netns exec llb_ns1 ifconfig llb_tap1 192.168.1.1/24 up
[root@C7-Server01 ~]# ip netns exec llb_ns2 ifconfig llb_tap2 192.168.1.2/24 up

# 测试两个tap设备互ping
[root@C7-Server01 ~]# ip netns exec llb_ns1 ping -c 5 192.168.1.2
[root@C7-Server01 ~]# ip netns exec llb_ns2 ping -c 5 192.168.1.1

通过veth pair我们可以连接两个namespace中的两个tap设备,但是veth pair一端只能连接两个tap,如果是三个或多个namespace内的tap要实现互联怎么办?这时候,就只能使用Linux Bridge来完成。

Linux Bridge/vSwitch

在Linux的网络部分,Bridge和Switch是一个概念。所以,大家把Linux Bridge看做一个交换机来理解就行,也就是2层的一个汇聚设备。Linux实现Bridge功能的是brctl模块。在命令行里敲一下brctl,如果能显示相关内容,则表示有此模块,否则还需要安装。安装命令是:

1
[root@C7-Server01 ~]# yum install -y bridge-utils

Bridge本身的概念,我们通过一个综合测试用例来讲述Bridge的基本用法,同时也涵盖前面所述的几个概念:tap、namesapce、veth pair。实验拓扑图如下:

上图中,有4个namespace,每个namespace都有一个tap与交换机上一个tap口组成veth pair。这样4个namespace就通过veth pair及Bridge互联起来。配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 创建veth pair设备
[root@C7-Server01 ~]# ip link add llb_tap1 type veth peer name tap1_peer
[root@C7-Server01 ~]# ip link add llb_tap2 type veth peer name tap2_peer
[root@C7-Server01 ~]# ip link add llb_tap3 type veth peer name tap3_peer
[root@C7-Server01 ~]# ip link add llb_tap4 type veth peer name tap4_peer

# 创建namespace
[root@C7-Server01 ~]# ip netns add llb_ns1
[root@C7-Server01 ~]# ip netns add llb_ns2
[root@C7-Server01 ~]# ip netns add llb_ns3
[root@C7-Server01 ~]# ip netns add llb_ns4

# 把tap设备移动到对应namespace中
[root@C7-Server01 ~]# ip link set llb_tap1 netns llb_ns1
[root@C7-Server01 ~]# ip link set llb_tap2 netns llb_ns2
[root@C7-Server01 ~]# ip link set llb_tap3 netns llb_ns3
[root@C7-Server01 ~]# ip link set llb_tap4 netns llb_ns4

# 创建Bridge
[root@C7-Server01 ~]# brctl addbr br1

# 把相应的tap设备添加到br1的端口上
[root@C7-Server01 ~]# brctl addif br1 tap1_peer
[root@C7-Server01 ~]# brctl addif br1 tap2_peer
[root@C7-Server01 ~]# brctl addif br1 tap3_peer
[root@C7-Server01 ~]# brctl addif br1 tap4_peer

# 配置相应tap设备的ip,并启动
[root@C7-Server01 ~]# ip netns exec llb_ns1 ifconfig llb_tap1 192.168.1.1/24 up
[root@C7-Server01 ~]# ip netns exec llb_ns2 ifconfig llb_tap2 192.168.1.2/24 up
[root@C7-Server01 ~]# ip netns exec llb_ns3 ifconfig llb_tap3 192.168.1.3/24 up
[root@C7-Server01 ~]# ip netns exec llb_ns4 ifconfig llb_tap4 192.168.1.4/24 up

# 将Bridge及所有tap设备状态设置为up
[root@C7-Server01 ~]# ip link set br1 up
[root@C7-Server01 ~]# ip link set tap1_peer up
[root@C7-Server01 ~]# ip link set tap2_peer up
[root@C7-Server01 ~]# ip link set tap3_peer up
[root@C7-Server01 ~]# ip link set tap4_peer up

# 进行互ping测试
[root@C7-Server01 ~]# ip netns exec llb_ns1 ping -c 3 192.168.1.4
[root@C7-Server01 ~]# ip netns exec llb_ns3 ping -c 3 192.168.1.2
[root@C7-Server01 ~]# ip netns exec llb_ns2 ping -c 3 192.168.1.1
[root@C7-Server01 ~]# ip netns exec llb_ns4 ping -c 3 192.168.1.2

Router

Linux创建Router不像Bridge一样有一个直接命令,甚至连间接命令都没有。因为它自身就是一个路由器(Router)。不过Linux默认没有打开路由转发功能。可以用less /proc/sys/net/ipv4/ip_forward这个命令验证。这个命令就是查看一下这个文件(/proc/sys/net/ipv4/ip_forward)的内容。该内容是一个数字。如果是“0”,则表示没有打开路由功能。把“0”修改为“1”,就是打开了Linux的路由转发功能,修改命令为echo “1” > /proc/sys/net/ipv4/ip_forward。这种打开方法,在机器重启以后就会失效了。一劳永逸的方法是修改配置文件“/etc/sysctl.conf”,将net.ipv4.ip_forward=0修改为1,保存后退出即可。

下面,我们还是通过一个示例来直观感受一下Route的功能。实验拓扑图如下:

在上图中,llb_ns5/llb_tap5与llb_ns6/llb_tap6不在同一个网段中,中间需要经一个路由器进行转发才能互通。图中的Router是一个示意,其实就是Linux开通了路由转发功能。配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建veth pair设备
[root@C7-Server01 ~]# ip link add llb_tap5 type veth peer name tap5_peer
[root@C7-Server01 ~]# ip link add llb_tap6 type veth peer name tap6_peer

# 创建namespace:llb_ns5、llb_ns6
[root@C7-Server01 ~]# ip netns add llb_ns5
[root@C7-Server01 ~]# ip netns add llb_ns6

# 将tap设备迁移到对应namespace中
[root@C7-Server01 ~]# ip link set llb_tap5 netns llb_ns5
[root@C7-Server01 ~]# ip link set llb_tap6 netns llb_ns6

# 配置tap设备ip地址,并启动
[root@C7-Server01 ~]# ip netns exec llb_ns5 ifconfig llb_tap5 192.168.100.5/24 up
[root@C7-Server01 ~]# ip netns exec llb_ns6 ifconfig llb_tap6 192.168.200.5/24 up
[root@C7-Server01 ~]# ifconfig tap5_peer 192.168.100.1/24 up
[root@C7-Server01 ~]# ifconfig tap6_peer 192.168.200.1/24 up

现在,我们先来做个ping测试,提示网络不可达,如下图所示。

我们查看下llb_ns5的路由表信息,如下图所示,llb_ns5并没有到达192.168.200.0/24网段的路由表项,因此需要手工进行添加。

在llb_ns5中添加到192.168.200.0/24网段静态路由信息,同时在llb_ns6中添加到192.168.100.0/24回程路由信息。配置代码如下:

1
2
3
4
5
6
7
8
9
# 在llb_ns5中添加到192.168.200.0/24的静态路由
[root@C7-Server01 ~]# ip netns exec llb_ns5 route add -net 192.168.200.0 netmask 255.255.255.0 gw 192.168.100.1

# 同理,在llb_ns6中添加到192.168.100.0/24的回程路由
[root@C7-Server01 ~]# ip netns exec llb_ns6 route add -net 192.168.100.0 netmask 255.255.255.0 gw 192.168.200.1

# 查看llb_ns5、llb_ns6的路由信息
[root@C7-Server01 ~]# ip netns exec llb_ns5 route -ne
[root@C7-Server01 ~]# ip netns exec llb_ns6 route -ne

再次进行ping尝试,可以ping通,结果如下图所示。

Tun

在前面tap的时候就介绍过tun和tap其实同一数据结构,只是通过flags标志位来区分。Tap是二层虚拟以太网设备,那么tun就是三层的点对点的虚拟隧道设备。也就是说Linux原生支持三层隧道技术。至于什么是隧道技术?在上一篇数据中心的网络虚拟化技术有详细介绍,这里不再赘述。

Linux一共原生支持5种三层隧道技术,分别是:

  • ipip:IP in IP,在IPv4报文的基础上再封装一个IPv4报文头,属于IPv4 in IPv4。
  • GRE:通用路由封装(Generic Routing Encapsulation),定义在任意一种网络层协议上封装任意一个其他网络层协议的协议,属于IPv4/IPv6 over IPv4。
  • sit:与ipip类似,只不过用IPv4报文头封装一个IPv6报文,属于IPv6 over IPv4。
  • isatap:站内自动隧道寻址协议,一般用于IPv4网络中的IPv6/IPv4节点间的通信。
  • vti:全称是Virtual Tunnel Interface,为IPSec隧道提供一个可路由的接口类型。

国际惯例,我们还是通过一个具体的测试实例来理解tun。实验拓扑图如下:

上图的tun1、tun2,如果我们先忽略的话,剩下的就是我们在前面讲述过的内容。测试用例的第一步,就是使图中的tap7与tap8配置能通,借助上面route的配置,这里我们不再重复。当tap7和tap8配通以后,如果我们不把图中的tun1和tun2暂时当做tun设备,而是当做两个“死”设备(比如当做是两个不做任何配置的网卡),那么这个时候tun1和tun2就像两个孤岛,不仅互相不通,而且跟tap7、tap8也没有关系。因此,我们就需要对tun1、tun2做相关配置,以使这两个两个孤岛能够互相通信。我们以ipip tunnel为例进行配置。

首先我们要加载ipip模块,Linux默认是没有加载这个模块的。通过命令行lsmod|grep ip进行查看,如果没有加载,可以通过命令modprobe ipip来加载ipip模块。具体过程参见tap部分,这里不再赘述。加载了ipip模块以后,我们就可以创建tun,并且给tun绑定一个ipip隧道。配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建veth pair设备
[root@C7-Server01 ~]# ip link add llb_tap7 type veth peer name tap7_peer
[root@C7-Server01 ~]# ip link add llb_tap8 type veth peer name tap8_peer

# 创建namespace:llb_ns7,llb_ns8
[root@C7-Server01 ~]# ip netns add llb_ns7
[root@C7-Server01 ~]# ip netns add llb_ns8

# 将tap设备移动到对应的namespace
[root@C7-Server01 ~]# ip link set llb_tap7 netns llb_ns7
[root@C7-Server01 ~]# ip link set llb_tap8 netns llb_ns8

# 配置ip地址,并启动
[root@C7-Server01 ~]# ip netns exec llb_ns7 ifconfig llb_tap7 192.168.100.6/24 up
[root@C7-Server01 ~]# ip netns exec llb_ns8 ifconfig llb_tap8 192.168.200.6/24 up
[root@C7-Server01 ~]# ifconfig tap7_peer 192.168.100.1/24 up
[root@C7-Server01 ~]# ifconfig tap8_peer 192.168.200.1/24 up

# 配置路由和回程路由
[root@C7-Server01 ~]# ip netns exec llb_ns7 route add -net 192.168.200.0 netmask 255.255.255.0 gw 192.168.100.1
[root@C7-Server01 ~]# ip netns exec llb_ns8 route add -net 192.168.100.0 netmask 255.255.255.0 gw 192.168.200.1

测试互通,结果如下,表明底层underlay网络可达。

下来,我们创建隧道设备tun,构建overlay网络,代码如下

1
2
3
4
5
6
7
8
9
# 在llb_ns7和llb_ns8中创建tun1、tun2和ipip tunnel
[root@C7-Server01 ~]# ip netns exec llb_ns7 ip tunnel add tun1 mode ipip remote 192.168.200.6 local 192.168.100.6
[root@C7-Server01 ~]# ip netns exec llb_ns8 ip tunnel add tun2 mode ipip remote 192.168.100.6 local 192.168.200.6

# 激活tun设备,并配置ip地址
[root@C7-Server01 ~]# ip netns exec llb_ns7 ip link set tun1 up
[root@C7-Server01 ~]# ip netns exec llb_ns8 ip link set tun2 up
[root@C7-Server01 ~]# ip netns exec llb_ns7 ip addr add 10.10.10.50 peer 10.10.20.50 dev tun1
[root@C7-Server01 ~]# ip netns exec llb_ns8 ip addr add 10.10.20.50 peer 10.10.10.50 dev tun2

互通测试,结果如下:

把上面的命令行脚本中的ipip换成gre,其余不变,就创建了一个gre隧道的tun设备对。因为我们说tun是一个设备,那么我们可以通过ifconfig这个命令,来看看这个设备的信息,代码如下:

可以看到,tun1是一个ipip tunnel的一个端点,IP是10.10.10.50,其对端IP是10.10.20.50。 再看路由表信息,代码如下:

图中的内容告诉我们,到达目的地10.10.10.50的路由是一个直连路由直接从tun1出去即可,其实tun1和tun2就是overlay网络的VTEP。

至此,Linux原生网络虚拟化介绍完毕。其实,还有个iptables,一般我们把它理解为Linux的包过滤防火墙,其实当其所属服务器处于网络报转发的中间节点时,它也是一个网络防火墙。它的“防火”机制就是通过一个个链结合策略表完成,而那一个个链其本质就是一个三层的虚拟网络设备。由于篇幅原因,iptables会放在Linux常用运维工具分类中介绍。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!