docker跨主机网络方案

docker跨主机网络方案

docker跨主机网络方案包括:

  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 等

libnetwork CNM 定义了 docker 容器的网络模型,按照该模型开发出的 driver 就能与 docker daemon 协同工作,实现容器网络。docker 原生的 driver 包括 none、bridge、overlay 和 macvlan,第三方 driver 包括 flannel、weave、calico 等。

为支持容器跨主机通信,Docker 提供了 overlay driver,使用户可以创建基于 VxLAN 的 overlay 网络。VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。有关 VxLAN 更详细的内容可参考 CloudMan 在《每天5分钟玩转 OpenStack》中的相关章节

Docerk overlay 网络需要一个 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint、IP 等。Consul、Etcd 和 ZooKeeper 都是 Docker 支持的 key-vlaue 软件,我们这里使用 Consul。

1)实验环境描述

node1 10.0.2.10host1 10.0.2.11 host2 10.0.2.12

最简单的方式是以容器方式运行 Consul:

在node1上部署支持的组件 consul

[root@node1 ~]# docker run -d -p 8500:8500 -h consul --name consul progrium/consul -server -bootstrap

容器启动后,可以通过 http://10.0.2.10:8500 访问 Consul。

接下来修改 host1 和 host2 的 docker daemon 的配置文件/etc/systemd/system/docker.service.d/10-machine.conf

[root@host1 ~]# vim /etc/systemd/system/docker.service.d/10-machine.conf 

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
        /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
        --cluster-store=consul://10.0.2.10:8500 --cluster-advertise=eth0:2376
Environment=

重启 docker daemon。

systemctl daemon-reload

systemctl restart docker.service

host1 和 host2 将自动注册到 Consul 数据库中。

http://10.0.2.10:8500/ui/#/dc1/kv/docker/nodes/

在 host1 中创建 overlay 网络 ov_net1:

[root@host1 ~]# docker network create -d overlay ov_net1
c82b3b1be3e8035d0088952ff2e2ab42d323496dcde08bd813aaea72f09b991a
[root@host1 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9e1bc9528540        bridge              bridge              local
12e0fcdac081        host                host                local
797eee7fca29        none                null                local
c82b3b1be3e8        ov_net1             overlay             global

-d overlay 指定 driver 为 overaly

注意到 ov_net1 的 SCOPE 为 global,而其他网络为 local。在 host2 上查看存在的网络:

[root@host2 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
a8b54548ffa0        bridge              bridge              local
146fe14636bd        host                host                local
3ef7d580564a        none                null                local
c82b3b1be3e8        ov_net1             overlay             global

host2 上也能看到 ov_net1。这是因为创建 ov_net1 时 host1 将 overlay 网络信息存入了 consul,host2 从 consul 读取到了新网络的数据。之后 ov_net 的任何变化都会同步到 host1 和 host2。

docker network inspect 查看 ov_net1 的详细信息:

 "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1"
                }
            ]
        },

IPAM 是指 IP Address Management,docker 自动为 ov_net1 分配的 IP 空间为 10.0.0.0/24。

在ov_net1网络上创建运行容器

[root@host1 ~]# docker run --name bbox1 -itd --network ov_net1 busybox
99e3ce359a1ae41667f7b09b544a4a8c6e3009b18da40cd96832b8aedb76dc7d

[root@host1 ~]# 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 网络的容器提供访问外网的能力。

[root@host1 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9e1bc9528540        bridge              bridge              local
a4d35bce9b5a        docker_gwbridge     bridge              local
12e0fcdac081        host                host                local
797eee7fca29        none                null                local
c82b3b1be3e8        ov_net1             overlay             global

查看网络

[root@host1 ~]# docker network inspect  docker_gwbridge 
[
    {
        "Name": "docker_gwbridge",
        "Id": "a4d35bce9b5a03ef3c3cbaf8139873aad139b7c6ca222cbb59d72cfa05c96675",
        "Created": "2018-12-17T16:12:45.850674561+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "99e3ce359a1ae41667f7b09b544a4a8c6e3009b18da40cd96832b8aedb76dc7d": {
                "Name": "gateway_1c1435110045",
                "EndpointID": "f876cab800aecfc9eaf4a8573b662ef3305891a8c64eab52a2fefefc858b059e",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_icc": "false",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.name": "docker_gwbridge"
        },
        "Labels": {}
    }
]

而且此网络的网关就是网桥 docker_gwbridge 的 IP 172.18.0.1

[root@host1 ~]# ifconfig docker_gwbridge
docker_gwbridge: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
        inet6 fe80::42:61ff:fe3d:13fa  prefixlen 64  scopeid 0x20<link>
        ether 02:42:61:3d:13:fa  txqueuelen 0  (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

这样容器 bbox1 就可以通过 docker_gwbridge 访问外网。

[root@host1 ~]# docker exec bbox1 ping -c 3 www.baidu.com
PING www.baidu.com (163.177.151.109): 56 data bytes
64 bytes from 163.177.151.109: seq=0 ttl=55 time=12.698 ms
64 bytes from 163.177.151.109: seq=1 ttl=55 time=11.510 ms
64 bytes from 163.177.151.109: seq=2 ttl=55 time=12.060 ms

--- www.baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 11.510/12.089/12.698 ms

如果外网要访问容器,可通过主机端口映射,比如:

docker run -p 80:80 -d --net ov_net1 --name web1 httpd

2)跨主机通信

在 host2 中运行容器 bbox2:

[root@host2 ~]# docker run --name bbox2 -itd --network ov_net1 busybox
1e004591c73ffc6e76098a70fa26ca6db7acf8d386bcc7dbdcce872f741404a5
[root@host2 ~]# docker exec bbox2 ip r
default via 172.18.0.1 dev eth1 
10.0.0.0/24 dev eth0 scope link  src 10.0.0.3 
172.18.0.0/16 dev eth1 scope link  src 172.18.0.2 
[root@host2 ~]# docker exec bbox2 ping -c 2 bbox1
PING bbox1 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=5.838 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.657 ms

bbox2 IP 为 10.0.0.3,可以直接 ping bbox1:

可见 overlay 网络中的容器可以直接通信,同时 docker 也实现了 DNS 服务。

3)overlay 网络的连通:

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-c82b3b1be3,

[root@host2 ~]# ln -s /var/run/docker/netns/ /var/run/netns
[root@host2 ~]# ip netns
c6bd1da0cdcf (id: 2)
1-c82b3b1be3 (id: 1)
535be03f749a (id: 0)

[root@host1 ~]# ln -s /var/run/docker/netns/ /var/run/netns
[root@host1 ~]#  ip netns
1c1435110045 (id: 1)
1-c82b3b1be3 (id: 0)

这就是 ov_net1 的 namespace,查看 namespace 中的 br0 上的设备。

[root@host1 ~]yum install -y bridge-utils
[root@host1 ~]# ip netns exec 1-c82b3b1be3 brctl show
bridge name	bridge id		STP enabled	interfaces
br0		8000.be0804033d22	no		veth0
							vxlan0

查看 vxlan1 设备的具体配置信息可知此 overlay 使用的 VNI为256

9: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default 
    link/ether ca:6b:e3:85:45:b9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1 
    vxlan id 256 

4)overlay 网络隔离

不同的 overlay 网络是相互隔离的

我们创建第二个 overlay 网络 ov_net2 并运行容器 bbox3。

[root@host1 ~]# docker network create -d overlay ov_net2
9dce57a6c3430c29c214787cdee62b81d5c2af1509b0712864946d62b9fc01d5
[root@host1 ~]# docker run --name bbox3 -itd --network ov_net2 busybox
e247d50629401d9a1c2e3fc5a2b852cb3bc99cb08d1afaf9c0312a025d08d8e2
[root@host1 ~]# docker exec -it bbox3 ip r
default via 172.18.0.1 dev eth1 
10.0.1.0/24 dev eth0 scope link  src 10.0.1.2 
172.18.0.0/16 dev eth1 scope link  src 172.18.0.3 

bbox3 分配到的 IP 是 10.0.1.2,尝试 ping bbox1(10.0.0.2)

[root@host1 ~]# docker exec -it bbox3 ping -c 2 10.0.0.2
PING 10.0.0.2 (10.0.0.2): 56 data bytes

--- 10.0.0.2 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss

ping 失败,可见不同 overlay 网络之间是隔离的。即便是通过 docker_gwbridge 也不能通信。如果要实现 bbox3 与 bbox1 通信,可以将 bbox3 也连接到 ov_net1。

[root@host1 ~]# docker network connect ov_net1 bbox3
[root@host1 ~]# docker exec -it bbox3 ping -c 2 10.0.0.2
PING 10.0.0.2 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.423 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.127 ms

--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.127/0.275/0.423 ms

2、 macvlan

除了 overlay,docker 还开发了另一个支持跨主机容器网络的 driver:macvlan。

macvlan 本身是 linxu kernel 模块,其功能是允许在同一个物理网卡上配置多个 MAC 地址,即多个 interface,每个 interface 可以配置自己的 IP。macvlan 本质上是一种网卡虚拟化技术,Docker 用 macvlan 实现容器网络就不奇怪了。

macvlan 的最大优点是性能极好,相比其他实现,macvlan 不需要创建 Linux bridge,而是直接通过以太 interface 连接到物理网络。

1)实验环境描述

我们会使用 host1 和 host2 上单独的网卡 eth0创建 macvlan。为保证多个 MAC 地址的网络包都可以从 eth0 通过,我们需要打开网卡的混杂模式。

[root@host1 ~]# ip link set eth0 promisc on

在 host1 和 host2 中创建 macvlan 网络 mac_net1

[root@host1 ~]# docker network create -d macvlan \     
> --subnet=172.16.100.0/24 \
> --gateway=172.16.100.1 \
> -o parent=eth0 mac_net1
2cb22bc8ead6b896e2b2e8e8a2e4f35ec26a61937ede424c7ea1e42d5be24c2b

注意:在 host2 中也要执行相同的命令

-d macvlan 指定 driver 为 macvlan。

② macvlan 网络是 local 网络,为了保证跨主机能够通信,用户需要自己管理 IP subnet。

③ 与其他网络不同,docker 不会为 macvlan 创建网关,这里的网关应该是真实存在的,否则容器无法路由。

-o parent 指定使用的网络 interface。

在 host1 中运行容器 bbox1 并连接到 mac_net1。

[root@host1 ~]# docker run --name bbox1 -itd --ip=172.16.100.10 --network mac_net1 busybox
090490a6ea47d773ae865620766cb8f959f68320375568688d8f81a1257362dc

由于 host1 中的 mac_net1 与 host2 中的 mac_net1 本质上是独立的,为了避免自动分配造成 IP 冲突,我们最好通过 --ip 指定 bbox1 地址为 172.16.100.10

2)跨主机通信

在 host2 中运行容器 bbox2,指定 IP 172.16.86.11。

验证 bbox1 和 bbox2 的连通性。

root@host2 ~]# docker exec bbox2 ping -c 2 172.16.100.10
PING 172.16.100.10 (172.16.100.10): 56 data bytes
64 bytes from 172.16.100.10: seq=0 ttl=64 time=8.227 ms
64 bytes from 172.16.100.10: seq=1 ttl=64 time=0.618 ms

--- 172.16.100.10 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.618/4.422/8.227 ms

bbox2 能够 ping 到 bbox1 的 IP 172.16.86.10,但无法解析 “bbox1” 主机名。

可见 docker 没有为 macvlan 提供 DNS 服务,这点与 overlay 网络是不同的。

3)网络结构分析

macvlan 不依赖 Linux bridge,brctl show 可以确认没有创建新的 bridge。

查看一下容器 bbox1 的网络设备:

oot@host1 ~]# docker exec bbox1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
22: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:10:64:0a brd ff:ff:ff:ff:ff:ff

除了 lo,容器只有一个 eth0,请注意 eth0 后面的 @if2,这表明该 interface 有一个对应的 interface,其全局的编号为 2。根据 macvlan 的原理,我们有理由猜测这个 interface 就是主机的 eth0,确认如下:

[root@host1 ~]# ip link show eth0
2: eth0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:92:32:12 brd ff:ff:ff:ff:ff:ff

可见,容器的 eth0 就是 eth0 通过 macvlan 虚拟出来的 interface。容器的 interface 直接与主机的网卡连接,这种方案使得容器无需通过 NAT 和端口映射就能与外网直接通信(只要有网关),在网络上与其他独立主机没有区别。

4)用 sub-interface 实现多 macvlan 网络

macvlan 会独占主机的网卡,也就是说一个网卡只能创建一个 macvlan 网络,否则会报错:

但主机的网卡数量是有限的,如何支持更多的 macvlan 网络呢?

好在 macvlan 不仅可以连接到 interface(如eth0),也可以连接到 sub-interface(如eth0.xxx)。

VLAN 是现代网络常用的网络虚拟化技术,它可以将物理的二层网络划分成多达 4094 个逻辑网络,这些逻辑网络在二层上是隔离的,每个逻辑网络(即 VLAN)由 VLAN ID 区分,VLAN ID 的取值为 1-4094。

Linux 的网卡也能支持 VLAN(yum install vlan),同一个 interface 可以收发多个 VLAN 的数据包,不过前提是要创建 VLAN 的 sub-interface。

比如希望eth0 同时支持 VLAN10 和 VLAN20,则需创建 sub-interfaceeth0.10 和eth0.20。

在交换机上,如果某个 port 只能收发单个 VLAN 的数据,该 port 为 Access 模式,如果支持多 VLAN,则为 Trunk 模式,所以接下来实验的前提是:

eth0 要接在交换机的 trunk 口上。虚拟机测试跳过

配置Vlan (host1 host2都要配置)

[root@host1 network-scripts]# pwd
/etc/sysconfig/network-scripts
[root@host1 network-scripts]# vim ifcfg-eth0.10

DEVICE=eth0.10
BOOTPROTO=none
ONBOOT=yes
IPADDR=10.0.2.21
PREFIX=24
NETWORK=10.0.2.0
VLAN=yes

[root@host1 network-scripts]# vim ifcfg-eth0.20
DEVICE=eth0.20
BOOTPROTO=none
ONBOOT=yes
IPADDR=10.0.2.31
PREFIX=24
NETWORK=10.0.2.0
VLAN=yes

创建VLAN网卡(host1 host2都要配置)

[root@host1 network-scripts]# ip link add link eth0 name eth0.10 type vlan id 10
root@host1 network-scripts]# systemctl restart network
[root@host1 network-scripts]# ip a
......
23: eth0.10@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:0c:29:92:32:12 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2..211/24 brd 10.0.2..255 scope global noprefixroute eth0.10
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe92:3212/64 scope link

[root@host1 network-scripts]#  ip link add link eth0 name eth0.20 type vlan id 20
[root@host1 network-scripts]# systemctl restart network

创建 macvlan 网络:(host1 host2都要配置)

[root@host1 network-scripts]# docker network create -d macvlan --subnet=172.16.10.0/24 --gateway=172.16.10.1 -o parent=eth0.10 mac_net10
86eb515908d06ddabab7c2a5ed1313c6a7ad821df10c1585804e678137b46ea2
[root@host1 network-scripts]#  docker network create -d macvlan --subnet=172.16.20.0/24 --gateway=172.16.20.1 -o parent=eth0.20 mac_net20
8cf792fe4f85730027796a6eb2d0a02d39893a7462d3bdab023182c58fd78dec

在 host1 中运行容器:

[root@host1 ~]# docker run --name bbox1 -itd --ip=172.16.10.10 --network mac_net10 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
6f58a52b1dcb643074d56294505089b0bc98838809a2bf66a0be44f02a2a51cd
[root@host1 ~]# docker run --name bbox2 -itd --ip=172.16.20.10 --network mac_net20 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
83774612f7947b8cd019ebcfb7dde2af0f40043340d7c1fcb7bd865609c65adf
docker: Error response from daemon: network mac_net20 not found.

在 host2 中运行容器:

[root@host2 ~]# docker run --name bbox3 -itd --ip=172.16.10.11 --network mac_net10 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
2ea31530a8c05d37d7b50aba54be467625b9258c5bd0ac658540dda69b3c4751
[root@host2 ~]# docker run --name bbox4 -itd --ip=172.16.20.11 --network mac_net20 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
80a04f49f098fba413957fa95d6256d717e4a8756e4da712407393d7a699c123

验证 macvlan 之间的连通性。

[root@host1 ~]# docker exec bbox1 ping -c 2 172.16.10.11
PING 172.16.10.11 (172.16.10.11): 56 data bytes
64 bytes from 172.16.10.11: seq=0 ttl=64 time=8.106 ms
64 bytes from 172.16.10.11: seq=1 ttl=64 time=0.687 ms

bbox1 能 ping 通 bbox3,bbox2 能 ping 通 bbox4。即:同一 macvlan 网络能通信。

bbox1 无法 ping 通 bbox2 和 bbox4。即:不同 macvlan 网络之间不能通信。但更准确的说法应该是:不同 macvlan 网络不能 在二层上 通信。在三层上可以通过网关将 macvlan 连通,下面我们就启用网关。

我们会将 Host 10.0.2.10配置成一个虚拟路由器,设置网关并转发 VLAN10 和 VLAN20 的流量。当然也可以使用物理路由器达到同样的效果。首先确保操作系统 IP Forwarding 已经启用。

[root@host1 ~]# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

在node1配置Vlan

[root@node1 network-scripts]# pwd
/etc/sysconfig/network-scripts
[root@host1 network-scripts]# vim ifcfg-eth0.10

DEVICE=eth0.10
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
VLAN=yes

[root@host1 network-scripts]# vim ifcfg-eth0.20
DEVICE=eth0.20
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
VLAN=yes

将网关 IP 配置到

[root@host1 network-scripts]#ip link add link eth0 name eth0.10 type vlan id 10
[root@host1 network-scripts]#ip link add link eth0 name eth0.20 type vlan id 20
[root@host1 network-scripts]#systemctl restart network

开启VLAN

[root@node1 network-scripts]#ifconfig eth0.10 172.16.10.1 netmask 255.255.255.0 up
[root@host1 network-scripts]#ifconfig eth0.20 172.16.20.1 netmask 255.255.255.0 up

设置iptables路由转发

root@node1 ~]# iptables -t nat -A POSTROUTING -o eth0.10 -j MASQUERADE
[root@node1 ~]# iptables -t nat -A POSTROUTING -o  eth0.20 -j MASQUERADE
[root@node1 ~]# iptables -A FORWARD -i  eth0.10 -o eth0.20 -m state --state RELATED,ESTABLISHED -j ACCEPT
[root@node1 ~]# iptables -A FORWARD -i eth0.20 -o eth0.10 -m state --state RELATED,ESTABLISHED -j ACCEPT
[root@node1 ~]# iptables -A FORWARD -i eth0.10 -o eth0.20 -j ACCEPT
[root@node1 ~]# iptables -A FORWARD -i eth0.20 -o eth0.10 -j ACCEPT

创建 macvlan 网络:(host1 host2都要配置)

[root@node1 network-scripts]# docker network create -d macvlan --subnet=172.16.10.0/24 --gateway=172.16.10.1 -o parent=eth0.10 mac_net10
86eb515908d06ddabab7c2a5ed1313c6a7ad821df10c1585804e678137b46ea2
[root@node1 network-scripts]#  docker network create -d macvlan --subnet=172.16.20.0/24 --gateway=172.16.20.1 -o parent=eth0.20 mac_net20
8cf792fe4f85730027796a6eb2d0a02d39893a7462d3bdab023182c58fd78dec

现在 host1 上位于 mac_net10 的 bbox1 已经可以与 host2 上位于 mac_net20 的 bbox4 通信了。

macvlan 网络的连通和隔离完全依赖 VLAN、IP subnet 和路由,docker 本身不做任何限制,用户可以像管理传统 VLAN 网络那样管理 macvlan。

3、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

1)实验环境描述

10.0.2.10 node1 backend

10.0.2.11 host1 flanel

10.0.2.12 host2 flanel

etcd 部署在 10.0.2.10,host1 和 host2 上运行 flanneld,首先安装配置 etcd。

在node1 配置脚本

[root@node1 ~]# cd /server/scripts/
[root@node1 scripts]# vim etcd.sh
#!/bin/sh
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 监听端口。

[root@node1 scripts]# etcd -listen-client-urls http://10.0.2.10:2379 -advertise-client-urls http://10.0.2.10:2379

测试 etcd 是否可用:

[root@node1 ~]# etcdctl --endpoint=10.0.0.10:2379 set foo "hello"
hello
[root@node1 ~]# etcdctl --endpoint=10.0.0.10:2379 get foo
hello

可以正常在 etcd 中存取数据了。

2)配置flannel vxlan

flannel 没有现成的执行文件可用,必须自己 build,最可靠的方法是在 Docker 容器中 build。不过用于做 build 的 docker 镜像托管在 gcr.io,国内可能无法直接访问,为方便大家,cloudman把它 mirror 到了 docker hub,构建步骤如下:

1\下载并命名image
[root@node1 ~]# docker pull cloudman6/kube-cross:v1.6.2-2
[root@node1 ~]# docker tag cloudman6/kube-cross:v1.6.2-2 gcr.io/google_containers/kube-cross:v1.6.2-2
2\ 下载 flannel 源码
[root@node1 ~]# git clone https://github.com/coreos/flannel.git
正克隆到 'flannel'...
remote: Enumerating objects: 363, done.
remote: Counting objects: 100% (363/363), done.
remote: Compressing objects: 100% (290/290), done.
remote: Total 25600 (delta 83), reused 212 (delta 62), pack-reused 25237
接收对象中: 100% (25600/25600), 45.50 MiB | 2.37 MiB/s, done.
处理 delta 中: 100% (9187/9187), done.
3\ 开始构建
[root@node1 ~]# cd flannel/
[root@node1 flannel]# make dist/flanneld-amd64
if [ "qemu-amd64-static" = "qemu-amd64-static" ]; then \
	wget -O dist/qemu-amd64-static https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-x86_64-static; \
  elif [ "qemu-amd64-static" = "qemu-arm64-static"]; then \
	wget -O dist/qemu-arm64-static https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-aarch64-static; \
else \
	wget -O dist/qemu-amd64-static https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-amd64-static; \
fi 
--2018-12-18 12:29:32--  https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-x86_64-static
正在解析主机 github.com (github.com)... 13.229.188.59, 13.250.177.223, 52.74.223.119
正在连接 github.com (github.com)|13.229.188.59|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 302 Found
位置:https://github-production-release-asset-2e65be.s3.amazonaws.com/47342812/d4478d00-cffb-11e8-8a74-22686fad33ae?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20181218%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20181218T042934Z&X-Amz-Expires=300&X-Amz-Signature=6ef4ed517480ee30e58b7f6874a05dc3a6c26b015beb11c478e756941deb1048&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dqemu-x86_64-static&response-content-type=application%2Foctet-stream [跟随至新的 URL]
--2018-12-18 12:29:34--  https://github-production-release-asset-2e65be.s3.amazonaws.com/47342812/d4478d00-cffb-11e8-8a74-22686fad33ae?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20181218%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20181218T042934Z&X-Amz-Expires=300&X-Amz-Signature=6ef4ed517480ee30e58b7f6874a05dc3a6c26b015beb11c478e756941deb1048&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dqemu-x86_64-static&response-content-type=application%2Foctet-stream
正在解析主机 github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)... 52.216.82.248
正在连接 github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.216.82.248|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:4443728 (4.2M) [application/octet-stream]
正在保存至: “dist/qemu-amd64-static”

100%[=======================================================>] 4,443,728   1.90MB/s 用时 2.2s   

2018-12-18 12:29:38 (1.90 MB/s) - 已保存 “dist/qemu-amd64-static” [4443728/4443728])

# valid values for ARCH are [amd64 arm arm64 ppc64le s390x]
docker run -e CGO_ENABLED=1 -e GOARCH=amd64 \
	-u 0:0 \
	-v /root/flannel/dist/qemu-amd64-static:/usr/bin/qemu-amd64-static \
	-v /root/flannel:/go/src/github.com/coreos/flannel:ro \
	-v /root/flannel/dist:/go/src/github.com/coreos/flannel/dist \
	golang:1.10.3 /bin/bash -c '\
	cd /go/src/github.com/coreos/flannel && \
	make -e dist/flanneld && \
	mv dist/flanneld dist/flanneld-amd64'
Unable to find image 'golang:1.10.3' locally
1.10.3: Pulling from library/golang
55cbf04beb70: Pull complete 
1607093a898c: Pull complete 
9a8ea045c926: Pull complete 
d4eee24d4dac: Pull complete 
9c35c9787a2f: Pull complete 
6a66653f6388: Pull complete 
102f6b19f797: Pull complete 
Digest: sha256:3c54fa85d6262d2ef7695ee2f8793f1f4f9809ce4a08ca2e213235ef4cfdcb66
Status: Downloaded newer image for golang:1.10.3
go build -o dist/flanneld \
  -ldflags '-s -w -X github.com/coreos/flannel/version.Version=v0.10.0-69-g39af3d7e -extldflags "-static"'
# github.com/coreos/flannel
/tmp/go-link-965670900/000022.o: In function `mygetgrouplist':
/workdir/go/src/os/user/getgrouplist_unix.go:15: warning: Using 'getgrouplist' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetgrgid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:38: warning: Using 'getgrgid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetgrnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:43: warning: Using 'getgrnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetpwnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:33: warning: Using 'getpwnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetpwuid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:28: warning: Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000004.o: In function `_cgo_f7895c2c5a3a_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:46: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
4\将 flanneld 执行文件拷贝到 host1 和 host2。
[root@node1 flannel]# scp dist/flanneld-amd64 10.0.2.11:/usr/local/bin/flanneld
flanneld-amd64                                                 100%   34MB  21.2MB/s   00:01    
[root@node1 flannel]# scp dist/flanneld-amd64 10.0.2.12:/usr/local/bin/flanneld
flanneld-amd64                                                 100%   34MB  22.2MB/s   00:01  

将 flannel 网络的配置信息保存到 etcd

先将配置信息写到文件 flannel-config.json 中,内容为:

[root@node1 flannel]# vim flannel-config.json

{
  "Network": "10.2.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan"
  }
}
  1. Network 定义该网络的 IP 池为 10.2.0.0/16
  2. SubnetLen 指定每个主机分配到的 subnet 大小为 24 位,即10.2.X.0/24
  3. Backendvxlan,即主机间通过 vxlan 通信,后面我们还会讨论host-gw

将配置存入 etcd:

[root@node1 flannel]# etcdctl --endpoint=10.0.0.10:2379 set /docker-test/network/config < flannel-config.json
{
  "Network": "10.2.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "vxlan"
  }
}

/docker-test/network/config 是此 etcd 数据项的 key,其 value 为 flannel-config.json 的内容。key 可以任意指定,这个 key 后面会作为 flanneld 的一个启动参数。执行 etcdctl get 确保设置成功

5\启动flannel

在 host1 和 host2 上执行如下命令:

[root@host1 ~]# flanneld -etcd-endpoints=http://10.0.2.10:2379 -iface=eth0 -etcd-prefix=/docker-test/network
#eth0被选作与外部通信的interface
I1218 13:53:33.082312    9063 main.go:527] Using interface with name eth0 and address 10.0.2.11
I1218 13:53:33.082516    9063 main.go:544] Defaulting external address to interface address (10.0.2.11)
I1218 13:53:33.085850    9063 main.go:244] Created subnet
manager: Etcd Local Manager with Previous Subnet: None
I1218 13:53:33.085886    9063 main.go:247] Installing signal handlers
I1218 13:53:33.093993    9063 main.go:386] Found network config - Backend type: vxlan
I1218 13:53:33.094100    9063 vxlan.go:120] VXLAN config: VNI=1 Port=0 GBP=false DirectRouting=false
#识别 flannel 网络池 10.2.0.0/16。
I1218 13:53:33.311792    9063 local_manager.go:234] Picking subnet in range 10.2.1.0 ... 10.2.255.0
# 分配的 subnet 为 10.2.19.0/24。
I1218 13:53:33.315619    9063 local_manager.go:220] Allocated lease (10.2.19.0/24) to current node (10.0.2.11) 
I1218 13:53:33.317893    9063 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1218 13:53:33.317920    9063 main.go:321] Running backend.
I1218 13:53:33.323052    9063 vxlan_network.go:60] watching for new subnet leases
I1218 13:53:33.325031    9063 main.go:429] Waiting for 22h59m59.953184611s to renew lease
I1218 13:53:33.380670    9063 iptables.go:145] Some iptables rules are missing; deleting and recreating rules
I1218 13:53:33.380727    9063 iptables.go:167] Deleting iptables rule: -s 10.2.0.0/16 -j ACCEPT
I1218 13:53:33.390787    9063 iptables.go:167] Deleting iptables rule: -d 10.2.0.0/16 -j ACCEPT
I1218 13:53:33.396709    9063 iptables.go:155] Adding iptables rule: -s 10.2.0.0/16 -j ACCEPT
I1218 13:53:33.424823    9063 iptables.go:155] Adding iptables rule: -d 10.2.0.0/16 -j ACCEPT

-etcd-endpoints 指定 etcd url。

-iface 指定主机间数据传输使用的 interface。

-etcd-prefix 指定 etcd 存放 flannel 网络配置信息的 key。

6\ flanneld 启动后

flanneld 启动后,host1 内部网络会发生一些变化:

  1. 一个新的 interface flannel.1 被创建,而且配置上 subnet 的第一个 IP 10.2.19.0。
  2. host1 添加了一条路由:目的地址为 flannel 网络 10.2.54.0/24 的数据包都由 flannel.1 转发。
[root@host1 ~]# ip addr show flannel.1
17: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether 3a:de:9e:a8:2f:ea brd ff:ff:ff:ff:ff:ff
    inet 10.2.19.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::38de:9eff:fea8:2fea/64 scope link 
       valid_lft forever preferred_lft forever
[root@host1 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.54.0/24 via 10.2.54.0 dev flannel.1 onlink 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.120 metric 101 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.11 metric 100 

host2 输出类似,主要区别是 host2 的 subnet 为 10.2.54.0/24:

目的地址为 flannel 网络 10.2.19.0/24 的数据包都由 flannel.1 转发。

[root@host2 ~]# ip addr show flannel.1
8: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether d2:06:eb:04:17:62 brd ff:ff:ff:ff:ff:ff
    inet 10.2.54.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::d006:ebff:fe04:1762/64 scope link 
       valid_lft forever preferred_lft forever
[root@host2 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 via 10.2.19.0 dev flannel.1 onlink 
169.254.0.0/16 dev eth0.10 scope link metric 1004 
169.254.0.0/16 dev eth0.20 scope link metric 1005 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.12 metric 101 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.12 metric 100 

3)配置 Docker 连接 flannel

编辑 host1 的 Docker 配置文件/etc/systemd/system/docker.service.d/10-machine.conf,设置 --bip--mtu

[root@host1 docker.service.d]# pwd 
/etc/systemd/system/docker.service.d
[root@host1 docker.service.d]# cp 10-machine.conf 10-machine.conf.bak
[root@host1 docker.service.d]# vim 10-machine.conf

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
        /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--bip=10.2.19.1/24 --mtu=1450
Environment=

这两个参数的值必须与 /run/flannel/subnet.env 中 FLANNEL_SUBNETFLANNEL_MTU 一致。

[root@host1 ~]# cat /run/flannel/subnet.env 

FLANNEL_NETWORK=10.2.0.0/16
FLANNEL_SUBNET=10.2.19.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=false

重启 Docker daemon。

[root@host1 docker.service.d]# systemctl daemon-reload
[root@host1 docker.service.d]# systemctl restart docker

docker 会将10.2.19.1配置到Linux bridge docker0上,并添加10.2.19.0/24的路由

[root@host1 docker.service.d]# ip r
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1 

host2 配置类似:

[root@host2 ~]# cd /etc/systemd/system/docker.service.d/
[root@host2 docker.service.d]# cp 10-machine.conf 10-machine.conf.bak
[root@host2 docker.service.d]# vim 10-machine.conf

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
        /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--bip=10.2.54.1/24 --mtu=1450
Environment=
[root@host2 ~]# cat /run/flannel/subnet.env 
FLANNEL_NETWORK=10.2.0.0/16
FLANNEL_SUBNET=10.2.54.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=false
[root@host2 docker.service.d]# systemctl daemon-reload
[root@host2 docker.service.d]# systemctl restart docker
[root@host2 docker.service.d]# ip r
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 via 10.2.19.0 dev flannel.1 onlink 
10.2.54.0/24 dev docker0 proto kernel scope link src 10.2.54.1 

可见:flannel 没有创建新的 docker 网络,而是直接使用默认的 bridge 网络。同一主机的容器通过 docker0 连接,跨主机流量通过 flannel.1 转发。

4)将容器连接到flannel

在 host1 中运行容器 bbox1并查看IP:

在 host2 中运行容器 bbox2并查看IP:

[root@host1 ~]# docker run --name bbox1 -itd busybox
ea50e600956f0dba7a321913b01b0977e45c34ee694be1a17b5329c6395024aa
[root@host1 ~]# docker exec bbox1 ip r
default via 10.2.19.1 dev eth0 
10.2.19.0/24 dev eth0 scope link  src 10.2.19.2 


[root@host2 ~]# docker run --name bbox2 -itd busybox
0aa6b6f1461707d9f15b49e66dd240f7611ed4f831e450f7ce18b365cb9a13b0
[root@host2 ~]# docker exec bbox2 ip r
default via 10.2.54.1 dev eth0 
10.2.54.0/24 dev eth0 scope link  src 10.2.54.2 

bbox1 和 bbox2 的 IP 分别为10.2.19.2 10.2.54.2

5)flannel vxlan网络连通性

测试 bbox1 和 bbxo2 的连通性:

[root@host1 ~]# docker exec bbox1 ping -c 3 10.2.54.2
PING 10.2.54.2 (10.2.54.2): 56 data bytes
64 bytes from 10.2.54.2: seq=0 ttl=62 time=6.290 ms
64 bytes from 10.2.54.2: seq=1 ttl=62 time=0.828 ms
64 bytes from 10.2.54.2: seq=2 ttl=62 time=0.738 ms

--- 10.2.54.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.738/2.618/6.290 ms

bbox1 能够 ping 到位于不同 subnet 的 bbox2,通过 traceroute 分析一下 bbox1 到 bbox2 的路径。

[root@host1 ~]# docker exec bbox1 traceroute 10.2.54.2
traceroute to 10.2.54.2 (10.2.54.2), 30 hops max, 46 byte packets
 1  bogon (10.2.19.1)  0.033 ms  0.023 ms  0.021 ms
 2  bogon (10.2.54.0)  2.005 ms  0.611 ms  2.684 ms
 3  bogon (10.2.54.2)  1.844 ms  1.881 ms  1.843 ms

1)bbox1和bbox2不是一个subnet 数据包发送给默认网关10.2.19.1(docker0)

2)根据host1的路由表,数据包会发给flannel.1

3)flannel.1将数据包封装成VxLAN,通过eth0发送给host2

3)host2收到解封,发现数据包目的地址为10.2.54.2,根据路由表将数据包发送给flannel.1 通过docker0到达bbox2

另外,flannel 是没有 DNS 服务的,容器无法通过 hostname 通信。

6)flannel vxlan 网络隔离

flannel 为每个主机分配了独立的 subnet,但 flannel.1 将这些 subnet 连接起来了,相互之间可以路由。本质上,flannel 将各主机上相互独立的 docker0 容器网络组成了一个互通的大网络,实现了容器跨主机通信。flannel 没有提供隔离。

因为 flannel 网络利用的是默认的 bridge 网络,所以容器与外网的连通方式与 bridge 网络一样,即:

  1. 容器通过 docker0 NAT 访问外网
  2. 通过主机端口映射,外网可以访问容器

7)使用 flannel host-gw backend

flannel 支持多种 backend,前面我们讨论的是 vxlan backend,host-gw 是 flannel 的另一个 backend。

与 vxlan 不同,host-gw 不会封装数据包,而是在主机的路由表中创建到其他主机 subnet 的路由条目,从而实现容器跨主机通信。要使用 host-gw 首先修改 flannel 的配置 flannel-config.json:

[root@node1 flannel]# cp flannel-config.json flannel-config.json.bak
[root@node1 flannel]# vim flannel-config.json

{
  "Network": "10.2.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "host-gw"
  }
}

Typehost-gw 替换原先的 vxlan。更新 etcd 数据库:

[root@node1 flannel]# etcdctl --endpoint=10.0.0.10:2379 set /docker-test/network/config < flannel-config.json
{
  "Network": "10.2.0.0/16",
  "SubnetLen": 24,
  "Backend": {
    "Type": "host-gw"
  }
}

Ctrl+C 掉之前 host1 和 host2 的 flanneld 进程并重启。

host1上输入如下

[root@host1 ~]# flanneld -etcd-endpoints=http://10.0.2.10:2379 -iface=eth0 -etcd-prefix=/docker-test/network
I1218 14:44:37.393295   11092 main.go:527] Using interface with name eth0 and address 10.0.2.11
I1218 14:44:37.393941   11092 main.go:544] Defaulting external address to interface address (10.0.2.11)
I1218 14:44:37.394544   11092 main.go:244] Created subnet manager: Etcd Local Manager with Previous Subnet: 10.2.19.0/24
I1218 14:44:37.394572   11092 main.go:247] Installing signal handlers
I1218 14:44:37.400960   11092 main.go:386] Found network config - Backend type: host-gw
#flanneld 检查到原先已分配的 subnet 10.2.19.0/24,重用之
I1218 14:44:37.405021   11092 local_manager.go:147] Found lease (10.2.19.0/24) for current IP (10.0.2.11), reusing
I1218 14:44:37.407613   11092 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1218 14:44:37.407644   11092 main.go:321] Running backend.
I1218 14:44:37.419064   11092 route_network.go:53] Watching for new subnet leases
I1218 14:44:37.420677   11092 main.go:429] Waiting for 22h59m59.97814884s to renew lease
I1218 14:44:37.422758   11092 route_network.go:85] Subnet added: 10.2.54.0/24 via 10.0.2.12
#flanneld 从 etcd 数据库中检索到 host2 的 subnet 10.2.54.0/24,但因为其 type=vxlan,立即忽略。
W1218 14:44:37.422806   11092 route_network.go:88] Ignoring non-host-gw subnet: type=vxlan
#再次发现 subnet 10.2.17.0/24,将其加到路由表中。这次没有忽略 subnet 的原因是此时我们在 host2 上重启了 flanneld,根据当前 etcd 的配置使用 host-gw backend
I1218 14:44:39.939055   11092 route_network.go:85] Subnet added: 10.2.54.0/24 via 10.0.2.12
W1218 14:44:39.939498   11092 route_network.go:102] Replacing existing route to 10.2.54.0/24 via 10.2.54.0 dev index 17 with 10.2.54.0/24 via 10.0.2.12 dev index 2.

查看 host1 的路由表,增加了一条到 10.2.19.0/24 的路由,网关为 host2 的 IP 10.0.2.11。

[root@host1 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1 
10.2.54.0/24 via 10.0.2.12 dev eth0 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.120 metric 101 
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.11 metric 100 

类似的,host2 启动 flanneld 时会重用 subnet 10.2.54.0/24,并将 host1 的 subnet 10.2.19.0/24 添加到路由表中,网关为 host1 IP 10.0.2.10。

从 /run/flannel/subnet.env 可以看到 host-gw 使用的 MTU 为 1500:

[root@host1 ~]# cat /run/flannel/subnet.env 
FLANNEL_NETWORK=10.2.0.0/16
FLANNEL_SUBNET=10.2.19.1/24
FLANNEL_MTU=1500
FLANNEL_IPMASQ=false

这与 vxlan MTU=1450 不同,所以应该修改 docker 启动参数 --mtu=1500并重启 docker daemon。

[root@host1 ~]# vim /etc/systemd/system/docker.service.d/10-machine.conf

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
        /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--bip=10.2.19.1/24 --mtu=1500
Environment=

8)比较host-gw和vxlan

下面对 host-gw 和 vxlan 这两种 backend 做个简单比较。

  1. host-gw 把每个主机都配置成网关,主机知道其他主机的 subnet 和转发地址。vxlan 则在主机间建立隧道,不同主机的容器都在一个大的网段内(比如 10.2.0.0/16)。
  2. 虽然 vxlan 与 host-gw 使用不同的机制建立主机之间连接,但对于容器则无需任何改变,bbox1 仍然可以与 bbox2 通信。
  3. 由于 vxlan 需要对数据进行额外打包和拆包,性能会稍逊于 host-gw。

4、weave

weave 是 Weaveworks 开发的容器网络解决方案。weave 创建的虚拟网络可以将部署在多个主机上的容器连接起来。对容器来说,weave 就像一个巨大的以太网交换机,所有容器都被接入这个交换机,容器可以直接通信,无需 NAT 和端口映射。除此之外,weave 的 DNS 模块使容器可以通过 hostname 访问。

1)实验环境描述

weave 不依赖分布式数据库(例如 etcd 和 consul)交换网络信息,每个主机上只需运行 weave 组件就能建立起跨主机容器网络。我们会在 host1 和 host2 上部署 weave 并实践 weave 的各项特性。

2)部署 weave

weave 安装非常简单,在 host1 和 host2 上执行如下命令:

[root@host1 ~]# curl -L git.io/weave -o /usr/local/bin/weave
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:02 --:--:--     0
  0     0    0   595    0     0    177      0 --:--:--  0:00:03 --:--:--  581k
100 52227  100 52227    0     0   8402      0  0:00:06  0:00:06 --:--:-- 38772
[root@host1 ~]# chmod a+x /usr/local/bin/weave

在host1中启动weave

在 host1 中执行 weave launch 命令,启动 weave 相关服务。weave 的所有组件都是以容器方式运行的,weave 会从 docker hub 下载最新的 image 并启动容器。

weave 运行了三个容器:

weave 是主程序,负责建立 weave 网络,收发数据 ,提供 DNS 服务等。

weaveplugin 是 libnetwork CNM driver,实现 Docker 网络。

weaveproxy 提供 Docker 命令的代理服务,当用户运行 Docker CLI 创建容器时,它会自动将容器添加到 weave 网络。

[root@host1 ~]# weave launch 
2.5.0: Pulling from weaveworks/weave
605ce1bd3f31: Pull complete 
18e9c1482d54: Pull complete 
20978932838c: Pull complete 
4738e62f8d03: Pull complete 
68add50beeee: Pull complete 
Digest: sha256:3a6086f15bf1f68092e372bfbb08d2d3679cf8a2b0f501ceb11c2fccd06a4b03
Status: Downloaded newer image for weaveworks/weave:2.5.0
latest: Pulling from weaveworks/weavedb
9b0681f946a1: Pull complete 
Digest: sha256:c280cf4e7208f4ca0d2514539e0f476dd12db70beacdc368793b7736de023d8d
Status: Downloaded newer image for weaveworks/weavedb:latest
Unable to find image 'weaveworks/weaveexec:2.5.0' locally
2.5.0: Pulling from weaveworks/weaveexec
605ce1bd3f31: Already exists 
18e9c1482d54: Already exists 
20978932838c: Already exists 
4738e62f8d03: Already exists 
68add50beeee: Already exists 
c10a1d502a6f: Pull complete 
bec5b671028d: Pull complete 
0467a09afdc2: Pull complete 
ade22b35f72f: Pull complete 
Digest: sha256:425c74052faaf6e76525f5a088a584a44353fb04fa51f6d800644e0acd64fce1
Status: Downloaded newer image for weaveworks/weaveexec:2.5.0
16a9df891e0091dfc06563f66c1c0c542b8f76c697049f74a214a978e488653f
[root@host1 ~]# docker image ls
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
weaveworks/weavedb     latest              4ac51c93545a        6 weeks ago         698B
weaveworks/weaveexec   2.5.0               6568ae41694a        6 weeks ago         166MB
weaveworks/weave       2.5.0               a5fd9a080afc        6 weeks ago         111MB
busybox                latest              59788edf1f3e        2 months ago        1.15MB

weave 会创建一个新的 Docker 网络 weave

driver 为 weavemesh,IP 范围 10.32.0.0/12

[root@host1 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
07644a612080        bridge              bridge              local
a4d35bce9b5a        docker_gwbridge     bridge              local
12e0fcdac081        host                host                local
2cb22bc8ead6        mac_net1            macvlan             local
ad9f7224b03d        mac_net20           macvlan             local
797eee7fca29        none                null                local
2d6fa187b80f        weave               weavemesh           local
[root@host1 ~]# docker network inspect weave
[
    {
        "Name": "weave",
        "Id": "2d6fa187b80fb2ca5ca072bec52b01c5370de2c1a420b143169d23299c64bfee",
        "Created": "2018-12-18T15:07:13.669491609+08:00",
        "Scope": "local",
        "Driver": "weavemesh",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "weavemesh",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.32.0.0/12"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {
            "works.weave.multicast": "true"
        },
        "Labels": {}
    }
]

weave 已经安装配置完毕.

3)网络结构分析

在 host1 中运行容器 bbox1:

[root@host1 ~]# eval $(weave env)
[root@host1 ~]# docker run --name bbox1 -itd busybox
81e6e68e09921da4363f87274ef2a75a376a49dc53a9dbd8e3fbc8c385b927f0

首先执行 eval $(weave env) 很重要,其作用是将后续的 docker 命令发给 weave proxy 处理。如果要恢复之前的环境,可执行 eval $(weave env --restore)

查看一下当前容器 bbox1 的网络配置:

[root@host1 ~]# docker exec -it bbox1 ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
28: eth0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:0a:02:13:02 brd ff:ff:ff:ff:ff:ff
    inet 10.2.19.2/24 brd 10.2.19.255 scope global eth0
       valid_lft forever preferred_lft forever
30: ethwe@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1376 qdisc noqueue 
    link/ether 32:ca:97:e1:98:7a brd ff:ff:ff:ff:ff:ff
    inet 10.32.0.1/12 brd 10.47.255.255 scope global ethwe
       valid_lft forever preferred_lft forever

bbox1有两个网络接口eth0和ethwe,其中eth0连接的就是默认的bridge网络,即docker0。

现在重点分析ethwe,看分配的ip和名字猜测ethwe和weave相关,ethwe@if31告诉我们与ethwe对应的是编号31的interface,从host1的ip link命令找出该interface

[root@host1 ~]# ip link |grep 31
31: vethwepl12678@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue master weave state UP mode DEFAULT group default 

vethwepl12678和ethwe是一丢veth pair,而且 vethwepl12678挂载host1的Linux bridge weave上

[root@host1 ~]# brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.02423482878d	no		veth5568f50
docker_gwbridge		8000.024266baa276	no		
weave		8000.86413d6585b5	no		vethwe-bridge
							vethwepl12678

除了 vethwepl12678,weave 上还挂了一个 vethwe-bridge,这是什么?让我们更深入的分析一下,查看 ip -d link 输出:

这里出现了多个新 interface:

vethwe-bridgevethwe-datapath 是 veth pair。

vethwe-datapath 的父设备(master)是 datapath

datapath 是一个 openvswitch。

vxlan-6784 是 vxlan interface,其 master 也是 datapath,weave 主机间是通过 VxLAN 通信的。

weave 网络包含两个虚拟交换机:Linux bridge weave 和 Open vSwitch datapath,veth pair vethwe-bridgevethwe-datapath 将二者连接在一起。
weavedatapath 分工不同,weave 负责将容器接入 weave 网络,datapath 负责在主机间 VxLAN 隧道中并收发数据。

在host1内再运行一个容器 bbox2。

[root@host1 ~]# docker run --name bbox2 -itd busybox
7f7dbf50a7d69ad65e35e7802e2b95e4712b7b14729f27da025f8a410749619b

weave DNS 为容器创建了默认域名 weave.local,bbox1 能够直接通过 hostname 与 bbox2 通信。

[root@host1 ~]# docker exec bbox1 hostname
bbox1.weave.local
[root@host1 ~]# docker exec bbox1 ping -c 3 bbox2
PING bbox2 (10.32.0.2): 56 data bytes
64 bytes from 10.32.0.2: seq=0 ttl=64 time=0.240 ms
64 bytes from 10.32.0.2: seq=1 ttl=64 time=0.133 ms
64 bytes from 10.32.0.2: seq=2 ttl=64 time=0.133 ms

--- bbox2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.133/0.168/0.240 ms

4)Weave 的连通

首先在host2 执行如下命令:

[root@host2 ~]# weave launch 10.0.2.11

这里必须指定 host1 的 IP 10.0.2.11,这样 host1 和 host2 才能加入到同一个 weave 网络。

运行容器 bbox3:

[root@host2 ~]# eval $(weave env)
[root@host2 ~]# docker run --name bbox3 -itd busybox
c4583ee7ddca631a6997d34a268d3437836f752a8501afada020418219da2c58

bbox3 能够直接 ping bbox1 和 bbox2。

[root@host2 ~]# docker exec bbox3 ping -c 3 bbox1
PING bbox1 (10.32.0.1): 56 data bytes
64 bytes from 10.32.0.1: seq=0 ttl=64 time=6.829 ms
64 bytes from 10.32.0.1: seq=1 ttl=64 time=1.001 ms
64 bytes from 10.32.0.1: seq=2 ttl=64 time=0.898 ms

--- bbox1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.898/2.909/6.829 ms

bbox1、bbox2 和 bbox3 的 IP 分别为 10.32.0.1/12、10.32.0.2/12 和 10.44.0.0/12,注意掩码为 12 位,实际上这三个 IP 位于同一个 subnet 10.32.0.0/12。通过 host1 和 host2 之间的 VxLAN 隧道,三个容器逻辑上是在同一个 LAN 中的,当然能直接通信了。

[root@host2 ~]# docker exec bbox3 ip route
default via 10.2.54.1 dev eth0 
10.2.54.0/24 dev eth0 scope link  src 10.2.54.2 
10.32.0.0/12 dev ethwe scope link  src 10.44.0.0 
224.0.0.0/4 dev ethwe scope link 

流程如下:

1、host2 weave查询到目的地主机 将数据通过VxLAN发送给host1 。

2、host1 weave 接收到数据,根据目的IP将数据转发给bbox1

5)weave网络隔离

默认配置下,weave 使用一个大 subnet(例如 10.32.0.0/12),所有主机的容器都从这个地址空间中分配 IP,因为同属一个 subnet,容器可以直接通信。如果要实现网络隔离,可以通过环境变量 WEAVE_CIDR 为容器分配不同 subnet 的 IP,

本人测试未成功,net和ip均无效

[root@host1 ~]# docker run -e WEAVE_CDIR=net:10.32.2.0/24 -it busybox
[root@host1 ~]# docker run -e WEAVE_CDIR=ip:10.32.2.8/24 -it busybox

6)weave外部通信

weave 是一个私有的 VxLAN 网络,默认与外部网络隔离。外部网络如何才能访问到 weave 中的容器呢?

答案是:

  1. 首先将主机加入到 weave 网络。
  2. 然后把主机当作访问 weave 网络的网关。

要将主机加入到 weave,执行 weave expose

[root@host1 ~]# weave expose
10.32.0.3

这个 IP 10.32.0.3 会被配置到 host1 的 weave 网桥上。

[root@host1 ~]# ip addr show weave
22: weave: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue state UP group default qlen 1000
    link/ether 86:41:3d:65:85:b5 brd ff:ff:ff:ff:ff:ff
    inet 10.32.0.3/12 brd 10.47.255.255 scope global weave
       valid_lft forever preferred_lft forever
    inet6 fe80::8441:3dff:fe65:85b5/64 scope link 
       valid_lft forever preferred_lft forever

weave 网桥位于 root namespace,它负责将容器接入 weave 网络。给 weave 配置同一 subnet 的 IP 其本质就是将 host1 接入 weave 网络。 host1 现在已经可以直接与同一 weave 网络中的容器通信了,无论容器是否位于 host1。

在 host1 中 ping 同一主机的 bbox1:

[root@host1 ~]# ping -c 3 10.32.0.1
PING 10.32.0.1 (10.32.0.1) 56(84) bytes of data.
64 bytes from 10.32.0.1: icmp_seq=1 ttl=64 time=0.511 ms
64 bytes from 10.32.0.1: icmp_seq=2 ttl=64 time=0.074 ms
64 bytes from 10.32.0.1: icmp_seq=3 ttl=64 time=0.084 ms

--- 10.32.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.074/0.223/0.511/0.203 ms

ping host2上的bbox3

[root@host1 ~]# ping -c 3 10.44.0.0
PING 10.44.0.0 (10.44.0.0) 56(84) bytes of data.
64 bytes from 10.44.0.0: icmp_seq=1 ttl=64 time=2.28 ms
64 bytes from 10.44.0.0: icmp_seq=2 ttl=64 time=0.780 ms
64 bytes from 10.44.0.0: icmp_seq=3 ttl=64 time=0.691 ms

--- 10.44.0.0 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 0.691/1.253/2.288/0.732 ms

接下来要让其他非 weave 主机访问到 bbox1 和 bbox3,只需将网关指向 host1。例如在 node1 10.0.2.10上添加如下路由:

[root@node1 ~]# ip route add 10.32.0.0/12 via 10.0.2.11
[root@node1 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.32.0.0/12 via 10.0.2.11 dev eth0 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.110 metric 101 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.0.10metric 100 
[root@node1 ~]# ping -c 3 10.44.0.0
PING 10.44.0.0 (10.44.0.0) 56(84) bytes of data.
64 bytes from 10.44.0.0: icmp_seq=1 ttl=63 time=1.62 ms
64 bytes from 10.44.0.0: icmp_seq=2 ttl=63 time=2.52 ms
64 bytes from 10.44.0.0: icmp_seq=3 ttl=63 time=1.08 ms

--- 10.44.0.0 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 4008ms
rtt min/avg/max/mdev = 1.087/1.585/2.521/0.497 ms

通过上面的配置我们实现了外网到 weave 这个方向的通信,反方向呢?

其实答案很简单:因为容器本身就挂在默认的 bridge 网络上,docker0 已经实现了 NAT,所以容器无需额外配置就能访问外网。

10.32.0.0/12 是 weave 网络使用的默认 subnet,如果此地址空间与现有 IP 冲突,可以通过 --ipalloc-range 分配特定的 subnet。

weave launch --ipalloc-range 10.2.0.0/16

不过请确保所有 host 都使用相同的 subnet。

5、calico

Calico 是一个纯三层的虚拟网络方案,Calico 为每个容器分配一个 IP,每个 host 都是 router,把不同 host 的容器连接起来。与 VxLAN 不同的是,Calico 不对数据包做额外封装,不需要 NAT 和端口映射,扩展性和性能都很好。

与其他容器网络方案相比,Calico 还有一大优势:network policy。用户可以动态定义 ACL 规则,控制进出容器的数据包,实现业务需求。

1)实验环境描述

Calico 依赖 etcd 在不同主机间共享和交换信息,存储 Calico 网络状态。我们将在 node1 10.0.2.10上运行 etcd。

Calico 网络中的每个主机都需要运行 Calico 组件,提供容器 interface 管理、动态路由、动态 ACL、报告状态等功能。

启动etcd

etcd 安装配置详细方法请参考 flannel 章节.

在node1 运行命令启动etcd:

[root@node1 ~]# etcd -listen-client-urls http://10.0.2.10:2379 -advertise-client-urls http://10.0.2.10:2379

修改 host1 和 host2 的 Docker daemon 配置文件 /etc/systemd/system/docker.service.d/10-machine.conf, 连接 etcd:

[root@host1 ~]# !vim
vim /etc/systemd/system/docker.service.d/10-machine.conf

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
        /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
        --cluster-store=etcd://10.0.2.10:2379
Environment=

[root@host1 ~]# systemctl daemon-reload 
[root@host1 ~]# systemctl restart docker

2)部署 calico

host1 和 host2 下载 calicoctl:

[root@host1 ~]# wget -O /usr/local/bin/calicoctl https://github.com/projectcalico/calicoctl/releases/download/v1.6.5/calicoctl
[root@host1 ~]# chmod +x /usr/local/bin/calicoctl
[root@host1 ~]# calicoctl --version
calicoctl version v1.6.5, build 614fcf12

在 host1 和 host2 上启动 calico:

创建calico配置文件#### 注意格式和缩进

[root@host1 ~]# cat /etc/calico/calicoctl.cfg 
apiVersion: v1
kind: calicoApiConfig
metadata:
spec:
  datastoreType: "etcdv2"
  etcdEndpoints: "http://10.0.2.10:2379"

启动

[root@host1 ~]# calicoctl node run --node-image=quay.io/calico/node:v2.6.12 -c /etc/calico/calicoctl.cfg 
Running command to load modules: modprobe -a xt_set ip6_tables
Enabling IPv4 forwarding
Enabling IPv6 forwarding
Increasing conntrack limit
Removing old calico-node container (if running).
Running the following command to start calico-node:

docker run --net=host --privileged --name=calico-node -d --restart=always -e CALICO_LIBNETWORK_ENABLED=true -e ETCD_ENDPOINTS=http://10.0.2.10:2379 -e NODENAME=host1 -e CALICO_NETWORKING_BACKEND=bird -v /var/log/calico:/var/log/calico -v /var/run/calico:/var/run/calico -v /lib/modules:/lib/modules -v /run:/run -v /run/docker/plugins:/run/docker/plugins -v /var/run/docker.sock:/var/run/docker.sock quay.io/calico/node:v2.6.12

Image may take a short time to download if it is not available locally.
Container started, checking progress logs.

2018-12-19 03:09:54.698 [INFO][8] startup.go 173: Early log level set to info
2018-12-19 03:09:54.698 [INFO][8] client.go 202: Loading config from environment
2018-12-19 03:09:54.698 [INFO][8] startup.go 83: Skipping datastore connection test
2018-12-19 03:09:54.704 [INFO][8] startup.go 259: Building new node resource Name="host1"
2018-12-19 03:09:54.704 [INFO][8] startup.go 273: Initialise BGP data
2018-12-19 03:09:54.707 [INFO][8] startup.go 467: Using autodetected IPv4 address on interface eth37: 172.16.1.120/24
2018-12-19 03:09:54.707 [INFO][8] startup.go 338: Node IPv4 changed, will check for conflicts
2018-12-19 03:09:54.710 [INFO][8] etcd.go 430: Error enumerating host directories error=100: Key not found (/calico) [15]
2018-12-19 03:09:54.711 [INFO][8] startup.go 530: No AS number configured on node resource, using global value
2018-12-19 03:09:54.713 [INFO][8] etcd.go 105: Ready flag is now set
2018-12-19 03:09:54.715 [INFO][8] client.go 133: Assigned cluster GUID ClusterGUID="28d6ce342fb54ad69e6edb9d752e16d4"
2018-12-19 03:09:54.733 [INFO][8] startup.go 419: CALICO_IPV4POOL_NAT_OUTGOING is true (defaulted) through environment variable
2018-12-19 03:09:54.733 [INFO][8] startup.go 659: Ensure default IPv4 pool is created. IPIP mode: off
2018-12-19 03:09:54.735 [INFO][8] startup.go 670: Created default IPv4 pool (192.168.0.0/16) with NAT outgoing true. IPIP mode: off
2018-12-19 03:09:54.736 [INFO][8] startup.go 419: FELIX_IPV6SUPPORT is true (defaulted) through environment variable
2018-12-19 03:09:54.736 [INFO][8] startup.go 626: IPv6 supported on this platform: true
2018-12-19 03:09:54.736 [INFO][8] startup.go 419: CALICO_IPV6POOL_NAT_OUTGOING is false (defaulted) through environment variable
2018-12-19 03:09:54.736 [INFO][8] startup.go 659: Ensure default IPv6 pool is created. IPIP mode: off
2018-12-19 03:09:54.738 [INFO][8] startup.go 670: Created default IPv6 pool (fd80:24e2:f998:72d6::/64) with NAT outgoing false. IPIP mode: off
2018-12-19 03:09:54.767 [INFO][8] startup.go 131: Using node name: host1
2018-12-19 03:09:55.081 [INFO][12] client.go 202: Loading config from environment
Starting libnetwork service
Calico node started successfully

启动过程如下:

① 设置主机网络,例如 enable IP forwarding。

② 下载并启动 calico-node 容器,calico 会以容器的形式运行(与 weave 类似)。

③ 连接 etcd。

④ calico 启动成功

查看calico运行状态:

[root@host1 ~]# calicoctl node status
Calico process is running.

IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+--------------+-------------------+-------+----------+-------------+
| 172.16.1.12 | node-to-node mesh | up    | 03:18:14 | Established |
+--------------+-------------------+-------+----------+-------------+

IPv6 BGP status
No IPv6 peers found.
[root@host2 scripts]# calicoctl node status
Calico process is running.

IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+--------------+-------------------+-------+----------+-------------+
| 172.16.1.120 | node-to-node mesh | up    | 03:18:15 | Established |
+--------------+-------------------+-------+----------+-------------+

IPv6 BGP status
No IPv6 peers found.

创建calico网络

在 host1 或 host2 上执行如下命令创建 calico 网络 cal_ent1:

[root@host1 ~]# docker network create --driver calico --ipam-driver calico-ipam cal_net1
d0760b57695c3dbcf5cb69571984f909456502f69c9a13030305905da68fb4dd

--driver calico 指定使用 calico 的 libnetwork CNM driver。

--ipam-driver calico-ipam 指定使用 calico 的 IPAM driver 管理 IP。

calico 为 global 网络,etcd 会将 cal_net 同步到所有主机。

[root@host1 ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
963caa36a8d1        bridge              bridge              local
d0760b57695c        cal_net1            calico              global
12e0fcdac081        host                host                local
797eee7fca29        none                null                local
3a1e0d8a2730        weave               weavemesh           local

3)calico网络结构

在 host1 中运行容器 bbox1 并连接到 cal_net1:

[root@host1 ~]# docker run  --name bbox1 --net cal_net1 -itd busybox
19d88eb8ee187ef9a0e1592b6ae9f59dafa7481d6e4eea9a6e25c8ca30b316b1

查看 bbox1 的网络配置。

[root@host1 ~]# docker exec bbox1 ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
26: cali0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
    inet 192.168.119.2/32 brd 192.168.119.2 scope global cali0
       valid_lft forever preferred_lft forever

cali0 是 calico interface,分配的 IP 为 192.168.119.2。cali0 对应 host1 编号 27 的 interface cali08a3cd4c842

[root@host1 ~]# ip a
......
27: cali08a3cd4c842@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether c2:3d:a6:92:fe:b8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::c03d:a6ff:fe92:feb8/64 scope link 
       valid_lft forever preferred_lft forever

host1 将作为 router 负责转发目的地址为 bbox1 的数据包。

[root@host1 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.120 metric 101 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.11 metric 100 
blackhole 192.168.119.0/26 proto bird 
192.168.119.2 dev cali08a3cd4c842 scope link 

所有发送到 bbox1 的数据都会发给 cali08a3cd4c842,因为 cali08a3cd4c842cali0 是一对 veth pair,bbox1 能够接收到数据。

接下来我们在 host2 中运行容器 bbox2,也连接到 cal_net1:

[root@host2 scripts]# docker run --name bbox2 --net cal_net1 -itd busybox
ac8faffa86318a830397a8030ca136386fec0063d75e050426a08444bfdcbced
[root@host2 scripts]# docker exec bbox2 ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
20: cali0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
    inet 192.168.183.66/32 brd 192.168.183.66 scope global cali0
       valid_lft forever preferred_lft forever

[root@host2 scripts]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.54.0/24 dev docker0 proto kernel scope link src 10.2.54.1 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.12 metric 101 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.12 metric 100 
192.168.119.0/26 via 172.16.1.120 dev eth37 proto bird 
blackhole 192.168.183.64/26 proto bird 
192.168.183.66 dev calia8c668b6de2 scope link 

bbox2的IP为192.168.183.66 host2主机增加了两条路由:

1、目的地址为host1容器subnet192.168.119.0/26的路由

2、目的地址为本地bbox2的192.168.183.66的路由

同样 host1也自动添加了 192.168.183.64/26的路由

[root@host1 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.120 metric 101 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.11 metric 100 
blackhole 192.168.119.0/26 proto bird 
192.168.119.2 dev cali08a3cd4c842 scope link 
192.168.183.64/26 via 172.16.1.12 dev eth37 proto bird 

4)calico的默认连通性

测试一下bbox1和bbox2的连通性

[root@host1 ~]# docker exec bbox1 ping -c 3 bbox2
PING bbox2 (192.168.183.66): 56 data bytes
64 bytes from 192.168.183.66: seq=0 ttl=62 time=6.818 ms
64 bytes from 192.168.183.66: seq=1 ttl=62 time=0.879 ms
64 bytes from 192.168.183.66: seq=2 ttl=62 time=0.773 ms

--- bbox2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.773/2.823/6.818 ms

ping 成功,

数据包流程为

1、bbox1的数据包从cal0发出

[root@host1 ~]# docker exec bbox1  ip route
default via 169.254.1.1 dev cali0 
169.254.1.1 dev cali0 scope link 

2、数据经过 veth pair到达host1,查看路由表数据由eth37 发给host2(10.0.2.12 172.16.1.12 为内网IP地址)

[root@host1 ~]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.120 metric 101 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.11 metric 100 
blackhole 192.168.119.0/26 proto bird 
192.168.119.2 dev cali08a3cd4c842 scope link 
192.168.183.64/26 via 172.16.1.12 dev eth37 proto bird 

3、host2收到数据包,根据路由表发送给,进而通过veth pair到达bbox2

[root@host2 scripts]# ip route
default via 10.0.2..1 dev eth0 proto static metric 100 
10.2.54.0/24 dev docker0 proto kernel scope link src 10.2.54.1 
172.16.1.0/24 dev eth37 proto kernel scope link src 172.16.1.12 metric 101 
10.0.2..0/24 dev eth0 proto kernel scope link src 10.0.2.12 metric 100 
192.168.119.0/26 via 172.16.1.120 dev eth37 proto bird 
blackhole 192.168.183.64/26 proto bird 
192.168.183.66 dev calia8c668b6de2 scope link 

接下来我们看看不同 calico 网络之间的连通性。

创建 cal_net2。

[root@host2 scripts]# docker network create --driver calico --ipam-driver calico-ipam cal_net2
2b7e049df6cd8b0ea5d346d1aa80500a524b1ee14a9c0e9c6faa7b9ef5128e2d

在host1中运行容器bbox3,连接到cal_net2

[root@host1 ~]# docker run --name bbox3 --net cal_net2 -itd busybox
157cf44e2b18e4c2b023805241818f34d444bb9bc0cc122f002e59ec8da8ae6e
[root@host1 ~]# docker exec bbox3 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
28: cali0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
    inet 192.168.119.3/32 brd 192.168.119.3 scope global cali0
       valid_lft forever preferred_lft forever

calico给bbix分配的ip为 192.168.119.3

验证bbox1和bbox3的连通性

[root@host1 ~]# docker exec bbox3 ping -c 3 192.168.119.2
PING 192.168.119.2 (192.168.119.2): 56 data bytes

--- 192.168.119.2 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

虽然 bbox1 和 bbox3 都位于 host1,而且都在一个 subnet 192.168.119.0/26,但它们属于不同的 calico 网络,默认不能通行。

calico 默认的 policy 规则是:容器只能与同一个 calico 网络中的容器通信。

calico 的每个网络都有一个同名的 profile,profile 中定义了该网络的 policy。我们具体看一下 cal_net1 的 profile:

[root@host1 ~]# calicoctl get profile cal_net1 -o yaml
- apiVersion: v1
  kind: profile
  metadata:
    name: cal_net1
    tags:
    - cal_net1
  spec:
    egress:
    - action: allow
      destination: {}
      source: {}
    ingress:
    - action: allow
      destination: {}
      source:
        tag: cal_net1

1)name: cal_net1 :命名为cal_net1 这就是calico网络cal_net1的prifile

2)- cal_net1: 为 profile 添加一个 tag cal_net1。注意,这个 tag 虽然也叫 cal_net1,其实可以随便设置,这跟上面的 name: cal_net1 没有任何关系。此 tag 后面会用到。

3) egress:对从容器发出的数据包进行控制,当前没有任何限制。

4) ingress: 对进入容器的数据包进行限制,当前设置是接收来自 tag cal_net1 的容器,根据第 ① 步设置我们知道,实际上就是只接收本网络的数据包,这也进一步解释了前面的实验结果。

既然这是默认 policy,那就有方法定制 policy,这也是 calico 较其他网络方案最大的特性。

5)定制 calico policy

Calico 能够让用户定义灵活的 policy 规则,精细化控制进出容器的流量,下面我们就来实践一个场景:

  1. 创建一个新的 calico 网络 cal_web 并部署一个 httpd 容器 web1
  2. 定义 policy 允许 cal_net2 中的容器访问 web1 的 80 端口。

首先创建cal_web

[root@host1 ~]# docker network create --driver calico --ipam-driver calico-ipam cal_web
741b5fded82ffba3edac7d94ed405e533cfcc63b121bcbed3c892bf0d71cac85

在 host1 中运行容器 web1,连接到 cal_web:

[root@host1 ~]# docker run --name web1 --net cal_web -d httpd
17eccfdf171de1355deef178fd33fd0e3a2cb3ec4fcee7945f8bf949c52c9b3f
[root@host1 ~]# docker exec -it web1 /bin/sh
# cat /etc/hosts  
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
192.168.119.17	17eccfdf171d

[root@host1 ~]# docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS               NAMES
17eccfdf171d        httpd                         "httpd-foreground"       4 minutes ago       Up 4 minutes   

web1的ip为192.168.119.17

目前 bbox3 还无法访问 web1 的 80 端口。

[root@host1 ~]# docker exec bbox3 wget 192.168.119.17
Connecting to 192.168.119.17 (192.168.119.17:80)
wget: can't connect to remote host (192.168.119.17): Connection timed out

创建 policy 文件 web.yml,内容为:

[root@host1 ~]# vim web.yml

- apiVersion: v1
  kind: profile
  metadata:
    name: cal_web
  spec:
    ingress:
    - action: allow
      protocol: tcp
      source:
        tag: cal_net2
      destination:
        ports:
        - 80

profile 与 cal_web 网络同名,cal_web 的所有容器(web1)都会应用此 profile 中的 policy。

ingress 允许 cal_net2 中的容器(bbox3)访问。

只开放 80 端口。

应用该 policy。

[root@host1 ~]# calicoctl apply -f web.yml 
Successfully applied 1 'profile' resource(s)

现在bbox3已经可以访问web1的http服务

不过 ping 还是不行,因为只放开了 80 端口。

[root@host1 ~]# docker exec bbox3 wget 192.168.119.17
Connecting to 192.168.119.17 (192.168.119.17:80)
index.html           100% |********************************|    45  0:00:00 ETA

[root@host1 ~]# docker exec bbox3 ping -c 3 192.168.119.17
PING 192.168.119.17 (192.168.119.17): 56 data bytes

--- 192.168.119.17 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

上面这个例子比较简单,不过已经向我们展示了 calico 强大的 policy 功能。通过 policy,可以动态实现非常复杂的容器访问控制。有关 calico policy 更多的配置,可参看官网文档 http://docs.projectcalico.org/v2.0/reference/calicoctl/resources/policy。

6)定制IP Pool

我们没有特别配置,calico 会为自动为网络分配 subnet,当然我们也可以定制。

首先定义一个 IP Pool

[root@host1 ~]# vim ipPool.yml

- apiVersion: v1
  kind: ipPool
  metadata:
    cidr: 17.2.0.0/16

[root@host1 ~]# calicoctl create -f ipPool.yml 
Successfully created 1 'ipPool' resource(s)

用此 IP Pool 创建 calico 网络。

[root@host1 ~]# docker network create --driver calico --ipam-driver calico-ipam --subnet=17.2.0.0/16 my_net
c09c90a736872401a991f2431bcf7275c5ba51e3c1e271b466d00e24d3f924e7

此时运行容器将分配到指定 subnet 中的 IP。

[root@host1 ~]# docker run --net my_net -it busybox
/ # ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
62: cali0@if63: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
    inet 17.2.119.0/32 brd 17.2.119.0 scope global cali0
       valid_lft forever preferred_lft forever

当然也可以通过 --ip 为容器指定 IP,但必须在 subnet 范围之内。

[root@host1 ~]# docker run --net my_net --ip 17.2.3.11 -it busybox
[root@host1 ~]# docker exec b3c485a2e81a ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
68: cali0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
    inet 17.2.3.11/32 brd 17.2.3.11 scope global cali0
       valid_lft forever preferred_lft forever

6、各种跨主机方案对比

我们将从如下几个方面比较,大家可以根据不同场景选择最合适的方案。

网络模型
采用何种网络模型支持 multi-host 网络?

Distributed Store
是否需要 etcd 或 consul 这类分布式 key-value 数据库存储网络信息?

IPMA
如何管理容器网络的 IP?

连通与隔离
提供怎样的网络连通性?支持容器间哪个级别和哪个类型的隔离?

性能
性能比较。

1)网络模型

跨主机网络意味着将不同主机上的容器用同一个虚拟网络连接起来。这个虚拟网络的拓扑结构和实现技术就是网络模型。

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 网络。

2)Distributed Store

Docker Overlay、Flannel 和 Calico 都需要 etcd 或 consul。Macvlan 是简单的 local 网络,不需要保存和共享网络信息。Weave 自己负责在主机间交换网络配置信息,也不需要 Distributed Store。

3)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。

4)连通与隔离

同一 Docker Overlay 网络中的容器可以通信,但不同网络之间无法通信,要实现跨网络访问,只有将容器加入多个网络。与外网通信可以通过 docker_gwbridge 网络。

Macvlan 网络的连通或隔离完全取决于二层 VLAN 和三层路由。

不同 Flannel 网络中的容器直接就可以通信,没有提供隔离。与外网通信可以通过 bridge 网络。

Weave 网络默认配置下所有容器在一个大的 subnet 中,可以自由通信,如果要实现隔离,需要为容器指定不同的 subnet 或 IP。与外网通信的方案是将主机加入到 weave 网络,并把主机当作网关。

Calico 默认配置下只允许位于同一网络中的容器之间通信,但通过其强大的 Policy 能够实现几乎任意场景的访问控制。

5)性能

性能测试是一个非常严谨和复杂的工程,这里我们只尝试从技术方案的原理上比较各方案的性能。

最朴素的判断是:Underlay 网络性能优于 Overlay 网络

Overlay 网络利用隧道技术,将数据包封装到 UDP 中进行传输。因为涉及数据包的封装和解封,存在额外的 CPU 和网络开销。虽然几乎所有 Overlay 网络方案底层都采用 Linux kernel 的 vxlan 模块,这样可以尽量减少开销,但这个开销与 Underlay 网络相比还是存在的。所以 Macvlan、Flannel host-gw、Calico 的性能会优于 Docker overlay、Flannel vxlan 和 Weave。

Overlay 较 Underlay 可以支持更多的二层网段,能更好地利用已有网络,以及有避免物理交换机 MAC 表耗尽等优势,所以在方案选型的时候需要综合考虑。

猜你喜欢

转载自www.cnblogs.com/wangxiaopang/p/12720276.html