高级 Kubernetes 网络详解
1. Pod 与服务通信
在 Kubernetes 集群中,Pod 之间可以通过 IP 地址和端口直接通信,但这要求 Pod 知晓彼此的 IP 地址。然而,Kubernetes 集群中的 Pod 会不断地被销毁和创建。服务提供了一层间接性,即使响应请求的实际 Pod 集合不断变化,服务依然稳定。此外,每个节点上的 Kube - proxy 会负责将流量重定向到正确的 Pod,从而实现自动的高可用负载均衡。
1.1 外部访问
部分容器最终需要从外部世界访问,但 Pod 的 IP 地址在外部不可见。服务是实现外部访问的合适途径,但通常需要两次重定向。例如,云提供商的负载均衡器虽然支持 Kubernetes,但无法将流量直接导向运行能处理请求的 Pod 的节点。相反,公共负载均衡器会将流量导向集群中的任意节点,若该节点未运行所需的 Pod,该节点上的 Kube - proxy 会再次将流量重定向到合适的 Pod。
1.2 外部负载均衡器工作流程
graph LR
A[外部负载均衡器] --> B[节点 1]
A --> C[节点 2]
A --> D[节点 3]
B --> E[Kube - proxy]
C --> F[Kube - proxy]
D --> G[Kube - proxy]
E --> H[Pod]
F --> I[Pod]
G --> J[Pod]
2. Kubernetes 网络与 Docker 网络对比
2.1 Docker 网络模型
Docker 网络遵循不同的模型,不过随着时间推移,它逐渐向 Kubernetes 模型靠拢。在 Docker 网络中,每个容器都有一个来自 172.xxx.xxx.xxx 地址空间的私有 IP 地址,且局限于其所在的节点。容器可以通过各自的 172.xxx.xxx.xxx IP 地址与同一节点上的其他容器通信。这对于 Docker 来说是合理的,因为它没有包含多个相互交互容器的 Pod 概念,而是将每个容器建模为具有自己网络标识的轻量级虚拟机。
2.2 Kubernetes 网络模型
在 Kubernetes 中,运行在同一节点上的不同 Pod 中的容器通常不能通过 localhost 连接(除非暴露主机端口,但这并不推荐)。因为 Kubernetes 可以在任何地方销毁和创建 Pod,所以不同的 Pod 通常不应依赖同一节点上的其他 Pod。Daemon sets 是一个显著的例外,但 Kubernetes 网络模型旨在适用于所有用例,不会为同一节点上不同 Pod 之间的直接通信添加特殊情况。
2.3 Docker 容器跨节点通信
Docker 容器跨节点通信时,容器必须将端口发布到主机。这显然需要进行端口协调,因为如果两个容器尝试发布相同的主机端口,它们会相互冲突。然后,容器(或其他进程)连接到主机的端口,该端口会将流量引入容器。一个很大的缺点是容器无法向外部服务自我注册,因为它们不知道其主机的 IP 地址。可以通过在运行容器时将主机的 IP 地址作为环境变量传递来解决这个问题,但这需要外部协调并使过程复杂化。
2.4 对比表格
| 对比项 | Kubernetes 网络 | Docker 网络 |
|---|---|---|
| 容器通信 | 不同 Pod 容器通常不能通过 localhost 连接 | 同一节点容器可通过私有 IP 通信 |
| 跨节点通信 | 服务与 Kube - proxy 实现 | 容器发布端口到主机 |
| 自我注册 | 服务自动注册 Pod | 容器无法自我注册 |
3. 查找与发现
为了使 Pod 和容器能够相互通信,它们需要找到彼此。容器定位其他容器或自我宣告的方式有多种,也有一些架构模式允许容器间接交互,每种方法都有其优缺点。
3.1 自我注册
当容器运行时,它知道其所在 Pod 的 IP 地址。每个希望在集群中被其他容器访问的容器可以连接到某个注册服务,并注册其 IP 地址和端口。其他容器可以向注册服务查询所有已注册容器的 IP 地址和端口,并与之连接。当容器被正常销毁时,它会自动注销。如果容器异常死亡,则需要建立某种机制来检测。例如,注册服务可以定期 ping 所有已注册的容器,或者容器需要定期向注册服务发送保活消息。
自我注册的优点包括:一旦通用注册服务就位(无需为不同目的进行定制),就无需担心跟踪容器;容器可以采用复杂的策略,在因本地条件不可用时(如容器繁忙不想接收更多请求)暂时注销,实现智能的去中心化动态负载均衡。缺点是注册服务是另一个非标准组件,容器需要了解它才能定位其他容器。
3.2 服务与端点
Kubernetes 服务可以被视为一种注册服务。属于某个服务的 Pod 会根据其标签自动注册。其他 Pod 可以查找端点以找到所有服务 Pod,或者直接向服务发送消息,该消息将被路由到其中一个后端 Pod。大多数情况下,Pod 会直接向服务发送消息,服务会将其转发到其中一个后端 Pod。
3.3 松散耦合的队列通信
如果容器可以在不知道彼此的 IP 地址、端口、服务 IP 地址或网络名称的情况下进行通信,并且大部分通信可以是异步和解耦的,那么队列可以促进这种松散耦合的系统。组件(容器)从队列中监听消息,响应消息,执行任务,并将有关进度、完成状态和错误的消息发布到队列。
队列的优点包括:
- 无需协调即可轻松增加处理能力,只需添加更多监听队列的容器。
- 可以通过队列深度轻松跟踪整体负载。
- 通过对消息和/或主题进行版本控制,可轻松让多个版本的组件并行运行。
- 通过让多个消费者以不同模式处理请求,可轻松实现负载均衡和冗余。
队列的缺点包括:
- 必须确保队列提供适当的持久性和高可用性,以免成为关键的单点故障。
- 容器需要使用异步队列 API(可以进行抽象)。
- 实现请求 - 响应需要在响应队列上进行有些繁琐的监听。
3.4 松散耦合的数据存储通信
另一种松散耦合的方法是使用数据存储(如 Redis)来存储消息,然后其他容器可以读取这些消息。虽然这是可行的,但这不是数据存储的设计目标,结果通常很繁琐、脆弱,并且性能不佳。数据存储是为数据存储而优化的,而不是为通信。不过,数据存储可以与队列结合使用,其中一个组件将一些数据存储在数据存储中,然后向队列发送消息表示数据已准备好处理。多个组件监听该消息并并行开始处理数据。
4. Kubernetes 入口
Kubernetes 提供了入口资源和控制器,旨在将 Kubernetes 服务暴露给外部世界。当然,用户也可以自行完成此操作,但对于特定类型的入口(如 Web 应用程序、CDN 或 DDoS 防护器),定义入口涉及的许多任务在大多数应用程序中是常见的。用户还可以编写自己的入口对象。入口对象通常用于智能负载均衡和 TLS 终止。
5. Kubernetes 网络插件
由于网络的多样性,不同的人希望以不同的方式实现网络,因此 Kubernetes 拥有网络插件系统,它足够灵活以支持任何场景。主要的网络插件是 CNI,此外还有一个更简单的网络插件 Kubenet。在详细介绍之前,先了解一下 Linux 网络的基础知识。
5.1 基本 Linux 网络
5.1.1 IP 地址和端口
网络实体通过 IP 地址进行标识。服务器可以在多个端口上监听传入连接,客户端可以在其网络内连接(TCP)或发送数据(UDP)到服务器。
5.1.2 网络命名空间
命名空间将一组网络设备分组,使得它们可以与同一命名空间中的其他服务器通信,但无法与即使物理上在同一网络中的其他服务器通信。可以通过网桥、交换机、网关和路由来连接网络或网络段。
5.1.3 子网、网络掩码和 CIDR
在设计和维护网络时,将网络划分为更小的子网是很有用的。子网可以通过表示子网大小(可容纳的主机数量)的位掩码来定义。例如,网络掩码 255.255.255.0 表示前三个八位字节用于路由,只有 256(实际为 254)个单独的主机可用。无类域间路由(CIDR)表示法通常用于此目的,因为它更简洁,编码更多信息,并且允许组合来自多个旧类(A、B、C、D、E)的主机。例如,172.27.15.0/24 表示前 24 位(三个八位字节)用于路由。
5.1.4 虚拟以太网设备
虚拟以太网(veth)设备代表物理网络设备。当创建一个与物理设备链接的 veth 时,可以将该 veth(以及扩展的物理设备)分配到一个命名空间中,在该命名空间中,来自其他命名空间的设备无法直接访问它,即使它们在物理上位于同一本地网络中。
5.1.5 网桥
网桥将多个网络段连接到一个聚合网络,以便所有节点可以相互通信。网桥在 OSI 网络模型的 L1(物理)和 L2(数据链路)层进行操作。
5.1.6 路由
路由通常基于路由表连接单独的网络,路由表指示网络设备如何将数据包转发到其目的地。路由通过各种网络设备(如路由器、网桥、网关、交换机和防火墙,包括普通的 Linux 主机)来实现。
5.1.7 最大传输单元
最大传输单元(MTU)决定了数据包的最大大小。例如,在以太网网络中,MTU 为 1500 字节。MTU 越大,有效负载与报头的比率越好,但最小延迟会降低,因为必须等待整个数据包到达,而且如果发生故障,必须重新传输整个数据包。
5.2 Pod 网络
Pod、主机和全球互联网在网络层面通过 veth0 建立关系,可用以下 mermaid 流程图表示:
graph LR
A[全球互联网] --> B[主机]
B --> C[Pod]
B <--> C(veth0)
5.3 Kubenet
Kubenet 是一个非常基础的网络插件,它为每个 Pod 创建一个名为 cbr0 的 Linux 网桥和一个 veth。云提供商通常使用它来设置节点之间的通信路由规则,或在单节点环境中使用。veth 对使用主机 IP 地址范围内的 IP 地址将每个 Pod 连接到其主机节点。
5.3.1 要求
Kubenet 插件有以下要求:
- 节点必须分配一个子网,以便为其 Pod 分配 IP 地址。
- 版本 0.2.0 或更高版本需要标准的 CNI 网桥、lo 和 host - local 插件。
- Kubelet 必须使用
--network - plugin = kubenet
参数运行。
- Kubelet 必须使用
--non - masquerade - cidr = <clusterCidr>
参数运行。
5.3.2 设置 MTU
MTU 对网络性能至关重要。Kubernetes 网络插件(如 Kubenet)会尽力推断最佳 MTU,但有时需要手动干预。如果现有网络接口(如 Docker 的 docker0 网桥)设置了较小的 MTU,Kubenet 会重用它。例如,IPSEC 由于 IPSEC 封装开销需要降低 MTU,但 Kubenet 网络插件不会考虑这一点。解决方案是避免依赖 MTU 的自动计算,而是通过
--network - plugin - mtu
命令行开关告诉 Kubelet 网络插件应使用的 MTU。目前,只有 Kubenet 网络插件会考虑此命令行开关。
5.4 容器网络接口(CNI)
CNI 既是一个规范,也是一组用于编写网络插件的库,用于配置 Linux 容器(不仅仅是 Docker)中的网络接口。该规范实际上源于 rkt 网络提案,并且有很多组织在使用 CNI,如 Kubernetes、Kurma、Cloud foundry、Nuage、RedHat、Mesos 等。
5.4.1 CNI 插件列表
CNI 团队维护了一些核心插件,也有很多第三方插件为 CNI 的成功做出了贡献,如下表所示:
| 插件名称 | 描述 |
| ---- | ---- |
| Project Calico | 三层虚拟网络 |
| Weave | 多主机 Docker 网络 |
| Contiv networking | 基于策略的网络 |
| Cilium | 用于容器的 BPF 和 XDP |
| Multus | 多插件 |
| CNI - Genie | 通用 CNI 网络插件 |
| Flannel | 为 Kubernetes 设计的容器网络结构 |
| Infoblox | 企业级容器 IP 地址管理 |
5.4.2 容器运行时
CNI 为网络应用容器定义了插件规范,但插件必须插入到提供某些服务的容器运行时中。在 CNI 的上下文中,应用容器是一个可网络寻址的实体(有自己的 IP 地址)。对于 Docker,每个容器都有自己的 IP 地址;对于 Kubernetes,每个 Pod 有自己的 IP 地址,Pod 是 CNI 容器,而不是 Pod 内的容器。rkt 的应用容器也类似于 Kubernetes 的 Pod,可能包含多个 Linux 容器。运行时的工作是配置网络,然后执行一个或多个 CNI 插件,并以 JSON 格式将网络配置传递给它们。
5.4.3 CNI 插件
CNI 插件的工作是将网络接口添加到容器网络命名空间中,并通过 veth 对将容器连接到主机。然后,它应通过 IPAM(IP 地址管理)插件分配 IP 地址并设置路由。
容器运行时(Docker、rkt 或任何其他符合 CRI 的运行时)将 CNI 插件作为可执行文件调用。插件需要支持以下操作:
- 将容器添加到网络
- 从网络中移除容器
- 报告版本
插件使用简单的命令行界面、标准输入/输出和环境变量。网络配置以 JSON 格式通过标准输入传递给插件,其他参数定义为环境变量:
-
CNI_COMMAND
:指示所需的操作;ADD、DEL 或 VERSION。
-
CNI_CONTAINERID
:容器 ID。
-
CNI_NETNS
:网络命名空间文件的路径。
-
CNI_IFNAME
:要设置的接口名称;插件必须遵守此接口名称,否则返回错误。
-
CNI_ARGS
:用户在调用时传递的额外参数。字母数字键值对用分号分隔,例如
FOO = BAR;ABC = 123
。
-
CNI_PATH
:搜索 CNI 插件可执行文件的路径列表。路径由特定于操作系统的列表分隔符分隔,例如 Linux 上的
:
和 Windows 上的
;
。
如果命令成功,插件返回零退出代码,并且生成的接口(在 ADD 命令的情况下)以 JSON 格式流式传输到标准输出。以下是调用 CNI 插件的 ADD 命令的结果示例:
{
"cniVersion": "0.3.0",
"interfaces": [
{
"name": "<name>",
"mac": "<MAC address>",
"sandbox": "<netns path or hypervisor identifier>"
}
],
"ip": [
{
"version": "<4 - or - 6>",
"address": "<ip - and - prefix - in - CIDR>",
"gateway": "<ip - address - of - the - gateway>",
"interface": <numeric index into 'interfaces' list>
}
],
"routes": [
{
"dst": "<ip - and - prefix - in - cidr>",
"gw": "<ip - of - next - hop>"
}
],
"dns": {
"nameservers": <list - of - nameservers>,
"domain": <name - of - local - domain>,
"search": <list - of - additional - search - domains>,
"options": <list - of - options>
}
}
输入的网络配置包含很多信息,例如:
{
"cniVersion": "0.3.0",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"ipam": {
"type": "host - local",
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
可以添加额外的特定于插件的元素,在这个例子中,
bridge: cni0
元素是特定网桥插件能理解的自定义元素。
Kubernetes网络详解
超级会员免费看
950

被折叠的 条评论
为什么被折叠?



