这是本节的多页打印视图。
点击此处打印 .
返回本页常规视图 .
服务、负载均衡和联网
Kubernetes 网络背后的概念和资源。
Kubernetes 网络模型
集群中每一个 Pod
都会获得自己的、
独一无二的 IP 地址,
这就意味着你不需要显式地在 Pod
之间创建链接,你几乎不需要处理容器端口到主机端口之间的映射。
这将形成一个干净的、向后兼容的模型;在这个模型里,从端口分配、命名、服务发现、
负载均衡 、
应用配置和迁移的角度来看,Pod
可以被视作虚拟机或者物理主机。
Kubernetes 强制要求所有网络设施都满足以下基本要求(从而排除了有意隔离网络的策略):
Pod 能够与所有其他节点 上的 Pod 通信,
且不需要网络地址转译(NAT)
节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有 Pod 通信
说明:对于支持在主机网络中运行 Pod
的平台(比如:Linux),
当 Pod 挂接到节点的宿主网络上时,它们仍可以不通过 NAT 和所有节点上的 Pod 通信。
这个模型不仅不复杂,而且还和 Kubernetes 的实现从虚拟机向容器平滑迁移的初衷相符,
如果你的任务开始是在虚拟机中运行的,你的虚拟机有一个 IP,
可以和项目中其他虚拟机通信。这里的模型是基本相同的。
Kubernetes 的 IP 地址存在于 Pod
范围内 —— 容器共享它们的网络命名空间 ——
包括它们的 IP 地址和 MAC 地址。
这就意味着 Pod
内的容器都可以通过 localhost
到达对方端口。
这也意味着 Pod
内的容器需要相互协调端口的使用,但是这和虚拟机中的进程似乎没有什么不同,
这也被称为“一个 Pod 一个 IP”模型。
如何实现以上需求是所使用的特定容器运行时的细节。
也可以在 Node
本身请求端口,并用这类端口转发到你的 Pod
(称之为主机端口),
但这是一个很特殊的操作。转发方式如何实现也是容器运行时的细节。
Pod
自己并不知道这些主机端口的存在。
Kubernetes 网络解决四方面的问题:
使用 Service 连接到应用 教程通过一个实际的示例让你了解
Service 和 Kubernetes 如何联网。
集群网络 解释了如何为集群设置网络,
还概述了所涉及的技术。
1 - 服务(Service)
将在集群中运行的应用通过同一个面向外界的端点公开出去,即使工作负载分散于多个后端也完全可行。
Kubernetes 中 Service 是 将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的方法。
Kubernetes 中 Service 的一个关键目标是让你无需修改现有应用以使用某种不熟悉的服务发现机制。
你可以在 Pod 集合中运行代码,无论该代码是为云原生环境设计的,
还是被容器化的老应用。
你可以使用 Service 让一组 Pod 可在网络上访问,这样客户端就能与之交互。
如果你使用 Deployment 来运行你的应用,
Deployment 可以动态地创建和销毁 Pod。
在任何时刻,你都不知道有多少个这样的 Pod 正在工作以及它们健康与否;
你可能甚至不知道如何辨别健康的 Pod。
Kubernetes Pod 的创建和销毁是为了匹配集群的预期状态。
Pod 是临时资源(你不应该期待单个 Pod 既可靠又耐用)。
每个 Pod 会获得属于自己的 IP 地址(Kubernetes 期待网络插件来保证这一点)。
对于集群中给定的某个 Deployment,这一刻运行的 Pod 集合可能不同于下一刻运行该应用的
Pod 集合。
这就带来了一个问题:如果某组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)
集合提供功能,前端要如何发现并跟踪要连接的 IP 地址,以便其使用负载的后端组件呢?
Kubernetes 中的 Service
Service API 是 Kubernetes 的组成部分,它是一种抽象,帮助你将 Pod 集合在网络上公开出去。
每个 Service 对象定义端点的一个逻辑集合(通常这些端点就是 Pod)以及如何访问到这些 Pod 的策略。
例如,考虑一个无状态的图像处理后端,其中运行 3 个副本(Replicas)。
这些副本是可互换的 —— 前端不需要关心它们调用的是哪个后端。
即便构成后端集合的实际 Pod 可能会发生变化,前端客户端不应该也没必要知道这些,
而且它们也不必亲自跟踪后端的状态变化。
Service 抽象使这种解耦成为可能。
Service 所对应的 Pod 集合通常由你定义的选择算符 来确定。
若想了解定义 Service 端点的其他方式,可以查阅不带 选择算符的 Service 。
如果你的工作负载使用 HTTP 通信,你可能会选择使用
Ingress 来控制
Web 流量如何到达该工作负载。Ingress 不是一种 Service,但它可用作集群的入口点。
Ingress 能让你将路由规则整合到同一个资源内,这样你就能将工作负载的多个组件公开出去,
这些组件使用同一个侦听器,但各自独立地运行在集群中。
用于 Kubernetes 的 Gateway API
能够提供 Ingress 和 Service 所不具备的一些额外能力。
Gateway 是使用 CustomResourceDefinitions
实现的一系列扩展 API。
你可以添加 Gateway 到你的集群中,之后就可以使用它们配置如何访问集群中运行的网络服务。
云原生服务发现
如果你想要在自己的应用中使用 Kubernetes API 进行服务发现,可以查询
API 服务器 ,
寻找匹配的 EndpointSlice 对象。
只要 Service 中的 Pod 集合发生变化,Kubernetes 就会为其更新 EndpointSlice。
对于非本地应用,Kubernetes 提供了在应用和后端 Pod 之间放置网络端口或负载均衡器的方法。
无论采用那种方式,你的负载都可以使用这里的服务发现 机制找到希望连接的目标。
定义 Service
Kubernetes 中的 Service 是一个对象
(与 Pod 或 ConfigMap 类似)。你可以使用 Kubernetes API 创建、查看或修改 Service 定义。
通常你会使用 kubectl
这类工具来替你发起这些 API 调用。
例如,假定有一组 Pod,每个 Pod 都在侦听 TCP 端口 9376,并且它们还被打上
app.kubernetes.io/name=MyApp
标签。你可以定义一个 Service 来发布该 TCP 侦听器。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
应用上述清单时,系统将创建一个名为 "my-service" 的、
服务类型 默认为 ClusterIP 的 Service。
该 Service 指向带有标签 app.kubernetes.io/name: MyApp
的所有 Pod 的 TCP 端口 9376。
Kubernetes 为该服务分配一个 IP 地址(称为 “集群 IP”),供虚拟 IP 地址机制使用。
有关该机制的更多详情,请阅读虚拟 IP 和服务代理 。
此 Service 的控制器不断扫描与其选择算符匹配的 Pod 集合,然后对 Service 的
EndpointSlice 集合执行必要的更新。
Service 对象的名称必须是有效的
RFC 1035 标签名称 。
说明:
Service 能够将任意入站 port
映射到某个 targetPort
。
默认情况下,出于方便考虑,targetPort
会被设置为与 port
字段相同的值。
端口定义
Pod 中的端口定义是有名字的,你可以在 Service 的 targetPort
属性中引用这些名字。
例如,我们可以通过以下方式将 Service 的 targetPort
绑定到 Pod 端口:
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
app.kubernetes.io/name : proxy
spec :
containers :
- name : nginx
image : nginx:stable
ports :
- containerPort : 80
name : http-web-svc
---
apiVersion : v1
kind : Service
metadata :
name : nginx-service
spec :
selector :
app.kubernetes.io/name : proxy
ports :
- name : name-of-service-port
protocol : TCP
port : 80
targetPort : http-web-svc
即使在 Service 中混合使用配置名称相同的多个 Pod,各 Pod 通过不同的端口号支持相同的网络协议,
此机制也可以工作。这一机制为 Service 的部署和演化提供了较高的灵活性。
例如,你可以在后端软件的新版本中更改 Pod 公开的端口号,但不会影响到客户端。
Service 的默认协议是 TCP ;
你还可以使用其他受支持的任何协议 。
由于许多 Service 需要公开多个端口,所以 Kubernetes 为同一服务定义多个端口 。
每个端口定义可以具有相同的 protocol
,也可以具有不同协议。
没有选择算符的 Service
由于选择算符的存在,Service 的最常见用法是为 Kubernetes Pod 集合提供访问抽象,
但是当与相应的 EndpointSlice
对象一起使用且没有设置选择算符时,Service 也可以为其他类型的后端提供抽象,
包括在集群外运行的后端。
例如:
你希望在生产环境中使用外部数据库集群,但在测试环境中使用自己的数据库。
你希望让你的 Service 指向另一个名字空间(Namespace) 中或其它集群中的服务。
你正在将工作负载迁移到 Kubernetes 上来。在评估所采用的方法时,你仅在 Kubernetes
中运行一部分后端。
在所有这些场景中,你都可以定义不 指定用来匹配 Pod 的选择算符的 Service。例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9376
由于此 Service 没有选择算符,因此不会自动创建对应的 EndpointSlice(和旧版的 Endpoints)对象。
你可以通过手动添加 EndpointSlice 对象,将 Service 映射到该服务运行位置的网络地址和端口:
apiVersion : discovery.k8s.io/v1
kind : EndpointSlice
metadata :
name : my-service-1 # 按惯例将服务的名称用作 EndpointSlice 名称的前缀
labels :
# 你应设置 "kubernetes.io/service-name" 标签。
# 设置其值以匹配服务的名称
kubernetes.io/service-name : my-service
addressType : IPv4
ports :
- name : '' # 留空,因为 port 9376 未被 IANA 分配为已注册端口
appProtocol : http
protocol : TCP
port : 9376
endpoints : # 此列表中的 IP 地址可以按任何顺序显示
- addresses :
- "10.4.5.6"
- addresses :
- "10.1.2.3"
自定义 EndpointSlices
当为 Service 创建 EndpointSlice 对象时,可以为 EndpointSlice 使用任何名称。
一个名字空间中的各个 EndpointSlice 都必须具有一个唯一的名称。通过在 EndpointSlice 上设置
kubernetes.io/service-name
标签 可以将
EndpointSlice 链接到 Service。
说明:
端点 IP 地址必须不是 :本地回路地址(IPv4 的 127.0.0.0/8、IPv6 的 ::1/128)
或链路本地地址(IPv4 的 169.254.0.0/16 和 224.0.0.0/24、IPv6 的 fe80::/64)。
端点 IP 地址不能是其他 Kubernetes 服务的集群 IP,因为
kube-proxy 不支持将虚拟 IP 作为目标地址。
对于你自己或在你自己代码中创建的 EndpointSlice,你还应该为
endpointslice.kubernetes.io/managed-by
标签设置一个值。如果你创建自己的控制器代码来管理 EndpointSlice,
请考虑使用类似于 "my-domain.example/name-of-controller"
的值。
如果你使用的是第三方工具,请使用全小写的工具名称,并将空格和其他标点符号更改为短划线 (-
)。
如果直接使用 kubectl
之类的工具来管理 EndpointSlice 对象,请使用用来描述这种手动管理的名称,
例如 "staff"
或 "cluster-admins"
。你要避免使用保留值 "controller"
;
该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlice。
访问没有选择算符的 Service
访问没有选择算符的 Service 与有选择算符的 Service 的原理相同。
在没有选择算符的 Service 示例 中,
流量被路由到 EndpointSlice 清单中定义的两个端点之一:
通过 TCP 协议连接到 10.1.2.3 或 10.4.5.6 的端口 9376。
说明:
Kubernetes API 服务器不允许将流量代理到未被映射至 Pod 上的端点。由于此约束,当 Service
没有选择算符时,诸如 kubectl proxy <service-name>
之类的操作将会失败。这可以防止
Kubernetes API 服务器被用作调用者可能无权访问的端点的代理。
ExternalName
Service 是 Service 的特例,它没有选择算符,而是使用 DNS 名称。
更多的相关信息,请参阅 ExternalName 一节。
EndpointSlices
特性状态: Kubernetes v1.21 [stable]
EndpointSlice
对象表示某个服务的后端网络端点的子集(切片 )。
你的 Kubernetes 集群会跟踪每个 EndpointSlice 所表示的端点数量。
如果 Service 的端点太多以至于达到阈值,Kubernetes 会添加另一个空的
EndpointSlice 并在其中存储新的端点信息。
默认情况下,一旦现有 EndpointSlice 都包含至少 100 个端点,Kubernetes
就会创建一个新的 EndpointSlice。
在需要添加额外的端点之前,Kubernetes 不会创建新的 EndpointSlice。
参阅 EndpointSlice
了解有关该 API 的更多信息。
Endpoints
在 Kubernetes API 中,Endpoints
(该资源类别为复数形式)定义的是网络端点的列表,通常由 Service 引用,
以定义可以将流量发送到哪些 Pod。
推荐使用 EndpointSlice API 替换 Endpoints。
超出容量的端点
Kubernetes 限制单个 Endpoints 对象中可以容纳的端点数量。
当一个 Service 拥有 1000 个以上支撑端点时,Kubernetes 会截断 Endpoints 对象中的数据。
由于一个 Service 可以链接到多个 EndpointSlice 之上,所以 1000 个支撑端点的限制仅影响旧版的
Endpoints API。
如出现端点过多的情况,Kubernetes 选择最多 1000 个可能的后端端点存储到 Endpoints 对象中,
并在 Endpoints 上设置注解
endpoints.kubernetes.io/over-capacity: truncated
。
如果后端 Pod 的数量降至 1000 以下,控制平面也会移除该注解。
请求流量仍会被发送到后端,但任何依赖旧版 Endpoints API 的负载均衡机制最多只能将流量发送到
1000 个可用的支撑端点。
这一 API 限制也意味着你不能手动将 Endpoints 更新为拥有超过 1000 个端点。
应用协议
特性状态: Kubernetes v1.20 [stable]
appProtocol
字段提供了一种为每个 Service 端口设置应用协议的方式。
此字段被实现代码用作一种提示信息,以便针对实现能够理解的协议提供更为丰富的行为。
此字段的取值会被映射到对应的 Endpoints 和 EndpointSlice 对象中。
此字段遵循标准的 Kubernetes 标签语法。合法的取值值可以是以下之一:
IANA 标准服务名称 。
由具体实现所定义的、带有 mycompany.com/my-custom-protocol
这类前缀的名称。
Kubernetes 定义的前缀名称:
协议
描述
kubernetes.io/h2c
基于明文的 HTTP/2 协议,如 RFC 7540 所述
多端口 Service
对于某些 Service,你需要公开多个端口。Kubernetes 允许你为 Service 对象配置多个端口定义。
为 Service 使用多个端口时,必须为所有端口提供名称,以使它们无歧义。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- name : http
protocol : TCP
port : 80
targetPort : 9376
- name : https
protocol : TCP
port : 443
targetPort : 9377
说明:
与一般的 Kubernetes 名称一样,端口名称只能包含小写字母、数字和 -
。
端口名称还必须以字母或数字开头和结尾。
例如,名称 123-abc
和 web
是合法的,但是 123_abc
和 -web
不合法。
服务类型
对一些应用的某些部分(如前端),你可能希望将其公开于某外部 IP 地址,
也就是可以从集群外部访问的某个地址。
Kubernetes Service 类型允许指定你所需要的 Service 类型。
可用的 type
值及其行为有:
ClusterIP
通过集群的内部 IP 公开 Service,选择该值时 Service 只能够在集群内部访问。
这也是你没有为服务显式指定 type
时使用的默认值。
你可以使用 Ingress
或者 Gateway API 向公共互联网公开服务。
NodePort
通过每个节点上的 IP 和静态端口(NodePort
)公开 Service。
为了让 Service 可通过节点端口访问,Kubernetes 会为 Service 配置集群 IP 地址,
相当于你请求了 type: ClusterIP
的服务。
LoadBalancer
使用云平台的负载均衡器向外部公开 Service。Kubernetes 不直接提供负载均衡组件;
你必须提供一个,或者将你的 Kubernetes 集群与某个云平台集成。
ExternalName
将服务映射到 externalName
字段的内容(例如,映射到主机名 api.foo.bar.example
)。
该映射将集群的 DNS 服务器配置为返回具有该外部主机名值的 CNAME
记录。
集群不会为之创建任何类型代理。
服务 API 中的 type
字段被设计为层层递进的形式 - 每层都建立在前一层的基础上。
但是,这种层层递进的形式有一个例外。
你可以在定义 LoadBalancer
服务时禁止负载均衡器分配 NodePort
。
type: ClusterIP
此默认 Service 类型从你的集群中为此预留的 IP 地址池中分配一个 IP 地址。
其他几种 Service 类型在 ClusterIP
类型的基础上进行构建。
如果你定义的 Service 将 .spec.clusterIP
设置为 "None"
,则 Kubernetes
不会为其分配 IP 地址。有关详细信息,请参阅无头服务 。
选择自己的 IP 地址
在创建 Service
的请求中,你可以通过设置 spec.clusterIP
字段来指定自己的集群 IP 地址。
比如,希望复用一个已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。
你所选择的 IP 地址必须是合法的 IPv4 或者 IPv6 地址,并且这个 IP 地址在 API 服务器上所配置的
service-cluster-ip-range
CIDR 范围内。
如果你尝试创建一个带有非法 clusterIP
地址值的 Service,API 服务器会返回 HTTP 状态码 422,
表示值不合法。
请阅读避免冲突 节,
以了解 Kubernetes 如何协助降低两个不同的 Service 试图使用相同 IP 地址的风险和影响。
type: NodePort
如果你将 type
字段设置为 NodePort
,则 Kubernetes 控制平面将在
--service-node-port-range
标志所指定的范围内分配端口(默认值:30000-32767)。
每个节点将该端口(每个节点上的相同端口号)上的流量代理到你的 Service。
你的 Service 在其 .spec.ports[*].nodePort
字段中报告已分配的端口。
使用 NodePort 可以让你自由设置自己的负载均衡解决方案,
配置 Kubernetes 不完全支持的环境,
甚至直接公开一个或多个节点的 IP 地址。
对于 NodePort 服务,Kubernetes 额外分配一个端口(TCP、UDP 或 SCTP 以匹配 Service 的协议)。
集群中的每个节点都将自己配置为监听所分配的端口,并将流量转发到与该 Service 关联的某个就绪端点。
通过使用合适的协议(例如 TCP)和适当的端口(分配给该 Service)连接到任何一个节点,
你就能够从集群外部访问 type: NodePort
服务。
选择你自己的端口
如果需要特定的端口号,你可以在 nodePort
字段中指定一个值。
控制平面将或者为你分配该端口,或者报告 API 事务失败。
这意味着你需要自行注意可能发生的端口冲突。
你还必须使用有效的端口号,该端口号在配置用于 NodePort 的范围内。
以下是 type: NodePort
服务的一个清单示例,其中指定了 NodePort 值(在本例中为 30007):
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
type : NodePort
selector :
app.kubernetes.io/name : MyApp
ports :
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port : 80
targetPort : 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号
#(默认:30000-32767)
nodePort : 30007
预留 NodePort 端口范围以避免发生冲突
特性状态: Kubernetes v1.28 [beta]
为 NodePort 服务分配端口的策略既适用于自动分配的情况,也适用于手动分配的场景。
当某个用于希望创建一个使用特定端口的 NodePort 服务时,该目标端口可能与另一个已经被分配的端口冲突。
这时,你可以启用特性门控 ServiceNodePortStaticSubrange
,进而为 NodePort Service
使用不同的端口分配策略。用于 NodePort 服务的端口范围被分为两段。
动态端口分配默认使用较高的端口段,并且在较高的端口段耗尽时也可以使用较低的端口段。
用户可以从较低端口段中分配端口,降低端口冲突的风险。
为 type: NodePort
服务自定义 IP 地址配置
你可以配置集群中的节点使用特定 IP 地址来支持 NodePort 服务。
如果每个节点都连接到多个网络(例如:一个网络用于应用流量,另一网络用于节点和控制平面之间的流量),
你可能想要这样做。
如果你要指定特定的 IP 地址来为端口提供代理,可以将 kube-proxy 的 --nodeport-addresses
标志或
kube-proxy 配置文件 中的等效字段
nodePortAddresses
设置为特定的 IP 段。
此标志接受逗号分隔的 IP 段列表(例如 10.0.0.0/8
、192.0.2.0/25
),用来设置 IP 地址范围。
kube-proxy 应视将其视为所在节点的本机地址。
例如,如果你使用 --nodeport-addresses=127.0.0.0/8
标志启动 kube-proxy,
则 kube-proxy 仅选择 NodePort 服务的本地回路接口。
--nodeport-addresses
的默认值是一个空的列表。
这意味着 kube-proxy 将认为所有可用网络接口都可用于 NodePort 服务
(这也与早期的 Kubernetes 版本兼容。)
说明:
此 Service 的可见形式为 <NodeIP>:spec.ports[*].nodePort
以及 .spec.clusterIP:spec.ports[*].port
。
如果设置了 kube-proxy 的 --nodeport-addresses
标志或 kube-proxy 配置文件中的等效字段,
则 <NodeIP>
将是一个被过滤的节点 IP 地址(或可能是多个 IP 地址)。
type: LoadBalancer
在使用支持外部负载均衡器的云平台时,如果将 type
设置为 "LoadBalancer"
,
则平台会为 Service 提供负载均衡器。
负载均衡器的实际创建过程是异步进行的,关于所制备的负载均衡器的信息将会通过 Service 的
status.loadBalancer
字段公开出来。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
clusterIP : 10.0.171.239
type : LoadBalancer
status :
loadBalancer :
ingress :
- ip : 192.0.2.127
来自外部负载均衡器的流量将被直接重定向到后端各个 Pod 上,云平台决定如何进行负载平衡。
要实现 type: LoadBalancer
的服务,Kubernetes 通常首先进行与请求 type: NodePort
服务类似的更改。cloud-controller-manager 组件随后配置外部负载均衡器,
以将流量转发到所分配的节点端口。
你可以将负载均衡 Service 配置为忽略 分配节点端口,
前提是云平台实现支持这点。
某些云平台允许你设置 loadBalancerIP
。这时,平台将使用用户指定的 loadBalancerIP
来创建负载均衡器。如果没有设置 loadBalancerIP
字段,平台将会给负载均衡器分配一个临时 IP。
如果设置了 loadBalancerIP
,但云平台并不支持这一特性,所设置的 loadBalancerIP
值将会被忽略。
说明:
针对 Service 的 .spec.loadBalancerIP
字段已在 Kubernetes v1.24 中被弃用。
此字段的定义模糊,其含义因实现而异。它也不支持双协议栈联网。
此字段可能会在未来的 API 版本中被移除。
如果你正在集成某云平台,该平台通过(特定于平台的)注解为 Service 指定负载均衡器 IP 地址,
你应该切换到这种做法。
如果你正在为集成到 Kubernetes 的负载均衡器编写代码,请避免使用此字段。
你可以与 Gateway 而不是 Service 集成,
或者你可以在 Service 上定义自己的(特定于提供商的)注解,以指定等效的细节。
混合协议类型的负载均衡器
特性状态: Kubernetes v1.26 [stable]
默认情况下,对于 LoadBalancer 类型的 Service,当其中定义了多个端口时,
所有端口必须使用相同的协议,并且该协议必须是被云平台支持的。
当服务中定义了多个端口时,特性门控 MixedProtocolLBService
(从 kube-apiserver 1.24
版本起默认为启用)允许 LoadBalancer 类型的服务使用不同的协议。
说明:
可用于负载均衡服务的协议集合由你的云平台决定,他们可能在
Kubernetes API 强制执行的限制之外另加一些约束。
禁用负载均衡服务的节点端口分配
特性状态: Kubernetes v1.24 [stable]
通过设置 Service 的 spec.allocateLoadBalancerNodePorts
为 false
,你可以对 LoadBalancer
类型的 Service 禁用节点端口分配操作。
这仅适用于负载均衡器的实现能够直接将流量路由到 Pod 而不是使用节点端口的情况。
默认情况下,spec.allocateLoadBalancerNodePorts
为 true
,LoadBalancer 类型的 Service
也会继续分配节点端口。如果某已有 Service 已被分配节点端口,如果将其属性
spec.allocateLoadBalancerNodePorts
设置为 false
,这些节点端口不会 被自动释放。
你必须显式地在每个 Service 端口中删除 nodePorts
项以释放对应的端口。
设置负载均衡器实现的类别
特性状态: Kubernetes v1.24 [stable]
对于 type
设置为 LoadBalancer
的 Service,spec.loadBalancerClass
字段允许你使用有别于云平台的默认负载均衡器的实现。
默认情况下,.spec.loadBalancerClass
是未设置的,如果集群使用 --cloud-provider
件标志配置了云平台,LoadBalancer
类型 Service 会使用云平台的默认负载均衡器实现。
如果你设置了 .spec.loadBalancerClass
,则假定存在某个与所指定的类相匹配的负载均衡器实现在监视
Service 变更。所有默认的负载均衡器实现(例如,由云平台所提供的)都会忽略设置了此字段的 Service。
.spec.loadBalancerClass
只能设置到类型为 LoadBalancer
的 Service 之上,
而且一旦设置之后不可变更。
.spec.loadBalancerClass
的值必须是一个标签风格的标识符,
可以有选择地带有类似 "internal-vip
" 或 "example.com/internal-vip
" 这类前缀。
没有前缀的名字是保留给最终用户的。
内部负载均衡器
在混合环境中,有时有必要在同一(虚拟)网络地址段内路由来自 Service 的流量。
在水平分割(Split-Horizon) DNS 环境中,你需要两个 Service 才能将内部和外部流量都路由到你的端点。
如要设置内部负载均衡器,请根据你所使用的云平台,为 Service 添加以下注解之一:
metadata :
name : my-service
annotations :
networking.gke.io/load-balancer-type : "Internal"
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-internal : "true"
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/azure-load-balancer-internal : "true"
metadata :
name : my-service
annotations :
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type : "private"
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/openstack-internal-load-balancer : "true"
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/cce-load-balancer-internal-vpc : "true"
metadata :
annotations :
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid : subnet-xxxxx
metadata :
annotations :
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type : "intranet"
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/oci-load-balancer-internal : true
ExternalName 类型
类型为 ExternalName 的 Service 将 Service 映射到 DNS 名称,而不是典型的选择算符,
例如 my-service
或者 cassandra
。你可以使用 spec.externalName
参数指定这些服务。
例如,以下 Service 定义将 prod
名字空间中的 my-service
服务映射到 my.database.example.com
:
apiVersion : v1
kind : Service
metadata :
name : my-service
namespace : prod
spec :
type : ExternalName
externalName : my.database.example.com
说明:
type: ExternalName
的服务接受 IPv4 地址字符串,但将该字符串视为由数字组成的 DNS 名称,
而不是 IP 地址(然而,互联网不允许在 DNS 中使用此类名称)。
类似于 IPv4 地址的外部名称无法被 DNS 服务器解析。
如果你想要将服务直接映射到某特定 IP 地址,请考虑使用无头服务 。
当查找主机 my-service.prod.svc.cluster.local
时,集群 DNS 服务返回 CNAME
记录,
其值为 my.database.example.com
。访问 my-service
的方式与访问其他 Service 的方式相同,
主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发来完成。
如果后来你决定将数据库移到集群中,则可以启动其 Pod,添加适当的选择算符或端点并更改
Service 的 type
。
注意:
针对 ExternalName 服务使用一些常见的协议,包括 HTTP 和 HTTPS,可能会有问题。
如果你使用 ExternalName 服务,那么集群内客户端使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,这一差异可能会导致错误或意外响应。
HTTP 请求将具有源服务器无法识别的 Host:
标头;
TLS 服务器将无法提供与客户端连接的主机名匹配的证书。
无头服务(Headless Services)
有时你并不需要负载均衡,也不需要单独的 Service IP。遇到这种情况,可以通过显式设置
集群 IP(spec.clusterIP
)的值为 "None"
来创建无头服务(Headless Service) 。
你可以使用无头 Service 与其他服务发现机制交互,而不必绑定到 Kubernetes 的实现。
无头 Service 不会获得集群 IP,kube-proxy 不会处理这类 Service,
而且平台也不会为它们提供负载均衡或路由支持。
取决于 Service 是否定义了选择算符,DNS 会以不同的方式被自动配置。
带选择算符的服务
对定义了选择算符的无头 Service,Kubernetes 控制平面在 Kubernetes API 中创建
EndpointSlice 对象,并且修改 DNS 配置返回 A 或 AAAA 记录(IPv4 或 IPv6 地址),
这些记录直接指向 Service 的后端 Pod 集合。
无选择算符的服务
对没有定义选择算符的无头 Service,控制平面不会创建 EndpointSlice 对象。
然而 DNS 系统会执行以下操作之一:
对于 type: ExternalName
Service,查找和配置其 CNAME 记录;
对所有其他类型的 Service,针对 Service 的就绪端点的所有 IP 地址,查找和配置
DNS A / AAAA 记录:
对于 IPv4 端点,DNS 系统创建 A 记录。
对于 IPv6 端点,DNS 系统创建 AAAA 记录。
当你定义无选择算符的无头 Service 时,port
必须与 targetPort
匹配。
服务发现
对于在集群内运行的客户端,Kubernetes 支持两种主要的服务发现模式:环境变量和 DNS。
环境变量
当 Pod 运行在某 Node 上时,kubelet 会在其中为每个活跃的 Service 添加一组环境变量。
kubelet 会添加环境变量 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
。
这里 Service 的名称被转为大写字母,横线被转换成下划线。
它还支持与 Docker Engine 的 "legacy container links "
特性兼容的变量
(参阅 makeLinkVariables ) 。
例如,一个 Service redis-primary
公开了 TCP 端口 6379,
同时被分配了集群 IP 地址 10.0.0.11,这个 Service 生成的环境变量如下:
REDIS_PRIMARY_SERVICE_HOST = 10.0.0.11
REDIS_PRIMARY_SERVICE_PORT = 6379
REDIS_PRIMARY_PORT = tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP = tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP_PROTO = tcp
REDIS_PRIMARY_PORT_6379_TCP_PORT = 6379
REDIS_PRIMARY_PORT_6379_TCP_ADDR = 10.0.0.11
说明:
当你的 Pod 需要访问某 Service,并且你在使用环境变量方法将端口和集群 IP 发布到客户端
Pod 时,必须在客户端 Pod 出现之前 创建该 Service。
否则,这些客户端 Pod 中将不会出现对应的环境变量。
如果仅使用 DNS 来发现 Service 的集群 IP,则无需担心此顺序问题。
Kubernetes 还支持并提供与 Docker Engine 的
"legacy container links "
兼容的变量。
你可以阅读 makeLinkVariables
来了解这是如何在 Kubernetes 中实现的。
DNS
你可以(并且几乎总是应该)使用插件(add-on)
来为 Kubernetes 集群安装 DNS 服务。
能够感知集群的 DNS 服务器(例如 CoreDNS)会监视 Kubernetes API 中的新 Service,
并为每个 Service 创建一组 DNS 记录。如果在整个集群中都启用了 DNS,则所有 Pod
都应该能够通过 DNS 名称自动解析 Service。
例如,如果你在 Kubernetes 命名空间 my-ns
中有一个名为 my-service
的 Service,
则控制平面和 DNS 服务共同为 my-service.my-ns
生成 DNS 记录。
名字空间 my-ns
中的 Pod 应该能够通过按名检索 my-service
来找到服务
(my-service.my-ns
也可以)。
其他名字空间中的 Pod 必须将名称限定为 my-service.my-ns
。
这些名称将解析为分配给 Service 的集群 IP。
Kubernetes 还支持命名端口的 DNS SRV(Service)记录。
如果 Service my-service.my-ns
具有名为 http
的端口,且协议设置为 TCP,
则可以用 _http._tcp.my-service.my-ns
执行 DNS SRV 查询以发现 http
的端口号以及 IP 地址。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName
类型的 Service 的方式。
关于 ExternalName
解析的更多信息可以查看
Service 与 Pod 的 DNS 。
虚拟 IP 寻址机制
阅读虚拟 IP 和 Service 代理 以了解
Kubernetes 提供的使用虚拟 IP 地址公开服务的机制。
流量策略
你可以设置 .spec.internalTrafficPolicy
和 .spec.externalTrafficPolicy
字段来控制 Kubernetes 如何将流量路由到健康(“就绪”)的后端。
有关详细信息,请参阅流量策略 。
会话的黏性
如果你想确保来自特定客户端的连接每次都传递到同一个 Pod,你可以配置基于客户端 IP
地址的会话亲和性。可阅读会话亲和性
来进一步学习。
外部 IP
如果有外部 IP 能够路由到一个或多个集群节点上,则 Kubernetes Service 可以在这些 externalIPs
上公开出去。当网络流量进入集群时,如果外部 IP(作为目的 IP 地址)和端口都与该 Service 匹配,
Kubernetes 所配置的规则和路由会确保流量被路由到该 Service 的端点之一。
定义 Service 时,你可以为任何服务类型 指定 externalIPs
。
在下面的例子中,名为 my-service
的服务可以在 "198.51.100.32:80
"
(根据 .spec.externalIPs[]
和 .spec.ports[].port
得出)上被客户端使用 TCP 协议访问。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- name : http
protocol : TCP
port : 80
targetPort : 49152
externalIPs :
- 198.51.100.32
说明:
Kubernetes 不负责管理 externalIPs
的分配,这一工作是集群管理员的职责。
API 对象
Service 是 Kubernetes REST API 中的顶级资源。你可以找到有关
Service 对象 API
的更多详细信息。
接下来
进一步学习 Service 及其在 Kubernetes 中所发挥的作用:
更多上下文,可以阅读以下内容:
2 - Ingress
使用一种能感知协议配置的机制来解析 URI、主机名称、路径等 Web 概念, 让你的 HTTP(或 HTTPS)网络服务可被访问。 Ingress 概念允许你通过 Kubernetes API 定义的规则将流量映射到不同后端。
特性状态: Kubernetes v1.19 [stable]
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
说明:
入口(Ingress)目前已停止更新。新的功能正在集成至网关 API 中。
术语
为了表达更加清晰,本指南定义以下术语:
节点(Node): Kubernetes 集群中的一台工作机器,是集群的一部分。
集群(Cluster): 一组运行容器化应用程序的 Node,这些应用由 Kubernetes 管理。
在此示例和在大多数常见的 Kubernetes 部署环境中,集群中的节点都不在公共网络中。
边缘路由器(Edge Router): 在集群中强制执行防火墙策略的路由器。
可以是由云提供商管理的网关,也可以是物理硬件。
集群网络(Cluster Network): 一组逻辑的或物理的连接,基于 Kubernetes
网络模型 实现集群内的通信。
服务(Service):Kubernetes 服务(Service) ,
使用标签 选择算符(Selectors)
来选择一组 Pod。除非另作说明,否则假定 Service 具有只能在集群网络内路由的虚拟 IP。
Ingress 是什么?
Ingress
提供从集群外部到集群内服务 的
HTTP 和 HTTPS 路由。
流量路由由 Ingress 资源所定义的规则来控制。
下面是 Ingress 的一个简单示例,可将所有流量都发送到同一 Service:
图. Ingress
通过配置,Ingress 可为 Service 提供外部可访问的 URL、对其流量作负载均衡、
终止 SSL/TLS,以及基于名称的虚拟托管等能力。
Ingress 控制器
负责完成 Ingress 的工作,具体实现上通常会使用某个负载均衡器,
不过也可以配置边缘路由器或其他前端来帮助处理流量。
Ingress 不会随意公开端口或协议。
将 HTTP 和 HTTPS 以外的服务开放到 Internet 时,通常使用
Service.Type=NodePort
或 Service.Type=LoadBalancer
类型的 Service。
环境准备
你必须拥有一个 Ingress 控制器 才能满足
Ingress 的要求。仅创建 Ingress 资源本身没有任何效果。
你可能需要部署一个 Ingress 控制器,例如 ingress-nginx 。
你可以从许多 Ingress 控制器 中进行选择。
理想情况下,所有 Ingress 控制器都应遵从参考规范。
但实际上,各个 Ingress 控制器操作略有不同。
说明:
确保你查看了 Ingress 控制器的文档,以了解选择它的注意事项。
Ingress 资源
一个最小的 Ingress 资源示例:
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : minimal-ingress
annotations :
nginx.ingress.kubernetes.io/rewrite-target : /
spec :
ingressClassName : nginx-example
rules :
- http :
paths :
- path : /testpath
pathType : Prefix
backend :
service :
name : test
port :
number : 80
Ingress 需要指定 apiVersion
、kind
、 metadata
和 spec
字段。
Ingress 对象的命名必须是合法的 DNS 子域名名称 。
关于如何使用配置文件的一般性信息,请参见部署应用 、
配置容器 、
管理资源 。
Ingress 经常使用注解(Annotations)来配置一些选项,具体取决于 Ingress 控制器,
例如 rewrite-target 注解 。
不同的 Ingress 控制器 支持不同的注解。
查看你所选的 Ingress 控制器的文档,以了解其所支持的注解。
Ingress 规约
提供了配置负载均衡器或者代理服务器所需要的所有信息。
最重要的是,其中包含对所有入站请求进行匹配的规则列表。
Ingress 资源仅支持用于转发 HTTP(S) 流量的规则。
如果 ingressClassName
被省略,那么你应该定义一个默认的 Ingress 类 。
有些 Ingress 控制器不需要定义默认的 IngressClass
。比如:Ingress-NGINX
控制器可以通过参数
--watch-ingress-without-class
来配置。
不过仍然推荐
按下文 所示来设置默认的 IngressClass
。
Ingress 规则
每个 HTTP 规则都包含以下信息:
可选的 host
。在此示例中,未指定 host
,因此该规则基于所指定 IP 地址来匹配所有入站 HTTP 流量。
如果提供了 host
(例如 foo.bar.com
),则 rules
适用于所指定的主机。
路径列表(例如 /testpath
)。每个路径都有一个由 service.name
和 service.port.name
或 service.port.number
确定的关联后端。
主机和路径都必须与入站请求的内容相匹配,负载均衡器才会将流量引导到所引用的 Service,
backend
(后端)是 Service 文档 中所述的 Service 和端口名称的组合,
或者是通过 CRD
方式来实现的自定义资源后端 。
对于发往 Ingress 的 HTTP(和 HTTPS)请求,如果与规则中的主机和路径匹配,
则会被发送到所列出的后端。
通常会在 Ingress 控制器中配置 defaultBackend
(默认后端),
以便为无法与规约中任何路径匹配的所有请求提供服务。
默认后端
没有设置规则的 Ingress 将所有流量发送到同一个默认后端,而在这种情况下
.spec.defaultBackend
则是负责处理请求的那个默认后端。
defaultBackend
通常是
Ingress 控制器 的配置选项,
而非在 Ingress 资源中设置。
如果未设置 .spec.rules
,则必须设置 .spec.defaultBackend
。
如果未设置 defaultBackend
,那么如何处理与所有规则都不匹配的流量将交由
Ingress 控制器决定(请参考你的 Ingress 控制器的文档以了解它是如何处理这种情况的)。
如果 Ingress 对象中主机和路径都没有与 HTTP 请求匹配,则流量将被路由到默认后端。
资源后端
Resource
后端是一个 ObjectRef 对象,指向同一名字空间中的另一个 Kubernetes 资源,
将其视为 Ingress 对象。
Resource
后端与 Service 后端是互斥的,在二者均被设置时会无法通过合法性检查。
Resource
后端的一种常见用法是将所有入站数据导向保存静态资产的对象存储后端。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : ingress-resource-backend
spec :
defaultBackend :
resource :
apiGroup : k8s.example.com
kind : StorageBucket
name : static-assets
rules :
- http :
paths :
- path : /icons
pathType : ImplementationSpecific
backend :
resource :
apiGroup : k8s.example.com
kind : StorageBucket
name : icon-assets
创建了如上的 Ingress 之后,你可以使用下面的命令查看它:
kubectl describe ingress ingress-resource-backend
Name: ingress-resource-backend
Namespace: default
Address:
Default backend: APIGroup: k8s.example.com, Kind: StorageBucket, Name: static-assets
Rules:
Host Path Backends
---- ---- --------
*
/icons APIGroup: k8s.example.com, Kind: StorageBucket, Name: icon-assets
Annotations: <none>
Events: <none>
路径类型
Ingress 中的每个路径都需要有对应的路径类型(Path Type)。未明确设置 pathType
的路径无法通过合法性检查。当前支持的路径类型有三种:
ImplementationSpecific
:对于这种路径类型,匹配方法取决于 IngressClass。
具体实现可以将其作为单独的 pathType
处理或者作与 Prefix
或 Exact
类型相同的处理。
Exact
:精确匹配 URL 路径,且区分大小写。
Prefix
:基于以 /
分隔的 URL 路径前缀匹配。匹配区分大小写,
并且对路径中各个元素逐个执行匹配操作。
路径元素指的是由 /
分隔符分隔的路径中的标签列表。
如果每个 p 都是请求路径 p 的元素前缀,则请求与路径 p 匹配。
说明: 如果路径的最后一个元素是请求路径中最后一个元素的子字符串,则不会被视为匹配
(例如:/foo/bar
匹配 /foo/bar/baz
, 但不匹配 /foo/barbaz
)。
示例
类型
路径
请求路径
匹配与否?
Prefix
/
(所有路径)
是
Exact
/foo
/foo
是
Exact
/foo
/bar
否
Exact
/foo
/foo/
否
Exact
/foo/
/foo
否
Prefix
/foo
/foo
, /foo/
是
Prefix
/foo/
/foo
, /foo/
是
Prefix
/aaa/bb
/aaa/bbb
否
Prefix
/aaa/bbb
/aaa/bbb
是
Prefix
/aaa/bbb/
/aaa/bbb
是,忽略尾部斜线
Prefix
/aaa/bbb
/aaa/bbb/
是,匹配尾部斜线
Prefix
/aaa/bbb
/aaa/bbb/ccc
是,匹配子路径
Prefix
/aaa/bbb
/aaa/bbbxyz
否,字符串前缀不匹配
Prefix
/
, /aaa
/aaa/ccc
是,匹配 /aaa
前缀
Prefix
/
, /aaa
, /aaa/bbb
/aaa/bbb
是,匹配 /aaa/bbb
前缀
Prefix
/
, /aaa
, /aaa/bbb
/ccc
是,匹配 /
前缀
Prefix
/aaa
/ccc
否,使用默认后端
混合
/foo
(Prefix), /foo
(Exact)
/foo
是,优选 Exact 类型
多重匹配
在某些情况下,Ingress 中会有多条路径与同一个请求匹配。这时匹配路径最长者优先。
如果仍然有两条同等的匹配路径,则精确路径类型优先于前缀路径类型。
主机名通配符
主机名可以是精确匹配(例如 “foo.bar.com
”)或者使用通配符来匹配
(例如 “*.foo.com
”)。
精确匹配要求 HTTP host
头部字段与 host
字段值完全匹配。
通配符匹配则要求 HTTP host
头部字段与通配符规则中的后缀部分相同。
主机
host 头部
匹配与否?
*.foo.com
bar.foo.com
基于相同的后缀匹配
*.foo.com
baz.bar.foo.com
不匹配,通配符仅覆盖了一个 DNS 标签
*.foo.com
foo.com
不匹配,通配符仅覆盖了一个 DNS 标签
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : ingress-wildcard-host
spec :
rules :
- host : "foo.bar.com"
http :
paths :
- pathType : Prefix
path : "/bar"
backend :
service :
name : service1
port :
number : 80
- host : "*.foo.com"
http :
paths :
- pathType : Prefix
path : "/foo"
backend :
service :
name : service2
port :
number : 80
Ingress 类
Ingress 可以由不同的控制器实现,通常使用不同的配置。
每个 Ingress 应当指定一个类,也就是一个对 IngressClass 资源的引用。
IngressClass 资源包含额外的配置,其中包括应当实现该类的控制器名称。
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
name : external-lb
spec :
controller : example.com/ingress-controller
parameters :
apiGroup : k8s.example.com
kind : IngressParameters
name : external-lb
IngressClass 中的 .spec.parameters
字段可用于引用其他资源以提供与该
IngressClass 相关的配置。
参数(parameters
)的具体类型取决于你在 IngressClass 的 .spec.controller
字段中指定的 Ingress 控制器。
IngressClass 的作用域
取决于你所使用的 Ingress 控制器,你可能可以使用集群作用域的参数或某个名字空间作用域的参数。
IngressClass 参数的默认作用域是集群范围。
如果你设置了 .spec.parameters
字段且未设置 .spec.parameters.scope
字段,或是将 .spec.parameters.scope
字段设为了 Cluster
,
那么该 IngressClass 所引用的即是一个集群作用域的资源。
参数的 kind
(和 apiGroup
一起)指向一个集群作用域的 API 类型
(可能是一个定制资源(Custom Resource)),而其 name
字段则进一步确定
该 API 类型的一个具体的、集群作用域的资源。
示例:
---
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
name : external-lb-1
spec :
controller : example.com/ingress-controller
parameters :
# 此 IngressClass 的配置定义在一个名为 “external-config-1” 的
# ClusterIngressParameter(API 组为 k8s.example.net)资源中。
# 这项定义告诉 Kubernetes 去寻找一个集群作用域的参数资源。
scope : Cluster
apiGroup : k8s.example.net
kind : ClusterIngressParameter
name : external-config-1
特性状态: Kubernetes v1.23 [stable]
如果你设置了 .spec.parameters
字段且将 .spec.parameters.scope
字段设为了 Namespace
,那么该 IngressClass 将会引用一个名字空间作用域的资源。
.spec.parameters.namespace
必须和此资源所处的名字空间相同。
参数的 kind
(和 apiGroup
一起)指向一个命名空间作用域的 API 类型
(例如:ConfigMap),而其 name
则进一步确定指定 API 类型的、
位于你指定的命名空间中的具体资源。
名字空间作用域的参数帮助集群操作者将对工作负载所需的配置数据(比如:负载均衡设置、
API 网关定义)的控制权力委派出去。如果你使用集群作用域的参数,那么你将面临一下情况之一:
每次应用一项新的配置变更时,集群操作团队需要批准其他团队所作的修改。
集群操作团队必须定义具体的准入控制规则,比如 RBAC
角色与角色绑定,以使得应用程序团队可以修改集群作用域的配置参数资源。
IngressClass API 本身是集群作用域的。
这里是一个引用名字空间作用域配置参数的 IngressClass 的示例:
---
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
name : external-lb-2
spec :
controller : example.com/ingress-controller
parameters :
# 此 IngressClass 的配置定义在一个名为 “external-config” 的
# IngressParameter(API 组为 k8s.example.com)资源中,
# 该资源位于 “external-configuration” 名字空间中。
scope : Namespace
apiGroup : k8s.example.com
kind : IngressParameter
namespace : external-configuration
name : external-config
已废弃的注解
在 Kubernetes 1.18 版本引入 IngressClass 资源和 ingressClassName
字段之前,
Ingress 类是通过 Ingress 中的一个 kubernetes.io/ingress.class
注解来指定的。
这个注解从未被正式定义过,但是得到了 Ingress 控制器的广泛支持。
Ingress 中新的 ingressClassName
字段用来替代该注解,但并非完全等价。
注解通常用于引用实现该 Ingress 的控制器的名称,而这个新的字段则是对一个包含额外
Ingress 配置的 IngressClass 资源的引用,其中包括了 Ingress 控制器的名称。
默认 Ingress 类
你可以将一个特定的 IngressClass 标记为集群默认 Ingress 类。
将某个 IngressClass 资源的 ingressclass.kubernetes.io/is-default-class
注解设置为
true
将确保新的未指定 ingressClassName
字段的 Ingress 能够被赋予这一默认
IngressClass.
注意:
如果集群中有多个 IngressClass 被标记为默认,准入控制器将阻止创建新的未指定
ingressClassName
的 Ingress 对象。
解决这个问题需要确保集群中最多只能有一个 IngressClass 被标记为默认。
有一些 Ingress 控制器不需要定义默认的 IngressClass
。比如:Ingress-NGINX
控制器可以通过参数
--watch-ingress-without-class
来配置。
不过仍然推荐
设置默认的 IngressClass
。
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
labels :
app.kubernetes.io/component : controller
name : nginx-example
annotations :
ingressclass.kubernetes.io/is-default-class : "true"
spec :
controller : k8s.io/ingress-nginx
Ingress 类型
由单个 Service 来支持的 Ingress
现有的 Kubernetes 概念允许你暴露单个 Service(参见替代方案 )。
你也可以使用 Ingress 并设置无规则的默认后端 来完成这类操作。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : test-ingress
spec :
defaultBackend :
service :
name : test
port :
number : 80
如果使用 kubectl apply -f
创建此 Ingress,则应该能够查看刚刚添加的 Ingress 的状态:
kubectl get ingress test-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
test-ingress external-lb * 203.0.113.123 80 59s
其中 203.0.113.123
是由 Ingress 控制器分配的 IP,用以服务于此 Ingress。
说明:
Ingress 控制器和负载平衡器的 IP 地址分配操作可能需要一两分钟。
在此之前,你通常会看到地址字段的取值为 <pending>
。
简单扇出
一个扇出(Fanout)配置根据请求的 HTTP URI 将来自同一 IP 地址的流量路由到多个 Service。
Ingress 允许你将负载均衡器的数量降至最低。例如,这样的设置:
图. Ingress 扇出
这将需要一个如下所示的 Ingress:
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : simple-fanout-example
spec :
rules :
- host : foo.bar.com
http :
paths :
- path : /foo
pathType : Prefix
backend :
service :
name : service1
port :
number : 4200
- path : /bar
pathType : Prefix
backend :
service :
name : service2
port :
number : 8080
当你使用 kubectl apply -f
创建 Ingress 时:
kubectl describe ingress simple-fanout-example
Name: simple-fanout-example
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:4200 (10.8.0.90:4200)
/bar service2:8080 (10.8.0.91:8080)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 22s loadbalancer-controller default/test
此 Ingress 控制器构造一个特定于实现的负载均衡器来供 Ingress 使用,
前提是 Service (service1
、service2
)存在。
当它完成负载均衡器的创建时,你会在 Address 字段看到负载均衡器的地址。
基于名称的虚拟主机服务
基于名称的虚拟主机支持将针对多个主机名的 HTTP 流量路由到同一 IP 地址上。
图. 基于名称实现虚拟托管的 Ingress
以下 Ingress 让后台负载均衡器基于
host 头部字段 来路由请求。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : name-virtual-host-ingress
spec :
rules :
- host : foo.bar.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service1
port :
number : 80
- host : bar.foo.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service2
port :
number : 80
如果你所创建的 Ingress 资源没有在 rules
中定义主机,则规则可以匹配指向
Ingress 控制器 IP 地址的所有网络流量,而无需基于名称的虚拟主机。
例如,下面的 Ingress 对象会将请求 first.bar.com
的流量路由到 service1
,将请求
second.bar.com
的流量路由到 service2
,而将所有其他流量路由到 service3
。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : name-virtual-host-ingress-no-third-host
spec :
rules :
- host : first.bar.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service1
port :
number : 80
- host : second.bar.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service2
port :
number : 80
- http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service3
port :
number : 80
TLS
你可以通过设定包含 TLS 私钥和证书的Secret
来保护 Ingress。
Ingress 资源只支持一个 TLS 端口 443,并假定 TLS 连接终止于 Ingress 节点
(与 Service 及其 Pod 间的流量都以明文传输)。
如果 Ingress 中的 TLS 配置部分指定了不同主机,那么它们将通过
SNI TLS 扩展指定的主机名(如果 Ingress 控制器支持 SNI)在同一端口上进行复用。
TLS Secret 的数据中必须包含键名为 tls.crt
的证书和键名为 tls.key
的私钥,
才能用于 TLS 目的。例如:
apiVersion : v1
kind : Secret
metadata :
name : testsecret-tls
namespace : default
data :
tls.crt : base64 编码的证书
tls.key : base64 编码的私钥
type : kubernetes.io/tls
在 Ingress 中引用此 Secret 将会告诉 Ingress 控制器使用 TLS 加密从客户端到负载均衡器的通道。
你要确保所创建的 TLS Secret 创建自包含 https-example.foo.com
的公共名称
(Common Name,CN)的证书。这里的公共名称也被称为全限定域名(Fully Qualified Domain Name,FQDN)。
说明:
注意,不能针对默认规则使用 TLS,因为这样做需要为所有可能的子域名签发证书。
因此,tls
字段中的 hosts
的取值需要与 rules
字段中的 host
完全匹配。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : tls-example-ingress
spec :
tls :
- hosts :
- https-example.foo.com
secretName : testsecret-tls
rules :
- host : https-example.foo.com
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : service1
port :
number : 80
说明:
各种 Ingress 控制器在所支持的 TLS 特性上参差不齐。请参阅与
nginx 、
GCE
或者任何其他平台特定的 Ingress 控制器有关的文档,以了解 TLS 如何在你的环境中工作。
负载均衡
Ingress 控制器启动引导时使用一些适用于所有 Ingress 的负载均衡策略设置,
例如负载均衡算法、后端权重方案等。
更高级的负载均衡概念(例如持久会话、动态权重)尚未通过 Ingress 公开。
你可以通过用于 Service 的负载均衡器来获取这些功能。
值得注意的是,尽管健康检查不是通过 Ingress 直接暴露的,在 Kubernetes
中存在就绪态探针
这类等价的概念,供你实现相同的目的。
请查阅特定控制器的说明文档(例如:nginx 、
GCE )
以了解它们是怎样处理健康检查的。
更新 Ingress
要更新现有的 Ingress 以添加新的 Host,可以通过编辑资源来更新它:
kubectl describe ingress test
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 35s loadbalancer-controller default/test
kubectl edit ingress test
这一命令将打开编辑器,允许你以 YAML 格式编辑现有配置。
修改它来增加新的主机:
spec :
rules :
- host : foo.bar.com
http :
paths :
- backend :
service :
name : service1
port :
number : 80
path : /foo
pathType : Prefix
- host : bar.baz.com
http :
paths :
- backend :
service :
name : service2
port :
number : 80
path : /foo
pathType : Prefix
..
保存更改后,kubectl 将更新 API 服务器上的资源,该资源将告诉 Ingress 控制器重新配置负载均衡器。
验证:
kubectl describe ingress test
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
bar.baz.com
/foo service2:80 (10.8.0.91:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 45s loadbalancer-controller default/test
你也可以针对修改后的 Ingress YAML 文件,通过 kubectl replace -f
命令获得同样结果。
跨可用区的失效
不同的云厂商使用不同的技术来实现跨故障域的流量分布。
请查看相关 Ingress 控制器 的文档以了解详细信息。
替代方案
不直接使用 Ingress 资源,也有多种方法暴露 Service:
接下来
3 - Ingress 控制器
为了让
Ingress 在你的集群中工作, 必须有一个 Ingress 控制器正在运行。你需要选择至少一个 Ingress 控制器并确保其已被部署到你的集群中。 本页列出了你可以部署的常见 Ingress 控制器。
为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器。
与作为 kube-controller-manager
可执行文件的一部分运行的其他类型的控制器不同,
Ingress 控制器不是随集群自动启动的。
基于此页面,你可选择最适合你的集群的 ingress 控制器实现。
Kubernetes 作为一个项目,目前支持和维护
AWS 、
GCE
和 Nginx Ingress 控制器。
其他控制器
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
使用多个 Ingress 控制器
你可以使用
Ingress 类 在集群中部署任意数量的
Ingress 控制器。
请注意你的 Ingress 类资源的 .metadata.name
字段。
当你创建 Ingress 时,你需要用此字段的值来设置 Ingress 对象的 ingressClassName
字段(请参考
IngressSpec v1 reference )。
ingressClassName
是之前的注解 做法的替代。
如果你不为 Ingress 指定 IngressClass,并且你的集群中只有一个 IngressClass 被标记为默认,那么
Kubernetes 会将此集群的默认 IngressClass
应用 到 Ingress 上。
IngressClass。
你可以通过将
ingressclass.kubernetes.io/is-default-class
注解
的值设置为 "true"
来将一个 IngressClass 标记为集群默认。
理想情况下,所有 Ingress 控制器都应满足此规范,但各种 Ingress 控制器的操作略有不同。
说明: 确保你查看了 ingress 控制器的文档,以了解选择它的注意事项。
接下来
4 - EndpointSlice
EndpointSlice API 是 Kubernetes 用于扩缩 Service 以处理大量后端的机制,还允许集群高效更新其健康后端的列表。
特性状态: Kubernetes v1.21 [stable]
Kubernetes 的 EndpointSlice API 提供了一种简单的方法来跟踪
Kubernetes 集群中的网络端点(network endpoints)。EndpointSlices 为
Endpoints
提供了一种可扩缩和可拓展的替代方案。
EndpointSlice API
在 Kubernetes 中,EndpointSlice
包含对一组网络端点的引用。
控制面会自动为设置了选择算符 的
Kubernetes Service 创建 EndpointSlice。
这些 EndpointSlice 将包含对与 Service 选择算符匹配的所有 Pod 的引用。
EndpointSlice 通过唯一的协议、端口号和 Service 名称将网络端点组织在一起。
EndpointSlice 的名称必须是合法的
DNS 子域名 。
例如,下面是 Kubernetes Service example
所拥有的 EndpointSlice 对象示例。
apiVersion : discovery.k8s.io/v1
kind : EndpointSlice
metadata :
name : example-abc
labels :
kubernetes.io/service-name : example
addressType : IPv4
ports :
- name : http
protocol : TCP
port : 80
endpoints :
- addresses :
- "10.1.2.3"
conditions :
ready : true
hostname : pod-1
nodeName : node-1
zone : us-west2-a
默认情况下,控制面创建和管理的 EndpointSlice 将包含不超过 100 个端点。
你可以使用 kube-controller-manager
的 --max-endpoints-per-slice
标志设置此值,最大值为 1000。
当涉及如何路由内部流量时,EndpointSlice 可以充当
kube-proxy
的决策依据。
地址类型
EndpointSlice 支持三种地址类型:
每个 EndpointSlice
对象代表一个特定的 IP 地址类型。如果你有一个支持 IPv4 和 IPv6 的 Service,
那么将至少有两个 EndpointSlice
对象(一个用于 IPv4,一个用于 IPv6)。
状况
EndpointSlice API 存储了可能对使用者有用的、有关端点的状况。
这三个状况分别是 ready
、serving
和 terminating
。
Ready(就绪)
ready
状况是映射 Pod 的 Ready
状况的。
对于处于运行中的 Pod,它的 Ready
状况被设置为 True
,应该将此 EndpointSlice 状况也设置为 true
。
出于兼容性原因,当 Pod 处于终止过程中,ready
永远不会为 true
。
消费者应参考 serving
状况来检查处于终止中的 Pod 的就绪情况。
该规则的唯一例外是将 spec.publishNotReadyAddresses
设置为 true
的 Service。
这些 Service 的端点将始终将 ready
状况设置为 true
。
Serving(服务中)
特性状态: Kubernetes v1.26 [stable]
serving
状况几乎与 ready
状况相同,不同之处在于它不考虑终止状态。
如果 EndpointSlice API 的使用者关心 Pod 终止时的就绪情况,就应检查 serving
状况。
说明:
尽管 serving
与 ready
几乎相同,但是它是为防止破坏 ready
的现有含义而增加的。
如果对于处于终止中的端点,ready
可能是 true
,那么对于现有的客户端来说可能是有些意外的,
因为从始至终,Endpoints 或 EndpointSlice API 从未包含处于终止中的端点。
出于这个原因,ready
对于处于终止中的端点 总是 false
,
并且在 v1.20 中添加了新的状况 serving
,以便客户端可以独立于 ready
的现有语义来跟踪处于终止中的 Pod 的就绪情况。
Terminating(终止中)
特性状态: Kubernetes v1.22 [beta]
Terminating
是表示端点是否处于终止中的状况。
对于 Pod 来说,这是设置了删除时间戳的 Pod。
拓扑信息
EndpointSlice 中的每个端点都可以包含一定的拓扑信息。
拓扑信息包括端点的位置,对应节点、可用区的信息。
这些信息体现为 EndpointSlices 的如下端点字段:
nodeName
- 端点所在的 Node 名称;
zone
- 端点所处的可用区。
说明:
在 v1 API 中,逐个端点设置的 topology
实际上被去除,
以鼓励使用专用的字段 nodeName
和 zone
。
对 EndpointSlice
对象的 endpoint
字段设置任意的拓扑结构信息这一操作已被废弃,
不再被 v1 API 所支持。取而代之的是 v1 API 所支持的 nodeName
和 zone
这些独立的字段。这些字段可以在不同的 API 版本之间自动完成转译。
例如,v1beta1 API 中 topology
字段的 topology.kubernetes.io/zone
取值可以在 v1 API 中通过 zone
字段访问。
管理
通常,控制面(尤其是端点切片的控制器 )
会创建和管理 EndpointSlice 对象。EndpointSlice 对象还有一些其他使用场景,
例如作为服务网格(Service Mesh)的实现。
这些场景都会导致有其他实体或者控制器负责管理额外的 EndpointSlice 集合。
为了确保多个实体可以管理 EndpointSlice 而且不会相互产生干扰,
Kubernetes 定义了标签
endpointslice.kubernetes.io/managed-by
,用来标明哪个实体在管理某个 EndpointSlice。
端点切片控制器会在自己所管理的所有 EndpointSlice 上将该标签值设置为
endpointslice-controller.k8s.io
。
管理 EndpointSlice 的其他实体也应该为此标签设置一个唯一值。
属主关系
在大多数场合下,EndpointSlice 都由某个 Service 所有,
(因为)该端点切片正是为该服务跟踪记录其端点。这一属主关系是通过为每个 EndpointSlice
设置一个属主(owner)引用,同时设置 kubernetes.io/service-name
标签来标明的,
目的是方便查找隶属于某 Service 的所有 EndpointSlice。
EndpointSlice 镜像
在某些场合,应用会创建定制的 Endpoints 资源。为了保证这些应用不需要并发的更改
Endpoints 和 EndpointSlice 资源,集群的控制面将大多数 Endpoints
映射到对应的 EndpointSlice 之上。
控制面对 Endpoints 资源进行映射的例外情况有:
Endpoints 资源上标签 endpointslice.kubernetes.io/skip-mirror
值为 true
。
Endpoints 资源包含标签 control-plane.alpha.kubernetes.io/leader
。
对应的 Service 资源不存在。
对应的 Service 的选择算符不为空。
每个 Endpoints 资源可能会被转译到多个 EndpointSlices 中去。
当 Endpoints 资源中包含多个子网或者包含多个 IP 协议族(IPv4 和 IPv6)的端点时,
就有可能发生这种状况。
每个子网最多有 1000 个地址会被镜像到 EndpointSlice 中。
EndpointSlices 的分布问题
每个 EndpointSlice 都有一组端口值,适用于资源内的所有端点。
当为 Service 使用命名端口时,Pod 可能会就同一命名端口获得不同的端口号,
因而需要不同的 EndpointSlice。这有点像 Endpoints 用来对子网进行分组的逻辑。
控制面尝试尽量将 EndpointSlice 填满,不过不会主动地在若干 EndpointSlice
之间执行再平衡操作。这里的逻辑也是相对直接的:
列举所有现有的 EndpointSlices,移除那些不再需要的端点并更新那些已经变化的端点。
列举所有在第一步中被更改过的 EndpointSlices,用新增加的端点将其填满。
如果还有新的端点未被添加进去,尝试将这些端点添加到之前未更改的切片中,
或者创建新切片。
这里比较重要的是,与在 EndpointSlice 之间完成最佳的分布相比,第三步中更看重限制
EndpointSlice 更新的操作次数。例如,如果有 10 个端点待添加,有两个 EndpointSlice
中各有 5 个空位,上述方法会创建一个新的 EndpointSlice 而不是将现有的两个
EndpointSlice 都填满。换言之,与执行多个 EndpointSlice 更新操作相比较,
方法会优先考虑执行一个 EndpointSlice 创建操作。
由于 kube-proxy 在每个节点上运行并监视 EndpointSlice 状态,EndpointSlice
的每次变更都变得相对代价较高,因为这些状态变化要传递到集群中每个节点上。
这一方法尝试限制要发送到所有节点上的变更消息个数,即使这样做可能会导致有多个
EndpointSlice 没有被填满。
在实践中,上面这种并非最理想的分布是很少出现的。大多数被 EndpointSlice
控制器处理的变更都是足够小的,可以添加到某已有 EndpointSlice 中去的。
并且,假使无法添加到已有的切片中,不管怎样都很快就会创建一个新的
EndpointSlice 对象。Deployment 的滚动更新为重新为 EndpointSlice
打包提供了一个自然的机会,所有 Pod 及其对应的端点在这一期间都会被替换掉。
重复的端点
由于 EndpointSlice 变化的自身特点,端点可能会同时出现在不止一个 EndpointSlice
中。鉴于不同的 EndpointSlice 对象在不同时刻到达 Kubernetes 的监视/缓存中,
这种情况的出现是很自然的。
说明:
EndpointSlice API 的客户端必须遍历与 Service 关联的所有现有 EndpointSlices,
并构建唯一网络端点的完整列表。值得一提的是端点可能在不同的 EndpointSlices 中重复。
你可以在 kube-proxy
中的 EndpointSliceCache
代码中找到有关如何执行此端点聚合和重复数据删除的参考实现。
与 Endpoints 的比较
原来的 Endpoints API 提供了在 Kubernetes 中跟踪网络端点的一种简单而直接的方法。随着 Kubernetes
集群和服务 逐渐开始为更多的后端 Pod 处理和发送请求,
原来的 API 的局限性变得越来越明显。最明显的是那些因为要处理大量网络端点而带来的挑战。
由于任一 Service 的所有网络端点都保存在同一个 Endpoints 对象中,这些 Endpoints
对象可能变得非常巨大。对于保持稳定的服务(长时间使用同一组端点),影响不太明显;
即便如此,Kubernetes 的一些使用场景也没有得到很好的服务。
当某 Service 存在很多后端端点并且该工作负载频繁扩缩或上线新更改时,对该 Service 的单个 Endpoints
对象的每次更新都意味着(在控制平面内以及在节点和 API 服务器之间)Kubernetes 集群组件之间会出现大量流量。
这种额外的流量在 CPU 使用方面也有开销。
使用 EndpointSlices 时,添加或移除单个 Pod 对于正监视变更的客户端会触发相同数量的更新,
但这些更新消息的大小在大规模场景下要小得多。
EndpointSlices 还支持围绕双栈网络和拓扑感知路由等新功能的创新。
接下来
5 - 网络策略
如果你希望在 IP 地址或端口层面(OSI 第 3 层或第 4 层)控制网络流量, NetworkPolicy 可以让你为集群内以及 Pod 与外界之间的网络流量指定规则。 你的集群必须使用支持 NetworkPolicy 实施的网络插件。
如果你希望针对 TCP、UDP 和 SCTP 协议在 IP 地址或端口层面控制网络流量,
则你可以考虑为集群中特定应用使用 Kubernetes 网络策略(NetworkPolicy)。
NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许
Pod 与网络上的各类网络“实体”
(我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语,
这些术语在 Kubernetes 中有特定含义)通信。
NetworkPolicy 适用于一端或两端与 Pod 的连接,与其他连接无关。
Pod 可以通信的 Pod 是通过如下三个标识符的组合来辩识的:
其他被允许的 Pods(例外:Pod 无法阻塞对自身的访问)
被允许的名字空间
IP 组块(例外:与 Pod 运行所在的节点的通信总是被允许的,
无论 Pod 或节点的 IP 地址)
在定义基于 Pod 或名字空间的 NetworkPolicy 时,
你会使用选择算符 来设定哪些流量可以进入或离开与该算符匹配的 Pod。
另外,当创建基于 IP 的 NetworkPolicy 时,我们基于 IP 组块(CIDR 范围)来定义策略。
前置条件
网络策略通过网络插件 来实现。
要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。
创建一个 NetworkPolicy 资源对象而没有控制器来使它生效的话,是没有任何作用的。
Pod 隔离的两种类型
Pod 有两种隔离: 出口的隔离和入口的隔离。它们涉及到可以建立哪些连接。
这里的“隔离”不是绝对的,而是意味着“有一些限制”。
另外的,“非隔离方向”意味着在所述方向上没有限制。这两种隔离(或不隔离)是独立声明的,
并且都与从一个 Pod 到另一个 Pod 的连接有关。
默认情况下,一个 Pod 的出口是非隔离的,即所有外向连接都是被允许的。如果有任何的 NetworkPolicy
选择该 Pod 并在其 policyTypes
中包含 “Egress”,则该 Pod 是出口隔离的,
我们称这样的策略适用于该 Pod 的出口。当一个 Pod 的出口被隔离时,
唯一允许的来自 Pod 的连接是适用于出口的 Pod 的某个 NetworkPolicy 的 egress
列表所允许的连接。
这些 egress
列表的效果是相加的。
默认情况下,一个 Pod 对入口是非隔离的,即所有入站连接都是被允许的。如果有任何的 NetworkPolicy
选择该 Pod 并在其 policyTypes
中包含 “Ingress”,则该 Pod 被隔离入口,
我们称这种策略适用于该 Pod 的入口。当一个 Pod 的入口被隔离时,唯一允许进入该 Pod
的连接是来自该 Pod 节点的连接和适用于入口的 Pod 的某个 NetworkPolicy 的 ingress
列表所允许的连接。这些 ingress
列表的效果是相加的。
网络策略是相加的,所以不会产生冲突。如果策略适用于 Pod 某一特定方向的流量,
Pod 在对应方向所允许的连接是适用的网络策略所允许的集合。
因此,评估的顺序不影响策略的结果。
要允许从源 Pod 到目的 Pod 的连接,源 Pod 的出口策略和目的 Pod 的入口策略都需要允许连接。
如果任何一方不允许连接,建立连接将会失败。
NetworkPolicy 资源
参阅 NetworkPolicy
来了解资源的完整定义。
下面是一个 NetworkPolicy 的示例:
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : test-network-policy
namespace : default
spec :
podSelector :
matchLabels :
role : db
policyTypes :
- Ingress
- Egress
ingress :
- from :
- ipBlock :
cidr : 172.17.0.0 /16
except :
- 172.17.1.0 /24
- namespaceSelector :
matchLabels :
project : myproject
- podSelector :
matchLabels :
role : frontend
ports :
- protocol : TCP
port : 6379
egress :
- to :
- ipBlock :
cidr : 10.0.0.0 /24
ports :
- protocol : TCP
port : 5978
说明:
除非选择支持网络策略的网络解决方案,否则将上述示例发送到API服务器没有任何效果。
必需字段 :与所有其他的 Kubernetes 配置一样,NetworkPolicy 需要 apiVersion
、
kind
和 metadata
字段。关于配置文件操作的一般信息,
请参考配置 Pod 以使用 ConfigMap
和对象管理 。
spec :NetworkPolicy 规约
中包含了在一个名字空间中定义特定网络策略所需的所有信息。
podSelector :每个 NetworkPolicy 都包括一个 podSelector
,
它对该策略所适用的一组 Pod 进行选择。示例中的策略选择带有 "role=db" 标签的 Pod。
空的 podSelector
选择名字空间下的所有 Pod。
policyTypes :每个 NetworkPolicy 都包含一个 policyTypes
列表,其中包含
Ingress
或 Egress
或两者兼具。policyTypes
字段表示给定的策略是应用于进入所选
Pod 的入站流量还是来自所选 Pod 的出站流量,或两者兼有。
如果 NetworkPolicy 未指定 policyTypes
则默认情况下始终设置 Ingress
;
如果 NetworkPolicy 有任何出口规则的话则设置 Egress
。
ingress :每个 NetworkPolicy 可包含一个 ingress
规则的白名单列表。
每个规则都允许同时匹配 from
和 ports
部分的流量。示例策略中包含一条简单的规则:
它匹配某个特定端口,来自三个来源中的一个,第一个通过 ipBlock
指定,第二个通过 namespaceSelector
指定,第三个通过 podSelector
指定。
egress :每个 NetworkPolicy 可包含一个 egress
规则的白名单列表。
每个规则都允许匹配 to
和 port
部分的流量。该示例策略包含一条规则,
该规则将指定端口上的流量匹配到 10.0.0.0/24
中的任何目的地。
所以,该网络策略示例:
隔离 default
名字空间下 role=db
的 Pod (如果它们不是已经被隔离的话)。
(Ingress 规则)允许以下 Pod 连接到 default
名字空间下的带有 role=db
标签的所有 Pod 的 6379 TCP 端口:
default
名字空间下带有 role=frontend
标签的所有 Pod
带有 project=myproject
标签的所有名字空间中的 Pod
IP 地址范围为 172.17.0.0–172.17.0.255 和 172.17.2.0–172.17.255.255
(即,除了 172.17.1.0/24 之外的所有 172.17.0.0/16)
(Egress 规则)允许 default
名字空间中任何带有标签 role=db
的 Pod 到 CIDR
10.0.0.0/24 下 5978 TCP 端口的连接。
参阅声明网络策略 演练了解更多示例。
选择器 to
和 from
的行为
可以在 ingress
的 from
部分或 egress
的 to
部分中指定四种选择器:
podSelector :此选择器将在与 NetworkPolicy 相同的名字空间中选择特定的
Pod,应将其允许作为入站流量来源或出站流量目的地。
namespaceSelector :此选择器将选择特定的名字空间,应将所有 Pod
用作其入站流量来源或出站流量目的地。
namespaceSelector 和 podSelector :一个指定 namespaceSelector
和 podSelector
的 to
/from
条目选择特定名字空间中的特定 Pod。
注意使用正确的 YAML 语法;下面的策略:
...
ingress :
- from :
- namespaceSelector :
matchLabels :
user : alice
podSelector :
matchLabels :
role : client
...
此策略在 from
数组中仅包含一个元素,只允许来自标有 role=client
的 Pod
且该 Pod 所在的名字空间中标有 user=alice
的连接。但是这项 策略:
...
ingress :
- from :
- namespaceSelector :
matchLabels :
user : alice
- podSelector :
matchLabels :
role : client
...
它在 from
数组中包含两个元素,允许来自本地名字空间中标有 role=client
的
Pod 的连接,或 来自任何名字空间中标有 user=alice
的任何 Pod 的连接。
如有疑问,请使用 kubectl describe
查看 Kubernetes 如何解释该策略。
ipBlock :此选择器将选择特定的 IP CIDR 范围以用作入站流量来源或出站流量目的地。
这些应该是集群外部 IP,因为 Pod IP 存在时间短暂的且随机产生。
集群的入站和出站机制通常需要重写数据包的源 IP 或目标 IP。
在发生这种情况时,不确定在 NetworkPolicy 处理之前还是之后发生,
并且对于网络插件、云提供商、Service
实现等的不同组合,其行为可能会有所不同。
对入站流量而言,这意味着在某些情况下,你可以根据实际的原始源 IP 过滤传入的数据包,
而在其他情况下,NetworkPolicy 所作用的 源IP
则可能是 LoadBalancer
或
Pod 的节点等。
对于出站流量而言,这意味着从 Pod 到被重写为集群外部 IP 的 Service
IP
的连接可能会或可能不会受到基于 ipBlock
的策略的约束。
默认策略
默认情况下,如果名字空间中不存在任何策略,则所有进出该名字空间中 Pod 的流量都被允许。
以下示例使你可以更改该名字空间中的默认行为。
默认拒绝所有入站流量
你可以通过创建选择所有 Pod 但不允许任何进入这些 Pod 的入站流量的 NetworkPolicy
来为名字空间创建 “default” 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-ingress
spec :
podSelector : {}
policyTypes :
- Ingress
这确保即使没有被任何其他 NetworkPolicy 选择的 Pod 仍将被隔离以进行入口。
此策略不影响任何 Pod 的出口隔离。
允许所有入站流量
如果你想允许一个名字空间中所有 Pod 的所有入站连接,你可以创建一个明确允许的策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-ingress
spec :
podSelector : {}
ingress :
- {}
policyTypes :
- Ingress
有了这个策略,任何额外的策略都不会导致到这些 Pod 的任何入站连接被拒绝。
此策略对任何 Pod 的出口隔离没有影响。
默认拒绝所有出站流量
你可以通过创建选择所有容器但不允许来自这些容器的任何出站流量的 NetworkPolicy
来为名字空间创建 “default” 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-egress
spec :
podSelector : {}
policyTypes :
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许流出流量。
此策略不会更改任何 Pod 的入站流量隔离行为。
允许所有出站流量
如果要允许来自名字空间中所有 Pod 的所有连接,
则可以创建一个明确允许来自该名字空间中 Pod 的所有出站连接的策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-egress
spec :
podSelector : {}
egress :
- {}
policyTypes :
- Egress
有了这个策略,任何额外的策略都不会导致来自这些 Pod 的任何出站连接被拒绝。
此策略对进入任何 Pod 的隔离没有影响。
默认拒绝所有入站和所有出站流量
你可以为名字空间创建“默认”策略,以通过在该名字空间中创建以下 NetworkPolicy
来阻止所有入站和出站流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-all
spec :
podSelector : {}
policyTypes :
- Ingress
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许入站或出站流量。
网络流量过滤
NetworkPolicy 是为第 4 层 连接
(TCP、UDP 和可选的 SCTP)所定义的。对于所有其他协议,这种网络流量过滤的行为可能因网络插件而异。
说明:
你必须使用支持 SCTP 协议 NetworkPolicy 的 CNI 插件。
当 deny all
网络策略被定义时,此策略只能保证拒绝 TCP、UDP 和 SCTP 连接。
对于 ARP 或 ICMP 这类其他协议,这种网络流量过滤行为是未定义的。
相同的情况也适用于 allow 规则:当特定 Pod 被允许作为入口源或出口目的地时,
对于(例如)ICMP 数据包会发生什么是未定义的。
ICMP 这类协议可能被某些网络插件所允许,而被另一些网络插件所拒绝。
针对某个端口范围
特性状态: Kubernetes v1.25 [stable]
在编写 NetworkPolicy 时,你可以针对一个端口范围而不是某个固定端口。
这一目的可以通过使用 endPort
字段来实现,如下例所示:
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : multi-port-egress
namespace : default
spec :
podSelector :
matchLabels :
role : db
policyTypes :
- Egress
egress :
- to :
- ipBlock :
cidr : 10.0.0.0 /24
ports :
- protocol : TCP
port : 32000
endPort : 32768
上面的规则允许名字空间 default
中所有带有标签 role=db
的 Pod 使用 TCP 协议与
10.0.0.0/24
范围内的 IP 通信,只要目标端口介于 32000 和 32768 之间就可以。
使用此字段时存在以下限制:
endPort
字段必须等于或者大于 port
字段的值。
只有在定义了 port
时才能定义 endPort
。
两个字段的设置值都只能是数字。
说明:
你的集群所使用的 CNI 插件必须支持在
NetworkPolicy 规约中使用 endPort
字段。
如果你的网络插件 不支持
endPort
字段,而你指定了一个包含 endPort
字段的 NetworkPolicy,
策略只对单个 port
字段生效。
按标签选择多个命名空间
在这种情况下,你的 Egress
NetworkPolicy 使用名字空间的标签名称来将多个名字空间作为其目标。
为此,你需要为目标名字空间设置标签。例如:
kubectl label namespace frontend namespace = frontend
kubectl label namespace backend namespace = backend
在 NetworkPolicy 文档中的 namespaceSelector 下添加标签。例如:
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : egress-namespaces
spec :
podSelector :
matchLabels :
app : myapp
policyTypes :
- Egress
egress :
- to :
- namespaceSelector :
matchExpressions :
- key : namespace
operator : In
values : ["frontend" , "backend" ]
说明:
你不可以在 NetworkPolicy 中直接指定命名空间的名称。
你必须使用带有 matchLabels
或 matchExpressions
的 namespaceSelector
来根据标签选择命名空间。
基于名字指向某名字空间
Kubernetes 控制面会在所有名字空间上设置一个不可变更的标签
kubernetes.io/metadata.name
。该标签的值是名字空间的名称。
如果 NetworkPolicy 无法在某些对象字段中指向某名字空间,
你可以使用标准的标签方式来指向特定名字空间。
Pod 生命周期
说明:
以下内容适用于使用了合规网络插件和 NetworkPolicy 合规实现的集群。
当新的 NetworkPolicy 对象被创建时,网络插件可能需要一些时间来处理这个新对象。
如果受到 NetworkPolicy 影响的 Pod 在网络插件完成 NetworkPolicy 处理之前就被创建了,
那么该 Pod 可能会最初处于无保护状态,而在 NetworkPolicy 处理完成后被应用隔离规则。
一旦 NetworkPolicy 被网络插件处理,
所有受给定 NetworkPolicy 影响的新建 Pod 都将在启动前被隔离。
NetworkPolicy 的实现必须确保过滤规则在整个 Pod 生命周期内是有效的,
这个生命周期要从该 Pod 的任何容器启动的第一刻算起。
因为 NetworkPolicy 在 Pod 层面被应用,所以 NetworkPolicy 同样适用于 Init 容器、边车容器和常规容器。
Allow 规则最终将在隔离规则之后被应用(或者可能同时被应用)。
在最糟的情况下,如果隔离规则已被应用,但 allow 规则尚未被应用,
那么新建的 Pod 在初始启动时可能根本没有网络连接。
用户所创建的每个 NetworkPolicy 最终都会被网络插件处理,但无法使用 Kubernetes API 来获知确切的处理时间。
因此,若 Pod 启动时使用非预期的网络连接,它必须保持稳定。
如果你需要确保 Pod 在启动之前能够访问特定的目标,可以使用
Init 容器 在
kubelet 启动应用容器之前等待这些目的地变得可达。
每个 NetworkPolicy 最终都会被应用到所选定的所有 Pod 之上。
由于网络插件可能以分布式的方式实现 NetworkPolicy,所以当 Pod 被首次创建时或当
Pod 或策略发生变化时,Pod 可能会看到稍微不一致的网络策略视图。
例如,新建的 Pod 本来应能立即访问 Node 1 上的 Pod A 和 Node 2 上的 Pod B,
但可能你会发现新建的 Pod 可以立即访问 Pod A,但要在几秒后才能访问 Pod B。
NetworkPolicy 和 hostNetwork
Pod
针对 hostNetwork
Pod 的 NetworkPolicy 行为是未定义的,但应限制为以下两种可能:
网络插件可以从所有其他流量中辨别出 hostNetwork
Pod 流量
(包括能够从同一节点上的不同 hostNetwork
Pod 中辨别流量),
网络插件还可以像处理 Pod 网络流量一样,对 hostNetwork
Pod 应用 NetworkPolicy。
网络插件无法正确辨别 hostNetwork
Pod 流量,因此在匹配 podSelector
和 namespaceSelector
时会忽略 hostNetwork
Pod。流向/来自 hostNetwork
Pod 的流量的处理方式与流向/来自节点 IP
的所有其他流量一样。(这是最常见的实现方式。)
这适用于以下情形:
hostNetwork
Pod 被 spec.podSelector
选中。
...
spec :
podSelector :
matchLabels :
role : client
...
hostNetwork
Pod 在 ingress
或 egress
规则中被 podSelector
或 namespaceSelector
选中。
...
ingress :
- from :
- podSelector :
matchLabels :
role : client
...
同时,由于 hostNetwork
Pod 具有与其所在节点相同的 IP 地址,所以它们的连接将被视为节点连接。
例如,你可以使用 ipBlock
规则允许来自 hostNetwork
Pod 的流量。
通过网络策略(至少目前还)无法完成的工作
到 Kubernetes 1.28 为止,NetworkPolicy API 还不支持以下功能,
不过你可能可以使用操作系统组件(如 SELinux、OpenVSwitch、IPTables 等等)
或者第七层技术(Ingress 控制器、服务网格实现)或准入控制器来实现一些替代方案。
如果你对 Kubernetes 中的网络安全性还不太了解,了解使用 NetworkPolicy API
还无法实现下面的用户场景是很值得的。
强制集群内部流量经过某公用网关(这种场景最好通过服务网格或其他代理来实现);
与 TLS 相关的场景(考虑使用服务网格或者 Ingress 控制器);
特定于节点的策略(你可以使用 CIDR 来表达这一需求不过你无法使用节点在
Kubernetes 中的其他标识信息来辩识目标节点);
基于名字来选择服务(不过,你可以使用 标签
来选择目标 Pod 或名字空间,这也通常是一种可靠的替代方案);
创建或管理由第三方来实际完成的“策略请求”;
实现适用于所有名字空间或 Pods 的默认策略(某些第三方 Kubernetes 发行版本或项目可以做到这点);
高级的策略查询或者可达性相关工具;
生成网络安全事件日志的能力(例如,被阻塞或接收的连接请求);
显式地拒绝策略的能力(目前,NetworkPolicy 的模型默认采用拒绝操作,
其唯一的能力是添加允许策略);
禁止本地回路或指向宿主的网络流量(Pod 目前无法阻塞 localhost 访问,
它们也无法禁止来自所在节点的访问请求)。
接下来
参阅声明网络策略 演练了解更多示例;
有关 NetworkPolicy 资源所支持的常见场景的更多信息,
请参见此指南 。
6 - Service 与 Pod 的 DNS
你的工作负载可以使用 DNS 发现集群内的 Service,本页说明具体工作原理。
Kubernetes 为 Service 和 Pod 创建 DNS 记录。
你可以使用一致的 DNS 名称而非 IP 地址访问 Service。
Kubernetes 发布有关 Pod 和 Service 的信息,这些信息被用来对 DNS 进行编程。
Kubelet 配置 Pod 的 DNS,以便运行中的容器可以通过名称而不是 IP 来查找服务。
集群中定义的 Service 被赋予 DNS 名称。
默认情况下,客户端 Pod 的 DNS 搜索列表会包含 Pod 自身的名字空间和集群的默认域。
Service 的名字空间
DNS 查询可能因为执行查询的 Pod 所在的名字空间而返回不同的结果。
不指定名字空间的 DNS 查询会被限制在 Pod 所在的名字空间内。
要访问其他名字空间中的 Service,需要在 DNS 查询中指定名字空间。
例如,假定名字空间 test
中存在一个 Pod,prod
名字空间中存在一个服务
data
。
Pod 查询 data
时没有返回结果,因为使用的是 Pod 的名字空间 test
。
Pod 查询 data.prod
时则会返回预期的结果,因为查询中指定了名字空间。
DNS 查询可以使用 Pod 中的 /etc/resolv.conf
展开。
Kubelet 为每个 Pod 配置此文件。
例如,对 data
的查询可能被展开为 data.test.svc.cluster.local
。
search
选项的取值会被用来展开查询。要进一步了解 DNS 查询,可参阅
resolv.conf
手册页面 。
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
概括起来,名字空间 test 中的 Pod 可以成功地解析 data.prod
或者
data.prod.svc.cluster.local
。
DNS 记录
哪些对象会获得 DNS 记录呢?
Services
Pods
以下各节详细介绍已支持的 DNS 记录类型和布局。
其它布局、名称或者查询即使碰巧可以工作,也应视为实现细节,
将来很可能被更改而且不会因此发出警告。
有关最新规范请查看
Kubernetes 基于 DNS 的服务发现 。
Service
A/AAAA 记录
除了无头 Service 之外的 “普通” Service 会被赋予一个形如 my-svc.my-namespace.svc.cluster-domain.example
的 DNS A 和/或 AAAA 记录,取决于 Service 的 IP 协议族(可能有多个)设置。
该名称会解析成对应 Service 的集群 IP。
没有集群 IP 的无头 Service
也会被赋予一个形如 my-svc.my-namespace.svc.cluster-domain.example
的 DNS A 和/或 AAAA 记录。
与普通 Service 不同,这一记录会被解析成对应 Service 所选择的 Pod IP 的集合。
客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。
SRV 记录
Kubernetes 根据普通 Service 或无头 Service
中的命名端口创建 SRV 记录。每个命名端口,
SRV 记录格式为 _port-name._port-protocol.my-svc.my-namespace.svc.cluster-domain.example
。
普通 Service,该记录会被解析成端口号和域名:my-svc.my-namespace.svc.cluster-domain.example
。
无头 Service,该记录会被解析成多个结果,及该服务的每个后端 Pod 各一个 SRV 记录,
其中包含 Pod 端口号和格式为 hostname.my-svc.my-namespace.svc.cluster-domain.example
的域名。
Pod
A/AAAA 记录
一般而言,Pod 会对应如下 DNS 名字解析:
pod-ip-address.my-namespace.pod.cluster-domain.example
例如,对于一个位于 default
名字空间,IP 地址为 172.17.0.3 的 Pod,
如果集群的域名为 cluster.local
,则 Pod 会对应 DNS 名称:
172-17-0-3.default.pod.cluster.local
通过 Service 暴露出来的所有 Pod 都会有如下 DNS 解析名称可用:
pod-ip-address.service-name.my-namespace.svc.cluster-domain.example
Pod 的 hostname 和 subdomain 字段
当前,创建 Pod 时其主机名(从 Pod 内部观察)取自 Pod 的 metadata.name
值。
Pod 规约中包含一个可选的 hostname
字段,可以用来指定一个不同的主机名。
当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名(同样是从 Pod 内部观察)。
举个例子,给定一个 spec.hostname
设置为 “my-host”
的 Pod,
该 Pod 的主机名将被设置为 “my-host”
。
Pod 规约还有一个可选的 subdomain
字段,可以用来表明该 Pod 是名字空间的子组的一部分。
举个例子,某 Pod 的 spec.hostname
设置为 “foo”
,spec.subdomain
设置为 “bar”
,
在名字空间 “my-namespace”
中,主机名称被设置成 “foo”
并且对应的完全限定域名(FQDN)为
“foo.bar.my-namespace.svc.cluster-domain.example
”(还是从 Pod 内部观察)。
如果 Pod 所在的名字空间中存在一个无头服务,其名称与子域相同,
则集群的 DNS 服务器还会为 Pod 的完全限定主机名返回 A 和/或 AAAA 记录。
示例:
apiVersion : v1
kind : Service
metadata :
name : busybox-subdomain
spec :
selector :
name : busybox
clusterIP : None
ports :
- name : foo # 实际上不需要指定端口号
port : 1234
---
apiVersion : v1
kind : Pod
metadata :
name : busybox1
labels :
name : busybox
spec :
hostname : busybox-1
subdomain : busybox-subdomain
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
name : busybox
---
apiVersion : v1
kind : Pod
metadata :
name : busybox2
labels :
name : busybox
spec :
hostname : busybox-2
subdomain : busybox-subdomain
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
name : busybox
鉴于上述服务 “busybox-subdomain”
和将 spec.subdomain
设置为 “busybox-subdomain”
的 Pod,
第一个 Pod 将看到自己的 FQDN 为 “busybox-1.busybox-subdomain.my-namespace.svc.cluster-domain.example”
。
DNS 会为此名字提供一个 A 记录和/或 AAAA 记录,指向该 Pod 的 IP。
Pod “busybox1
” 和 “busybox2
” 都将有自己的地址记录。
EndpointSlice
对象可以为任何端点地址及其 IP 指定 hostname
。
说明: 由于 A 和 AAAA 记录不是基于 Pod 名称创建,因此需要设置了 hostname
才会生成 Pod 的 A 或 AAAA 记录。
没有设置 hostname
但设置了 subdomain
的 Pod 只会为
无头 Service 创建 A 或 AAAA 记录(busybox-subdomain.my-namespace.svc.cluster-domain.example
)
指向 Pod 的 IP 地址。
另外,除非在服务上设置了 publishNotReadyAddresses=True
,否则只有 Pod 准备就绪
才会有与之对应的记录。
Pod 的 setHostnameAsFQDN 字段
特性状态: Kubernetes v1.22 [stable]
当 Pod 配置为具有全限定域名 (FQDN) 时,其主机名是短主机名。
例如,如果你有一个具有完全限定域名 busybox-1.busybox-subdomain.my-namespace.svc.cluster-domain.example
的 Pod,
则默认情况下,该 Pod 内的 hostname
命令返回 busybox-1
,而 hostname --fqdn
命令返回 FQDN。
当你在 Pod 规约中设置了 setHostnameAsFQDN: true
时,kubelet 会将 Pod
的全限定域名(FQDN)作为该 Pod 的主机名记录到 Pod 所在名字空间。
在这种情况下,hostname
和 hostname --fqdn
都会返回 Pod 的全限定域名。
说明:
在 Linux 中,内核的主机名字段(struct utsname
的 nodename
字段)限定最多 64 个字符。
如果 Pod 启用这一特性,而其 FQDN 超出 64 字符,Pod 的启动会失败。
Pod 会一直出于 Pending
状态(通过 kubectl
所看到的 ContainerCreating
),
并产生错误事件,例如
"Failed to construct FQDN from Pod hostname and cluster domain, FQDN
long-FQDN
is too long (64 characters is the max, 70 characters requested)."
(无法基于 Pod 主机名和集群域名构造 FQDN,FQDN long-FQDN
过长,至多 64 个字符,请求字符数为 70)。
对于这种场景而言,改善用户体验的一种方式是创建一个
准入 Webhook 控制器 ,
在用户创建顶层对象(如 Deployment)的时候控制 FQDN 的长度。
Pod 的 DNS 策略
DNS 策略可以逐个 Pod 来设定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。
这些策略可以在 Pod 规约中的 dnsPolicy
字段设置:
"Default
": Pod 从运行所在的节点继承名称解析配置。
参考相关讨论 获取更多信息。
"ClusterFirst
": 与配置的集群域后缀不匹配的任何 DNS 查询(例如 "www.kubernetes.io")
都会由 DNS 服务器转发到上游名称服务器。集群管理员可能配置了额外的存根域和上游 DNS 服务器。
参阅相关讨论
了解在这些场景中如何处理 DNS 查询的信息。
"ClusterFirstWithHostNet
": 对于以 hostNetwork 方式运行的 Pod,应将其 DNS 策略显式设置为
"ClusterFirstWithHostNet
"。否则,以 hostNetwork 方式和 "ClusterFirst"
策略运行的
Pod 将会做出回退至 "Default"
策略的行为。
注意:这在 Windows 上不支持。 有关详细信息,请参见下文 。
"None
": 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置。Pod 会使用其 dnsConfig
字段所提供的 DNS 设置。
参见 Pod 的 DNS 配置 节。
说明:
"Default" 不是默认的 DNS 策略。如果未明确指定 dnsPolicy
,则使用 "ClusterFirst"。
下面的示例显示了一个 Pod,其 DNS 策略设置为 "ClusterFirstWithHostNet
",
因为它已将 hostNetwork
设置为 true
。
apiVersion : v1
kind : Pod
metadata :
name : busybox
namespace : default
spec :
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
imagePullPolicy : IfNotPresent
name : busybox
restartPolicy : Always
hostNetwork : true
dnsPolicy : ClusterFirstWithHostNet
Pod 的 DNS 配置
特性状态: Kubernetes v1.14 [stable]
Pod 的 DNS 配置可让用户对 Pod 的 DNS 设置进行更多控制。
dnsConfig
字段是可选的,它可以与任何 dnsPolicy
设置一起使用。
但是,当 Pod 的 dnsPolicy
设置为 "None
" 时,必须指定 dnsConfig
字段。
用户可以在 dnsConfig
字段中指定以下属性:
nameservers
:将用作于 Pod 的 DNS 服务器的 IP 地址列表。
最多可以指定 3 个 IP 地址。当 Pod 的 dnsPolicy
设置为 "None
" 时,
列表必须至少包含一个 IP 地址,否则此属性是可选的。
所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。
searches
:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。
指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。
重复的域名将被删除。Kubernetes 最多允许 32 个搜索域。
options
:可选的对象列表,其中每个对象可能具有 name
属性(必需)和 value
属性(可选)。
此属性中的内容将合并到从指定的 DNS 策略生成的选项。
重复的条目将被删除。
以下是具有自定义 DNS 设置的 Pod 示例:
apiVersion : v1
kind : Pod
metadata :
namespace : default
name : dns-example
spec :
containers :
- name : test
image : nginx
dnsPolicy : "None"
dnsConfig :
nameservers :
- 192.0.2.1 # 这是一个示例
searches :
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options :
- name : ndots
value : "2"
- name : edns0
创建上面的 Pod 后,容器 test
会在其 /etc/resolv.conf
文件中获取以下内容:
nameserver 192.0.2.1
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0
对于 IPv6 设置,搜索路径和名称服务器应按以下方式设置:
kubectl exec -it dns-example -- cat /etc/resolv.conf
输出类似于:
nameserver 2001:db8:30::a
search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.example
options ndots:5
DNS 搜索域列表限制
特性状态: Kubernetes 1.28 [stable]
Kubernetes 本身不限制 DNS 配置,最多可支持 32 个搜索域列表,所有搜索域的总长度不超过 2048。
此限制分别适用于节点的解析器配置文件、Pod 的 DNS 配置和合并的 DNS 配置。
说明:
早期版本的某些容器运行时可能对 DNS 搜索域的数量有自己的限制。
根据容器运行环境,那些具有大量 DNS 搜索域的 Pod 可能会卡在 Pending 状态。
众所周知 containerd v1.5.5 或更早版本和 CRI-O v1.21 或更早版本都有这个问题。
Windows 节点上的 DNS 解析
在 Windows 节点上运行的 Pod 不支持 ClusterFirstWithHostNet。
Windows 将所有带有 .
的名称视为全限定域名(FQDN)并跳过全限定域名(FQDN)解析。
在 Windows 上,可以使用的 DNS 解析器有很多。
由于这些解析器彼此之间会有轻微的行为差别,建议使用
Resolve-DNSName
powershell cmdlet 进行名称查询解析。
在 Linux 上,有一个 DNS 后缀列表,当解析全名失败时可以使用。
在 Windows 上,你只能有一个 DNS 后缀,
即与该 Pod 的命名空间相关联的 DNS 后缀(例如:mydns.svc.cluster.local
)。
Windows 可以解析全限定域名(FQDN),和使用了该 DNS 后缀的 Services 或者网络名称。
例如,在 default
命名空间中生成一个 Pod,该 Pod 会获得的 DNS 后缀为 default.svc.cluster.local
。
在 Windows 的 Pod 中,你可以解析 kubernetes.default.svc.cluster.local
和 kubernetes
,
但是不能解析部分限定名称(kubernetes.default
和 kubernetes.default.svc
)。
接下来
有关管理 DNS 配置的指导,
请查看配置 DNS 服务
7 - IPv4/IPv6 双协议栈
Kubernetes 允许你配置单协议栈 IPv4 网络、单协议栈 IPv6 网络或同时激活这两种网络的双协议栈网络。本页说明具体配置方法。
特性状态: Kubernetes v1.23 [stable]
IPv4/IPv6 双协议栈网络能够将 IPv4 和 IPv6 地址分配给
Pod 和
Service 。
从 1.21 版本开始,Kubernetes 集群默认启用 IPv4/IPv6 双协议栈网络,
以支持同时分配 IPv4 和 IPv6 地址。
支持的功能
Kubernetes 集群的 IPv4/IPv6 双协议栈可提供下面的功能:
双协议栈 pod 网络 (每个 pod 分配一个 IPv4 和 IPv6 地址)
IPv4 和 IPv6 启用的服务
Pod 的集群外出口通过 IPv4 和 IPv6 路由
先决条件
为了使用 IPv4/IPv6 双栈的 Kubernetes 集群,需要满足以下先决条件:
Kubernetes 1.20 版本或更高版本,有关更早 Kubernetes 版本的使用双栈服务的信息,
请参考对应版本的 Kubernetes 文档。
提供商支持双协议栈网络(云提供商或其他提供商必须能够为 Kubernetes
节点提供可路由的 IPv4/IPv6 网络接口)
支持双协议栈的网络插件
配置 IPv4/IPv6 双协议栈
如果配置 IPv4/IPv6 双栈,请分配双栈集群网络:
kube-apiserver:
--service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
kube-controller-manager:
--cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
--service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
--node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6
对于 IPv4 默认为 /24,
对于 IPv6 默认为 /64
kube-proxy:
--cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
kubelet:
当没有 --cloud-provider
时,管理员可以通过 --node-ip
来传递逗号分隔的 IP 地址,
为该节点手动配置双栈 .status.addresses
。
如果 Pod 以 HostNetwork 模式在该节点上运行,则 Pod 会用 .status.podIPs
字段来报告它的 IP 地址。
一个节点中的所有 podIP
都会匹配该节点的由 .status.addresses
字段定义的 IP 组。
说明:
IPv4 CIDR 的一个例子:10.244.0.0/16
(尽管你会提供你自己的地址范围)。
IPv6 CIDR 的一个例子:fdXY:IJKL:MNOP:15::/64
(这里演示的是格式而非有效地址 - 请看 RFC 4193 )。
特性状态: Kubernetes v1.27 [alpha]
使用外部云驱动时,如果你在 kubelet 和外部云提供商中都启用了
CloudDualStackNodeIPs
特性门控,则可以将双栈 --node-ip
值传递给 kubelet。此特性需要保证云提供商支持双栈集群。
服务
你可以使用 IPv4 或 IPv6 地址来创建
Service 。
服务的地址族默认为第一个服务集群 IP 范围的地址族(通过 kube-apiserver 的
--service-cluster-ip-range
参数配置)。
当你定义服务时,可以选择将其配置为双栈。若要指定所需的行为,你可以设置
.spec.ipFamilyPolicy
字段为以下值之一:
SingleStack
:单栈服务。控制面使用第一个配置的服务集群 IP 范围为服务分配集群 IP。
PreferDualStack
:
为服务分配 IPv4 和 IPv6 集群 IP 地址。
RequireDualStack
:从 IPv4 和 IPv6 的地址范围分配服务的 .spec.ClusterIPs
从基于在 .spec.ipFamilies
数组中第一个元素的地址族的 .spec.ClusterIPs
列表中选择 .spec.ClusterIP
如果你想要定义哪个 IP 族用于单栈或定义双栈 IP 族的顺序,可以通过设置
服务上的可选字段 .spec.ipFamilies
来选择地址族。
说明:
.spec.ipFamilies
字段修改是有条件的:你可以添加或删除第二个 IP 地址族,
但你不能更改现有服务的主要 IP 地址族。
你可以设置 .spec.ipFamily
为以下任何数组值:
["IPv4"]
["IPv6"]
["IPv4","IPv6"]
(双栈)
["IPv6","IPv4"]
(双栈)
你所列出的第一个地址族用于原来的 .spec.ClusterIP
字段。
双栈服务配置场景
以下示例演示多种双栈服务配置场景下的行为。
新服务的双栈选项
此服务规约中没有显式设定 .spec.ipFamilyPolicy
。当你创建此服务时,Kubernetes
从所配置的第一个 service-cluster-ip-range
中为服务分配一个集群 IP,并设置
.spec.ipFamilyPolicy
为 SingleStack
。
(无选择算符的服务
和无头服务 的行为方式
与此相同。)
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
此服务规约显式地将 .spec.ipFamilyPolicy
设置为 PreferDualStack
。
当你在双栈集群上创建此服务时,Kubernetes 会为该服务分配 IPv4 和 IPv6 地址。
控制平面更新服务的 .spec
以记录 IP 地址分配。
字段 .spec.ClusterIPs
是主要字段,包含两个分配的 IP 地址;.spec.ClusterIP
是次要字段,
其取值从 .spec.ClusterIPs
计算而来。
对于 .spec.ClusterIP
字段,控制面记录来自第一个服务集群 IP 范围
对应的地址族的 IP 地址。
对于单协议栈的集群,.spec.ClusterIPs
和 .spec.ClusterIP
字段都
仅仅列出一个地址。
对于启用了双协议栈的集群,将 .spec.ipFamilyPolicy
设置为
RequireDualStack
时,其行为与 PreferDualStack
相同。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilyPolicy : PreferDualStack
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
下面的服务规约显式地在 .spec.ipFamilies
中指定 IPv6
和 IPv4
,并
将 .spec.ipFamilyPolicy
设定为 PreferDualStack
。
当 Kubernetes 为 .spec.ClusterIPs
分配一个 IPv6 和一个 IPv4 地址时,
.spec.ClusterIP
被设置成 IPv6 地址,因为它是 .spec.ClusterIPs
数组中的第一个元素,
覆盖其默认值。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilyPolicy : PreferDualStack
ipFamilies :
- IPv6
- IPv4
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
现有服务的双栈默认值
下面示例演示了在服务已经存在的集群上新启用双栈时的默认行为。
(将现有集群升级到 1.21 或者更高版本会启用双协议栈支持。)
在集群上启用双栈时,控制面会将现有服务(无论是 IPv4
还是 IPv6
)配置
.spec.ipFamilyPolicy
为 SingleStack
并设置 .spec.ipFamilies
为服务的当前地址族。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查现有服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : MyApp
name : my-service
spec :
clusterIP : 10.0.197.123
clusterIPs :
- 10.0.197.123
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app.kubernetes.io/name : MyApp
type : ClusterIP
status :
loadBalancer : {}
在集群上启用双栈时,带有选择算符的现有
无头服务
由控制面设置 .spec.ipFamilyPolicy
为 SingleStack
并设置 .spec.ipFamilies
为第一个服务集群 IP 范围的地址族(通过配置 kube-apiserver 的
--service-cluster-ip-range
参数),即使 .spec.ClusterIP
的设置值为 None
也如此。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查带有选择算符的现有无头服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : MyApp
name : my-service
spec :
clusterIP : None
clusterIPs :
- None
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app.kubernetes.io/name : MyApp
在单栈和双栈之间切换服务
服务可以从单栈更改为双栈,也可以从双栈更改为单栈。
要将服务从单栈更改为双栈,根据需要将 .spec.ipFamilyPolicy
从 SingleStack
改为
PreferDualStack
或 RequireDualStack
。
当你将此服务从单栈更改为双栈时,Kubernetes 将分配缺失的地址族,
以便现在该服务具有 IPv4 和 IPv6 地址。
编辑服务规约将 .spec.ipFamilyPolicy
从 SingleStack
改为 PreferDualStack
。
之前:
spec :
ipFamilyPolicy : SingleStack
之后:
spec :
ipFamilyPolicy : PreferDualStack
要将服务从双栈更改为单栈,请将 .spec.ipFamilyPolicy
从 PreferDualStack
或
RequireDualStack
改为 SingleStack
。
当你将此服务从双栈更改为单栈时,Kubernetes 只保留 .spec.ClusterIPs
数组中的第一个元素,并设置 .spec.ClusterIP
为那个 IP 地址,
并设置 .spec.ipFamilies
为 .spec.ClusterIPs
地址族。
无选择算符的无头服务
对于不带选择算符的无头服务 ,
若没有显式设置 .spec.ipFamilyPolicy
,则 .spec.ipFamilyPolicy
字段默认设置为 RequireDualStack
。
LoadBalancer 类型服务
要为你的服务提供双栈负载均衡器:
将 .spec.type
字段设置为 LoadBalancer
将 .spec.ipFamilyPolicy
字段设置为 PreferDualStack
或者 RequireDualStack
说明:
为了使用双栈的负载均衡器类型服务,你的云驱动必须支持 IPv4 和 IPv6 的负载均衡器。
出站流量
如果你要启用出站流量,以便使用非公开路由 IPv6 地址的 Pod 到达集群外地址
(例如公网),则需要通过透明代理或 IP 伪装等机制使 Pod 使用公共路由的
IPv6 地址。
ip-masq-agent 项目
支持在双栈集群上进行 IP 伪装。
Windows 支持
Windows 上的 Kubernetes 不支持单栈“仅 IPv6” 网络。 然而,
对于 Pod 和节点而言,仅支持单栈形式服务的双栈 IPv4/IPv6 网络是被支持的。
你可以使用 l2bridge
网络来实现 IPv4/IPv6 双栈联网。
说明:
Windows 上的 Overlay (VXLAN) 网络不 支持双栈网络。
关于 Windows 的不同网络模式,你可以进一步阅读
Windows 上的网络 。
接下来
8 - 拓扑感知路由
拓扑感知路由 提供了一种机制帮助保持网络流量处于流量发起的区域内。 在集群中 Pod 之间优先使用相同区域的流量有助于提高可靠性、性能(网络延迟和吞吐量)或降低成本。
特性状态: Kubernetes v1.23 [beta]
说明:
在 Kubernetes 1.27 之前,此特性称为拓扑感知提示(Topology Aware Hint) 。
拓扑感知路由(Toplogy Aware Routing) 调整路由行为,以优先保持流量在其发起区域内。
在某些情况下,这有助于降低成本或提高网络性能。
动机
Kubernetes 集群越来越多地部署在多区域环境中。
拓扑感知路由 提供了一种机制帮助流量保留在其发起所在的区域内。
计算 服务(Service) 的端点时,
EndpointSlice 控制器考虑每个端点的物理拓扑(地区和区域),并填充提示字段以将其分配到区域。
诸如 kube-proxy
等集群组件可以使用这些提示,影响流量的路由方式(优先考虑物理拓扑上更近的端点)。
启用拓扑感知路由
说明:
在 Kubernetes 1.27 之前,此行为是通过 service.kubernetes.io/topology-aware-hints
注解来控制的。
你可以通过将 service.kubernetes.io/topology-mode
注解设置为 Auto
来启用 Service 的拓扑感知路由。
当每个区域中有足够的端点可用时,系统将为 EndpointSlices 填充拓扑提示,把每个端点分配给特定区域,
从而使流量被路由到更接近其来源的位置。
何时效果最佳
此特性在以下场景中的工作效果最佳:
1. 入站流量均匀分布
如果大部分流量源自同一个区域,则该流量可能会使分配到该区域的端点子集过载。
当预计入站流量源自同一区域时,不建议使用此特性。
2. 服务在每个区域具有至少 3 个端点
在一个三区域的集群中,这意味着有至少 9 个端点。如果每个区域的端点少于 3 个,
则 EndpointSlice 控制器很大概率(约 50%)无法平均分配端点,而是回退到默认的集群范围的路由方法。
工作原理
“自动”启发式算法会尝试按比例分配一定数量的端点到每个区域。
请注意,这种启发方式对具有大量端点的 Service 效果最佳。
EndpointSlice 控制器
当启用此启发方式时,EndpointSlice 控制器负责在各个 EndpointSlice 上设置提示信息。
控制器按比例给每个区域分配一定比例数量的端点。
这个比例基于在该区域中运行的节点的可分配
CPU 核心数。例如,如果一个区域有 2 个 CPU 核心,而另一个区域只有 1 个 CPU 核心,
那么控制器将给那个有 2 CPU 的区域分配两倍数量的端点。
以下示例展示了提供提示信息后 EndpointSlice 的样子:
apiVersion : discovery.k8s.io/v1
kind : EndpointSlice
metadata :
name : example-hints
labels :
kubernetes.io/service-name : example-svc
addressType : IPv4
ports :
- name : http
protocol : TCP
port : 80
endpoints :
- addresses :
- "10.1.2.3"
conditions :
ready : true
hostname : pod-1
zone : zone-a
hints :
forZones :
- name : "zone-a"
kube-proxy
kube-proxy 组件依据 EndpointSlice 控制器设置的提示,过滤由它负责路由的端点。
在大多数场合,这意味着 kube-proxy 可以把流量路由到同一个区域的端点。
有时,控制器在另一不同的区域中分配端点,以确保在多个区域之间更平均地分配端点。
这会导致部分流量被路由到其他区域。
保护措施
Kubernetes 控制平面和每个节点上的 kube-proxy 在使用拓扑感知提示信息前,会应用一些保护措施规则。
如果规则无法顺利通过,kube-proxy 将无视区域限制,从集群中的任意位置选择端点。
端点数量不足: 如果一个集群中,端点数量少于区域数量,控制器不创建任何提示。
不可能实现均衡分配: 在一些场合中,不可能实现端点在区域中的平衡分配。
例如,假设 zone-a 比 zone-b 大两倍,但只有 2 个端点,
那分配到 zone-a 的端点可能收到比 zone-b 多两倍的流量。
如果控制器不能确保此“期望的过载”值低于每一个区域可接受的阈值,控制器将不添加提示信息。
重要的是,这不是基于实时反馈。所以对于特定的端点仍有可能超载。
一个或多个 Node 信息不足: 如果任一节点没有设置标签 topology.kubernetes.io/zone
,
或没有上报可分配的 CPU 数据,控制平面将不会设置任何拓扑感知提示,
进而 kube-proxy 也就不能根据区域来过滤端点。
至少一个端点没有设置区域提示: 当这种情况发生时,
kube-proxy 会假设从拓扑感知提示到拓扑感知路由(或反方向)的迁移仍在进行中,
在这种场合下过滤 Service 的端点是有风险的,所以 kube-proxy 回退到使用所有端点。
提示中不存在某区域: 如果 kube-proxy 无法找到提示中指向它当前所在的区域的端点,
它将回退到使用来自所有区域的端点。当你向现有集群新增新的区域时,这种情况发生概率很高。
限制
当 Service 的 internalTrafficPolicy
值设置为 Local
时,
系统将不使用拓扑感知提示信息。你可以在同一集群中的不同 Service 上使用这两个特性,
但不能在同一个 Service 上这么做。
这种方法不适用于大部分流量来自于一部分区域的 Service。
相反,这项技术的假设是入站流量与各区域中节点的服务能力成比例关系。
EndpointSlice 控制器在计算各区域的比例时,会忽略未就绪的节点。
在大部分节点未就绪的场景下,这样做会带来非预期的结果。
EndpointSlice 控制器忽略设置了 node-role.kubernetes.io/control-plane
或
node-role.kubernetes.io/master
标签的节点。如果工作负载也在这些节点上运行,也可能会产生问题。
EndpointSlice 控制器在分派或计算各区域的比例时,并不会考虑
容忍度 。
如果 Service 背后的各 Pod 被限制只能运行在集群节点的一个子集上,计算比例时不会考虑这点。
这项技术和自动扩缩容机制之间可能存在冲突。例如,如果大量流量来源于同一个区域,
那只有分配到该区域的端点才可用来处理流量。这会导致
Pod 自动水平扩缩容
要么不能处理这种场景,要么会在别的区域添加 Pod。
自定义启发方式
Kubernetes 的部署方式有很多种,没有一种按区域分配端点的启发式方法能够适用于所有场景。
此特性的一个关键目标是:如果内置的启发方式不能满足你的使用场景,则可以开发自定义的启发方式。
启用自定义启发方式的第一步包含在了 1.27 版本中。
这是一个限制性较强的实现,可能尚未涵盖一些重要的、可进一步探索的场景。
接下来
9 - Windows 网络
Kubernetes 支持运行 Linux 或 Windows 节点。
你可以在统一集群内混布这两种节点。
本页提供了特定于 Windows 操作系统的网络概述。
Windows 容器网络
Windows 容器网络通过 CNI 插件 暴露。
Windows 容器网络的工作方式与虚拟机类似。
每个容器都有一个连接到 Hyper-V 虚拟交换机(vSwitch)的虚拟网络适配器(vNIC)。
主机网络服务(Host Networking Service,HNS)和主机计算服务(Host Compute Service,HCS)
协同创建容器并将容器 vNIC 挂接到网络。
HCS 负责管理容器,而 HNS 负责管理以下网络资源:
虚拟网络(包括创建 vSwitch)
Endpoint / vNIC
命名空间
包括数据包封装、负载均衡规则、ACL 和 NAT 规则在内的策略。
Windows HNS 和 vSwitch 实现命名空间划分,且可以按需为 Pod 或容器创建虚拟 NIC。
然而,诸如 DNS、路由和指标等许多配置将存放在 Windows 注册表数据库中,
而不是像 Linux 将这些配置作为文件存放在 /etc
内。
针对容器的 Windows 注册表与主机的注册表是分开的,因此将 /etc/resolv.conf
从主机映射到一个容器的类似概念与 Linux 上的效果不同。
这些必须使用容器环境中运行的 Windows API 进行配置。
因此,实现 CNI 时需要调用 HNS,而不是依赖文件映射将网络详情传递到 Pod 或容器中。
网络模式
Windows 支持五种不同的网络驱动/模式:L2bridge、L2tunnel、Overlay (Beta)、Transparent 和 NAT。
在 Windows 和 Linux 工作节点组成的异构集群中,你需要选择一个同时兼容 Windows 和 Linux 的网络方案。
下表列出了 Windows 支持的树外插件,并给出了何时使用每种 CNI 的建议:
网络驱动
描述
容器数据包修改
网络插件
网络插件特点
L2bridge
容器挂接到一个外部 vSwitch。容器挂接到下层网络,但物理网络不需要了解容器的 MAC,因为这些 MAC 在入站/出站时被重写。
MAC 被重写为主机 MAC,可使用 HNS OutboundNAT 策略将 IP 重写为主机 IP。
win-bridge 、Azure-CNI 、Flannel host-gateway 使用 win-bridge
win-bridge 使用 L2bridge 网络模式,将容器连接到主机的下层,提供最佳性能。节点间连接需要用户定义的路由(UDR)。
L2Tunnel
这是 L2bridge 的一种特例,但仅用在 Azure 上。所有数据包都会被发送到应用了 SDN 策略的虚拟化主机。
MAC 被重写,IP 在下层网络上可见。
Azure-CNI
Azure-CNI 允许将容器集成到 Azure vNET,允许容器充分利用 Azure 虚拟网络 所提供的能力集合。例如,安全地连接到 Azure 服务或使用 Azure NSG。参考 azure-cni 了解有关示例 。
Overlay
容器被赋予一个 vNIC,连接到外部 vSwitch。每个上层网络都有自己的 IP 子网,由自定义 IP 前缀进行定义。该上层网络驱动使用 VXLAN 封装。
用外部头进行封装。
win-overlay 、Flannel VXLAN(使用 win-overlay)
当需要将虚拟容器网络与主机的下层隔离时(例如出于安全原因),应使用 win-overlay。如果你的数据中心的 IP 个数有限,可以将 IP 在不同的上层网络中重用(带有不同的 VNID 标记)。在 Windows Server 2019 上这个选项需要 KB4489899 。
Transparent(ovn-kubernetes 的特殊用例)
需要一个外部 vSwitch。容器挂接到一个外部 vSwitch,由后者通过逻辑网络(逻辑交换机和路由器)实现 Pod 内通信。
数据包通过 GENEVE 或 STT 隧道进行封装,以到达其它主机上的 Pod。 数据包基于 OVN 网络控制器提供的隧道元数据信息被转发或丢弃。 南北向通信使用 NAT。
ovn-kubernetes
通过 ansible 部署 。通过 Kubernetes 策略可以实施分布式 ACL。支持 IPAM。无需 kube-proxy 即可实现负载均衡。无需 iptables/netsh 即可进行 NAT。
NAT(Kubernetes 中未使用 )
容器被赋予一个 vNIC,连接到内部 vSwitch。DNS/DHCP 是使用一个名为 WinNAT 的内部组件 实现的
MAC 和 IP 重写为主机 MAC/IP。
nat
放在此处保持完整性。
如上所述,Windows 通过 VXLAN 网络后端 (Beta 支持 ;委派给 win-overlay)
和 host-gateway 网络后端 (稳定支持;委派给 win-bridge)
也支持 Flannel 的 CNI 插件 。
此插件支持委派给参考 CNI 插件(win-overlay、win-bridge)之一,配合使用 Windows
上的 Flannel 守护程序(Flanneld),以便自动分配节点子网租赁并创建 HNS 网络。
该插件读取自己的配置文件(cni.conf),并聚合 FlannelD 生成的 subnet.env 文件中的环境变量。
然后,委派给网络管道的参考 CNI 插件之一,并将包含节点分配子网的正确配置发送给 IPAM 插件(例如:host-local
)。
对于 Node、Pod 和 Service 对象,TCP/UDP 流量支持以下网络流:
Pod → Pod(IP)
Pod → Pod(名称)
Pod → Service(集群 IP)
Pod → Service(PQDN,但前提是没有 ".")
Pod → Service(FQDN)
Pod → 外部(IP)
Pod → 外部(DNS)
Node → Pod
Pod → Node
IP 地址管理(IPAM)
Windows 支持以下 IPAM 选项:
负载均衡和 Service
Kubernetes Service 是一种抽象:定义了逻辑上的一组 Pod 和一种通过网络访问这些 Pod 的方式。
在包含 Windows 节点的集群中,你可以使用以下类别的 Service:
NodePort
ClusterIP
LoadBalancer
ExternalName
Windows 容器网络与 Linux 网络有着很重要的差异。
更多细节和背景信息,参考 Microsoft Windows 容器网络文档 。
在 Windows 上,你可以使用以下设置来配置 Service 和负载均衡行为:
Windows Service 设置
功能特性
描述
支持的 Windows 操作系统最低版本
启用方式
会话亲和性
确保每次都将来自特定客户端的连接传递到同一个 Pod。
Windows Server 2022
将 service.spec.sessionAffinity
设为 “ClientIP”
Direct Server Return (DSR)
在负载均衡模式中 IP 地址修正和 LBNAT 直接发生在容器 vSwitch 端口;服务流量到达时源 IP 设置为原始 Pod IP。
Windows Server 2019
在 kube-proxy 中设置以下标志:--feature-gates="WinDSR=true" --enable-dsr=true
保留目标(Preserve-Destination)
跳过服务流量的 DNAT,从而在到达后端 Pod 的数据包中保留目标服务的虚拟 IP。也会禁用节点间的转发。
Windows Server,version 1903
在服务注解中设置 "preserve-destination": "true"
并在 kube-proxy 中启用 DSR。
IPv4/IPv6 双栈网络
进出集群和集群内通信都支持原生的 IPv4 间与 IPv6 间流量
Windows Server 2019
参考 IPv4/IPv6 双栈 。
客户端 IP 保留
确保入站流量的源 IP 得到保留。也会禁用节点间转发。
Windows Server 2019
将 service.spec.externalTrafficPolicy
设置为 “Local” 并在 kube-proxy 中启用 DSR。
警告: 如果目的地节点在运行 Windows Server 2022,则上层网络的 NodePort Service 存在已知问题。
要完全避免此问题,可以使用 externalTrafficPolicy: Local
配置服务。
在安装了 KB5005619 的 Windows Server 2022 或更高版本上,采用 L2bridge 网络时
Pod 间连接存在已知问题。
要解决此问题并恢复 Pod 间连接,你可以在 kube-proxy 中禁用 WinDSR 功能。
这些问题需要操作系统修复。
有关更新,请参考 https://github.com/microsoft/Windows-Containers/issues/204 。
限制
Windows 节点不支持 以下网络功能:
主机网络模式
从节点本身访问本地 NodePort(可以从其他节点或外部客户端进行访问)
为同一 Service 提供 64 个以上后端 Pod(或不同目的地址)
在连接到上层网络的 Windows Pod 之间使用 IPv6 通信
非 DSR 模式中的本地流量策略(Local Traffic Policy)
通过 win-overlay
、win-bridge
使用 ICMP 协议,或使用 Azure-CNI 插件进行出站通信。
具体而言,Windows 数据平面(VFP )不支持 ICMP 数据包转换,这意味着:
指向同一网络内目的地址的 ICMP 数据包(例如 Pod 间的 ping 通信)可正常工作;
TCP/UDP 数据包可正常工作;
通过远程网络指向其它地址的 ICMP 数据包(例如通过 ping 从 Pod 到外部公网的通信)无法被转换,
因此无法被路由回到这些数据包的源点;
由于 TCP/UDP 数据包仍可被转换,所以在调试与外界的连接时,
你可以将 ping <destination>
替换为 curl <destination>
。
其他限制:
由于缺少 CHECK
实现,Windows 参考网络插件 win-bridge 和 win-overlay 未实现
CNI 规约 的 v0.4.0 版本。
Flannel VXLAN CNI 插件在 Windows 上有以下限制:
使用 Flannel v0.12.0(或更高版本)时,节点到 Pod 的连接仅适用于本地 Pod。
Flannel 仅限于使用 VNI 4096 和 UDP 端口 4789。
有关这些参数的更多详细信息,请参考官方的 Flannel VXLAN 后端文档。
10 - Service ClusterIP 分配
在 Kubernetes 中,Service 是一种抽象的方式,
用于公开在一组 Pod 上运行的应用。
Service 可以具有集群作用域的虚拟 IP 地址(使用 type: ClusterIP
的 Service)。
客户端可以使用该虚拟 IP 地址进行连接,Kubernetes 通过不同的后台 Pod 对该 Service 的流量进行负载均衡。
Service ClusterIP 是如何分配的?
当 Kubernetes 需要为 Service 分配虚拟 IP 地址时,该分配会通过以下两种方式之一进行:
动态分配
集群的控制面自动从所配置的 IP 范围内为 type: ClusterIP
选择一个空闲 IP 地址。
静态分配
根据为 Service 所配置的 IP 范围,选定并设置你的 IP 地址。
在整个集群中,每个 Service 的 ClusterIP
都必须是唯一的。
尝试使用已分配的 ClusterIP
创建 Service 将返回错误。
为什么需要预留 Service 的 ClusterIP ?
有时你可能希望 Services 在众所周知的 IP 上面运行,以便集群中的其他组件和用户可以使用它们。
最好的例子是集群的 DNS Service。作为一种非强制性的约定,一些 Kubernetes 安装程序
将 Service IP 范围中的第 10 个 IP 地址分配给 DNS 服务。假设将集群的 Service IP 范围配置为
10.96.0.0/16,并且希望 DNS Service IP 为 10.96.0.10,则必须创建如下 Service:
apiVersion : v1
kind : Service
metadata :
labels :
k8s-app : kube-dns
kubernetes.io/cluster-service : "true"
kubernetes.io/name : CoreDNS
name : kube-dns
namespace : kube-system
spec :
clusterIP : 10.96.0.10
ports :
- name : dns
port : 53
protocol : UDP
targetPort : 53
- name : dns-tcp
port : 53
protocol : TCP
targetPort : 53
selector :
k8s-app : kube-dns
type : ClusterIP
但如前所述,IP 地址 10.96.0.10 尚未被保留。如果在 DNS 启动之前或同时采用动态分配机制创建其他 Service,
则它们有可能被分配此 IP,因此,你将无法创建 DNS Service,因为它会因冲突错误而失败。
如何避免 Service ClusterIP 冲突?
Kubernetes 中用來将 ClusterIP 分配给 Service 的分配策略降低了冲突的风险。
ClusterIP
范围根据公式 min(max(16, cidrSize / 16), 256)
进行划分,
描述为不小于 16 且不大于 256,并在二者之间有一个渐进的步长。
默认情况下,动态 IP 分配使用地址较高的一段,一旦用完,它将使用较低范围。
这将允许用户在冲突风险较低的较低地址段上使用静态分配。
示例
示例 1
此示例使用 IP 地址范围:10.96.0.0/24(CIDR 表示法)作为 Service 的 IP 地址。
范围大小:28 - 2 = 254
带宽偏移量:min(max(16, 256/16), 256)
= min(16, 256)
= 16
静态带宽起始地址:10.96.0.1
静态带宽结束地址:10.96.0.16
范围结束地址:10.96.0.254
pie showData
title 10.96.0.0/24
"静态分配" : 16
"动态分配" : 238
示例 2
此示例使用 IP 地址范围 10.96.0.0/20(CIDR 表示法)作为 Service 的 IP 地址。
范围大小:212 - 2 = 4094
带宽偏移量:min(max(16, 4096/16), 256)
= min(256, 256)
= 256
静态带宽起始地址:10.96.0.1
静态带宽结束地址:10.96.1.0
范围结束地址:10.96.15.254
pie showData
title 10.96.0.0/20
"静态分配" : 256
"动态分配" : 3838
示例 3
此示例使用 IP 地址范围 10.96.0.0/16(CIDR 表示法)作为 Service 的 IP 地址。
范围大小:216 - 2 = 65534
带宽偏移量:min(max(16, 65536/16), 256)
= min(4096, 256)
= 256
静态带宽起始地址:10.96.0.1
静态带宽结束地址:10.96.1.0
范围结束地址:10.96.255.254
pie showData
title 10.96.0.0/16
"静态分配" : 256
"动态分配" : 65278
接下来
11 - 服务内部流量策略
如果集群中的两个 Pod 想要通信,并且两个 Pod 实际上都在同一节点运行, 服务内部流量策略 可以将网络流量限制在该节点内。 通过集群网络避免流量往返有助于提高可靠性、增强性能(网络延迟和吞吐量)或降低成本。
特性状态: Kubernetes v1.26 [stable]
服务内部流量策略 开启了内部流量限制,将内部流量只路由到发起方所处节点内的服务端点。
这里的”内部“流量指当前集群中的 Pod 所发起的流量。
这种机制有助于节省开销,提升效率。
使用服务内部流量策略
你可以通过将 Service 的
.spec.internalTrafficPolicy
项设置为 Local
,
来为它指定一个内部专用的流量策略。
此设置就相当于告诉 kube-proxy 对于集群内部流量只能使用节点本地的服务端口。
说明: 如果某节点上的 Pod 均不提供指定 Service 的服务端点,
即使该 Service 在其他节点上有可用的服务端点,
Service 的行为看起来也像是它只有 0 个服务端点(只针对此节点上的 Pod)。
以下示例展示了把 Service 的 .spec.internalTrafficPolicy
项设为 Local
时,
Service 的样子:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
internalTrafficPolicy : Local
工作原理
kube-proxy 基于 spec.internalTrafficPolicy
的设置来过滤路由的目标服务端点。
当它的值设为 Local
时,只会选择节点本地的服务端点。
当它的值设为 Cluster
或缺省时,Kubernetes 会选择所有的服务端点。
接下来