Docker容器化2023版本——Docker的overlay网络

6,814 阅读14分钟

覆盖网络在大多数云原生微服务应用程序中处于核心位置。在本章中,我们将让您了解Docker上的覆盖网络。

Windows上的Docker覆盖网络与Linux具有功能平等性。这意味着本章中使用的示例在Linux和Windows上都可以工作。

我们将本章分为通常的三个部分:

  • 简要说明
  • 深入探讨
  • 命令

让我们进行一些网络魔法。

Docker叠加网络 - 简而言之

在现实世界中,容器必须能够在不同主机上、不同网络中可靠且安全地进行通信。这就是叠加网络发挥作用的地方。它们创建了一个横跨多个主机的平面、安全的第二层网络。不同主机上的容器可以连接到同一叠加网络并直接进行通信。

Docker提供了本地叠加网络,配置简单且默认情况下安全。

在幕后,它是建立在libnetwork和本地叠加驱动程序之上的。Libnetwork是容器网络模型(CNM)的官方实现,而叠加驱动程序则实现了所有的网络机制。

Docker叠加网络 - 深入

在2015年3月,Docker, Inc.收购了一家名为Socket Plane的容器网络初创公司。收购背后的两个原因是为Docker引入真正的网络功能,以及使容器网络足够简单,即使开发人员也能轻松使用它。

他们在这两方面都取得了超越预期的成就,叠加网络仍然是2023年以及可预见的未来容器网络的核心。

然而,在一些简单的网络命令背后隐藏着许多复杂性。这是在进行生产部署和尝试解决问题之前需要理解的内容。

本节的其余部分将分为两个部分:

  • 构建和测试Docker叠加网络
  • 解释叠加网络

构建和测试Docker叠加网络

以下示例将使用两个配置为集群的Docker节点。这两个节点位于由路由器连接的两个不同网络上。

如果您在跟随操作,节点是否在由路由器连接的两个不同网络上并不重要,但它们可以这样配置。唯一需要的是两个节点都运行Docker,具有网络连通性,并可以配置成一个集群。这意味着您可以在Play with Docker上跟随操作,也可以在本地机器上的几个Multipass虚拟机上跟随操作,或者在公共云中进行操作。

虽然在Docker Desktop上跟随操作是可能的,但您将无法获得完整的体验,因为您只能访问一个节点。

初始配置如图12.1所示。如果您的节点在同一网络上,一切都将正常工作,只是您的底层网络会更简单。我们稍后将解释底层网络。

image.png

搭建一个Swarm集群

首先要做的是将这两个节点配置为一个Swarm集群。这是因为Swarm模式是Docker Overlay网络的先决条件。

我们将在node1上运行docker swarm init命令以使其成为管理节点,然后在node2上运行docker swarm join命令以使其成为工作节点。这不是一个生产级别的设置,但对于学习实验来说足够了。鼓励您测试更多的管理节点和工作节点,并扩展示例。

如果您在自己的实验室中跟着进行操作,需要用您环境中正确的值替代IP地址和名称。您还需要确保没有任何防火墙阻止这两个节点之间的以下端口通信:

  • 2377/tcp 用于管理平面通信
  • 7946/tcp 和 7946/udp 用于控制平面通信(基于SWIM的通信)
  • 4789/udp 用于VXLAN数据平面

在node1上运行以下命令。

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

Swarm initialized: current node (1ex3...o3px) is now a manager.

复制输出中包含的docker swarm join命令,并将其粘贴到node2上的终端中。

$ docker swarm join \
  --token SWMTKN-1-0hz2ec...2vye \
  172.31.1.5:2377
This node joined a swarm as a worker.

现在我们有一个包含两个节点的Swarm,其中node1是管理节点,node2是工作节点。

创建一个新的覆盖网络

从node1(管理节点)运行以下命令来创建一个名为uber-net的新覆盖网络。

$ docker network create -d overlay uber-net
c740ydi1lm89khn5kd52skrd9

就这样。您刚刚创建了一个全新的覆盖网络,可供群集中的所有主机使用,并且其控制平面使用TLS进行加密(使用自动每12小时旋转密钥的AES GCM模式)。如果要加密数据平面,只需在命令中添加 -o encrypted 标志。但是,默认情况下不启用数据平面加密,因为会导致性能开销。在生产环境中启用数据平面加密之前,请务必测试性能。但是,如果启用了数据平面加密,它将由相同的AES GCM模式和密钥轮换保护。

如果您对诸如控制平面和数据平面之类的术语不太了解...控制平面流量是集群管理流量,而数据平面流量是应用程序流量。默认情况下,Docker覆盖网络加密集群管理流量,但不加密应用程序流量。您必须明确启用应用程序流量的加密。

您可以使用 docker network ls 命令列出每个节点上的所有网络。

$ docker network ls
NETWORK ID      NAME              DRIVER     SCOPE
ddac4ff813b7    bridge            bridge     local
389a7e7e8607    docker_gwbridge   bridge     local
a09f7e6b2ac6    host              host       local
ehw16ycy980s    ingress           overlay    swarm
2b26c11d3469    none              null       local
c740ydi1lm89    uber-net          overlay    swarm

新创建的网络位于列表底部,名为 uber-net。其他网络是在安装Docker和初始化群集时自动创建的。

如果您在node2上运行 docker network ls 命令,您会注意到它不会显示 uber-net 网络。这是因为只有当工作节点被分配运行网络上的容器任务时,才会将新的覆盖网络扩展到工作节点。这种惰性扩展覆盖网络的方法通过减少网络八卦的数量来提高可伸缩性。

将服务连接到覆盖网络

现在我们有了一个覆盖网络,让我们将一个新的Docker服务连接到它。这个示例将创建一个包含两个副本的服务,以便一个运行在node1上,另一个运行在node2上。这将自动将uber-net覆盖扩展到node2。

从node1运行以下命令。

$ docker service create --name test \
   --network uber-net \
   --replicas 2 \
   ubuntu sleep infinity

该命令创建了一个名为test的新服务,并将两个副本都附加到uber-net覆盖网络。因为我们在一个两节点的Swarm上运行两个副本,所以一个副本将被调度到每个节点上。

使用docker service ps命令来验证操作。

$ docker service ps test
ID          NAME    IMAGE   NODE    DESIRED STATE  CURRENT STATE
77q...rkx   test.1  ubuntu  node1   Running        Running
97v...pa5   test.2  ubuntu  node2   Running        Running

在node2上运行docker network ls,以验证它现在可以看到这个网络。

独立的容器,即不属于Swarm服务的容器,除非网络是使用attachable=true属性创建的,否则不能连接到覆盖网络。可以使用以下命令创建一个可附加的覆盖网络,独立容器可以连接到该网络。

$ docker network create -d overlay --attachable uber-net

恭喜你。你已经创建了一个覆盖网络,跨越了两个位于不同物理底层网络上的节点。你还将两个容器连接到了这个网络。这有多简单!

当你在理论部分的时候,当你意识到背后发生的事情有多么复杂时,你将完全欣赏到你所做的简单性!

测试覆盖网络

让我们使用ping命令来测试覆盖网络。

如图12.2所示,我们有两个位于不同网络上的Docker主机,以及一个跨越两者的覆盖网络。我们还有一个连接到每个节点上的覆盖网络的容器。让我们看看它们是否可以相互ping通。

image.png

您可以通过名称对远程容器执行测试。不过,示例将使用IP地址,因为这给了我们一个学习如何查找容器IP地址的机会。

运行docker inspect来查看分配给覆盖网络的子网以及分配给两个测试服务副本的IP地址。

$ docker inspect uber-net
[
    {
        "Name": "uber-net",
        "Id": "c740ydi1lm89khn5kd52skrd9",
        "Scope": "swarm",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",     <<---- Subnet info
                    "Gateway": "10.0.0.1"        <<---- Subnet info
                }
        "Containers": {
                "Name": "test.1.mfd1kn0qzgosu2f6bhfk5jc2p",    <<---- Container name
                "IPv4Address": "10.0.0.3/24",                  <<---- Container IP
                <Snip>
            },
                "Name": "test.2.m49f4psxp3daixlwfvy73v4j8",    <<---- Container name
                "IPv4Address": "10.0.0.4/24",                  <<---- Container IP
            },
<Snip>

输出已经经过了大量剪辑以提高可读性,但我们可以看到它显示了uber-net的子网是10.0.0.0/24。这与图12.2中显示的两个物理底层网络(172.31.1.0/24和192.168.1.0/24)都不匹配。您还可以看到分配给两个容器的IP地址。

在两个节点上运行以下两个命令。第一个命令获取副本的容器ID,第二个命令获取容器的IP地址。确保在第二个命令中使用您自己实验中的容器ID。

$ docker ps
CONTAINER ID   IMAGE           COMMAND           CREATED      STATUS     NAME
396c8b142a85   ubuntu:latest   "sleep infinity"  2 hours ago  Up 2 hrs   test.1.mfd...

$ docker inspect \
  --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 396c8b142a85
10.0.0.3

注意名称和IP是否与docker inspect命令的输出匹配。

图12.3显示了到目前为止的配置。在您的实验中,子网和IP地址可能会有所不同。

image.png

正如您所看到的,存在一个跨越两个节点的第2层叠加网络,每个容器都有一个IP地址。这意味着节点1上的容器将能够使用其10.0.0.4地址对节点2上的容器进行ping。这个方法是有效的,尽管这两个节点位于不同的第2层底层网络上。

让我们证明一下。

登录到节点1上的容器并ping远程容器。您需要在容器中安装ping工具以完成此任务。请记住,在您的环境中,容器ID将不同。

$ docker exec -it 396c8b142a85 bash

# apt-get update && apt-get install iputils-ping -y
<Snip>
Reading package lists... Done
Building dependency tree
Reading state information... Done
<Snip>
Setting up iputils-ping (3:20190709-3) ...
Processing triggers for libc-bin (2.31-0ubuntu9) ...

# ping 10.0.0.4
PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data.
64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=1.06 ms
64 bytes from 10.0.0.4: icmp_seq=2 ttl=64 time=1.07 ms
64 bytes from 10.0.0.4: icmp_seq=3 ttl=64 time=1.03 ms
64 bytes from 10.0.0.4: icmp_seq=4 ttl=64 time=1.26 ms
^C

恭喜!节点1上的容器可以通过叠加网络对节点2上的容器进行ping操作。如果您使用了-o encrypted标志创建网络,则该交换将已加密。

您还可以从容器内部跟踪ping命令的路由。这将报告一个单一的跳跃,证明容器正在通过叠加网络直接通信,对底层网络的任何遍历一无所知。

为使此操作生效,您需要在容器中安装traceroute。

# apt install inetutils-traceroute
<Snip>

# traceroute 10.0.0.4
traceroute to 10.0.0.4 (10.0.0.4), 30 hops max, 60 byte packets
 1  test-svc.2.97v...a5.uber-net (10.0.0.4)  1.110ms  1.034ms  1.073ms

到目前为止,我们已经使用一个命令创建了一个叠加网络。然后我们将容器添加到其中。这些容器被调度到了两个位于两个不同的第2层底层网络上的主机上。我们找到了容器的IP地址,并证明它们可以通过叠加网络直接通信。

既然我们已经看到了构建和使用安全叠加网络是多么容易,现在让我们看看它在幕后是如何组装的。

解释叠加网络原理

首先,Docker叠加网络使用VXLAN隧道来创建虚拟的第二层叠加网络。所以,在继续之前,让我们快速了解一下VXLAN的基本概念。

VXLAN入门

在最高级别上,VXLAN允许你在现有的三层基础设施之上创建二层网络。这意味着你可以创建简单的网络,隐藏了复杂的网络拓扑结构。我们之前使用的示例在两个通过路由器连接的三层IP网络之上创建了一个新的10.0.0.0/24二层网络。见图12.4。

image.png

VXLAN的美妙之处在于它是一种封装技术。这意味着现有的路由器和网络基础设施只会将其视为常规的IP/UDP数据包并进行处理,而无需进行任何更改。

为了创建覆盖网络,会通过底层网络创建一个VXLAN隧道。这个隧道是允许流量自由流动的关键,而无需与底层网络的复杂性互动。我们使用术语底层网络或底层基础设施来指代覆盖必须通过的网络。

VXLAN隧道的每一端由VXLAN隧道端点(VTEP)终止。正是这个VTEP对进入和离开隧道的流量进行封装和解封装。见图12.5。

image.png

这张图将层3基础设施表示为云,有两个原因:

  1. 它可能比之前的图表中所示的两个网络和一个路由器要复杂得多。
  2. VXLAN隧道抽象了复杂性,使其不可见。

我们来看一下我们的两个容器示例

前面的实例中,有两个主机通过一个 IP 网络连接在一起。每个主机运行一个容器,您为容器创建了一个单独的 overlay 网络。然而,为了实现这一切,幕后发生了许多事情...

在每个主机上创建了一个新的沙盒(网络命名空间)。

在沙盒内部创建了一个名为 Br0 的虚拟交换机。还创建了一个 VTEP,其中一个端口连接到 Br0 虚拟交换机,另一个端口连接到主机网络堆栈。主机网络堆栈上的端口获得了主机连接到的底层网络的 IP 地址,并绑定到端口 4789 上的 UDP 套接字。每个主机上的两个 VTEP 通过 VXLAN 隧道创建了覆盖层,如图 12.6 所示。

image.png

此时,VXLAN 覆盖层已经创建并准备好使用。

然后,每个容器都会获得自己的虚拟以太网(veth)适配器,该适配器还连接到本地的 Br0 虚拟交换机。最终的拓扑结构如图 12.7 所示,尽管它很复杂,但应该越来越容易看出这两个容器如何通过 VXLAN 覆盖层进行通信,尽管它们的主机位于两个不同的网络上。

image.png

通信示例

现在,我们已经了解了主要的管道元素,让我们看看这两个容器如何通信。

警告!这一部分变得非常技术化。但是,你不需要完全理解它们来进行日常操作。

在这个示例中,我们将称节点1上的容器为“C1”,将节点2上的容器称为“C2”。让我们假设C1想要像我们在前面的实际示例中所做的那样ping通C2。图12.8添加了容器及其IP。

image.png

C1创建ping请求并将目标IP地址设置为C2的10.0.0.4地址。

C1在其本地MAC地址表(ARP缓存)中没有C2的条目,因此它将数据包洪泛到所有接口上。VTEP接口连接到Br0,后者知道如何转发数据帧,因此会用自己的MAC地址进行响应。这是代理ARP回复,导致VTEP学习如何转发数据包并更新其MAC表,以便以后的所有数据包都直接传输到本地VTEP。Br0交换机了解C2,因为所有新启动的容器都使用网络内置的gossip协议将其网络详细信息传播到群集中的其他节点。

ping被发送到VTEP接口,它执行所需的封装以将其隧道传输到底层网络。在相当高的层次上,此封装向各个以太网帧添加了VXLAN标头。此标头包含用于将来自VLAN的帧映射到VXLAN和反之亦然的VXLAN网络ID(VNID)。每个VLAN都映射到VNID,以便可以在接收端解封封包并将其转发到正确的VLAN。这维护了网络隔离。

封装还将帧包装在UDP数据包中,并将远程节点2上的VTEP的IP添加到目标IP字段中。它还添加了UDP端口4789的套接字信息。此封装允许数据包通过底层网络发送,而底层网络无需了解VXLAN的任何信息。

当数据包到达节点2时,内核会看到它寻址到UDP端口4789。内核还知道它绑定到此套接字的VTEP。因此,它将数据包发送到VTEP,后者读取VNID,解封数据包,并将其发送到其自己的本地Br0交换机,该交换机在VNID对应的VLAN上运行。然后将其传递给容器C2。

这就是VXLAN技术如何由原生Docker覆盖网络利用的方式 - 通过几个Docker命令美化的一堆令人惊叹的复杂性。

希望这足以让你开始使用任何生产Docker部署。它还应该让你具备与网络团队讨论Docker基础架构的网络方面的知识。在与网络团队交流时...我建议你不要认为自己现在已经了解了VXLAN的一切。如果你这样做,可能会让自己感到尴尬。我说出自己的经验;-)

最后一件事。Docker还支持覆盖网络内的Layer 3路由。例如,你可以创建一个具有两个子网的覆盖网络,Docker将负责它们之间的路由。创建此类网络的命令可能是docker network create --subnet=10.1.1.0/24 --subnet=11.1.1.0/24 -d overlay prod-net。这将导致两个虚拟开关,Br0和Br1,在sandbox内,路由由Docker自动处理。

Docker覆盖网络 - 命令

  • docker network create是我们用来创建新容器网络的命令。-d标志指定要使用的驱动程序,最常见的驱动程序是overlay。您还可以安装并使用来自第三方的驱动程序。对于覆盖网络,默认情况下会加密控制平面。您可以通过添加-o encrypted标志来加密数据平面,但可能会带来性能开销。

  • docker network ls列出Docker主机可见的所有容器网络。在swarm模式下运行的Docker主机只会看到连接到这些网络的容器的覆盖网络,从而将与网络相关的信息保持最小。

  • docker network inspect显示有关特定容器网络的详细信息。这包括范围、驱动程序、IPv4和IPv6信息、子网配置、已连接容器的IP地址、VXLAN网络ID和加密状态。

  • docker network rm删除一个网络。