Kubernetes 是为运行分布式集群而建立的,分布式系统的本质使得网络成为 Kubernetes 的核心和必要组成部分,了解 Kubernetes 网络模型可以使你能够正确运行、监控和排查应用程序故障。
网络所涉及的内容很多,拥有许多成熟的技术。对于不熟悉的人来说可能会非常痛苦,因为大多数人对网络都有先入为主的观念,并且有很多新旧概念需要理解并组合成一个连贯的整体。所说的网络可能包括网络命名空间、虚拟接口、IP 转发和网络地址转换等技术。本指南旨在通过讨论每种 Kubernetes 相关技术以及如何使用这些技术来启用 Kubernetes 网络模型的描述来揭开 Kubernetes 网络的神秘面纱。
本指南相当长,分为几个部分。我们首先讨论一些基本的 Kubernetes 术语,以确保在整个指南中正确使用术语,然后讨论 Kubernetes 网络模型以及它强加的设计和实施决策。接下来是本指南中最长且最有趣的部分: 深入讨论如何使用几个不同的用例展示在Kubernetes内是如何进行通信的。
文章大纲如下:
1、Kubernetes基础知识
Kubernetes 由几个核心概念构建而成,这些概念组合成越来越强大的功能。本节列出了这些概念中的每一个,并提供了一个简短的概述,以帮助促进讨论。Kubernetes 的内容远不止此,这里仅仅简要阐述一些基础知识。如果您已经熟悉 Kubernetes,请随意跳过本节。
1.1、Kubernetes API server
在 Kubernetes 中,一切都是由 Kubernetes API 服务器(kube-apiserver)提供的 API 调用。API 服务器是 etcd 数据存储的网关,它维护应用程序集群的所需状态。要更新 Kubernetes 集群的状态,您可以对描述所需状态的 API 服务器进行 API 调用。
1.2、Controllers
控制器是用于构建 Kubernetes 的核心抽象。一旦您使用 API 服务器声明了集群的所需状态,控制器就会通过持续观察 API 服务器的状态并对任何更改做出反应来确保集群的当前状态与所需状态相匹配。控制器内部实现了一个循环,该循环不断检查集群的当前状态与集群的期望状态。如果有任何差异,控制器将执行任务以使当前状态与所需状态匹配。在伪代码中:
while true: X = currentState() Y = desiredState() if X == Y: return # Do nothing else: do(tasks to get to Y)
例如,当您使用 API 服务器创建新 Pod 时,Kubernetes 调度程序(控制器)会注意到更改并决定将 Pod 放置在集群中的哪个位置。然后它使用 API 服务器(由 etcd 支持)写入状态更改。kubelet(一个控制器)然后会注意到新的变化并设置所需的网络功能以使 Pod 在集群内可访问。在这里,两个独立的控制器对两个独立的状态变化做出反应,以使集群的现实与用户的意图相匹配。
1.3、Pods
Pod 是 Kubernetes 的原子——用于构建应用程序的最小可部署对象。单个 Pod 代表集群中正在运行的工作负载,并封装了一个或多个 Docker 容器、任何所需的存储和唯一的 IP 地址,组成 pod 的容器被设计为在同一台机器上共同定位和调度。
1.4、Nodes
节点是运行 Kubernetes 集群的机器。这些可以是裸机、虚拟机或其他任何东西。主机一词通常与节点互换使用。我将尝试一致地使用术语节点,但有时会根据上下文使用虚拟机这个词来指代节点。
2、Kubernetes网络模型
Kubernetes 对 Pod 的联网方式做出了自以为是的选择。特别是,Kubernetes 对任何网络实现都规定了以下要求:
- 所有 Pod 都可以在不使用网络地址转换 (NAT) 的情况下与所有其他 Pod 通信。
- 所有节点都可以在没有 NAT 的情况下与所有 Pod 通信。
- Pod 认为自己的 IP 与其他人认为的 IP 相同。
鉴于这些限制,我们需要解决四个不同的网络问题:
- 容器到容器网络
- Pod 到 Pod 网络
- Pod 到服务网络
- Internet 到服务网络
本指南的其余部分将讨论这些问题中的每一个 以及他们的解决方案。
3、容器和容器之间网络通信
通常,我们将虚拟机中的网络通信视为直接与以太网设备交互,如图 1 所示。
实际上,情况比这更微妙。在 Linux 中,每个正在运行的进程都在一个网络命名空间内进行通信,该命名空间为逻辑网络堆栈提供了自己的路由、防火墙规则和网络设备。本质上,网络命名空间为命名空间内的所有进程提供了一个全新的网络堆栈。作为 Linux 用户,可以使用 ip 命令创建网络命名空间。例如,以下命令将创建一个名为 ns1 的新网络命名空间。
$ ip netns add ns1
创建命名空间时,会在 /var/run/netns 下为其创建一个挂载点,即使没有附加任何进程,命名空间也可以保留。
您可以通过列出 /var/run/netns 下的所有挂载点或使用 ip 命令来列出可用的命名空间。
$ ls /var/run/netns ns1 $ ip netns ns1
默认情况下,Linux 将每个进程分配给根网络命名空间以提供对外部世界的访问,如图 2 所示。
就 Docker 结构而言,Pod 被建模为一组共享网络命名空间的 Docker 容器。Pod 中的容器都具有相同的 IP 地址和端口空间,这些 IP 地址和端口空间是通过分配给 Pod 的网络命名空间分配的,并且可以通过 localhost 找到彼此,因为它们位于同一个命名空间中。我们可以为虚拟机上的每个 Pod 创建一个网络命名空间。这是使用 Docker 作为“Pod 容器”实现的,它保持网络命名空间打开,而“应用容器”(用户指定的东西)通过 Docker 的 –net=container: 函数加入该命名空间。图 3 显示了每个 Pod 如何由共享命名空间内的多个 Docker 容器 (ctr*) 组成。
Pod 中的应用程序还可以访问共享卷,这些卷被定义为 Pod 的一部分,并且可以挂载到每个应用程序的文件系统中。
4、Pod和Pod之间网络通信
在 Kubernetes 中,每个 Pod 都有一个真实的 IP 地址,并且每个 Pod 都使用该 IP 地址与其他 Pod 通信。现在任务是了解 Kubernetes 如何使用真实 IP 实现 Pod 到 Pod 的通信,无论 Pod 部署在集群中的同一个物理节点还是不同的节点上。我们通过考虑驻留在同一台机器上的 Pod 来开始这个讨论,以避免通过内部网络跨节点通信的复杂性。
从 Pod 的角度来看,它存在于自己的以太网命名空间中,需要与同一节点上的其他网络命名空间进行通信。值得庆幸的是,可以使用 Li