深入浅出Docker 读书笔记(五)

                                         第12章 Docker 网络

Docker 在容器内部运行应用,这些应用之间的交互依赖于大量不同的网络,这意味着 Docker 需要强大的网络功能。幸运的是Docker 对于容器之间、容器与外部网络和 VLAN 之间的连接均有相应的解决方案。Docker 网络架构源自一种叫作容器网络模型(CNM)的方案,该方案是开源的并且支持插接式连接。Libnetwork 是 Docker 对 CNM 的一种实现,提供了 Docker 核心网络架构的全部功能。不同的驱动可以通过插拔的方式接入 Libnetwork 来提供定制化的网络拓扑。为了实现开箱即用的效果,Docker 封装了一系列本地驱动,覆盖了大部分常见的网络需求。其中包括单机桥接网络(Single-Host Bridge Network)、多机覆盖网络(Multi-Host Overlay),并且支持接入现有 VLAN。Docker 生态系统中的合作伙伴通过提供驱动的方式,进一步拓展了 Docker 的网络功能。Libnetwork 提供了本地服务发现和基础的容器负载均衡解决方案。在顶层设计中,Docker 网络架构由 3 个主要部分构成:1)CNM :一种设计标准。在 CNM中规定了 Docker 网络架构的基础组成要素。抽象来讲CNM 定义了 3 个基本要素:沙盒(Sandbox)、终端(Endpoint)和网络(Network)。

  • 沙盒是一个独立的网络栈。其中包括以太网接口、端口、路由表以及 DNS 配置。
  • 终端就是虚拟网络接口。就像普通网络接口一样,终端主要职责是负责创建连接。在CNM中终端负责将沙盒连接到网络。
  • 网络是 802.1d 网桥(类似的交换机)的软件实现。因此网络就是需要交互的终端的集合,并且终端之间相互独立。

CNM组件与容器进行关联

​Docker 环境中最小的调度单位就是容器,CNM就负责为容器提供网络功能。上图展示了CNM组件是如何与容器进行关联的—沙盒被放置在容器内部,为容器提供网络连接。容器 A 只有一个接口(终端)并连接到了网络 A。容器 B 有两个接口(终端)并且分别接入了网络 A 和网络 B。容器 A 与 B 之间是可以相互通信的,因为都接入了网络 A。但是如果没有三层路由器的支持,容器 B 的两个终端之间是不能进行通信的。终端与常见的网络适配器类似,这意味着终端只能接入某一个网络。因此如果容器需要接入到多个网络,就需要多个终端

2)Libnetwork 是 CNM 的具体实现,并且被 Docker 采用,它通过 Go 语言编写,并实现了 CNM 中列举的核心组件。CNM 是设计规范文档,Libnetwork 是标准的实现。Libnetwork 一个开源、Go编写、跨平台且被 Docker 所使用。在 Docker 早期阶段,网络部分代码都存在于 daemon 当中。daemon 变得臃肿,并且不符合 UNIX 工具模块化设计原则,即既能独立工作,又易于集成到其他项目。所以Docker 将该网络部分从 daemon 中拆分,并重构为一个叫作 Libnetwork 的外部类库。现在,Docker 核心网络架构代码都在 Libnetwork 当中。Libnetwork 实现了 CNM 中定义的全部 3 个组件。此外它还实现了本地服务发现(Service Discovery)、基于 Ingress 的容器负载均衡,以及网络控制层和管理层功能。

3)驱动通过实现特定网络拓扑的方式来拓展该模型的能力。如果说 Libnetwork 实现了控制层和管理层功能,那么驱动就负责实现数据层。比如网络连通性和隔离性是由驱动来处理的,驱动层实际创建网络对象也是如此,其关系如下图所示。

控制层、管理层与数据层的关系

Docker 封装了原生驱动或者本地驱动若干内置驱动,在 Linux 上包括 Bridge、Overlay 以及 Macvlan,在 Windows 上包括 NAT、Overlay、Transport 以及 L2 Bridge。第三方也可以编写 Docker 网络驱动。这些驱动叫作远程驱动,例如 Calico、Contiv、Kuryr 以及 Weave。每个驱动都负责其上所有网络资源的创建和管理。举例说明,一个叫作“prod-fe-cuda”的覆盖网络由 Overlay 驱动所有并管理。这意味着 Overlay 驱动会在创建、管理和删除其上网络资源的时候被调用。为了满足复杂且不固定的环境需求,Libnetwork 支持同时激活多个网络驱动。这意味着 Docker 环境可以支持一个庞大的异构网下图展示了顶层设计中的每个部分是如何组装在一起的。

顶层设计

​最简单的Docker网络就是单机桥接网络。单机意味着该网络只能在单个 Docker 主机上运行,并且只能与所在 Docker 主机上的容器进行连接,桥接意味着这是 802.1.d 桥接的一种实现(二层交换机)。Linux Docker 创建单机桥接网络采用内置的桥接驱动,而 Windows Docker 创建时使用内置的 NAT 驱动。实际上,这两种驱动工作起来毫无差异。下图展示了两个均包含相同本地桥接网络 mynet 的 Docker 主机。虽然网络是相同的,但却是两个独立的网络。这意味着图中容器无法直接进行通信,因为并不在一个网络当中。

容器无法直接进行通信

扫描二维码关注公众号,回复: 10745988 查看本文章

每个 Docker 主机都有一个默认的单机桥接网络。在 Linux 上网络名称为 bridge,在 Windows 上叫作 nat。除非读者通过命令行创建容器时指定参数--network,否则默认情况下新创建的容器都会连接到该网络。在 Linux 主机上Docker 网络由 Bridge 驱动创建,而 Bridge 底层是基于 Linux 内核意味着 Bridge 是高性能并且非常稳定的,可以通过标准的 Linux 工具来查看这些网络$ ip link show docker0     在 Linux Docker 主机之上,默认的“bridge”网络被映射到内核中为“docker0”的 Linux 网桥。使用 docker network create 命令创建新的单机桥接网络,名为“localnet”。//Linux $ docker network create -d bridge localnet //Windows   > docker network create -d nat localnet。如果使用 Linux主机内核中还会创建一个新的 Linux 网桥。使用 Linux brctl 工具来查看系统中的 Linux 网桥。$ brctl show 输出内容中包含了两个网桥。第一行是前文提过的 docker0 网桥,该网桥对应 Docker 中的默认网络 bridge;第二个网桥(br-20c)与新建的“localnet”Docker 桥接网络相对应。两个网桥目前都没有开启 STP,并且也都没有任何设备接入(对应的 interfaces 列为空)。接下来创建一个新的容器,并接入到新建桥接网络 localnet 当中。在Windows 上进行操作需要将命令中“alpine sleep 1d”替换为“microsoft/powershell:nanoserver pwsh.exe -Command Start-Sleep 86400”。$ docker container run -d --name c1 \        --network localnet \             alpine sleep 1d    容器现在接入了 localnet 网络当中。

c1的网络接口连接到了br-20c2e8ae4bbb网桥

如果在相同网络中继续接入新的容器,那么在新接入容器中是可以通过“c1”的容器名称来 ping 通的。这是因为新容器都注册到了指定的 Docker DNS 服务,所以相同网络中的容器可以解析其他容器的名称。Linux 上默认的 Bridge 网络是不支持通过 Docker DNS 服务进行域名解析的。自定义桥接网络可以, 到目前为止前面提到的桥接网络中的容器只能与位于相同网络中的容器进行通信。但是,可以使用端口映射(Port Mapping)来绕开这个限制。端口映射允许将某个容器端口映射到 Docker 主机端口上。对于配置中指定的 Docker 主机端口,任何发送到该端口的流量,都会被转发到容器。下图中展示了具体流量动向。

具体流量动向

如上图所示,容器内部应用开放端口为 80。该端口被映射到了 Docker 主机的 10.0.0.15 接口的 5000 端口之上。最终结果就是访问 10.0.0.15:5000 的所有流量都被转发到了容器的 80 端口。能够将容器化应用连接到外部系统以及物理网络的能力是非常必要的。常见的例子是部分容器化的应用——应用中已容器化的部分需要与那些运行在物理网络和 VLAN 上的未容器化部分进行通信。Docker内置的 Macvlan 驱动(Windows 上是 Transparent)通过为容器提供 MAC 和 IP 地址,让容器在物理网络上成为“一等公民”。下图展示了具体内容。

将容器化应用连接到网络

Macvlan 的优点是性能优异,因为无须端口映射或者额外桥接,可以直接通过主机接口(或者子接口)访问容器接口。但是,Macvlan 的缺点是需要将主机网卡(NIC)设置为混杂模式(Promiscuous Mode),这在大部分公有云平台上是不允许的。
所以 Macvlan 对于公司内部的数据中心网络来说很棒(假设公司网络组能接受 NIC 设置为混杂模式),但是 Macvlan 在公有云上并不可行。假设有一个物理网络,其上配置了两个 VLAN 100:10.0.0.0/24 和 VLAN 200:192.168.3.0/24接下来,添加一个 Docker 主机并连接到该网络,如下图所示。
 

添加Docker主机并连接到该网络

有一个需求是将容器接入 VLAN 100。为了实现该需求,首先使用 Macvlan 驱动创建新的 Docker 网络。但是,Macvlan 驱动在连接到目标网络前,需要设置几个参数。比如以下几点。1) 子网信息。2) 网关。3) 可分配给容器的IP范围。4) 主机使用的接口或者子接口。Docker Macvlan 驱动基于稳定可靠的同名 Linux内核驱动构建而成。因此,Macvlan 也支持 VLAN 的 Trunk 功能。这意味着可以在相同的 Docker 主机上创建多个 Macvlan 网络,并且将容器按照下图的方式连接起来。

连接容器


以上内容基本能涵盖 Macvlan。Windows 也提供了类似的解决方案 Transparent 驱动。用于故障排除的容器和服务日志当认为遇到容器间网络连接问题时,检查 daemon 日志以及容器日志(应用日志)是非常有必要的。在 Windows 上,daemon 日志存储在 ∼AppData\Local\Docker,可以通过 Windows 事件查看器来浏览。在 Linux 上,daemon 日志的存储位置取决于当前系统正在使用的初始化方式。如果是 Systemd,日志会存储在 Journald,可以通过 journalctl -u docker.service 命令查看。

服务发现:Libnetwork 还提供了一些重要的网络服务——服务发现。它允许容器和 Swarm 服务通过名称互相定位。唯一的要求就是需要处于同一个网络当中。其底层实现是利用了 Docker 内置的 DNS 服务器,为每个容器提供 DNS 解析功能。下图展示了容器“c1”通过名称 ping 容器“c2”的过程。Swarm 服务原理相同。
 

容器“c1”通过名称ping容器“c2”


1) ping c2 命令调用本地 DNS 解析器,尝试将“c2”解析为具体 IP 地址。每个 Docker 容器都有本地 DNS 解析器。
2) 如果本地解析器在本地缓存中没有找到“c2”对应的 IP 地址,本地解析器会向 Docker DNS 服务器发起一个递归查询。本地服务解析器是预先配置好并知道 Docker DNS 服务器细节的。
3) Docker DNS 服务器记录了全部容器名称和 IP 地址的映射关系,其中容器名称是容器在创建时通过 --name 或者 --net-alias 参数设置的。这意味着 Docker DNS 服务器知道容器“c2”的 IP 地址。
4) DNS 服务返回“c2”对应的 IP 地址到“c1”本地 DNS 解析器。之所以会这样是因为两个容器位于相同的网络当中,如果所处网络不同则该命令不可行。
5) ping 命令被发往“c2”对应的 IP 地址。
每个启动时使用了 --name 参数的 Swarm 服务或者独立的容器,都会将自己的名称和 IP 地址注册到 Docker DNS 服务。这意味着容器和服务副本可以通过 Docker DNS 服务互相发现。但是,服务发现是受网络限制的,这意味着名称解析只对位于同一网络中的容器和服务生效。如果两个容器在不同的网络,那么就不能互相解析。用户可以为 Swarm 服务和独立容器进行自定义DNS 配置。举个例子,--dns 参数允许读者指定自定义的 DNS 服务列表,以防出现内置的 Docker DNS 服务器解析失败的况。此外也可以使用 --dns-search 参数指定自定义查询时所使用的域名(例如当查询名称并非完整域名的时候)。

服务发布: Docker Swarm 支持两种服务发布模式,两种模式均保证服务从集群外可访问。1) Ingress模式(默认)。2)Host模式。通过 Ingress 模式发布的服务,可以保证从 Swarm 集群内任一节点(即使没有运行服务的副本)都能访问该服务;以 Host 模式发布的服务只能通过运行服务副本的节点来访问。下图展示了两种模式的区别。

Ingress模式与Host模式

Ingress 模式是默认方式,这意味着任何时候读者通过 -p 或者 --publish 发布服务的时候,默认都是 Ingress 模式;如果需要以 Host 模式发布服务,则读者需要使用 --publish 参数的完整格式,并添加 mode=host。下面一起来看 Host 模式的例子。

$ docker service create -d --name svc1 \                 published=5000 表示服务通过端口 5000 提
--publish published=5000,target=80,mode=host \  供外部服务target=80表示发送到published
nginx                                                                       端口5000的请求会映射到服务副本的 80 端口之上mode=host 表示只有外部请求发送到运行了服务副本的节点才可以访问该服务。通常使用 Ingress 模式。在底层,Ingress 模式采用名为 Service Mesh 或者 Swarm Mode Service Mesh 的四层路由网络来实现。下图展示了 Ingress 模式下一个外部请求是如何流转,最终访问到服务的。

Ingress模式下访问服务


上图中最上方命令部署了一个名为“svc1”的 Swarm 服务。该服务连接到了 overnet 网络,并发布到 5000 端口。按上述方式发布 Swarm 服务(--publish published=5000,target=80)会在 Ingress 网络的 5000 端口进行发布。因为 Swarm 全部节点都接入了 Ingress 网络,所以这个端口被发布到了Swarm范围内。集群确保到达 Ingress 网络中任意节点的 5000 端口的流量,都会被路由到 80 端口的“svc1”服务。当前“svc1”服务只部署了一个副本,集群中有一条映射规则:“所有访问 Ingress 网络 5000 端口的流量都需要路由到运行了“svc1”服务副本的节点之上”。红线展示了访问 Node 的 15000 端口的流量,通过 Ingress 网络,被路由到了 Node2 节点正在运行的服务副本之上。入站流量可能访问 4 个 Swarm 节点中的任意一个,但是结果都是一样的,了解这一点很重要。这是因为服务通过 Ingress 网络实现了 Swarm 范围内的发布。此外:如果存在多个运行中的副本,流量会平均到每个副本之上,如下图中展示的一样。

流量平均到每个副本之上

以下是Docker网络子命令如下表所示

子命令 说明
docker network connect 将容器连接到网络。
docker network create 创建新的 Docker 网络。默认情况下,在 Windows 上会采用 NAT 驱动,在 Linux 上会采用
Bridge 驱动。可以使用 -d 参数指定驱动(网络类型)。
docker network disconnect 断开容器的网络。
docker network inspect 提供 Docker 网络的详细配置信息。
docker network ls 用于列出运行在本地 Docker 主机上的全部网络。
docker network prune 删除 Docker 主机上全部未使用的网络。
docker network rm 删除 Docker 主机上指定网络。
发布了105 篇原创文章 · 获赞 86 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/dingyahui123/article/details/104383043