相关知识

Overlay Network?

An overlay network is a computer network that is built on top of another network. 覆盖网络,是一个建立在另一个网络上的计算机网络。覆盖网络中的节点被认为是通过虚拟或逻辑链接相连的,其中每一条链接对应一条路径(PATH)。Wikipedia基于多租户的云计算Overlay网络

Overlay的主要技术标准:VXLAN

  • VXLAN(Virtual Extensible LAN: 虚拟可扩展局域网):是一种Overlay网络(在网络之上构建的一层网络),基于隧道技术实现。
  • Linux Bridge默认支持VXLAN, OpenVSwitch也支持VXLAN
  • VxLan 类似于 VLan技术,最大的不同点是,VxLan可以拥有更多的网段,远远大于VLan的网段。 VLan只支持4094个网段。
  • VXLAN 的数据包是封装到UDP通过三层网络转发,可使用所有的网络路径; VXLAN的传输协议是 IP+UDP

Docker网络架构和libnetwork

libnetwork 是Docker容器网络库,最核心的内容是其定义的 Container Network Model(CNM), 这个模型对容器网络进行了抽象

CNM 主要由以下三类组件构成:

  • Sandbox(沙箱):容器的网络栈,包含容器的Interface、路由表和DNS设置。Linux Network Namespace 是sandbox的标准实现。sandbox可以包含来自不同Network的Endpoint

  • Endpoint(端点):用于将sandbox接入Network。Endpoint的典型实现是 veth pair。一个 Endpoint只能属于一个网络,也只能属于一个sandbox

  • Network(网络):Network包含一组Endpoint,同一Network的Endpoint可以直接通信。

架构示意图:

Docker-CNM

libnetwork中的5种内置驱动:

  • bridge 驱动:Docker默认的设置,使用这个驱动的时候,libnetwork将创建出来的Docker容器链接到Docker网桥上。

  • host 驱动:使用这种驱动的时候,libnetwork将不会为Docker容器创建网络协议栈,也就是不会创建独立的network namespace。Docker容器中的进程处于宿主机的网络环境中。

  • overlay 驱动:该驱动采用标准的VxLANFANGSHI, 并且VxLan被普遍认为是最适合大规模的云计算虚拟化环境的SDN controller模式。在使用过程中,需要一个额外的配置存储服务

  • remote 驱动:这个驱动并没有做真正的网络实现,而是调用了用户自行实现的网络驱动插件,使libnetwork实现了驱动的可插件化。

  • null 驱动:使用这种驱动的时候,Docker容器拥有自己的network namespace,但是不会进行任何网络配置。

相关方案简介

基于桥接的跨主机通信

基于桥接的跨主机通信

目前, Docker默认网络模式(bridge)下,单台主机上的Docker容器间可通过docker0网桥通信, 而不同主机上的Docker容器间只能通过在主机上做端口映射的方法通信。这种方案在Docker集群应用时很不方便, 如果可以直接使用容器IP跨主机通信则会简便很多。

基于Overlay的跨主机通信

Docker支持使用Overlay网络驱动实现跨主机网络通信

不同于 bridge 网络,使用overlay网络需要满足一下条件中的任意一个:

  • Docker运行在 swarm 模式
  • 一个键值存储集群(ZooKeeper | etcd | consul)

Note: 官方文档中Docker overlay网络是和Swarm共存的

基于Overlay网络和一个外部Key-Value存储数据库搭建Docker跨主机通信

参考方案·Docker Overlay跨主机通信

Overview

使用Docker Overlay网络,需要注意一下几点:

  • 一个可访问的键值存储系统:Docker支持 ConsulEtcdZooKeeper(分布式)键值存储。

  • 一个Docker Host集群,他们要链接到键值存储,并且集群内每个Docker Host的主机名必须要唯一。因为Docker守护进程与consul通信时,会以主机名相互区分。

  • 配置正确的Docker Daemon启动参数。让所有的Docker Host都可以访问集群key-value的服务端口

通过官网给出的需求,可以知道:

  • Docker Daemon要开启远程管理的端口(tcp xxx),这个端口可以用来远程给Docker(极度危险)

  • Docker Daemon还要开启一个tcp/udp的7946端口,Docker通过这个端口,用gossip协议学习各个宿主机上运行了那些容器。

  • VxLan本身也需要一个UDP的4789端口

Note:要在防火墙开启这些端口

实验环境说明

实验主机:

Host OS Description
192.168.1.180 Centos7 Kernel 3.10.0 Docker主机_A(搭建consul数据库)
192.168.1.181 Centos7 Kernel 3.10.0 Docker主机_B

启动Consul数据库

Consul 的相关介绍

Consul:一个用于服务发现和配置共享的服务软件,由HashiCorp公司用Go开发;Consul支持健康检查、并允许HTTP和DNS协议调用API存储键值对

在这里,我们使用Consul来保存Overlay的网络状态信息,包括Endpoint、Network & IP等;当然,我们不需要写代码,只需要安装Consul, 之后Docker会自动进行状态存储等。

https://blog.coding.net/blog/intro-consul

  • 安装Consul

最简单的安装方式即运行Docker.Consul容器哦, 这里 consul 服务搭建在主机:192.168.1.180

1
2
3
4
5
6
7
8
# 拉取镜像
$ docker pull progrium/consul

# 启动consul服务
$ docker run --resatrt=always -d -p 8500:8500 -h consul --name consul progrium/consul -server -bootstrap

# 查看consul容器是否运行成功
docker ps 

启动后,可在浏览器输入 host:port 可以看到Nodes节点已经添加了Consul;这里host:port=192.168.1.180:8500

配置Docker服务

Note: 所有加入Overlay网络的Docker Host都需要完成如下配置,且所有Docker Host主机名必须唯一

  • 修改Docker启动参数 ```bash

    修改 docekr.service

    $ vim /usr/lib/systemd/system/docker.service

修改 ExecStart 选项如下

ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock –cluster-store=consul://192.168.1.180:8500 –cluster-advertise=ens33:2376

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> Note: 
> 
> - --cluster-store: 指定键值存储的地址(Consul节点IP)
> - --cluster-advertise: 告诉存储服务自己的连接地址(ens33为docker节点IP地址所在的网卡名)

- 重启Docker服务

```bash
$ systemctl daemon-reload
$ systemctl restart docker.service

Note: 可以看到 docker/nodes下已经有两个节点

创建Overlay网络

  • 在HostA(192.168.1.180)上创建Overlay网络
1
2
3
4
5
# 创建 Overlay 网络 ovnet1, -d 指定网络模式
$ docker network create -d overlay ovnet1

# 创建 Overlay 网络时, 指定其子网和网关
$ docker network create -d overlay ovnet2 --subnet 172.19.0.0/24 --gateway 172.19.0.1

Note: 在之后运行容器时指定 –network=ovnet1即可使Overlay网络内的Docker主机实现通信

  • 在HostA(192.168.180)上查看网络
1
2
3
4
5
6
7
8
# Lookup HostA Docker Network
$ docker network ls 
NETWORK ID          NAME                DRIVER              SCOPE
2315b20ebe40        bridge              bridge              local
45fcb93505c0        docker_gwbridge     bridge              local
9ccf08201f10        host                host                local
e15bf8515ecd        none                null                local
0c7ad0717a77        ovnet1              overlay             global

Note: 刚才创建的ovnet1网络作用域(SCOPE)是global, 而其他为local

  • 在HostB(192.168.1.181)上查看网络
1
2
3
4
5
6
7
8
# Lookup HostB Docker Network
$ docker network ls 
NETWORK ID          NAME                DRIVER              SCOPE
2315b20ebe40        bridge              bridge              local
45fcb93505c0        docker_gwbridge     bridge              local
9ccf08201f10        host                host                local
e15bf8515ecd        none                null                local
0c7ad0717a77        ovnet1              overlay             global

Note:

  • 我们没有在HostB上创建网络,而这里可以发现HostA上创建的网络 ovnet1;这是因为创建ovnet1时HostA将overlay的网络信息写入了consul,而HostB从consul中读取到了网络数据完成了配置; 之后ovnet1的任何变化都会同步到HostA和HostB
  • 查看网络详细配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ docker inspect ovnet1

[
    {
        "Name": "ovnet1",
        "Id": "0c7ad0717a77b329357a251f79f43f8889be819313334249be45a4f04677a0de",
        "Created": "2017-09-21T08:51:50.324849876+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": {}
    }
]

Note: IPAM, 表示IP Address Management

创建容器并接入Overlay网络

  • 创建容器
1
$ docker run --name cotan1 --network ovnet1 -itd busybox
  • 查看容器内部网络
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ docker exec -it cotan1 sh
$ ip address

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    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
16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP 
    link/ether 02:42:0a:00:00:05 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.5/24 scope global eth0
       valid_lft forever preferred_lft forever
26: eth1@if27: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:12:00:05 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.5/16 scope global eth1
       valid_lft forever preferred_lft forever

$ ip route 

default via 172.18.0.1 dev eth1 
10.0.0.0/24 dev eth0  src 10.0.0.5 
172.18.0.0/16 dev eth1  src 172.18.0.5

Note: 可以看到容器内部有两块网卡, eth0连接overlay网络,eth1连接172.18.0.5; 且默认路由是从eth1网卡出去的

  • 查看宿主机网桥信息
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 在HostA执行
$ ip address

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:c5:85:c4 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.180/24 brd 192.168.1.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::c517:b883:c99a:290c/64 scope link 
       valid_lft forever preferred_lft forever
3: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN qlen 1000
    link/ether 52:54:00:9e:02:43 brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
       valid_lft forever preferred_lft forever
4: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 state DOWN qlen 1000
    link/ether 52:54:00:9e:02:43 brd ff:ff:ff:ff:ff:ff
5: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:31:81:ce:4e brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 scope global docker_gwbridge
       valid_lft forever preferred_lft forever
    inet6 fe80::42:31ff:fe81:ce4e/64 scope link 
       valid_lft forever preferred_lft forever
6: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:a8:41:f3:9e brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:a8ff:fe41:f39e/64 scope link 
       valid_lft forever preferred_lft forever
8: veth7f764b0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP 
    link/ether 66:03:b1:8f:c2:af brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::6403:b1ff:fe8f:c2af/64 scope link 
       valid_lft forever preferred_lft forever

可以看到多了 docker_gwbridge 网卡; 且Docker容器的eth1网卡连接的是docker_gwbridge网桥

测试连通性

  • 在HostB创建容器

    1
    
    $ docker run --name cotan2 --network ovnet1 -itd busybox
  • 查看容器IP地址并测试连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ docker exec -it cotan2 sh
$ ip address 

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    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
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue 
    link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.2/24 scope global eth0
       valid_lft forever preferred_lft forever
13: eth1@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.2/16 scope global eth1
       valid_lft forever preferred_lft forever

$ ping 10.0.0.5

64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.588 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.417 ms
 
--- container1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.417/0.502/0.588 ms

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

该方案存在的问题

http://www.chongchonggou.com/g_6115686.html

宿主机如何简单高效的访问Docker自带的Overlay网络中的所有容器(包括其他宿主机上的)

在Docker自带的Overlay网络中,容器内部访问外网使用的是自动创建的docker_gwbridge桥,那么 docker 宿主机如何访问overlay网络内部呢(比如其他宿主机上同时加入该overlay网络的容器)?

解决方法有如下几种:

  • 尝试把到overlay路由指到docker_gwbridge,但是无法访问其他宿主机上加入到这个overlay的容器,因为没有回程路由,给每台启动的容器加回程路由也是很不现实的。
  • 宿主机上,容器内都开反向代理。
  • 容器内开启iptables,做nat。
  • 传统模式给容器做nat映射宿主机端口。
  • 如果能指定overlay中的某个容器为整个overlay的默认路由的话,一切都好办了,奈何目前到1.10.2,gateway都会被自动指到某个网桥上,只能每台容器去修改,但是给每台容器赋予这么高的权限,个人是不愿意的。

Overlay网络实现原理

总结Overlay网络

  • Overlay网络会自动创建一个docker_gwbridge网卡,作用是为Docker容器提供上外网需求
  • Docker容器不能通过docker_gwbridge网卡互相通信,即使在同一台Docker主机, 同一个Overlay网络也不行
  • Docker容器间通信只能通过overlay网络
  • Overlay网络间是相互隔离的,通过VXLan隔离
  • Docker容器的网络命名空间与Overlay网络的命名空间通过一对veth pair连接起来
  • Docker容器的veth pair对端veth0与vxlan0设备通过br0这个Linux bridge桥接在一起, br0在同一宿主机上起到虚拟交换机的作用,如果目标地址在同一宿主机上,则直接通信,如果不在则通过设置vxlan0这个VXLan设备进行跨主机通信
  • Overlay网络独立的命名空间里面会有一个网桥br0
  • VXLan0设备会在创建时,由Docker daemon 为其分配vxlan隧道ID,起到网络隔离的作用
  • Docker Host集群会通过key/value存储共享数据,在7496端口上,互相之间通过gossip协议学习各个宿主机上运行了那些容器。守护进程根据这些数据在vxlan0设备上生成静态MAC转发表
  • 根据静态MAC转发表的设置,通过UDP端口4789,将流量转发到对端宿主机网卡上
  • 根据流量包中的VxLan隧道ID,将流量转发到对端宿主机的overlay网络的网络命名空间中。
  • 对端宿主机的overlay网络的网络命名空间中br0网桥,起到虚拟交换机的作用,将流量根据MAC地址转发到对应容器内部。

Issue

  • Issue1
1
2
3
[root@zhe0 ~]# docker run --name cotan1 --network ovnet1 --ip 10.0.0.10 -d busybox
402fb4295afa1fcf9ba7cc5493c9dedecf50edee1983fbbad376e8b93e560d20
docker: Error response from daemon: user specified IP address is supported only when connecting to networks with user configured subnets.
  • Issue2

1
2
在 ExecStart 最后添加: --cluster-store=consul://<consul_ip>:8500 --cluster-advertise=ens3:2376
其中 表示运行 consul 容器的节点IP。ens3为当前节点的ip地址对应的网卡

注意: ens3换成IP地址可能导致容器跨主机通信失败

See Also

Thanks to the authors 🙂