• Docker覆盖网络之所以独立一章记录,是因为覆盖网络在大部分与容器网络相关的场景中,都处于核心地位。下面以Docker Swarm使用覆盖网络来展开。
  • Docker覆盖网络允许创建扁平的二层网络来连接多个主机,容器可以连接到覆盖网络并直接互相通信。Docker基于Libnetwork以及相应的驱动(默认用overlay,可以插入其他驱动来实现)来构建覆盖网络(Docker提供了原生覆盖网络的支持)。

    1. Docker覆盖网络

    1.1 在Swarm模式中构建Docker覆盖网络

    需要基础条件:

    1. 至少两台Docker主机,并通过一个路由器上两个独立的二层网络连接在一起(因此,当前有三层网络)。
    2. Liunx容器主机内核不能低于4.4。

Docker覆盖网络 - 图1

  1. 构建Swarm
  • 在node1节点运行: ```bash

    初始化一个swarm

    $docker swarm init \ —advertise-addr=172.31.1.5 \ —listen-addr=172.31.1.5:2377

获取swarm worker token,复制到node2节点使用

$docker swarm join-token worker

  1. - node2节点运行:
  2. ```bash
  3. $ docker swarm join \
  4. --token SWMTKN-1-0hz2ec...2vye \
  5. 172.31.1.5:2377

现在就构成了一个拥有node1(管理节点)和node2(工作节点)两个节点的warm集群了。

  1. 创建新的覆盖网络
  • 创建uber-net的覆盖网络,在node1节点运行:
    1. # 如果需要对数据层加密,可以增加 -o encrypted 来使用TLS加密控制层。
    2. $docker network create -d overlay uber-net
  1. 将服务连接到覆盖网络
  • 在刚才的warm集群中创建服务并连接到该网络,这样会自动将node2节点接入uber-net网络。在node1上运行:
    1. $docker service create --name test \
    2. --network uber-net \
    3. --replics 2 \
    4. ubuntu sleep infinity
    现在创建了test服务,连入uber-net,基于ubuntu创建了两个副本(因为swarm包含两个节点,因此每个节点上都会运行一个副本)。sleep表示让容器休眠一段时间来保持容器运行,休眠结束后退出该容器。使用 docker service ps test 来验证上面的操作。
  1. 测试覆盖网络连接
  • 分别进入两个容器查看ip,使用命令 docker container inspect {容器ID} 。可以在node1容器 ping node2容器ip,是ping通状态。

    1.2 覆盖网络原理

  • 覆盖网络原理是在使用VXLAN来创建一个虚拟二层网络。为了创建二层网络VXLAN基于现有的三层IP网络创建了隧道,VXLAN隧道两端都是VXLAN隧道终端(VXLAN Tunnel Endpoint, VTEP),VTEP完成了封装和解压等步骤。

  1. 以上面的swarm创建覆盖网络的示例来解释。通过 IP 网络将两台主机连接起来。每个主机运行了一个容器,之后又为容器连接创建了一个 VXLAN 覆盖网络。为了实现上述场景,在每台主机上都新建了一个 Sandbox(网络命名空间),Sandbox 就像一个容器,但其中运行的不是应用,而是当前主机上独立的网络栈。在 Sandbox 内部创建了一个名为 Br0 的虚拟交换机(又称做虚拟网桥)。同时 Sandbox 内部还创建了一个 VTEP,其中一端接入到名为 Br0 的虚拟交换机当中,另一端接入主机网络栈(VTEP)。在主机网络栈中的终端从主机所连接的基础网络中获取到 IP 地址,并以 UDP Socket 的方式绑定到 4789 端口。不同主机上的两个 VTEP 通过 VXLAN 隧道创建了一个覆盖网络,如下:

Docker覆盖网络 - 图2
接下来每个容器都会有自己的虚拟以太网(veth)适配器,并接入本地 Br0 虚拟交换机。目前拓扑结构如下图所示,虽然是在主机所属网络互相独立的情况下,但这样能更容易看出两个分别位于不同主机上的容器之间是如何通过 VXLAN 上层网络进行通信的。
Docker覆盖网络 - 图3
将 node1 上的容器称为 C1,node2 上的容器称为 C2,如下图所示。假设 C1 希望 ping 通 C2。

  • C1 发起 ping 请求,目标 IP 为 C2 的地址 10.0.0.4。该请求的流量通过连接到 Br0 虚拟交换机 veth 接口发出。虚拟交换机并不知道将包发送到哪里,因为在虚拟交换机的 MAC 地址映射表(ARP 映射表)中并没有与当前目的 IP 对应的 MAC 地址。
  • 所以虚拟交换机会将该包发送到其上的全部端口。连接到 Br0 的 VTEP 接口知道如何转发这个数据帧,所以会将自己的 MAC 地址返回。这就是一个代理 ARP 响应,并且虚拟交换机 Br0 根据返回结果学会了如何转发该包。接下来虚拟交换机会更新自己的 ARP 映射表,将 10.0.0.4 映射到本地 VTEP 的 MAC 地址上。
  • 现在 Br0 交换机已经学会如何转发目标为 C2 的流量,接下来所有发送到 C2 的包都会被直接转发到 VTEP 接口。VTEP 接口知道 C2,是因为所有新启动的容器都会将自己的网络详情采用网络内置 Gossip 协议发送给相同 Swarm 集群内的其他节点。
  • 交换机会将包转发到 VTEP 接口,VTEP 完成数据帧的封装,这样就能在底层网络传输。具体来说,封装操作就是把 VXLAN Header 信息添加以太帧当中。
  • VXLAN Header 信息包含了 VXLAN 网络 ID(VNID),其作用是记录 VLAN 到 VXLAN 的映射关系。每个 VLAN 都对应一个 VNID,以便包可以在解析后被转发到正确的 VLAN。
  • 封装的时候会将数据帧放到 UDP 包中,并设置 UDP 的目的 IP 字段为 node2 节点的 VTEP 的 IP 地址,同时设置 UDP Socket 端口为 4789。这种封装方式保证了底层网络即使不知道任何关于 VXLAN 的信息,也可以完成数据传输。
  • 当包到达 node2 之后,内核发现目的端口为 UDP 端口 4789,同时还知道存在 VTEP 接口绑定到该 Socket。所以内核将包发给 VTEP,由 VTEP 读取 VNID,解压包信息,并根据 VNID 发送到本地名为 Br0 的连接到 VLAN 的交换机。在该交换机上,包被发送给容器 C2。
  1. Docker 支持使用同样的覆盖网络实现三层路由。例如,读者可以创建包含两个子网的覆盖网络,Docker 会负责子网间的路由。创建的命令如 docker network create —subnet=10.1.1.0/24 —subnet=11.1.1.0/24 -d overlay prod-net。该命令会在 Sandbox 中创建两个虚拟交换机,默认支持路由。