2

这篇文章将试图揭开在kubernetes集群中运行的网络的多层神秘感。 Kubernetes是一个功能强大的平台,其中包含许多智能的设计选择,但讨论交互的方式可能会造成混淆:Pod网络,服务网络,集群IP,容器端口,主机端口,节点端口……这会让我们更加迷惑。如果您想试一试,并弄清楚每一层是如何工作的,那么以一种相当优雅的方式来说,这一切都是有意义的。

为了突出重点,我将把该系列分为三个部分。第一部分将研究容器和Pod。第二个将介绍服务,服务是允许Pod临时使用的抽象层。最后一篇文章将探讨入口以及如何从集群外部为您的Pod提供流量。首先是一些声明。这篇文章并非旨在成为有关容器,kubernetes或pod的基本介绍。要了解有关容器如何工作的更多信息,请参阅Docker的概述。可以在这里找到kubernetes的高级概述,并在这里专门了解Pod的概述。最后,对网络IP地址空间有基本的了解会有所帮助。

f-01.png

Pods

那么什么是Pod呢?Pod由在同一主机上的一个或多个容器组成,并配置为共享网络堆栈和其他资源(例如卷)。 Pod是构建kubernetes应用程序的基本单位。 “共享网络堆栈”实际上是什么意思?实际上,这意味着Pod中的所有容器都可以在localhost上相互访问。如果我有一个运行nginx并在端口80上侦听的容器,另一个运行scrapyd的容器,则第二个容器可以通过http://localhost:80 连接到第一个容器。但是,这实际上如何工作?让我们看一下在本地计算机上启动docker容器时的典型情况:

f-02.png

从上至下,我们有一个物理网络接口eth0。附加到该网桥的是网桥docker0,附加到网桥的是虚拟网络接口veth0。请注意,docker0和veth0都在同一网络上,在此示例中为172.17.0.0/24。在该网络上,docker0被分配了172.17.0.1,并且是veth0的默认网关,它被分配了172.17.0.2。由于启动容器时配置网络名称空间的方式,容器内部的进程只能看到veth0,并通过docker0和eth0与外界通信。现在启动第二个容器:

f-03.png

如上图所示,第二个容器获得一个新的虚拟网络接口veth1,该接口连接到相同的docker0网桥。此接口分配为172.17.0.3,因此它与网桥和第一个容器位于同一逻辑网络上,并且两个容器都可以只要他们能够以某种方式发现另一个容器的IP地址,就可以通过网桥进行通信。

很好,但所有这些并不能使我们进入kubernetes连网的“共享网络堆栈”。幸运的是,名称空间非常灵活。 Docker可以启动一个容器,而不是为其创建一个新的虚拟网络接口,而是指定它共享一个现有接口。在这种情况下,上图看起来有些不同:

f-04.png

现在,第二个容器看到veth0,而不是像前面的示例那样获得自己的veth1。这有一些含义:首先,两个容器都可以从外部在172.17.0.2上寻址,而在内部,每个容器都可以命中在本地主机上由另一个打开的端口。这也意味着两个容器不能打开同一端口,这是一个限制,但与在单个主机上运行多个进程时的情况没有什么不同。这样,一组过程可以充分利用容器的去耦和隔离功能,同时在最简单的网络环境中进行协作。

Pod创建一个特殊的容器来实现这种模式,其唯一目的是为其他容器提供网络接口。如果您将SSH调度到已安排了Pod的kubernetes集群节点并运行docker ps,您将看到至少一个用pause命令启动的容器。pause命令将暂停当前进程,直到接收到信号为止,因此这些容器除了睡眠以外什么都不做,直到kubernetes向其发送SIGTERM为止。尽管缺乏活动,“pause”容器仍然是Pod的核心,它提供了虚拟网络接口,所有其他容器都将使用此接口与彼此以及与外界进行通信。因此,在假设的Pod中,最后一张图片看起来像这样:

f-05.png

Pod 网络

一切都很酷,但是一个装满了可以互相访问的容器的Pod并没有给我们提供系统。由于在下一篇我讨论服务的文章中将变得更加清楚的原因,kubernetes的设计核心是要求Pod能够与其他Pod通信,无论它们是在同一本地主机上还是在单独的主机上运行。要查看这种情况如何发生,我们需要上一级并查看集群中的节点。本节将包含一些对网络路由的介绍,我意识到这是所有人都希望避免的主题。很难找到清晰,简短的IP路由教程。

kubernetes集群由一个或多个节点组成。节点是一个主机系统,无论是物理系统还是虚拟系统,都具有容器运行时及其依存关系(即主要是docker)以及几个kubernetes系统组件,该主机系统连接到网络以使其能够访问群集中的其他节点。一个由两个节点组成的简单群集可能看起来像这样:

f-06.png

如果您是在GCP或AWS等云平台上运行集群,则绘制的图很近似于单个项目环境的默认网络架构。为了便于说明,在本示例中,我使用专用网络10.100.0.0/24,因此路由器为10.100.0.1,两个实例分别为10.100.0.2和10.100.0.3。通过此设置,每个实例可以在eth0上彼此通信。太好了,但请记住,我们上面看过的Pod不在此专用网络上:它完全悬挂在另一个网络上的桥上,该网络是虚拟的,仅存在于特定节点上。为了更加清楚,让我们将之前Pod的东西放到图片中:

f-07.png

左侧的主机的接口eth0地址为10.100.0.2,其默认网关为路由器10.100.0.1。连接到该接口的是地址为172.17.0.1的网桥docker0,并连接到地址为172.17.0.2的接口veth0。 veth0接口是使用pause容器创建的,并且通过共享网络堆栈在所有三个容器中可见。由于在创建网桥时设置了本地路由规则,因此到达eth0且目标地址为172.17.0.2的任何数据包都将转发到网桥,然后将其发送到veth0。到目前为止还可以。如果我们知道在此主机上有一个位于172.17.0.2的容器,则可以向路由器添加规则,将该地址的下一跳设置为10.100.0.2,它们将从那里转发到veth0。现在,让我们看看另一台主机。

右侧的主机也具有eth0,其地址为10.100.0.3,使用相同的默认网关10.100.0.1,并且再次连接到该主机是地址为172.17.0.1的docker0网桥。嗯这将是一个问题。现在,该地址实际上可能与主机1上的另一个网桥不同。我在这里做了相同的设置,因为这是最糟糕的情况,如果您刚刚安装了docker并让它做的话,它很可能会以这种方式解决它的东西。但是,即使选择的网络不同,这也突出了一个更基本的问题,即一个节点通常不知道将哪个专用地址空间分配给另一节点上的网桥,并且我们需要知道是否要发送数据包并让他们到达正确的地方。显然需要一些结构。

Kubernetes通过两种方式提供该结构。首先,它为每个节点上的网桥分配一个总体地址空间,然后根据网桥所基于的节点在该空间内分配网桥地址。其次,它在10.100.0.1处向网关添加了路由规则,告诉它应如何路由发往每个网桥的数据包​​,即可以通过哪个节点访问网桥的eth0。虚拟网络接口,网桥和路由规则的这种组合通常称为覆盖网络。在谈论kubernetes时,我通常将此网络称为“pod网络”,因为它是一个覆盖网络,允许Pod在任何节点上来回通信。这是上面kubernetes实际运行的图:

f-08.png

应该看出来的一件事是,已将网桥的名称从“ docker0”更改为“ cbr0”。Kubernetes不使用标准的docker网桥设备,实际上“ cbr”是“自定义网桥”的缩写。虽然不了解自定义的所有信息,但这是在kubernetes上运行的docker与默认安装之间的重要区别之一。另外要注意的是,在此示例中,分配给网桥的地址空间为10.0.0.0/14。取自我们在Google Cloud中的一个集群,因此是一个真实示例。您的群集可能会分配一个完全不同的范围。

通常,您无需考虑此网络的功能。当一个Pod与另一个Pod通信时,通常是通过抽象服务来实现的,服务是一种软件定义的代理,它将成为本系列下一篇文章的主题。但是pod网络地址将在日志中打印,并且在调试时,在某些情况下,您可能需要显式路由此网络。例如,默认情况下,离开kubernetes pod绑定到10.0.0.0/8范围内任何地址的流量不会进行NAT,因此,如果您与该范围内另一个专用网络上的服务进行通信,则可能需要设置规则以路由数据包回到Pod。希望本文将帮助您采取正确的步骤以使此类方案正确运行。


iyacontrol
1.4k 声望2.7k 粉丝

专注kubernetes,devops,aiops,service mesh。