Kubernetes 网络介绍(一)
本系列开始详细地讨论 Kubernetes 网络。在本篇中,我们将讨论 Pod 如何在内部和外部连接到集群。我们还将介绍 Kubernetes 的内部组件如何连接。Kubernetes 网络旨在解决以下四个网络问题:
-
高度耦合的容器到容器通信 -
Pod 到 Pod 通信 -
Pod 到服务的通信 -
外部到服务的通信
Docker 网络模型默认使用虚拟桥接网络,该网络是按主机定义的,是容器附加的专用网络。容器的IP地址被分配了一个私有IP地址,这意味着运行在不同机器上的容器不能相互通信。开发人员必须将主机端口映射到容器端口,然后使用 Docker 代理流量以跨节点到达。在这种情况下,Docker 管理员需要避免容器之间的端口冲突;通常,这是系统管理员。Kubernetes 网络以不同的方式处理这个问题。
Kubernetes 网络模型
Kubernetes 网络模型本身支持多主机集群网络。默认情况下,Pod 可以相互通信,无论它们部署在哪台主机上。Kubernetes 依赖 CNI 项目来满足以下要求:
-
所有容器必须在没有 NAT 的情况下相互通信。 -
节点无需 NAT 即可与容器通信。 -
容器的 IP 地址与其将自身视为容器外部的 IP 地址相同。
Kubernetes 中的工作单元称为 Pod。一个 Pod 包含一个或多个容器,这些容器总是被调度并在同一节点上“一起”运行。这种连接性允许服务的各个实例被分成不同的容器。例如,开发人员可能选择在一个容器中运行服务,并在另一个容器中运行日志转发器。在不同的容器中运行进程允许它们具有单独的资源配额(例如,“日志转发器不能使用超过 512 MB 的内存”)。它还允许通过减少构建容器所需的范围来分离容器构建和部署机制。
以下是最小的 pod 定义。我们省略了很多选项。Kubernetes 管理各种只读字段,例如 Pod 的状态:
apiVersion: v1
kind: Pod
metadata:
name: go-web
namespace: default
spec:
containers:
- name: go-web
image: go-web:v0.0.1
ports:
- containerPort: 8080
protocol: TCP
Kubernetes 用户通常不会直接创建 Pod。相反,用户创建高级工作负载,例如部署,它根据某些预期规范来管理 Pod。在部署的情况下,如图 4-1 所示,用户指定 pod 的模板,以及他们希望存在的 pod 数量(通常称为副本)。还有其他几种管理工作负载的方法,例如 ReplicaSet 和 StatefulSet,我们将后面系列介绍这些方法。有些提供中间类型的抽象,而另一些则直接管理 Pod。还有自定义资源定义 (CRD) 形式的第三方工作负载类型。Kubernetes 中的工作负载是一个复杂的主题,我们只会尝试涵盖非常基础的知识和适用于网络堆栈的部分。
图 4-1。Deployment 和 Pod 之间的关系
Pod 本身是短暂的,这意味着它们会被删除并被自身的新版本替换。Pod 的短暂生命周期对于熟悉半永久性、传统物理或虚拟机的开发人员和操作人员来说是主要的惊喜和挑战之一。本地磁盘状态、节点调度和IP地址都会在Pod的生命周期中定期更换。Pod 具有唯一的 IP 地址,该 IP 地址由 Pod 中的所有容器共享。为每个 Pod 提供 IP 地址的主要动机是消除端口号方面的限制。在 Linux 中,只有一个程序可以侦听给定的地址、端口和协议。如果 Pod 没有唯一的 IP 地址,则节点上的两个 Pod 可能会争用同一端口(例如两个 Web 服务器,都尝试侦听端口 80)。如果它们相同,则需要运行时配置来修复,例如 --port 标志。或者,在第三方软件的情况下,需要一个丑陋的脚本来更新配置文件。在某些情况下,第三方软件根本无法在自定义端口上运行,这将需要更复杂的解决方法,例如节点上的 iptables DNAT 规则。Web 服务器还有一个额外的问题,即在其软件中需要传统的端口号,例如 HTTP 的 80 和 HTTPS 的 443。打破这些约定需要通过负载均衡器进行反向代理或让下游消费者了解各种端口(这对于内部系统来说比外部系统容易得多)。有些系统,例如 Google 的 Borg,就使用这种模型。Kubernetes 选择每个 Pod 的 IP 模型是为了让开发人员更容易采用,并更轻松地运行第三方工作负载。对我们来说不幸的是,为每个 Pod 分配和路由 IP 地址会大大增加 Kubernetes 集群的复杂性。
这种被动连接意味着集群中的任何 Pod 都可以连接到同一集群中的任何其他 Pod。这很容易导致滥用,特别是在服务不使用身份验证或攻击者获取凭据的情况下。
使用自己的 IP 地址创建和删除的 Pod 可能会给不理解此行为的初学者带来问题。假设我们有一个在 Kubernetes 上运行的小型服务,采用具有三个 pod 副本的部署形式。当有人更新部署中的容器镜像时,Kubernetes 会执行滚动升级,删除旧的 Pod 并使用新的容器镜像创建新的 Pod。这些新 Pod 可能会有新的 IP 地址,从而导致旧的 IP 地址无法访问。手动在配置或 DNS 记录中引用 Pod IP,却导致解析失败,可能是初学者常犯的错误。这个错误是服务和端点试图解决的问题,这将在后续讨论。显式创建 pod 时,可以指定 IP 地址。StatefulSet 是一种内置工作负载类型,适用于数据库等工作负载,它维护 Pod 身份概念,并为新 Pod 提供与其替换的 Pod 相同的名称和 IP 地址。还有第三方 CRD 形式的其他示例,并且可以为特定网络目的编写 CRD。
每个 Kubernetes 节点都运行一个名为Kubelet的组件,用于管理节点上的 Pod。Kubelet 中的网络功能来自与节点上的 CNI 插件的 API 交互。CNI 插件负责管理 Pod IP 地址和单个容器网络配置。CNI 定义了管理容器网络的标准接口。使 CNI 成为一个接口的原因是为了有一个可互操作的标准,其中有多个 CNI 插件实现。CNI 插件负责分配 Pod IP 地址并维护所有(适用的)Pod 之间的路由。Kubernetes 不附带默认的 CNI 插件,这意味着在 Kubernetes 的标准安装中,pod 无法使用网络。让我们开始讨论 CNI 如何启用 Pod 网络以及不同的网络布局。
节点和 Pod 网络布局
集群必须有一组由其控制的IP地址才能为Pod分配IP地址,例如10.1.0.0/16。节点和 Pod 必须在此 IP 地址空间中具有 L3 连接。在 L3(互联网层)中,连接性意味着具有 IP 地址的数据包可以路由到具有该 IP 地址的主机。值得注意的是,传送数据包的能力比创建连接(L4 概念)更重要。在 L4 中,防火墙可以选择允许从主机 A 到 B 的连接,但拒绝从主机 B 到 A 发起的连接。必须允许从 A 到 B 的 L4 连接、L3 上的连接、A 到 B 和 B 到 A。如果没有 L3 连接,TCP 握手将不可能,因为无法传递 SYN-ACK。通常,Pod 没有 MAC 地址。因此,无法与 Pod 进行 L2 连接。CNI 将为 Pod 确定这一点。Kubernetes 中没有关于与外部世界的 L3 连接的要求。
尽管大多数集群都具有互联网连接,但出于安全原因,有些集群更加隔离。我们将广泛讨论入口(离开主机或集群的流量)和出口(进入主机或集群的流量)。我们在这里使用的“入口”不应与 Kubernetes 入口资源混淆,后者是一种将流量路由到 Kubernetes 服务的特定 HTTP 机制。构建集群网络的方法大致可采用三种方法(有多种变体):孤立网络、扁平网络和孤岛网络。我们将在这里讨论一般方法,然后在本章后面介绍 CNI 插件时更深入地讨论具体的实现细节。
隔离网络
在隔离的集群网络中,节点在更广泛的网络上是可路由的(即,不属于集群的主机可以到达集群中的节点),但 Pod 却不能。图 4-2 显示了这样一个集群。请注意,Pod 无法访问集群外部的其他 Pod(或任何其他主机)。由于集群无法从更广泛的网络进行路由,因此多个集群甚至可以使用相同的 IP 地址空间。请注意,如果外部系统或用户应该能够访问 Kubernetes API,则 Kubernetes API 服务器需要能够从更广泛的网络进行路由。许多托管 Kubernetes 提供商都有这样的“安全集群”选项,其中集群和互联网之间不可能有直接流量。如果集群的工作负载允许/需要这样的设置(例如用于批处理的集群),那么与本地集群的隔离对于安全性来说是非常好的。然而,这并不是对所有集群都是合理的。大多数集群需要连接外部系统和/或被外部系统连接,例如必须支持依赖于更广泛的互联网的服务的集群。负载均衡器和代理可用于突破此障碍,并允许互联网流量进出隔离集群。
图 4-2。同一网络中的两个隔离集群
扁平网络
在扁平网络中,所有 Pod 都有一个可从更广泛的网络路由的 IP 地址。除非有防火墙规则,网络上的任何主机都可以路由到集群内部或外部的任何 Pod。这种配置在网络简单性和性能方面有许多优点。Pod 可以直接连接到网络中的任意主机。请注意,在图 4-3 中,两个集群之间没有两个节点的 pod CIDR 重叠,因此不会为两个 pod 分配相同的 IP 地址。由于更广泛的网络可以将每个 Pod IP 地址路由到该 Pod 的节点,因此网络上的任何主机都可以与任何 Pod 进行访问。这种开放性允许任何拥有足够服务发现数据的主机决定哪个 Pod 将接收这些数据包。集群外部的负载均衡器可以对 Pod 进行负载均衡,例如另一个集群中的 gRPC 客户端。
图 4-3。同一平面网络中的两个集群
外部 Pod 流量(以及传入 Pod 流量,当连接的目标是特定 Pod IP 地址时)具有低延迟和低开销。任何形式的代理或数据包重写都会产生延迟和处理成本,虽然很小但很重要(特别是在涉及许多后端服务的应用程序架构中,每个延迟都会增加)。不幸的是,这个模型需要每个集群有一个大的、连续的IP地址空间(即一个IP地址范围,该范围内的每个IP地址都在你的控制之下)。Kubernetes 要求 pod IP 地址有一个 CIDR(对于每个 IP 系列)。此模型可以通过私有子网(例如 10.0.0.0/8 或 172.16.0.0/12)来实现;然而,使用公共 IP 地址(尤其是 IPv4 地址)要困难得多,成本也更高。管理员需要使用 NAT 将在私有 IP 地址空间中运行的集群连接到互联网。除了需要较大的 IP 地址空间外,管理员还需要一个易于编程的网络。CNI 插件必须分配 Pod IP 地址并确保存在到给定 Pod 节点的路由。私有子网上的扁平网络很容易在云提供商环境中实现。绝大多数云提供商网络将提供大型私有子网,并具有用于 IP 地址分配和路由管理的 API(甚至预先存在的 CNI 插件)。
Island Networks 岛屿网络
从高层次上看,岛群网络是孤立网络和扁平网络的组合。在孤岛集群设置中,如图 4-4 所示,节点与更广泛的网络具有 L3 连接,但 Pod 没有。进出 Pod 的流量必须通过某种形式的代理、节点。大多数情况下,这是通过对离开节点的 Pod 数据包进行 iptables 源 NAT 来实现的。这种设置称为masquerading ,它使用 SNAT 将数据包源从 pod 的 IP 地址重写为节点的 IP 地址(有关 SNAT 的回顾,请参阅第 2 章)。换句话说,数据包似乎是“来自”节点,而不是 Pod。
在使用 NAT 的同时共享 IP 地址会隐藏各个 Pod IP 地址。基于 IP 地址的防火墙和识别在跨集群边界时变得困难。在集群内,哪个 IP 地址是哪个 pod(以及哪个应用程序)仍然很明显。其他集群中的 Pod 或更广泛网络上的其他主机将不再具有该映射。基于 IP 地址的防火墙和允许列表本身并不足以保证安全,但却是一个有价值且有时是必需的层。
现在让我们看看如何使用 kubecontroller-manager 配置这些网络布局。控制平面是指确定使用哪条路径发送数据包或帧的所有功能和进程。数据平面是指基于控制平面逻辑将数据包/帧从一个接口转发到另一个接口的所有功能和过程。
图 4-4。在“孤岛网络”中的配置
Kube-控制器-管理器配置
kube-controller-manager 在一个二进制文件和一个进程中运行大多数单独的 Kubernetes 控制器,大多数 Kubernetes 逻辑都位于其中。从高层次来看,Kubernetes 术语中的控制器是一种软件,它监视资源并采取行动来同步或强制执行特定状态(所需状态或将当前状态反映为状态)。Kubernetes 有很多控制器,它们通常“拥有”特定的对象类型或特定的操作。
kube-controller-manager 包含多个管理 Kubernetes 网络堆栈的控制器。值得注意的是,管理员在这里设置集群 CIDR。
kube-controller-manager 由于运行大量控制器,因此也有大量标志。表 4-1 重点介绍了一些值得注意的网络配置标志。
表 4-1。Kube-控制器-管理器选项
旗帜 |
默认 |
描述 |
---|---|---|
--分配节点 cidrs |
真的 |
设置是否应在云提供商上分配和设置 Pod 的 CIDR。 |
--CIDR 分配器类型字符串 |
范围分配器 |
要使用的 CIDR 分配器的类型。 |
--集群-CIDR |
分配 Pod IP 地址的 CIDR 范围。要求 --allocate-node-cidrs 为 true。如果 kubecontroller-manager 启用了 IPv6DualStack,则 --cluster-CIDR 接受一对以逗号分隔的 IPv4 和 IPv6 CIDR。 | |
--配置云路由 |
真的 |
设置 CIDR 是否应由 allocatenode-cidrs 分配并在云提供商上配置。 |
--node-CIDR-掩码大小 |
IPv4 集群为 24 个,IPv6 集群为 64 个 |
集群中节点 CIDR 的掩码大小。Kubernetes 将为每个节点分配 2^(node-CIDR-mask-size) 个 IP 地址。集群中节点 CIDR 的掩码大小。在双堆栈集群中使用此标记以允许 IPv4 和 IPv6 设置。集群中节点 CIDR 的掩码大小。在双堆栈集群中使用此标记以允许 IPv4 和 IPv6 设置。 |
--node-CIDR-掩码大小-ipv4 |
24 | 集群中节点 CIDR 的掩码大小。在双堆栈集群中使用此标记以允许 IPv4 和 IPv6 设置。 |
--node-CIDR-掩码-大小-ipv6 |
64 | 集群中节点 CIDR 的掩码大小。在双堆栈集群中使用此标记以允许 IPv4 和 IPv6 设置。 |
--服务集群 IP 范围 |
集群中服务分配服务ClusterIP的CIDR范围。要求 --allocate-node-cidrs 为 true。如果 kube-controller-manager 启用了 IPv6Dual Stack,则 --service-cluster-ip-range 接受一对以逗号分隔的 IPv4 和 IPv6 CIDR。 |
现在我们已经讨论了 Kubernetes 控制平面中的高级网络架构和网络配置,下一篇让我们仔细看看 Kubernetes 工作节点如何处理网络。
原文始发于微信公众号(Docker中文社区):Kubernetes 网络介绍(一)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论