网络虚拟化是虚拟化技术的一部分,Docker的网络虚拟化除了Network Namespace之外还包含veth
,bridge
,NAT
,iptables
等很多关键因素。
0. Linux网络基础
由于网络虚拟化涉及到很多网络相关的基础知识,发现自己对很多基本概念还是没有理清楚,Linux中网络相关的东西跟《计算机网络》里面的内容还是有很大距离。
经过几天对Linux网络的摸索,将网络中关键模块的层级依赖关系大概总结成这么一个图:
1 | +-----------------------------------------------------------------------------+ |
有了一些新的认识:
- Linux内核中的协议栈(Protocol Stack)只实现了传输层和网络层的协议(TCP、UDP、ARP、IP、ICMP等),应用层的协议(HTTP、SFTP、FTP等)由用户态的应用程序自行实现,链路层的协议(ethernet、802.11等)由网络设备的驱动和硬件实现
- 实际的网络系统并不是严格按照协议分层去实现的,分层协议模型只是一个理论模型,各种分层模型中TCP/IP四层模型比较接近于现代互联网的实现
- Socket连接着应用层和传输层,也连接着内核空间和用户空间,是协议栈向外暴露的统一接口。用户空间中的应用程序可以通过Socket接口创建各种协议连接
ifconfig
命令列出的是所有的Network Interface,其中有物理网络设备也有虚拟网络设备,ens33
,eth0
等都是典型的物理网络设备,lo
、docker0
、veth
等都是典型的虚拟网络设备。内核集成了一些物理网络设备的驱动(但应该也有工作在用户态下的驱动),通过驱动控制物理网络设备发送接收数据。内核提供的虚拟网络设备应该是对网络设备驱动和物理网络设备的虚拟化,工作在协议栈之下- 路由表(routing tables)建立了一些IP地址和Network Interface的映射,协议栈中的网络层根据这些映射选择发送包的具体Network Interface
- 用户态(User Mode)和(Kernel Mode)是指程序运行的状态(State),运行在Kernel Mode下的程序可以不受限制的直接访问系统设备,运行在User Mode下的程序需要通过内核提供的系统调用(System Call)接口受限制的访问设备。User Space和Kernel Space是对内存空间的划分,User Mode的程序运行在User Space中,Kernel Mode的程序运行在Kernel Space中。Userland是指运行在User Mode下的软件集合
1. Linux Network Namespace
Docker的网络虚拟化基于Linux内核提供的Network Namespace特性实现,每一个Network Namespace都有自己的Network Stack,即网络接口,路由表,防火墙规则等,大概就是上面那个图中内容。
首先,抛开Docker,看看Linux中的Network Namespace是什么样的?
创建一个新的Network Namespace test1
1 | root@ubuntu:~# ip netns add test1 |
在test1
内执行ip a
,列出所有网络接口
1 | root@ubuntu:~# ip netns exec test1 ip a |
发现只有一个用于本地通信的回环(loopback)接口,处于DOWN
状态
1 | root@ubuntu:~# ip netns exec test1 ping 127.0.0.1 |
ping
自身失败,启动lo
设备,再ping
,发现成功
1 | root@ubuntu:~# ip netns exec test1 ip link set dev lo up |
2. 连接Network Namespace
此时test1
这个Network Namespace只能内部通信,无法与其他Network Namespace通信,例如就无法与宿主机所在的Default
Network Namespace通信,因此要将它们连接起来。
2.1 Veth Pair直连
连接Network Namespace有点像物理世界中的计算机组网,要连接两台计算机最简单粗暴的方式就是用一根网线直接将它们连接起来。连接两个Namespace需要用到Veth Pair,Veth是Linux内核提供的一种网络虚拟化设备,总是成对存在,就像一根虚拟网线的两端,分别连接到需要接通的两个Network Namespace上。
The veth devices are virtual Ethernet devices. They can act as tunnels between network namespaces to create a bridge to a physical network device in another namespace, but can also be used as standalone network devices.
veth devices are always created in interconnected pairs.
Veth Pair连接到两个Network Namespace的示意如图:
1 | +-------------------------------+ +-------------------------------+ |
在Default
Network Namespace上创建一对Veth
1 | root@ubuntu:~# ip link add veth-a type veth peer name veth-b |
将veth-b
移动到test1
中
1 | root@ubuntu:~# ip link set veth-b netns test1 |
要使得veth-a
和veth-b
正常工作,还需要启动它们,并为它们设置ip地址从而连接协议栈底层的网络层
设置Default
中的veth-a
ip为192.168.2.1/24
1 | root@ubuntu:~# ip addr add 192.168.2.1/24 dev veth-a |
设置test1
中的veth-b
ip为192.168.2.2/24
1 | root@ubuntu:~# ip netns exec test1 ip addr add 192.168.2.2/24 dev veth-b |
此时两个Network Namespace已经可以通过veth-a
和veth-b
通信
1 | root@ubuntu:~# ping 192.168.2.2 |
2.2 通过Linux bridge连接
在为物理主机组网时,用网线只能连接两台计算机,要想为两台以上的计算机组网,就需要用到交换机、路由器等网络设备。Network Namespace的连接也一样,要想连接将两个以上的Network Namspace连接起来,就需要用到虚拟交换机(virtual network switch),Docker使用的Linux bridge就是虚拟交换机的一种,是Linux提供一种虚拟交换机解决方案。
A bridge is a piece of software used to unite two or more network segments. A bridge behaves like a virtual network switch, working transparently (the other machines do not need to know or care about its existence). Any real devices (e.g.
eth0
) and virtual devices (e.g.tap0
) can be connected to it.
用Linux bridge连接两个Network Namespace的示意如图:
1 | +----------------------+ +----------------------+ |
创建两个Network Namespace并用bridge连接的命令行代码如下:
1 |
|
执行成功后,用ping
进行测试
1 | ip netns exec ns1 ping 192.168.2.3 |
在ubuntu 16.04上进行实验时,出现了ping
不通的情况,查了资料后发现是iptables的原因,bridge上的流量经过iptables处理时走到了FORWARD链上,而主机的iptables没有设置转发规则,因此触发FORWARD链的默认策略(Default Policy)丢弃(Drop),执行下面的命令使得bridge上的流量不经过iptables处理,从而解决问题,参考How To disable netfilter on Linux KVM bridge
1 | echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables |
3. 连接Network Namespace与外部网络
要使得Network Namespace组成的子网能够访问外部网络,还需要两方面条件
- 一方面要为这个子网设置一个网关(本例中为
br0
)与外部网络连通- 为
br0
设置一个IP地址 - 将
br0
的IP地址设置为ns1
和ns2
的网关地址
- 为
- 另一方面需要
default
中与外部网络连接的网卡(本例中为ens33
)帮助转发br0
子网与外部网络通信的数据包- 打开主机内核的IP FORWARD功能,这样主机就会像路由器这种传递设备一样转发数据包,而不是作为端设备只处理目的地址为自身数据
- 设置
default
iptables的转发规则,允许ens33<->br0
之间的数据包转发 - 设置
default
iptables的SNAT策略,将来自br0
网段的数据包源地址修改为ens33
(iptables会自动为ICMP协议进行DNAT,这样从br0
子网内部就能够ping
通外部网络了)
经过上述设置后的网络如下图(因为是在虚拟机上进行的实验,所以主机标记为VM):
1 | +---------------------------------------------------------------+-----------------------------------------+-----------------------------------------+ |
完成上述设置的Shell脚本:
1 |
|
上面实现的网络结构,与Docker的网络虚拟化网络结构已经很接近了,Docker的网络结构如下:
1 | +----------------------------------------------------------------+-----------------------------------------+-----------------------------------------+ |
(图参考来源进行了修改)
总结
- Docker网络的实现总的来说,基于Network Namespace实现了网络隔离,借助Linux提供的虚拟化网络设备
veth
,bridge
进行了组网 - 看到一个对虚拟化很精炼的总结:虚拟化就是由位于下层的软件模块,根据上层的软件模块的期待,抽象(虚拟)出一个虚拟的软件或硬件模块,使上一层软件直接运行在这个与自己期待完全一致的虚拟环境上
- 以前对网络认识就停留在《计算机网络》中的分层模型,看看实际中的实现才知道并不是那么回事,理论模型和实际实现之间还有很长一段距离。只学习理论是无意义的,理论模型没法创造出实际的价值,仅从实践出发不去思考是低效和盲目的,多将实践中的经验进行抽象的总结才能有提升
- 同样的东西不同的时候看的结果是不同的
参考
Linux Network Namespace Introduction http://docker-k8s-lab.readthedocs.io/en/latest/docker/netns.html
Linux Switching – Interconnecting Namespaces http://www.opencloudblog.com/?p=66
Linux 虚拟网络设备 veth-pair 详解 https://www.cnblogs.com/bakari/p/10613710.html
Linux虚拟网络设备之veth https://segmentfault.com/a/1190000009251098
Linux虚拟网络设备之bridge(桥) https://segmentfault.com/a/1190000009491002
Linux Manual Pageshttp://man7.org/linux/man-pages/man4/veth.4.html
使用 Linux bridge 将 Linux network namespace 连接外网 https://www.cnblogs.com/sammyliu/p/5763513.html
Using network namespaces and a virtual switch to isolate servers https://ops.tips/blog/using-network-namespaces-and-bridge-to-isolate-servers/
What’s a network interface? https://jvns.ca/blog/2017/09/03/network-interfaces/
Anatomy of the Linux networking stack http://140.120.7.21/LinuxRef/Network/LinuxNetworkStack.html