course:
Managing Docker Networking | Pluralsight
CNM: Container Network model
网络 抽象层级结构
Sandbox: a network namespace
沙箱:share 主机操作系统 的 环形围栏区域
container share:机器内核,操作系统。
similar: VM (虚拟CPUs,虚拟RAM, 虚拟硬盘,虚拟网卡……)
a container 包含一系列的namespace(一些列根文件 namespace,进程 namespace,网络 namespace……)
namespace: linux world的术语
endpoint: 连接 container的sandbox 到 外部网络世界
含:virtual Ethernet interface
网络:一组可供 通信的 endpoints
Q:
linux network namespace
在容器化大行其道的今天,Docker
可谓是容器界的宠儿。比起笨重的虚拟机,Docker 可谓是身轻如燕。当然,本文不是介绍虚拟机与 Docker 之间的优缺点,而是介绍 Docker 网络中重要的组成部分之一:
虚拟网络设备对:veth
在介绍 veth
前,我们先来介绍一下 网络命名空间(network namespace)
。
网络命名空间
网络命名空间
是 Linux 内核用来隔离不同容器间的网络资源(每个 Docker 容器都拥有一个独立的网络命名空间),网络命名空间主要隔离的资源包括:
- iptables规则表
- 路由规则表
- 网络设备列表
如下图所示,当系统中拥有 3 个网络命名空间:
由于不同的网络命名空间之间是相互隔离的,所以不同的网络命名空间之间并不能直接通信。比如在 网络命名空间A
配置了一个 IP 地址为 172.17.42.1
的设备,但在 网络命名空间B
里却不能访问,如下图所示:
就好比两台电脑,如果没有任何网线连接,它们之间是不能通信的。所以,Linux 内核提供了 虚拟网络设备对(veth)
这个功能,用于解决不同网络命名空间之间的通信。
virtual Ethernet interface(veth)
如何让一个独立的网络命名空间和主机的网络互通,这里我们需要用到linux虚拟网络设备veth。
veth设备
veth是linux的一种虚拟网络设备,它有点类似于两张网卡中间用一条网线连着,veth设备总是成对出现,通常用来连接不同网络命名空间(下面开始简称NS),一端连着NS1的内核协议栈,另一端连着NS2的内核协议栈,一端发送的数据会立刻被另一端接收。
虚拟网络设备对(veth)
虚拟网络设备对
用于解决不同网络命名空间之间的通信,可以将其看成是两块有网线连接的网卡。只要将其中一块网卡放置到网络命名空间A,另外一块网卡放置到网络命名空间B,那么两个不同的网络命名空间就能够通信,如下图所示:
如上图所示,veth0
与 veth1
组成一个虚拟网络设备对。虚拟网络设备对
就像管道一样,只要向其中一端发送数据,就可以从另外一端接收到数据。
Docker
就是使用 虚拟网络设备对
来实现不同容器之间的通信,其原理如下图:
从上图可以看出,每个容器之间并不是直接通过 虚拟网络设备对
来进行连接的,而是在主机上创建一个名为 docker0
的 网桥
,然后通过 虚拟网络设备对
来将各个容器连接到 网桥
上。网桥
有将多个 网络设备
连接起来的能力,就如现实中的 交换机
一样。
第一回是最简单的veth。
先说说什么是虚拟网络接口。与之对应的是真实的物理网络接口,即真实的网卡。而虚拟网络接口,则是没有实际的物理设备,而是通过软件“模拟”的网络接口。对于内核的来说,只要实现了net_device规定的几个必要接口,并且成功调用register_netdevice注册了该netdev,则内核就会认为这个netdev是一个网络接口。至于怎么能不能接收或者发送数据包,就看你自己个儿的了:)
其实虚拟网络接口没有那么神秘,vlan,bridge等,都是虚拟网络接口。
真实的网络接口连在实际的物理链路上,自然有正常的收发报文,并且一般通过中断进行通知。而对于虚拟接口来说,内核也早已设计好了框架,只要进行适当的接口调用,内核就会把数据包传递给你创建的虚拟接口。这里引用之前画的skb在内核的flow图的一个片断。
veth设备的特点
- veth和其它的网络设备都一样,一端连接的是内核协议栈。
- veth设备是成对出现的,另一端两个设备彼此相连
- 一个设备收到协议栈的数据发送请求后,会将数据发送到另一个设备上去。
下面这张关系图很清楚的说明了veth设备的特点:
+----------------------------------------------------------------+
| |
| +------------------------------------------------+ |
| | Newwork Protocol Stack | |
| +------------------------------------------------+ |
| ↑ ↑ ↑ |
|..............|...............|...............|.................|
| ↓ ↓ ↓ |
| +----------+ +-----------+ +-----------+ |
| | eth0 | | veth0 | | veth1 | |
| +----------+ +-----------+ +-----------+ |
|192.168.1.11 ↑ ↑ ↑ |
| | +---------------+ |
| | 192.168.2.11 192.168.2.1 |
+--------------|-------------------------------------------------+
↓
Physical Network
上图中,我们给物理网卡eth0配置的IP为192.168.1.11, 而veth0和veth1的IP分别是192.168.2.11和192.168.2.1。
Veth
Veth缩写是Virtual ETHernet。veth设备是在linux内核中是成对出现(所以也叫veth-pair
),两个设备彼此相连,一个设备从协议栈读取数据后,会将数据发送到另一个设备上去。这个设备其实是专门为container
所建的,作用就是把一个network namespace发出的数据包转发到另一个namespace(通常就是宿主机)。
Bridge
Bridge(桥)是 Linux 上用来做 TCP/IP 二层协议交换的设备,与现实世界中的交换机功能相似。Bridge 设备实例可以和 Linux 上其他网络设备实例连接,既 attach 一个从设备,类似于在现实世界中的交换机和一个用户终端之间连接一根网线。当有数据到达时,Bridge 会根据报文中的 MAC 信息进行广播、转发、丢弃处理。
2. bridge网络模式
2.1 介绍
bridge模式是docker默认的网络方式,当Docker 进程启动时,会自动在主机上创建一个 docker0 虚拟网桥,默认分配网段172.17.0.0/16
,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机,附加在其上的任何网卡之间都能自动转发数据包。
创建一个容器时,容器从docker0中的子网分配一个IP地址(也可以使用--ip
指定),并创建一对veth虚拟网络设备,一个设备在容器中作为容器的网卡名叫eth0,另一个设备在宿主机上叫做名称为vethXXX并桥接在docker0上,可通过命令brctl show
查看,通过这样的桥接方法宿主机上的所有容器都处于同一个二层网络中,这样使得容器与容器以及容器与宿主机之间能够互相通信,以下介绍其通信细节。(ps:这里的通信是单主机上容器通讯)
2.2 关于veth
veth 是 Virtual ETHernet 的缩写,是一种虚拟网络设备。它的特点是:当它被创建以后,总是以两张虚拟网卡(Veth peer)形式成对出现,并且在一个网卡上的数据包可直接转发给另一个与之对应的网卡上,即使这两个网卡不在同一个namespace中。
这里推荐一篇文章介绍veth: https://segmentfault.com/a/1190000009251098。
2.4 容器与外部网络通讯
同样的,介绍了容器与容器间的通讯,容器与主机间的通讯就容易理解了,如果在容器bs1 ping 另一个主机10.168.0.3,它发出去的数据包经过docker0网桥流向eth0,eth0在将数据包转发给与之相通的10.168.0.3,于是bs1就能和其主机节点进行通讯了。当然如果这个地址不是其他主机节点而是公网地址,只要eth0能到达,容器bs1都能与之通讯。以下是其示意图:
2.5 宿主机与容器通讯
当宿主机访问容器时,数据包从docker0流入到与容器对应的veth设备,在经容器的eth0到达到主机中,示意图如下:
2.6 外部访问容器
默认情况,其他外部网络(宿主机以外)无法访问到容器内的端口,通常的做法是使用-p
或者-P
选项(使用方法参考docker基础篇)来暴露容器中的端口到宿主机上,外部网络通过访问宿主机的端口从而访问到容器端口。
2.6 外部访问容器
默认情况,其他外部网络(宿主机以外)无法访问到容器内的端口,通常的做法是使用-p
或者-P
选项(使用方法参考docker基础篇)来暴露容器中的端口到宿主机上,外部网络通过访问宿主机的端口从而访问到容器端口。
本质上来说,该方式是通过iptables规则转发实现的,例如以下启动nginx容器并使用宿主机的8080映射到容器内部80端口:
查看其对应的iptables规则:
上述规则表明当访问宿主机的8080端口时,NAT到容器中的172.17.0.4的80中。
3. host网络模式
Host模式使用是在容器启动时候指明--network host
,此时容器共享宿主机的Network Namespace,容器内启动的端口直接是宿主机的端口,并且容器不会创建网卡和IP,直接使用宿主机的网卡和IP,但是容器内的其他资源是隔离的,如文件系统、用户和用户组。这种模式的好处就是效率高,因为不需要额外的网络开始,直接使用宿主机网络。同样启动一个nginx,此时共享主机网络:
问题来了:
- 如果是容器A内发送消息给宿主
- 如果是容器A内发送消息给其他容器,比如容器B
- 如果外部往容器A怎么发送?
4. container网络模式
理解了host模式就很容易理解container模式,host模式是容器共享宿主机的Network Namespace,而container模式即共享已存在的容器的Network Namespace,此时这两容器共同使用同一网卡、主机名、IP地址,容器间通讯可直接通过lo回环接口通讯,但是其他名称空间是隔离的,例如User、Mount。如果你了解kubernetes中的pod的话,pod内的网络也是靠这样的共享方式来实现的。
5. none网络模式
使用--network none
选项指定其网络模式,在该模式下虽然容器有着自己的Network Namespace,但是容器内没有网卡、IP、路由信息,只有一个lo回环接口。如果需要网络配置则需要用户手动进行配置网卡、ip以及路由信息,通常这样的容器用于承担某些无需网络介入的工作,如离线任务、备份等。启动一个none网络模式的容器如下:
6. 总结
本文着重对docker 单主机网络中默认bridge网络模式做了较多介绍,其核心是通过docker0网桥+Veth Pair设备实现,而host、container模式都是“共享”方式实现,host网络模式是容器共享宿主机Network Namespace,continer网络模式是容器共享已存在的容器的Network Namespace,none模式则是网络封闭状态,容器内使用lo接口通讯。