跨主机网络
跨主机网络方案包括:
1、docker 原生的 overlay 和 macvlan。
2、第三方方案:常用的包括 flannel、weave 和 calico。
docker 网络是一个非常活跃的技术领域,不断有新的方案开发出来,那么要问个非常重要的问题了:如此众多的方案是如何与 docker 集成在一起的?答案是:libnetwork 以及 CNM。
libnetwork & CNM
libnetwork 是 docker 容器网络库,最核心的内容是其定义的 Container Network Model (CNM),这个模型对容器网络进行了抽象,由以下三类组件组成:
- Sandbox
Sandbox 是容器的网络栈,包含容器的 interface、路由表和 DNS 设置。 Linux Network Namespace 是 Sandbox 的标准实现。Sandbox 可以包含来自不同 Network 的 Endpoint。
- Endpoint
Endpoint 的作用是将 Sandbox 接入 Network。Endpoint 的典型实现是 veth pair,。一个 Endpoint 只能属于一个网络,也只能属于一个 Sandbox。
- Network
Network 包含一组 Endpoint,同一 Network 的 Endpoint 可以直接通信。Network 的实现可以是 Linux Bridge、VLAN 等。
下面是 CNM 的示例:
如图所示两个容器,一个容器一个 Sandbox,每个 Sandbox 都有一个 Endpoint 连接到 Network 1,第二个 Sandbox 还有一个 Endpoint 将其接入 Network 2。
libnetwork CNM 定义了 docker 容器的网络模型,按照该模型开发出的 driver 就能与 docker daemon 协同工作,实现容器网络。docker 原生的 driver 包括 none、bridge、overlay 和 macvlan,第三方 driver 包括 flannel、weave、calico 等。
overlay driver
为支持容器跨主机通信,Docker 提供了 overlay driver,使用户可以创建基于 VxLAN 的 overlay 网络。VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。
- 实验环境
在 docker 主机 host1(10.10.8.126)和 host2(10.10.8.127)上实践各种跨主机网络方案,在 10.10.8.125 上部署支持的组件,比如 Consul。
最简单的方式是以容器方式运行 Consul:
docker run -d -p 8500:8500 -h consul --name consul progrium/consul -server -bootstrap
容器启动后,可以通过 http://10.10.8.125:8500 访问 Consul。
接下来修改 host1 和 host2 的 docker daemon 的配置文件/etc/systemd/system/docker.service。
--cluster-store 指定 consul 的地址。
--cluster-advertise 告知 consul 自己的连接地址。
重启 docker daemon。
systemctl daemon-reload
systemctl restart docker.service
host1 和 host2 将自动注册到 Consul 数据库中。
- 创建 overlay 网络
在 host1 中创建 overlay 网络 ov_net1,-d overlay 指定 driver 为 overaly:
docker network create -d overlay ov_net1
docker network ls 查看当前网络:
docker network ls
NETWORK ID NAME DRIVER SCOPE
db19978034cb bridge bridge local
bae92bff8199 host host local
deb4cbe62ba0 none null local
48b4931748ef ov_net1 overlay global
注意到 ov_net1 的 SCOPE 为 global,而其他网络为 local。host2 上也能看到 ov_net1。这是因为创建 ov_net1 时 host1 将 overlay 网络信息存入了 consul,host2 从 consul 读取到了新网络的数据。之后 ov_net 的任何变化都会同步到 host1 和 host2。
docker network inspect 查看 ov_net1 的详细信息:
docker network inspect ov_net1
[
{
"Name": "ov_net1",
"Id": "48b4931748ef3f7b33014b8e1a17fcf9786ff0114766d659d23a5b97fa90dfa2",
"Created": "2018-09-04T10:20:33.247929912+08:00",
"Scope": "global",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
IPAM 是指 IP Address Management,docker 自动为 ov_net1 分配的 IP 空间为 10.0.0.0/24。
- 在overlay中运行容器
运行一个 busybox 容器并连接到 ov_net1:
docker run -itd --name bbox1 --network ov_net1 busybox
查看容器的网络配置:
docker exec bbox1 ip r
default via 172.18.0.1 dev eth1
10.0.0.0/24 dev eth0 scope link src 10.0.0.2
172.18.0.0/16 dev eth1 scope link src 172.18.0.2
bbox1 有两个网络接口 eth0 和 eth1。eth0 IP 为 10.0.0.2,连接的是 overlay 网络 ov_net1。eth1 IP 172.18.0.2,容器的默认路由是走 eth1,eth1 是哪儿来的呢?
其实,docker 会创建一个 bridge 网络 “docker_gwbridge”,为所有连接到 overlay 网络的容器提供访问外网的能力。
如果外网要访问容器,可通过主机端口映射,比如:
docker run -p 80:80 -d --net ov_net1 --name web1 httpd
- overlay 网络跨主机通信
在 host2 中运行容器 bbox2:
docker run -itd --name bbox2 --network ov_net1 busybox
bbox2 IP 为 10.0.0.3,可以直接 ping bbox1,可见 overlay 网络中的容器可以直接通信,同时 docker 也实现了 DNS 服务。
docker 会为每个 overlay 网络创建一个独立的 network namespace,其中会有一个 linux bridge br0,endpoint 还是由 veth pair 实现,一端连接到容器中(即 eth0),另一端连接到 namespace 的 br0 上。
br0 除了连接所有的 endpoint,还会连接一个 vxlan 设备,用于与其他 host 建立 vxlan tunnel。容器之间的数据就是通过这个 tunnel 通信的。逻辑网络拓扑结构如图所示:
要查看 overlay 网络的 namespace 可以在 host1 和 host2 上执行 ip netns(请确保在此之前执行过 ln -s /var/run/docker/netns /var/run/netns),可以看到两个 host 上有一个相同的 namespace “1-f4af9b33c0”:
ip netns
1-f4af9b33c0
这就是 ov_net1 的 namespace,查看 namespace 中的 br0 上的设备。
ip netns exec 1-f4af9b33c0 brctl show
查看 vxlan1 设备的具体配置信息可知此 overlay 使用的 VNI(VxLAN ID)为 256。
ip netns exec 1-f4af9b33c0 ip -d l show vxlan1
- overlay 的网络隔离特性
不同的 overlay 网络是相互隔离的。创建第二个 overlay 网络 ov_net2 并运行容器 bbox3。bbox3 分配到的 IP 是 10.0.1.2,尝试 ping bbox1(10.0.0.2)。ping 失败,可见不同 overlay 网络之间是隔离的。即便是通过 docker_gwbridge 也不能通信。
如果要实现 bbox3 与 bbox1 通信,可以将 bbox3 也连接到 ov_net1。
docker network connect ov_net1 bbox3
docker 默认为 overlay 网络分配 24 位掩码的子网(10.0.X.0/24),所有主机共享这个 subnet,容器启动时会顺序从此空间分配 IP。当然我们也可以通过 --subnet 指定 IP 空间。
docker network create -d overlay --subnet 10.22.1.0/24 ov_net3
macvlan 网络
除了 overlay,docker 还开发了另一个支持跨主机容器网络的 driver:macvlan。
macvlan 本身是 linxu kernel 模块,其功能是允许在同一个物理网卡上配置多个 MAC 地址,即多个 interface,每个 interface 可以配置自己的 IP。macvlan 本质上是一种网卡虚拟化技术,Docker 用 macvlan 实现容器网络就不奇怪了。
macvlan 的最大优点是性能极好,相比其他实现,macvlan 不需要创建 Linux bridge,而是直接通过以太 interface 连接到物理网络。
- 准备实验环境
我们会使用 host1 和 host2 上单独的网卡 eth0 创建 macvlan。为保证多个 MAC 地址的网络包都可以从 eth0 通过,我们需要打开网卡的混杂模式。
ip link set eth0 promisc on
确保 eth0 状态 UP 并且 promisc 模式已经生效。
- 创建 macvlan网络
在 host1 和 host2 中创建 macvlan 网络 mac_net1:
# docker network create -d macvlan \
> --subnet=172.16.86.0/24 \
> --gateway=172.16.86.1 \
> -o parent=eth0 mac_net1
注意:在 host2 中也要执行相同的命令。
① -d macvlan 指定 driver 为 macvlan。
② macvlan 网络是 local 网络,为了保证跨主机能够通信,用户需要自己管理 IP subnet。
③ 与其他网络不同,docker 不会为 macvlan 创建网关,这里的网关应该是真实存在的,否则容器无法路由。
④ -o parent 指定使用的网络 interface。
在 host1 中运行容器 bbox1 并连接到 mac_net1。
docker run -itd --name bbox1 --ip=172.16.86.10 --network mac_net1 busybox
由于 host1 中的 mac_net1 与 host2 中的 mac_net1 本质上是独立的,为了避免自动分配造成 IP 冲突,我们最好通过 --ip 指定 bbox1 地址为 172.16.86.10。
在 host2 中运行容器 bbox2,指定 IP 172.16.86.11。
docker run -itd --name bbox2 --ip=172.16.86.11 --network mac_net1 busybox
bbox2 能够 ping 到 bbox1 的 IP 172.16.86.10,但无法解析 “bbox1” 主机名。
- macvlan 网络结构
macvlan 不依赖 Linux bridge,brctl show 可以确认没有创建新的 bridge。
查看一下容器 bbox1 的网络设备:
docker exec bbox1 ip link
1: lo: mtu 65536 qdisc noqueue qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
21: eth0@if2: mtu 1500 qdisc noqueue
link/ether 02:42:ac:10:56:0a brd ff:ff:ff:ff:ff:ff
除了 lo,容器只有一个 eth0,请注意 eth0 后面的 @if2,这表明该 interface 有一个对应的 interface,其全局的编号为 2。根据 macvlan 的原理,我们有理由猜测这个 interface 就是主机的 eth0,确认如下:
ip link show eth0
2: eth0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:0e:f7:cd brd ff:ff:ff:ff:ff:ff
可见,容器的 eth0 就是 主机的eth0 通过 macvlan 虚拟出来的 interface。容器的 interface 直接与主机的网卡连接,这种方案使得容器无需通过 NAT 和端口映射就能与外网直接通信(只要有网关),在网络上与其他独立主机没有区别。当前网络结构如图所示:
- 用 sub-interface 实现多 macvlan 网络
macvlan 会独占主机的网卡,也就是说一个网卡只能创建一个 macvlan 网络。但主机的网卡数量是有限的,如何支持更多的 macvlan 网络呢?好在 macvlan 不仅可以连接到 interface(如 eth0),也可以连接到 sub-interface(如 eth0.xxx)。
VLAN 是现代网络常用的网络虚拟化技术,它可以将物理的二层网络划分成多达 4094 个逻辑网络,这些逻辑网络在二层上是隔离的,每个逻辑网络(即 VLAN)由 VLAN ID 区分,VLAN ID 的取值为 1-4094。Linux 的网卡也能支持 VLAN(apt-get install vlan),同一个 interface 可以收发多个 VLAN 的数据包,不过前提是要创建 VLAN 的 sub-interface。
比如希望 eth0 同时支持 VLAN10 和 VLAN20,则需创建 sub-interface eth0.10 和 eth0.20。在交换机上,如果某个 port 只能收发单个 VLAN 的数据,该 port 为 Access 模式,如果支持多 VLAN,则为 Trunk 模式。
下面演示如何在 eth0.10 和 eth0.20 上创建 macvlan 网络。
首先编辑 host1 和 host2 的 /etc/network/interfaces,配置 sub-interface
iface eth0 inet manual
auto eth0.10
iface eth0.10 inet manual
vlan-raw-device eth0
auto eth0.20
iface eth0.20 inet manual
vlan-raw-device eth0
然后启用 sub-interface:
ifup eth0.10
ifup eth0.20
创建 macvlan 网络:
docker network create -d macvlan --subnet=172.16.10.0/24 --gateway=172.16.10.1 -o parent=eth0.10 mac_net10
docker network create -d macvlan --subnet=172.16.20.0/24 --gateway=172.16.20.1 -o parent=eth0.20 mac_net20
在 host1 中运行容器:
docker run -itd --name bbox1 --ip=172.16.10.10 --network mac_net10 busybox
docker run -itd --name bbox2 --ip=172.16.20.10 --network mac_net20 busybox
在 host2 中运行容器:
docker run -itd --name bbox3 --ip=172.16.10.11 --network mac_net10 busybox
docker run -itd --name bbox4 --ip=172.16.20.11 --network mac_net20 busybox
当前网络结构如图所示:
验证 macvlan 之间的连通性:
bbox1 能 ping 通 bbox3,bbox2 能 ping 通 bbox4。即:同一 macvlan 网络能通信。
bbox1 无法 ping 通 bbox2 和 bbox4。即:不同 macvlan 网络之间不能通信。但更准确的说法应该是:不同 macvlan 网络不能 在二层上 通信。在三层上可以通过网关将 macvlan 连通,下面我们就启用网关。
我们会将 Host 10.10.8.125 配置成一个虚拟路由器,设置网关并转发 VLAN10 和 VLAN20 的流量。当然也可以使用物理路由器达到同样的效果。首先确保操作系统 IP Forwarding 已经启用。
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
flannel网络
flannel 是 CoreOS 开发的容器网络解决方案。flannel 为每个 host 分配一个 subnet,容器从此 subnet 中分配 IP,这些 IP 可以在 host 间路由,容器间无需 NAT 和 port mapping 就可以跨主机通信。
每个 subnet 都是从一个更大的 IP 池中划分的,flannel 会在每个主机上运行一个叫 flanneld 的 agent,其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。
数据包如何在主机间转发是由 backend 实现的。flannel 提供了多种 backend,最常用的有 vxlan 和 host-gw,我们将在本章讨论这两种 backend。其他 backend 请参考 https://github.com/coreos/flannel。
- 实验环境
etcd 部署在 10.10.8.125,host1 和 host2 上运行 flanneld,首先安装配置 etcd。
- 安装配置 etcd
在10.10.8.125运行如下脚本:
ETCD_VER=v2.3.7
DOWNLOAD_URL=https://github.com/coreos/etcd/releases/download
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
mkdir -p /tmp/test-etcd && tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/test-etcd --strip-components=1
cp /tmp/test-etcd/etcd* /usr/local/bin/
该脚本从 github 上下载 etcd 的可执行文件并保存到 /usr/local/bin/,启动 etcd 并打开 2379 监听端口。
etcd -listen-client-urls http://10.10.8.125:2379 -advertise-client-urls http://10.10.8.125:2379
weave网络
weave 是 Weaveworks 开发的容器网络解决方案。weave 创建的虚拟网络可以将部署在多个主机上的容器连接起来。对容器来说,weave 就像一个巨大的以太网交换机,所有容器都被接入这个交换机,容器可以直接通信,无需 NAT 和端口映射。除此之外,weave 的 DNS 模块使容器可以通过 hostname 访问。
Calico网络
Calico 是一个纯三层的虚拟网络方案,Calico 为每个容器分配一个 IP,每个 host 都是 router,把不同 host 的容器连接起来。与 VxLAN 不同的是,Calico 不对数据包做额外封装,不需要 NAT 和端口映射,扩展性和性能都很好。
与其他容器网络方案相比,Calico 还有一大优势:network policy。用户可以动态定义 ACL 规则,控制进出容器的数据包,实现业务需求。
docker网络总结
Docker Overaly,Macvaln,Flannel,Weave 和 Calico 跨主机网络方案。目前这个领域是百家争鸣,而且还有新的方案不断涌现。
我们将从如下几个方面比较,大家可以根据不同场景选择最合适的方案。
网络模型
采用何种网络模型支持 multi-host 网络?
Distributed Store
是否需要 etcd 或 consul 这类分布式 key-value 数据库存储网络信息?
IPMA
如何管理容器网络的 IP?
连通与隔离
提供怎样的网络连通性?支持容器间哪个级别和哪个类型的隔离?
性能
性能比较。
- 网络模型
跨主机网络意味着将不同主机上的容器用同一个虚拟网络连接起来。这个虚拟网络的拓扑结构和实现技术就是网络模型。
Docker overlay 如名称所示,是 overlay 网络,建立主机间 VxLAN 隧道,原始数据包在发送端被封装成 VxLAN 数据包,到达目的后在接收端解包。
Macvlan 网络在二层上通过 VLAN 连接容器,在三层上依赖外部网关连接不同 macvlan。数据包直接发送,不需要封装,属于 underlay 网络。
Flannel 我们讨论了两种 backend:vxlan 和 host-gw。vxlan 与 Docker overlay 类似,属于 overlay 网络。host-gw 将主机作为网关,依赖三层 IP 转发,不需要像 vxlan 那样对包进行封装,属于 underlay 网络。
Weave 是 VxLAN 实现,属于 overlay 网络。
各方案的网络模型描述如下:
- Distributed Store
Docker Overlay、Flannel 和 Calico 都需要 etcd 或 consul。Macvlan 是简单的 local 网络,不需要保存和共享网络信息。Weave 自己负责在主机间交换网络配置信息,也不需要 Distributed Store。
- IPAM
Docker Overlay 网络中所有主机共享同一个 subnet,容器启动时会顺序分配 IP,可以通过 --subnet 定制此 IP 空间。
Macvlan 需要用户自己管理 subnet,为容器分配 IP,不同 subnet 通信依赖外部网关。
Flannel 为每个主机自动分配独立的 subnet,用户只需要指定一个大的 IP 池。不同 subnet 之间的路由信息也由 Flannel 自动生成和配置。
Weave 的默认配置下所有容器使用 10.32.0.0/12 subnet,如果此地址空间与现有 IP 冲突,可以通过 --ipalloc-range 分配特定的 subnet。
Calico 从 IP Pool(可定制)中为每个主机分配自己的 subnet。
- 连通与隔离
同一 Docker Overlay 网络中的容器可以通信,但不同网络之间无法通信,要实现跨网络访问,只有将容器加入多个网络。与外网通信可以通过 docker_gwbridge 网络。
Macvlan 网络的连通或隔离完全取决于二层 VLAN 和三层路由。
不同 Flannel 网络中的容器直接就可以通信,没有提供隔离。与外网通信可以通过 bridge 网络。
Weave 网络默认配置下所有容器在一个大的 subnet 中,可以自由通信,如果要实现隔离,需要为容器指定不同的 subnet 或 IP。与外网通信的方案是将主机加入到 weave 网络,并把主机当作网关。
Calico 默认配置下只允许位于同一网络中的容器之间通信,但通过其强大的 Policy 能够实现几乎任意场景的访问控制。
- 性能
性能测试是一个非常严谨和复杂的工程,这里我们只尝试从技术方案的原理上比较各方案的性能。
最朴素的判断是:Underlay 网络性能优于 Overlay 网络。
Overlay 网络利用隧道技术,将数据包封装到 UDP 中进行传输。因为涉及数据包的封装和解封,存在额外的 CPU 和网络开销。虽然几乎所有 Overlay 网络方案底层都采用 Linux kernel 的 vxlan 模块,这样可以尽量减少开销,但这个开销与 Underlay 网络相比还是存在的。所以 Macvlan、Flannel host-gw、Calico 的性能会优于 Docker overlay、Flannel vxlan 和 Weave。
Overlay 较 Underlay 可以支持更多的二层网段,能更好地利用已有网络,以及有避免物理交换机 MAC 表耗尽等优势,所以在方案选型的时候需要综合考虑。