写点什么

Docker 网络学习第一篇:Linux 虚拟网络

用户头像
Lazy
关注
发布于: 2020 年 07 月 14 日
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 helpUsage: 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 lstestns[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 addr1: 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 addr1: 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]# exitexit
复制代码

通过修改 bash 的前缀信息可以区分不同 shell, ip 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,路由数据,不同的是虚拟网络设备从协议栈接收数据,但怎么发送,发送到哪些是由驱动自身决定的。



tun/tap


在 linux 下,要实现核心态和用户态数据的交互,有多种方式:可以通用 socket 创建特殊套接字,利用套接字实现数据交互;通过 proc 文件系统创建文件来进行数据交互;还可以使用设备文件的方式,访问设备文件会调用设备驱动相应的例程,设备驱动本身就是核心态和用户态的一个接口,Tun/tap 驱动就是利用设备文件实现用户态和核心态的数据交互。


tun/tap 设备的用处是将协议栈中的部分数据包转发给用户空间的应用程序,给用户空间的程序一个处理数据包的机会。设备最常用的场景是 VPN。下图描述了 tun/tap 数据流程示意图,实际的原理比这个更加复杂。



tun/tap 设备实现 VPN 原理,vpn 通过操作系统的接口直接虚拟出一张网卡,后续整个操作系统的网络通讯都将通过这张虚拟的网卡进行收发。这和任何一个代理的实现思路都差不多,应用层并不知道网卡是虚拟的,这样 vpn 虚拟网卡将以中间人的身份对数据进行加工,从而实现各种效果,而虚拟网卡就是 tun 或者 tap。

比如在家里要通过 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。

2、网关 2 接收到数据包后,将其还原为包 A(原理和上一步相同,也是利用了 tun 或 tap 将数据传至应用层再解封还原的过程),最终转到目标服务器,




下面演示了如何在 centos7 中创建一个 tun 设备

第一步,确认内核是否有 tun 模块 modinfo tun, 如果有的话会输出 tun 相关描述信息;

第二步,确定内核模块是否已经加载,lsmod | grep tun , 如果没有加载使用modprobe tun命令加载;

[root@2030-edu-01-no ~]# lsmod | grep tuntun                    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.5PING 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 ms64 bytes from 192.168.1.5: icmp_seq=2 ttl=64 time=0.040 ms
复制代码

veth-pair

veth-pair 是一对的虚拟设备接口,和 tap/tun 设备不同的是,它都是成对出现的。一端连着协议栈,一端彼此相连。



当 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 upip netns exec testns ip a a 10.1.1.3/24 dev vethtestnsip 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.2PING 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 ms64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.055 ms64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=0.047 ms
复制代码

veth 实现了两两通信,通信过程简单描述如下,左侧是刚刚演示的 namespace 与主机的通信模型,右侧是两个 namespace 之间的通信模型,但是如果需要多个接口互相通信,veth 就无法胜任了。



Bridge

同样 Bridge 也是虚拟网络设备之一,具有网络设备的通性,普通虚拟设备只有两个端口,一进一出,而 Bridge 由多个端口,数据从任一端口进来,再根据 mac 地址从指定端口出去。可以看出 Bridge 是一套虚拟交换设备,和物理交换机的功能相同。

使用演示

通过命令ip link add name bridge0 type bridge && ip link set bridge0 up 创建并启动一个网桥

[root@2030-edu-01-no ~]# ifconfig bridge0bridge0: 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
复制代码

此时网桥一端连接协议栈,而另一端什么也没接。



上文提到了 veth 设备只能实现两两连接,如果有多个接口需要连接,此时就可以借助 Bridge 来实现。我们假设现在有三个 namespace 要互相通信,那么就需要创建三对 veth 设备,每对设备中的一个接到 namespace 中,另一个接入网桥中,具体的模型如下:



具体步骤为

  • 创建 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 1PING 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 0msrtt 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 1PING 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 0msrtt 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 bridge0bridge name	bridge id		STP enabled	interfacesbridge0		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 机器的协议栈,效率也比较高。



借助 Linux Bridge 功能,同主机或跨主机的虚拟机之间能够轻松实现通信,也能够让虚拟机访问到外网,这就是我们所熟知的桥接模式,一般在装 VMware 虚拟机或者 VirtualBox 虚拟机的时候,都会提示我们要选择哪种模式,其中常用的一种模式是桥接。


OVS

Bridge 充当虚拟交换机已经能满足网络的通信,但是仍有一些不足的地方,比如网络管理和监控的便利性、数据包寻路和转发的高效性、隧道协议支持类型等。OVS(Open vSwitch)是流行的虚拟交换机之一,扩展了很多高级特性。


参考资料


  1. http://www.360doc.com/content/18/0829/07/44856983_782027344.shtml

  2. https://zhuanlan.zhihu.com/p/73248894

  3. https://www.jianshu.com/p/2a14fe583cdf

  4. https://blog.csdn.net/u012707739/article/details/78163354

  5. https://www.ibm.com/developerworks/cn/linux/l-tuntap/

  6. https://segmentfault.com/a/1190000009249039

  7. https://segmentfault.com/a/1190000009491002

  8. https://www.cnblogs.com/bakari/p/10613710.html

  9. https://www.cnblogs.com/bakari/p/8097439.html


发布于: 2020 年 07 月 14 日阅读数: 157
用户头像

Lazy

关注

某知名软件上市公司小小开发者。 2019.10.17 加入

程序员要做工程师,不做码农; 程序员要看书啊;

评论 (5 条评论)

发布
用户头像
很好, 但是有一些看不明白, 能继续请教么? vx 278593673 谢谢您
2020 年 07 月 16 日 15:52
回复
用户头像
很赞!至少对我很有帮助~TKS!
2020 年 07 月 15 日 16:55
回复
感谢支持。
2020 年 07 月 16 日 09:48
回复
用户头像
赞,内容挺详细
2020 年 07 月 14 日 09:53
回复
谢谢。
2020 年 07 月 14 日 13:45
回复
没有更多了
Docker网络学习第一篇:Linux虚拟网络