这是本节的多页打印视图。
点击此处打印 .
返回本页常规视图 .
服务、负载均衡和联网
Kubernetes 网络背后的概念和资源。
Kubernetes 网络模型
每一个 Pod
都有它自己的IP地址,
这就意味着你不需要显式地在 Pod
之间创建链接, 你几乎不需要处理容器端口到主机端口之间的映射。
这将形成一个干净的、向后兼容的模型;在这个模型里,从端口分配、命名、服务发现、
负载均衡 、应用配置和迁移的角度来看,
Pod
可以被视作虚拟机或者物理主机。
Kubernetes 强制要求所有网络设施都满足以下基本要求(从而排除了有意隔离网络的策略):
节点 上的 Pod 可以不通过 NAT 和其他任何节点上的 Pod 通信
节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有 Pod 通信
备注:对于支持在主机网络中运行 Pod
的平台(比如:Linux):
运行在节点主机网络里的 Pod 可以不通过 NAT 和所有节点上的 Pod 通信
这个模型不仅不复杂,而且还和 Kubernetes 的实现从虚拟机向容器平滑迁移的初衷相符,
如果你的任务开始是在虚拟机中运行的,你的虚拟机有一个 IP,
可以和项目中其他虚拟机通信。这里的模型是基本相同的。
Kubernetes 的 IP 地址存在于 Pod
范围内 - 容器共享它们的网络命名空间 - 包括它们的 IP 地址和 MAC 地址。
这就意味着 Pod
内的容器都可以通过 localhost
到达对方端口。
这也意味着 Pod
内的容器需要相互协调端口的使用,但是这和虚拟机中的进程似乎没有什么不同,
这也被称为“一个 Pod 一个 IP”模型。
如何实现以上需求是所使用的特定容器运行时的细节。
也可以在 Node
本身请求端口,并用这类端口转发到你的 Pod
(称之为主机端口),
但这是一个很特殊的操作。转发方式如何实现也是容器运行时的细节。
Pod
自己并不知道这些主机端口的存在。
Kubernetes 网络解决四方面的问题:
1 - 使用拓扑键实现拓扑感知的流量路由
FEATURE STATE: Kubernetes v1.21 [deprecated]
说明:
此功能特性,尤其是 Alpha 阶段的 topologyKeys
API,在 Kubernetes v1.21
版本中已被废弃。Kubernetes v1.21 版本中引入的
拓扑感知的提示 ,
提供类似的功能。
服务拓扑(Service Topology)可以让一个服务基于集群的 Node 拓扑进行流量路由。
例如,一个服务可以指定流量是被优先路由到一个和客户端在同一个 Node 或者在同一可用区域的端点。
拓扑感知的流量路由
默认情况下,发往 ClusterIP
或者 NodePort
服务的流量可能会被路由到
服务的任一后端的地址。Kubernetes 1.7 允许将“外部”流量路由到接收到流量的
节点上的 Pod。对于 ClusterIP
服务,无法完成同节点优先的路由,你也无法
配置集群优选路由到同一可用区中的端点。
通过在 Service 上配置 topologyKeys
,你可以基于来源节点和目标节点的
标签来定义流量路由策略。
通过对源和目的之间的标签匹配,作为集群操作者的你可以根据节点间彼此“较近”和“较远”
来定义节点集合。你可以基于符合自身需求的任何度量值来定义标签。
例如,在公有云上,你可能更偏向于把流量控制在同一区内,因为区间流量是有费用成本的,
而区内流量则没有。
其它常见需求还包括把流量路由到由 DaemonSet
管理的本地 Pod 上,或者
把将流量转发到连接在同一机架交换机的节点上,以获得低延时。
使用服务拓扑
如果集群启用了 ServiceTopology
特性门控 ,
你就可以在 Service 规约中设定 topologyKeys
字段,从而控制其流量路由。
此字段是 Node
标签的优先顺序字段,将用于在访问这个 Service
时对端点进行排序。
流量会被定向到第一个标签值和源 Node
标签值相匹配的 Node
。
如果这个 Service
没有匹配的后端 Node
,那么第二个标签会被使用做匹配,
以此类推,直到没有标签。
如果没有匹配到,流量会被拒绝,就如同这个 Service
根本没有后端。
换言之,系统根据可用后端的第一个拓扑键来选择端点。
如果这个字段被配置了而没有后端可以匹配客户端拓扑,那么这个 Service
对那个客户端是没有后端的,链接应该是失败的。
这个字段配置为 "*"
意味着任意拓扑。
这个通配符值如果使用了,那么只有作为配置值列表中的最后一个才有用。
如果 topologyKeys
没有指定或者为空,就没有启用这个拓扑约束。
一个集群中,其 Node
的标签被打为其主机名,区域名和地区名。
那么就可以设置 Service
的 topologyKeys
的值,像下面的做法一样定向流量了。
只定向到同一个 Node
上的端点,Node
上没有端点存在时就失败:
配置 ["kubernetes.io/hostname"]
。
偏向定向到同一个 Node
上的端点,回退同一区域的端点上,然后是同一地区,
其它情况下就失败:配置 ["kubernetes.io/hostname", "topology.kubernetes.io/zone", "topology.kubernetes.io/region"]
。
这或许很有用,例如,数据局部性很重要的情况下。
偏向于同一区域,但如果此区域中没有可用的终结点,则回退到任何可用的终结点:
配置 ["topology.kubernetes.io/zone", "*"]
。
约束条件
服务拓扑和 externalTrafficPolicy=Local
是不兼容的,所以 Service
不能同时使用这两种特性。
但是在同一个集群的不同 Service
上是可以分别使用这两种特性的,只要不在同一个
Service
上就可以。
有效的拓扑键目前只有:kubernetes.io/hostname
、topology.kubernetes.io/zone
和
topology.kubernetes.io/region
,但是未来会推广到其它的 Node
标签。
拓扑键必须是有效的标签,并且最多指定16个。
通配符:"*"
,如果要用,则必须是拓扑键值的最后一个值。
示例
以下是使用服务拓扑功能的常见示例。
仅节点本地端点
仅路由到节点本地端点的一种服务。如果节点上不存在端点,流量则被丢弃:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "kubernetes.io/hostname"
首选节点本地端点
首选节点本地端点,如果节点本地端点不存在,则回退到集群范围端点的一种服务:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "kubernetes.io/hostname"
- "*"
仅地域或区域端点
首选地域端点而不是区域端点的一种服务。 如果以上两种范围内均不存在端点,
流量则被丢弃。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "topology.kubernetes.io/zone"
- "topology.kubernetes.io/region"
优先选择节点本地端点、地域端点,然后是区域端点
优先选择节点本地端点,地域端点,然后是区域端点,最后才是集群范围端点的
一种服务。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "kubernetes.io/hostname"
- "topology.kubernetes.io/zone"
- "topology.kubernetes.io/region"
- "*"
接下来
2 - 服务
将运行在一组
Pods 上的应用程序公开为网络服务的抽象方法。
使用 Kubernetes,你无需修改应用程序即可使用不熟悉的服务发现机制。
Kubernetes 为 Pods 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名,
并且可以在它们之间进行负载均衡。
动机
创建和销毁 Kubernetes Pod 以匹配集群状态。
Pod 是非永久性资源。
如果你使用 Deployment
来运行你的应用程序,则它可以动态创建和销毁 Pod。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,
那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?
进入 Services 。
Service 资源
Kubernetes Service 定义了这样一种抽象:逻辑上的一组 Pod,一种可以访问它们的策略 —— 通常称为微服务。
Service 所针对的 Pods 集合通常是通过选择算符 来确定的。
要了解定义服务端点的其他方法,请参阅不带选择算符的服务 。
举个例子,考虑一个图片处理后端,它运行了 3 个副本。这些副本是可互换的 ——
前端不需要关心它们调用了哪个后端副本。
然而组成这一组后端程序的 Pod 实际上可能会发生变化,
前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。
Service 定义的抽象能够解耦这种关联。
云原生服务发现
如果你想要在应用程序中使用 Kubernetes API 进行服务发现,则可以查询
API 服务器
的 Endpoints 资源,只要服务中的 Pod 集合发生更改,Endpoints 就会被更新。
对于非本机应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。
定义 Service
Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。
像所有的 REST 对象一样,Service 定义可以基于 POST
方式,请求 API server 创建新的实例。
Service 对象的名称必须是合法的
RFC 1035 标签名称 .。
例如,假定有一组 Pod,它们对外暴露了 9376 端口,同时还被打上 app=MyApp
标签:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
上述配置创建一个名称为 "my-service" 的 Service 对象,它会将请求代理到使用
TCP 端口 9376,并且具有标签 "app=MyApp"
的 Pod 上。
Kubernetes 为该服务分配一个 IP 地址(有时称为 "集群IP"),该 IP 地址由服务代理使用。
(请参见下面的 VIP 和 Service 代理 ).
服务选择算符的控制器不断扫描与其选择器匹配的 Pod,然后将所有更新发布到也称为
“my-service” 的 Endpoint 对象。
说明: 需要注意的是,Service 能够将一个接收 port
映射到任意的 targetPort
。
默认情况下,targetPort
将被设置为与 port
字段相同的值。
Pod 中的端口定义是有名字的,你可以在服务的 targetPort
属性中引用这些名称。
即使服务中使用单个配置的名称混合使用 Pod,并且通过不同的端口号提供相同的网络协议,此功能也可以使用。
这为部署和发展服务提供了很大的灵活性。
例如,你可以更改 Pods 在新版本的后端软件中公开的端口号,而不会破坏客户端。
服务的默认协议是 TCP;你还可以使用任何其他受支持的协议 。
由于许多服务需要公开多个端口,因此 Kubernetes 在服务对象上支持多个端口定义。
每个端口定义可以具有相同的 protocol
,也可以具有不同的协议。
没有选择算符的 Service
服务最常见的是抽象化对 Kubernetes Pod 的访问,但是它们也可以抽象化其他种类的后端。
实例:
希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
希望服务指向另一个 名字空间(Namespace) 中或其它集群中的服务。
你正在将工作负载迁移到 Kubernetes。 在评估该方法时,你仅在 Kubernetes 中运行一部分后端。
在任何这些场景中,都能够定义没有选择算符的 Service。
实例:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9376
由于此服务没有选择算符,因此不会自动创建相应的 Endpoint 对象。
你可以通过手动添加 Endpoint 对象,将服务手动映射到运行该服务的网络地址和端口:
apiVersion : v1
kind : Endpoints
metadata :
name : my-service
subsets :
- addresses :
- ip : 192.0.2.42
ports :
- port : 9376
Endpoints 对象的名称必须是合法的
DNS 子域名 。
说明: 端点 IPs 必须不可以 是:本地回路(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 作为目标。
访问没有选择算符的 Service,与有选择算符的 Service 的原理相同。
请求将被路由到用户定义的 Endpoint,YAML 中为:192.0.2.42:9376
(TCP)。
ExternalName Service 是 Service 的特例,它没有选择算符,但是使用 DNS 名称。
有关更多信息,请参阅本文档后面的ExternalName 。
超出容量的 Endpoints
如果某个 Endpoints 资源中包含的端点个数超过 1000,则 Kubernetes v1.22 版本
(及更新版本)的集群会将为该 Endpoints 添加注解
endpoints.kubernetes.io/over-capacity: truncated
。
这一注解表明所影响到的 Endpoints 对象已经超出容量,此外 Endpoints 控制器还会将 Endpoints 对象数量截断到 1000。
EndpointSlices
FEATURE STATE: Kubernetes v1.21 [stable]
EndpointSlices 是一种 API 资源,可以为 Endpoints 提供更可扩展的替代方案。
尽管从概念上讲与 Endpoints 非常相似,但 EndpointSlices 允许跨多个资源分布网络端点。
默认情况下,一旦到达 100 个 Endpoint,该 EndpointSlice 将被视为“已满”,
届时将创建其他 EndpointSlices 来存储任何其他 Endpoints。
EndpointSlices 提供了附加的属性和功能,这些属性和功能在
EndpointSlices
中有详细描述。
应用协议
FEATURE STATE: Kubernetes v1.20 [stable]
appProtocol
字段提供了一种为每个 Service 端口指定应用协议的方式。
此字段的取值会被映射到对应的 Endpoints 和 EndpointSlices 对象。
该字段遵循标准的 Kubernetes 标签语法。
其值可以是 IANA 标准服务名称
或以域名为前缀的名称,如 mycompany.com/my-custom-protocol
。
虚拟 IP 和 Service 代理
在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy
进程。
kube-proxy
负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是
ExternalName
的形式。
为什么不使用 DNS 轮询?
时不时会有人问到为什么 Kubernetes 依赖代理将入站流量转发到后端。那其他方法呢?
例如,是否可以配置具有多个 A 值(或 IPv6 为 AAAA)的 DNS 记录,并依靠轮询名称解析?
使用服务代理有以下几个原因:
DNS 实现的历史由来已久,它不遵守记录 TTL,并且在名称查找结果到期后对其进行缓存。
有些应用程序仅执行一次 DNS 查找,并无限期地缓存结果。
即使应用和库进行了适当的重新解析,DNS 记录上的 TTL 值低或为零也可能会给
DNS 带来高负载,从而使管理变得困难。
userspace 代理模式
这种模式,kube-proxy 会监视 Kubernetes 控制平面对 Service 对象和 Endpoints 对象的添加和移除操作。
对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。
任何连接到“代理端口”的请求,都会被代理到 Service 的后端 Pods
中的某个上面(如 Endpoints
所报告的一样)。
使用哪个后端 Pod,是 kube-proxy 基于 SessionAffinity
来确定的。
最后,它配置 iptables 规则,捕获到达该 Service 的 clusterIP
(是虚拟 IP)
和 Port
的请求,并重定向到代理端口,代理端口再代理请求到后端Pod。
默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。
iptables 代理模式
这种模式,kube-proxy
会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。
对每个 Service,它会配置 iptables 规则,从而捕获到达该 Service 的 clusterIP
和端口的请求,进而将请求重定向到 Service 的一组后端中的某个 Pod 上面。
对于每个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个后端组合。
默认的策略是,kube-proxy 在 iptables 模式下随机选择一个后端。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理,
而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应,
则连接失败。
这与用户空间模式不同:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接已失败,
并会自动使用其他后端 Pod 重试。
你可以使用 Pod 就绪探测器
验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。
这样做意味着你避免将流量通过 kube-proxy 发送到已知已失败的 Pod。
IPVS 代理模式
FEATURE STATE: Kubernetes v1.11 [stable]
在 ipvs
模式下,kube-proxy 监视 Kubernetes 服务和端点,调用 netlink
接口相应地创建 IPVS 规则,
并定期将 IPVS 规则与 Kubernetes 服务和端点同步。 该控制循环可确保IPVS
状态与所需状态匹配。访问服务时,IPVS 将流量定向到后端Pod之一。
IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数,
但是使用哈希表作为基础数据结构,并且在内核空间中工作。
这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy
重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。
与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS 提供了更多选项来平衡后端 Pod 的流量。 这些是:
rr
:轮替(Round-Robin)
lc
:最少链接(Least Connection),即打开链接数量最少者优先
dh
:目标地址哈希(Destination Hashing)
sh
:源地址哈希(Source Hashing)
sed
:最短预期延迟(Shortest Expected Delay)
nq
:从不排队(Never Queue)
说明: 要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前使 IPVS 在节点上可用。
当 kube-proxy 以 IPVS 代理模式启动时,它将验证 IPVS 内核模块是否可用。
如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。
在这些代理模型中,绑定到服务 IP 的流量:
在客户端不了解 Kubernetes 或服务或 Pod 的任何信息的情况下,将 Port 代理到适当的后端。
如果要确保每次都将来自特定客户端的连接传递到同一 Pod,
则可以通过将 service.spec.sessionAffinity
设置为 "ClientIP"
(默认值是 "None"),来基于客户端的 IP 地址选择会话关联。
你还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来设置最大会话停留时间。
(默认值为 10800 秒,即 3 小时)。
多端口 Service
对于某些服务,你需要公开多个端口。
Kubernetes 允许你在 Service 对象上配置多个端口定义。
为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : 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 地址
在 Service
创建的请求中,可以通过设置 spec.clusterIP
字段来指定自己的集群 IP 地址。
比如,希望替换一个已经已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。
用户选择的 IP 地址必须合法,并且这个 IP 地址在 service-cluster-ip-range
CIDR 范围内,
这对 API 服务器来说是通过一个标识来指定的。
如果 IP 地址不合法,API 服务器会返回 HTTP 状态码 422,表示值不合法。
流量策略
外部流量策略
你可以通过设置 spec.externalTrafficPolicy
字段来控制来自于外部的流量是如何路由的。
可选值有 Cluster
和 Local
。字段设为 Cluster
会将外部流量路由到所有就绪的端点,
设为 Local
会只路由到当前节点上就绪的端点。
如果流量策略设置为 Local
,而且当前节点上没有就绪的端点,kube-proxy 不会转发请求相关服务的任何流量。
说明:
FEATURE STATE: Kubernetes v1.22 [alpha]
如果你启用了 kube-proxy 的 ProxyTerminatingEndpoints
特性门控 ,
kube-proxy 会检查节点是否有本地的端点,以及是否所有的本地端点都被标记为终止中。
如果本地有端点,而且所有端点处于终止中的状态,那么 kube-proxy 会忽略任何设为 Local
的外部流量策略。
在所有本地端点处于终止中的状态的同时,kube-proxy 将请求指定服务的流量转发到位于其它节点的
状态健康的端点,如同外部流量策略设为 Cluster
。
针对处于正被终止状态的端点这一转发行为使得外部负载均衡器可以优雅地排出由
NodePort
服务支持的连接,就算是健康检查节点端口开始失败也是如此。
否则,当节点还在负载均衡器的节点池内,在 Pod 终止过程中的流量会被丢掉,这些流量可能会丢失。
内部流量策略
FEATURE STATE: Kubernetes v1.22 [beta]
你可以设置 spec.internalTrafficPolicy
字段来控制内部来源的流量是如何转发的。可设置的值有 Cluster
和 Local
。
将字段设置为 Cluster
会将内部流量路由到所有就绪端点,设置为 Local
只会路由到当前节点上就绪的端点。
如果流量策略是 Local
,而且当前节点上没有就绪的端点,那么 kube-proxy 会丢弃流量。
服务发现
Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS。
环境变量
当 Pod 运行在 Node
上,kubelet 会为每个活跃的 Service 添加一组环境变量。
它同时支持 Docker links兼容 变量
(查看 makeLinkVariables )、
简单的 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量。
这里 Service 的名称需大写,横线被转换成下划线。
举个例子,一个名称为 redis-master
的 Service 暴露了 TCP 端口 6379,
同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
REDIS_MASTER_SERVICE_HOST = 10.0.0.11
REDIS_MASTER_SERVICE_PORT = 6379
REDIS_MASTER_PORT = tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP = tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO = tcp
REDIS_MASTER_PORT_6379_TCP_PORT = 6379
REDIS_MASTER_PORT_6379_TCP_ADDR = 10.0.0.11
说明: 当你具有需要访问服务的 Pod 时,并且你正在使用环境变量方法将端口和集群 IP 发布到客户端
Pod 时,必须在客户端 Pod 出现 之前 创建服务。
否则,这些客户端 Pod 将不会设定其环境变量。
如果仅使用 DNS 查找服务的集群 IP,则无需担心此设定问题。
DNS
你可以(几乎总是应该)使用附加组件
为 Kubernetes 集群设置 DNS 服务。
支持集群的 DNS 服务器(例如 CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录。
如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。
例如,如果你在 Kubernetes 命名空间 my-ns
中有一个名为 my-service
的服务,
则控制平面和 DNS 服务共同为 my-service.my-ns
创建 DNS 记录。
my-ns
命名空间中的 Pod 应该能够通过按名检索 my-service
来找到服务
(my-service.my-ns
也可以工作)。
其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns
。
这些名称将解析为为服务分配的集群 IP。
Kubernetes 还支持命名端口的 DNS SRV(服务)记录。
如果 my-service.my-ns
服务具有名为 http
的端口,且协议设置为 TCP,
则可以对 _http._tcp.my-service.my-ns
执行 DNS SRV 查询查询以发现该端口号,
"http"
以及 IP 地址。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName
类型的 Service 的方式。
更多关于 ExternalName
信息可以查看
DNS Pod 和 Service 。
无头服务(Headless Services)
有时不需要或不想要负载均衡,以及单独的 Service IP。
遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP
)的值为 "None"
来创建 Headless
Service。
你可以使用无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes
的实现捆绑在一起。
对这无头 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,
而且平台也不会为它们进行负载均衡和路由。
DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。
带选择算符的服务
对定义了选择算符的无头服务,Endpoint 控制器在 API 中创建了 Endpoints 记录,
并且修改 DNS 配置返回 A 记录(IP 地址),通过这个地址直接到达 Service
的后端 Pod 上。
无选择算符的服务
对没有定义选择算符的无头服务,Endpoint 控制器不会创建 Endpoints
记录。
然而 DNS 系统会查找和配置,无论是:
对于 ExternalName
类型的服务,查找其 CNAME 记录
对所有其他类型的服务,查找与 Service 名称相同的任何 Endpoints
的记录
发布服务(服务类型)
对一些应用的某些部分(如前端),可能希望将其暴露给 Kubernetes 集群外部
的 IP 地址。
Kubernetes ServiceTypes
允许指定你所需要的 Service 类型,默认是 ClusterIP
。
Type
的取值以及行为如下:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。
这也是默认的 ServiceType
。
NodePort
:通过每个节点上的 IP 和静态端口(NodePort
)暴露服务。
NodePort
服务会路由到自动创建的 ClusterIP
服务。
通过请求 <节点 IP>:<节点端口>
,你可以从集群的外部访问一个 NodePort
服务。
LoadBalancer
:使用云提供商的负载均衡器向外部暴露服务。
外部负载均衡器可以将流量路由到自动创建的 NodePort
服务和 ClusterIP
服务上。
ExternalName
:通过返回 CNAME
和对应值,可以将服务映射到
externalName
字段的内容(例如,foo.bar.example.com
)。
无需创建任何类型代理。
说明: 你需要使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用 ExternalName
类型。
你也可以使用 Ingress 来暴露自己的服务。
Ingress 不是一种服务类型,但它充当集群的入口点。
它可以将路由规则整合到一个资源中,因为它可以在同一IP地址下公开多个服务。
NodePort 类型
如果你将 type
字段设置为 NodePort
,则 Kubernetes 控制平面将在
--service-node-port-range
标志指定的范围内分配端口(默认值:30000-32767)。
每个节点将那个端口(每个节点上的相同端口号)代理到你的服务中。
你的服务在其 .spec.ports[*].nodePort
字段中要求分配的端口。
如果你想指定特定的 IP 代理端口,则可以设置 kube-proxy 中的 --nodeport-addresses
参数
或者将kube-proxy 配置文件
中的等效 nodePortAddresses
字段设置为特定的 IP 块。
该标志采用逗号分隔的 IP 块列表(例如,10.0.0.0/8
、192.0.2.0/25
)来指定
kube-proxy 应该认为是此节点本地的 IP 地址范围。
例如,如果你使用 --nodeport-addresses=127.0.0.0/8
标志启动 kube-proxy,
则 kube-proxy 仅选择 NodePort Services 的本地回路接口。
--nodeport-addresses
的默认值是一个空列表。
这意味着 kube-proxy 应该考虑 NodePort 的所有可用网络接口。
(这也与早期的 Kubernetes 版本兼容)。
如果需要特定的端口号,你可以在 nodePort
字段中指定一个值。
控制平面将为你分配该端口或报告 API 事务失败。
这意味着你需要自己注意可能发生的端口冲突。
你还必须使用有效的端口号,该端口号在配置用于 NodePort 的范围内。
使用 NodePort 可以让你自由设置自己的负载均衡解决方案,
配置 Kubernetes 不完全支持的环境,
甚至直接暴露一个或多个节点的 IP。
需要注意的是,Service 能够通过 <NodeIP>:spec.ports[*].nodePort
和
spec.clusterIp:spec.ports[*].port
而对外可见。
如果设置了 kube-proxy 的 --nodeport-addresses
参数或 kube-proxy 配置文件中的等效字段,
<NodeIP>
将被过滤 NodeIP。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
type : NodePort
selector :
app : MyApp
ports :
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port : 80
targetPort : 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort : 30007
LoadBalancer 类型
在使用支持外部负载均衡器的云提供商的服务时,设置 type
的值为 "LoadBalancer"
,
将为 Service 提供负载均衡器。
负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的
status.loadBalancer
字段发布出去。
实例:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
clusterIP : 10.0.171.239
type : LoadBalancer
status :
loadBalancer :
ingress :
- ip : 192.0.2.127
来自外部负载均衡器的流量将直接重定向到后端 Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。
某些云提供商允许设置 loadBalancerIP
。
在这些情况下,将根据用户设置的 loadBalancerIP
来创建负载均衡器。
如果没有设置 loadBalancerIP
字段,将会给负载均衡器指派一个临时 IP。
如果设置了 loadBalancerIP
,但云提供商并不支持这种特性,那么设置的
loadBalancerIP
值将会被忽略掉。
混合协议类型的负载均衡器
FEATURE STATE: Kubernetes v1.20 [alpha]
默认情况下,对于 LoadBalancer 类型的服务,当定义了多个端口时,所有
端口必须具有相同的协议,并且该协议必须是受云提供商支持的协议。
如果为 kube-apiserver 启用了 MixedProtocolLBService
特性门控,
则当定义了多个端口时,允许使用不同的协议。
说明: 可用于 LoadBalancer 类型服务的协议集仍然由云提供商决定。
禁用负载均衡器节点端口分配
FEATURE STATE: Kubernetes v1.20 [alpha]
从 v1.20 版本开始, 你可以通过设置 spec.allocateLoadBalancerNodePorts
为 false
对类型为 LoadBalancer 的服务禁用节点端口分配。
这仅适用于直接将流量路由到 Pod 而不是使用节点端口的负载均衡器实现。
默认情况下,spec.allocateLoadBalancerNodePorts
为 true
,
LoadBalancer 类型的服务继续分配节点端口。
如果现有服务已被分配节点端口,将参数 spec.allocateLoadBalancerNodePorts
设置为 false
时,这些服务上已分配置的节点端口不会被自动释放。
你必须显式地在每个服务端口中删除 nodePorts
项以释放对应端口。
你必须启用 ServiceLBNodePortControl
特性门控才能使用该字段。
设置负载均衡器实现的类别
FEATURE STATE: Kubernetes v1.22 [beta]
spec.loadBalancerClass
允许你不使用云提供商的默认负载均衡器实现,转而使用指定的负载均衡器实现。
这个特性从 v1.21 版本开始可以使用,你在 v1.21 版本中使用这个字段必须启用 ServiceLoadBalancerClass
特性门控,这个特性门控从 v1.22 版本及以后默认打开。
默认情况下,.spec.loadBalancerClass
的取值是 nil
,如果集群使用 --cloud-provider
配置了云提供商,
LoadBalancer
类型服务会使用云提供商的默认负载均衡器实现。
如果设置了 .spec.loadBalancerClass
,则假定存在某个与所指定的类相匹配的
负载均衡器实现在监视服务变化。
所有默认的负载均衡器实现(例如,由云提供商所提供的)都会忽略设置了此字段
的服务。.spec.loadBalancerClass
只能设置到类型为 LoadBalancer
的 Service
之上,而且一旦设置之后不可变更。
.spec.loadBalancerClass
的值必须是一个标签风格的标识符,
可以有选择地带有类似 "internal-vip
" 或 "example.com/internal-vip
" 这类
前缀。没有前缀的名字是保留给最终用户的。
内部负载均衡器
在混合环境中,有时有必要在同一(虚拟)网络地址块内路由来自服务的流量。
在水平分割 DNS 环境中,你需要两个服务才能将内部和外部流量都路由到你的端点(Endpoints)。
如要设置内部负载均衡器,请根据你所使用的云运营商,为服务添加以下注解之一。
[...]
metadata :
name : my-service
annotations :
cloud.google.com/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"
[...]
AWS TLS 支持
为了对在 AWS 上运行的集群提供 TLS/SSL 部分支持,你可以向 LoadBalancer
服务添加三个注解:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-ssl-cert : arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
第一个指定要使用的证书的 ARN。 它可以是已上载到 IAM 的第三方颁发者的证书,
也可以是在 AWS Certificate Manager 中创建的证书。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-backend-protocol : (https|http|ssl|tcp)
第二个注解指定 Pod 使用哪种协议。 对于 HTTPS 和 SSL,ELB 希望 Pod 使用证书
通过加密连接对自己进行身份验证。
HTTP 和 HTTPS 选择第7层代理:ELB 终止与用户的连接,解析标头,并在转发请求时向
X-Forwarded-For
标头注入用户的 IP 地址(Pod 仅在连接的另一端看到 ELB 的 IP 地址)。
TCP 和 SSL 选择第4层代理:ELB 转发流量而不修改报头。
在某些端口处于安全状态而其他端口未加密的混合使用环境中,可以使用以下注解:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-backend-protocol : http
service.beta.kubernetes.io/aws-load-balancer-ssl-ports : "443,8443"
在上例中,如果服务包含 80
、443
和 8443
三个端口, 那么 443
和 8443
将使用 SSL 证书,
而 80
端口将转发 HTTP 数据包。
从 Kubernetes v1.9 起可以使用
预定义的 AWS SSL 策略
为你的服务使用 HTTPS 或 SSL 侦听器。
要查看可以使用哪些策略,可以使用 aws
命令行工具:
aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName'
然后,你可以使用 "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy
"
注解; 例如:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy : "ELBSecurityPolicy-TLS-1-2-2017-01"
AWS 上的 PROXY 协议支持
为了支持在 AWS 上运行的集群,启用
PROXY 协议 。
你可以使用以下服务注解:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol : "*"
从 1.3.0 版开始,此注解的使用适用于 ELB 代理的所有端口,并且不能进行其他配置。
AWS 上的 ELB 访问日志
有几个注解可用于管理 AWS 上 ELB 服务的访问日志。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-enabled
控制是否启用访问日志。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval
控制发布访问日志的时间间隔(以分钟为单位)。你可以指定 5 分钟或 60 分钟的间隔。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name
控制存储负载均衡器访问日志的 Amazon S3 存储桶的名称。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix
指定为 Amazon S3 存储桶创建的逻辑层次结构。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled : "true"
# 指定是否为负载均衡器启用访问日志
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval : "60"
# 发布访问日志的时间间隔。你可以将其设置为 5 分钟或 60 分钟。
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name : "my-bucket"
# 用来存放访问日志的 Amazon S3 Bucket 名称
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix : "my-bucket-prefix/prod"
# 你为 Amazon S3 Bucket 所创建的逻辑层次结构,例如 `my-bucket-prefix/prod`
AWS 上的连接排空
可以将注解 service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled
设置为 "true"
来管理 ELB 的连接排空。
注解 service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout
也可以用于设置最大时间(以秒为单位),以保持现有连接在注销实例之前保持打开状态。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled : "true"
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout : "60"
其他 ELB 注解
还有其他一些注解,用于管理经典弹性负载均衡器,如下所述。
metadata :
name : my-service
annotations :
# 按秒计的时间,表示负载均衡器关闭连接之前连接可以保持空闲
# (连接上无数据传输)的时间长度
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout : "60"
# 指定该负载均衡器上是否启用跨区的负载均衡能力
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled : "true"
# 逗号分隔列表值,每一项都是一个键-值耦对,会作为额外的标签记录于 ELB 中
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags : "environment=prod,owner=devops"
# 将某后端视为健康、可接收请求之前需要达到的连续成功健康检查次数。
# 默认为 2,必须介于 2 和 10 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold : ""
# 将某后端视为不健康、不可接收请求之前需要达到的连续不成功健康检查次数。
# 默认为 6,必须介于 2 和 10 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold : "3"
# 对每个实例进行健康检查时,连续两次检查之间的大致间隔秒数
# 默认为 10,必须介于 5 和 300 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval : "20"
# 时长秒数,在此期间没有响应意味着健康检查失败
# 此值必须小于 service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval
# 默认值为 5,必须介于 2 和 60 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout : "5"
# 由已有的安全组所构成的列表,可以配置到所创建的 ELB 之上。
# 与注解 service.beta.kubernetes.io/aws-load-balancer-extra-security-groups 不同,
# 这一设置会替代掉之前指定给该 ELB 的所有其他安全组,也会覆盖掉为此
# ELB 所唯一创建的安全组。
# 此列表中的第一个安全组 ID 被用来作为决策源,以允许入站流量流入目标工作节点
# (包括服务流量和健康检查)。
# 如果多个 ELB 配置了相同的安全组 ID,为工作节点安全组添加的允许规则行只有一个,
# 这意味着如果你删除了这些 ELB 中的任何一个,都会导致该规则记录被删除,
# 以至于所有共享该安全组 ID 的其他 ELB 都无法访问该节点。
# 此注解如果使用不当,会导致跨服务的不可用状况。
service.beta.kubernetes.io/aws-load-balancer-security-groups : "sg-53fae93f"
# 额外的安全组列表,将被添加到所创建的 ELB 之上。
# 添加时,会保留为 ELB 所专门创建的安全组。
# 这样会确保每个 ELB 都有一个唯一的安全组 ID 和与之对应的允许规则记录,
# 允许请求(服务流量和健康检查)发送到目标工作节点。
# 这里顶一个安全组可以被多个服务共享。
service.beta.kubernetes.io/aws-load-balancer-extra-security-groups : "sg-53fae93f,sg-42efd82e"
# 用逗号分隔的一个键-值偶对列表,用来为负载均衡器选择目标节点
service.beta.kubernetes.io/aws-load-balancer-target-node-labels : "ingress-gw,gw-name=public-api"
AWS 上网络负载均衡器支持
FEATURE STATE: Kubernetes v1.15 [beta]
要在 AWS 上使用网络负载均衡器,可以使用注解
service.beta.kubernetes.io/aws-load-balancer-type
,将其取值设为 nlb
。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-type : "nlb"
说明: NLB 仅适用于某些实例类。有关受支持的实例类型的列表,
请参见
AWS文档
中关于所支持的实例类型的 Elastic Load Balancing 说明。
与经典弹性负载平衡器不同,网络负载平衡器(NLB)将客户端的 IP 地址转发到该节点。
如果服务的 .spec.externalTrafficPolicy
设置为 Cluster
,则客户端的IP地址不会传达到最终的 Pod。
通过将 .spec.externalTrafficPolicy
设置为 Local
,客户端IP地址将传播到最终的 Pod,
但这可能导致流量分配不均。
没有针对特定 LoadBalancer 服务的任何 Pod 的节点将无法通过自动分配的
.spec.healthCheckNodePort
进行 NLB 目标组的运行状况检查,并且不会收到任何流量。
为了获得均衡流量,请使用 DaemonSet 或指定
Pod 反亲和性
使其不在同一节点上。
你还可以将 NLB 服务与内部负载平衡器
注解一起使用。
为了使客户端流量能够到达 NLB 后面的实例,使用以下 IP 规则修改了节点安全组:
Rule
Protocol
Port(s)
IpRange(s)
IpRange Description
Health Check
TCP
NodePort(s) (.spec.healthCheckNodePort
for .spec.externalTrafficPolicy = Local
)
Subnet CIDR
kubernetes.io/rule/nlb/health=<loadBalancerName>
Client Traffic
TCP
NodePort(s)
.spec.loadBalancerSourceRanges
(defaults to 0.0.0.0/0
)
kubernetes.io/rule/nlb/client=<loadBalancerName>
MTU Discovery
ICMP
3,4
.spec.loadBalancerSourceRanges
(defaults to 0.0.0.0/0
)
kubernetes.io/rule/nlb/mtu=<loadBalancerName>
为了限制哪些客户端IP可以访问网络负载平衡器,请指定 loadBalancerSourceRanges
。
spec :
loadBalancerSourceRanges :
- "143.231.0.0/16"
说明: 如果未设置 .spec.loadBalancerSourceRanges
,则 Kubernetes 允许从 0.0.0.0/0
到节点安全组的流量。
如果节点具有公共 IP 地址,请注意,非 NLB 流量也可以到达那些修改后的安全组中的所有实例。
有关弹性 IP 注解和更多其他常见用例,
请参阅AWS负载均衡控制器文档 。
腾讯 Kubernetes 引擎(TKE)上的 CLB 注解
以下是在 TKE 上管理云负载均衡器的注解。
metadata :
name : my-service
annotations :
# 绑定负载均衡器到指定的节点。
service.kubernetes.io/qcloud-loadbalancer-backends-label : key in (value1, value2)
# 为已有负载均衡器添加 ID。
service.kubernetes.io/tke-existed-lbid:lb-6swtxxxx
# 负载均衡器(LB)的自定义参数尚不支持修改 LB 类型。
service.kubernetes.io/service.extensiveParameters : ""
# 自定义负载均衡监听器。
service.kubernetes.io/service.listenerParameters : ""
# 指定负载均衡类型。
# 可用参数: classic (Classic Cloud Load Balancer) 或 application (Application Cloud Load Balancer)
service.kubernetes.io/loadbalance-type : xxxxx
# 指定公用网络带宽计费方法。
# 可用参数: TRAFFIC_POSTPAID_BY_HOUR(bill-by-traffic) 和 BANDWIDTH_POSTPAID_BY_HOUR (bill-by-bandwidth).
service.kubernetes.io/qcloud-loadbalancer-internet-charge-type : xxxxxx
# 指定带宽参数 (取值范围: [1,2000] Mbps).
service.kubernetes.io/qcloud-loadbalancer-internet-max-bandwidth-out : "10"
# 当设置该注解时,负载平衡器将只注册正在运行 Pod 的节点,
# 否则所有节点将会被注册。
service.kubernetes.io/local-svc-only-bind-node-with-pod : true
ExternalName 类型
类型为 ExternalName 的服务将服务映射到 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
说明: ExternalName 服务接受 IPv4 地址字符串,但作为包含数字的 DNS 名称,而不是 IP 地址。
类似于 IPv4 地址的外部名称不能由 CoreDNS 或 ingress-nginx 解析,因为外部名称旨在指定规范的 DNS 名称。
要对 IP 地址进行硬编码,请考虑使用
headless Services 。
当查找主机 my-service.prod.svc.cluster.local
时,集群 DNS 服务返回 CNAME
记录,
其值为 my.database.example.com
。
访问 my-service
的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。
如果以后你决定将数据库移到集群中,则可以启动其 Pod,添加适当的选择器或端点以及更改服务的 type
。
警告: 对于一些常见的协议,包括 HTTP 和 HTTPS,
你使用 ExternalName 可能会遇到问题。
如果你使用 ExternalName,那么集群内客户端使用的主机名
与 ExternalName 引用的名称不同。
对于使用主机名的协议,此差异可能会导致错误或意外响应。
HTTP 请求将具有源服务器无法识别的 Host:
标头;TLS 服
务器将无法提供与客户端连接的主机名匹配的证书。
外部 IP
如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。
通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service 的端口上的流量,
将会被路由到 Service 的 Endpoint 上。
externalIPs
不会被 Kubernetes 管理,它属于集群管理员的职责范畴。
根据 Service 的规定,externalIPs
可以同任意的 ServiceType
来一起指定。
在上面的例子中,my-service
可以在 "80.11.12.10:80
"(externalIP:port
) 上被客户端访问。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : MyApp
ports :
- name : http
protocol : TCP
port : 80
targetPort : 9376
externalIPs :
- 80.11.12.10
不足之处
为 VIP 使用用户空间代理,将只适合小型到中型规模的集群,不能够扩展到上千 Service 的大型集群。
查看最初设计方案 获取更多细节。
使用用户空间代理,隐藏了访问 Service 的数据包的源 IP 地址。
这使得一些类型的防火墙无法起作用。
iptables 代理不会隐藏 Kubernetes 集群内部的 IP 地址,但却要求客户端请求
必须通过一个负载均衡器或 Node 端口。
Type
字段支持嵌套功能 —— 每一层需要添加到上一层里面。
不会严格要求所有云提供商(例如,GCE 就没必要为了使一个 LoadBalancer
能工作而分配一个 NodePort
,但是 AWS 需要 ),但当前 API 是强制要求的。
虚拟IP实施
对很多想使用 Service 的人来说,前面的信息应该足够了。
然而,有很多内部原理性的内容,还是值去理解的。
避免冲突
Kubernetes 最主要的哲学之一,是用户不应该暴露那些能够导致他们操作失败、但又不是他们的过错的场景。
对于 Service 资源的设计,这意味着如果用户的选择有可能与他人冲突,那就不要让用户自行选择端口号。
这是一个隔离性的失败。
为了使用户能够为他们的 Service 选择一个端口号,我们必须确保不能有2个 Service 发生冲突。
Kubernetes 通过为每个 Service 分配它们自己的 IP 地址来实现。
为了保证每个 Service 被分配到一个唯一的 IP,需要一个内部的分配器能够原子地更新
etcd 中的一个全局分配映射表,
这个更新操作要先于创建每一个 Service。
为了使 Service 能够获取到 IP,这个映射表对象必须在注册中心存在,
否则创建 Service 将会失败,指示一个 IP 不能被分配。
在控制平面中,一个后台 Controller 的职责是创建映射表
(需要支持从使用了内存锁的 Kubernetes 的旧版本迁移过来)。
同时 Kubernetes 会通过控制器检查不合理的分配(如管理员干预导致的)
以及清理已被分配但不再被任何 Service 使用的 IP 地址。
Service IP 地址
不像 Pod 的 IP 地址,它实际路由到一个固定的目的地,Service 的 IP 实际上
不能通过单个主机来进行应答。
相反,我们使用 iptables
(Linux 中的数据包处理逻辑)来定义一个
虚拟 IP 地址(VIP),它可以根据需要透明地进行重定向。
当客户端连接到 VIP 时,它们的流量会自动地传输到一个合适的 Endpoint。
环境变量和 DNS,实际上会根据 Service 的 VIP 和端口来进行填充。
kube-proxy支持三种代理模式: 用户空间,iptables和IPVS;它们各自的操作略有不同。
Userspace
作为一个例子,考虑前面提到的图片处理应用程序。
当创建后端 Service 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。
假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy
实例观察到。
当代理看到一个新的 Service, 它会打开一个新的端口,建立一个从该 VIP 重定向到
新端口的 iptables,并开始接收请求连接。
当一个客户端连接到一个 VIP,iptables 规则开始起作用,它会重定向该数据包到
"服务代理" 的端口。
"服务代理" 选择一个后端,并将客户端的流量代理到后端上。
这意味着 Service 的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。
客户端可以连接到一个 IP 和端口,而不需要知道实际访问了哪些 Pod。
iptables
再次考虑前面提到的图片处理应用程序。
当创建后端 Service 时,Kubernetes 控制面板会给它指派一个虚拟 IP 地址,比如 10.0.0.1。
假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy
实例观察到。
当代理看到一个新的 Service, 它会配置一系列的 iptables 规则,从 VIP 重定向到每个 Service 规则。
该特定于服务的规则连接到特定于 Endpoint 的规则,而后者会重定向(目标地址转译)到后端。
当客户端连接到一个 VIP,iptables 规则开始起作用。一个后端会被选择(或者根据会话亲和性,或者随机),
数据包被重定向到这个后端。
不像用户空间代理,数据包从来不拷贝到用户空间,kube-proxy 不是必须为该 VIP 工作而运行,
并且客户端 IP 是不可更改的。
当流量打到 Node 的端口上,或通过负载均衡器,会执行相同的基本流程,
但是在那些案例中客户端 IP 是可以更改的。
IPVS
在大规模集群(例如 10000 个服务)中,iptables 操作会显着降低速度。 IPVS
专为负载平衡而设计,并基于内核内哈希表。
因此,你可以通过基于 IPVS 的 kube-proxy 在大量服务中实现性能一致性。
同时,基于 IPVS 的 kube-proxy 具有更复杂的负载均衡算法(最小连接、局部性、
加权、持久性)。
API 对象
Service 是 Kubernetes REST API 中的顶级资源。你可以在以下位置找到有关 API 对象的更多详细信息:
Service 对象 API .
受支持的协议
TCP
你可以将 TCP 用于任何类型的服务,这是默认的网络协议。
UDP
你可以将 UDP 用于大多数服务。 对于 type=LoadBalancer 服务,对 UDP 的支持取决于提供此功能的云提供商。
SCTP
FEATURE STATE: Kubernetes v1.20 [stable]
一旦你使用了支持 SCTP 流量的网络插件,你就可以使用 SCTP 于更多的服务。
对于 type = LoadBalancer 的服务,SCTP 的支持取决于提供此设施的云供应商(大多数不支持)。
警告
支持多宿主 SCTP 关联
警告: 支持多宿主SCTP关联要求 CNI 插件能够支持为一个 Pod 分配多个接口和IP地址。
用于多宿主 SCTP 关联的 NAT 在相应的内核模块中需要特殊的逻辑。
Windows
说明: 基于 Windows 的节点不支持 SCTP。
用户空间 kube-proxy
警告: 当 kube-proxy 处于用户空间模式时,它不支持 SCTP 关联的管理。
HTTP
如果你的云提供商支持它,则可以在 LoadBalancer 模式下使用服务来设置外部
HTTP/HTTPS 反向代理,并将其转发到该服务的 Endpoints。
说明: 你还可以使用
Ingress 代替
Service 来公开 HTTP/HTTPS 服务。
PROXY 协议
如果你的云提供商支持它,
则可以在 LoadBalancer 模式下使用 Service 在 Kubernetes 本身之外配置负载均衡器,
该负载均衡器将转发前缀为
PROXY 协议
的连接。
负载平衡器将发送一系列初始字节,描述传入的连接,类似于此示例
PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n
上述是来自客户端的数据。
接下来
3 - Pod 与 Service 的 DNS
Kubernetes 为服务和 Pods 创建 DNS 记录。
你可以使用一致的 DNS 名称而非 IP 地址来访问服务。
介绍
Kubernetes DNS 在集群上调度 DNS Pod 和服务,并配置 kubelet 以告知各个容器
使用 DNS 服务的 IP 来解析 DNS 名称。
集群中定义的每个 Service (包括 DNS 服务器自身)都被赋予一个 DNS 名称。
默认情况下,客户端 Pod 的 DNS 搜索列表会包含 Pod 自身的名字空间和集群
的默认域。
Service 的名字空间
DNS 查询可能因为执行查询的 Pod 所在的名字空间而返回不同的结果。
不指定名字空间的 DNS 查询会被限制在 Pod 所在的名字空间内。
要访问其他名字空间中的服务,需要在 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 的服务发现 。
服务
A/AAAA 记录
“普通” 服务(除了无头服务)会以 my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被分配一个 DNS A 或 AAAA 记录,取决于服务的 IP 协议族。
该名称会解析成对应服务的集群 IP。
“无头(Headless)” 服务(没有集群 IP)也会以
my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被指派一个 DNS A 或 AAAA 记录,
具体取决于服务的 IP 协议族。
与普通服务不同,这一记录会被解析成对应服务所选择的 Pod 集合的 IP。
客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。
SRV 记录
Kubernetes 会为命名端口创建 SRV 记录,这些端口是普通服务或
无头服务 的一部分。
对每个命名端口,SRV 记录具有 _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
这种形式。
对普通服务,该记录会被解析成端口号和域名:my-svc.my-namespace.svc.cluster-domain.example
。
对无头服务,该记录会被解析成多个结果,服务对应的每个后端 Pod 各一个;
其中包含 Pod 端口号和形为 auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example
的域名。
Pods
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 的 metadata.name
值。
Pod 规约中包含一个可选的 hostname
字段,可以用来指定 Pod 的主机名。
当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。
举个例子,给定一个 hostname
设置为 "my-host
" 的 Pod,
该 Pod 的主机名将被设置为 "my-host
"。
Pod 规约还有一个可选的 subdomain
字段,可以用来指定 Pod 的子域名。
举个例子,某 Pod 的 hostname
设置为 “foo
”,subdomain
设置为 “bar
”,
在名字空间 “my-namespace
” 中对应的完全限定域名(FQDN)为
“foo.bar.my-namespace.svc.cluster-domain.example
”。
示例:
apiVersion : v1
kind : Service
metadata :
name : default-subdomain
spec :
selector :
name : busybox
clusterIP : None
ports :
- name : foo # 实际上不需要指定端口号
port : 1234
targetPort : 1234
---
apiVersion : v1
kind : Pod
metadata :
name : busybox1
labels :
name : busybox
spec :
hostname : busybox-1
subdomain : default-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 : default-subdomain
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
name : busybox
如果某无头服务与某 Pod 在同一个名字空间中,且它们具有相同的子域名,
集群的 DNS 服务器也会为该 Pod 的全限定主机名返回 A 记录或 AAAA 记录。
例如,在同一个名字空间中,给定一个主机名为 “busybox-1”、
子域名设置为 “default-subdomain” 的 Pod,和一个名称为 “default-subdomain
”
的无头服务,Pod 将看到自己的 FQDN 为
"busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example
"。
DNS 会为此名字提供一个 A 记录或 AAAA 记录,指向该 Pod 的 IP。
“busybox1
” 和 “busybox2
” 这两个 Pod 分别具有它们自己的 A 或 AAAA 记录。
Endpoints 对象可以为任何端点地址及其 IP 指定 hostname
。
说明: 因为没有为 Pod 名称创建 A 记录或 AAAA 记录,所以要创建 Pod 的 A 记录
或 AAAA 记录需要 hostname
。
没有设置 hostname
但设置了 subdomain
的 Pod 只会为
无头服务创建 A 或 AAAA 记录(default-subdomain.my-namespace.svc.cluster-domain.example
)
指向 Pod 的 IP 地址。
另外,除非在服务上设置了 publishNotReadyAddresses=True
,否则只有 Pod 进入就绪状态
才会有与之对应的记录。
Pod 的 setHostnameAsFQDN 字段
FEATURE STATE: Kubernetes v1.22 [stable]
前置条件 :SetHostnameAsFQDN
特性门控
必须在 API 服务器
上启用。
当你在 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 查询的信息。
"ClusterFirstWithHostNet
":对于以 hostNetwork 方式运行的 Pod,应显式设置其 DNS 策略
"ClusterFirstWithHostNet
"。
"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 配置
FEATURE STATE: 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 最多允许 6 个搜索域。
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 :
- 1.2.3.4
searches :
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options :
- name : ndots
value : "2"
- name : edns0
创建上面的 Pod 后,容器 test
会在其 /etc/resolv.conf
文件中获取以下内容:
nameserver 1.2.3.4
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 fd00:79:30::a
search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.example
options ndots:5
扩展 DNS 配置
FEATURE STATE: Kubernetes 1.22 [alpha]
对于 Pod DNS 配置,Kubernetes 默认允许最多 6 个 搜索域( Search Domain)
以及一个最多 256 个字符的搜索域列表。
如果启用 kube-apiserver 和 kubelet 的特性门控 ExpandedDNSConfig
,Kubernetes 将可以有最多 32 个
搜索域以及一个最多 2048 个字符的搜索域列表。
功能的可用性
Pod DNS 配置和 DNS 策略 "None
" 的可用版本对应如下所示。
k8s 版本
特性支持
1.14
稳定
1.10
Beta(默认启用)
1.9
Alpha
接下来
有关管理 DNS 配置的指导,请查看
配置 DNS 服务
4 - 使用 Service 连接到应用
Kubernetes 连接容器的模型
既然有了一个持续运行、可复制的应用,我们就能够将它暴露到网络上。
Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。
Kubernetes 给每一个 Pod 分配一个集群私有 IP 地址,所以没必要在
Pod 与 Pod 之间创建连接或将容器的端口映射到主机端口。
这意味着同一个 Pod 内的所有容器能通过 localhost 上的端口互相连通,集群中的所有 Pod
也不需要通过 NAT 转换就能够互相看到。
本文档的剩余部分详述如何在上述网络模型之上运行可靠的服务。
本指南使用一个简单的 Nginx 服务器来演示概念验证原型。
在集群中暴露 Pod
我们在之前的示例中已经做过,然而让我们以网络连接的视角再重做一遍。
创建一个 Nginx Pod,注意其中包含一个容器端口的规约:
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
selector :
matchLabels :
run : my-nginx
replicas : 2
template :
metadata :
labels :
run : my-nginx
spec :
containers :
- name : my-nginx
image : nginx
ports :
- containerPort : 80
这使得可以从集群中任何一个节点来访问它。检查节点,该 Pod 正在运行:
kubectl apply -f ./run-my-nginx.yaml
kubectl get pods -l run = my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m
my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd
检查 Pod 的 IP 地址:
kubectl get pods -l run = my-nginx -o yaml | grep podIP
podIP: 10.244.3.4
podIP: 10.244.2.5
你应该能够通过 ssh 登录到集群中的任何一个节点上,并使用诸如 curl
之类的工具向这两个 IP 地址发出查询请求。
需要注意的是,容器不会使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。
这意味着可以在同一个节点上运行多个 Nginx Pod,使用相同的 containerPort
,并且可以从集群中任何其他的
Pod 或节点上使用 IP 的方式访问到它们。
如果你想的话,你依然可以将宿主节点的某个端口的流量转发到 Pod 中,但是出于网络模型的原因,你不必这么做。
如果对此好奇,请参考 Kubernetes 网络模型 。
创建 Service
我们有一组在一个扁平的、集群范围的地址空间中运行 Nginx 服务的 Pod。
理论上,你可以直接连接到这些 Pod,但如果某个节点死掉了会发生什么呢?
Pod 会终止,Deployment 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。
Kubernetes Service 是集群中提供相同功能的一组 Pod 的抽象表达。
当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP)。
这个 IP 地址与 Service 的生命周期绑定在一起,只要 Service 存在,它就不会改变。
可以配置 Pod 使它与 Service 进行通信,Pod 知道与 Service 通信将被自动地负载均衡到该 Service 中的某些 Pod 上。
可以使用 kubectl expose
命令为 2个 Nginx 副本创建一个 Service:
kubectl expose deployment/my-nginx
service/my-nginx exposed
这等价于使用 kubectl create -f
命令及如下的 yaml 文件创建:
apiVersion : v1
kind : Service
metadata :
name : my-nginx
labels :
run : my-nginx
spec :
ports :
- port : 80
protocol : TCP
selector :
run : my-nginx
上述规约将创建一个 Service,该 Service 会将所有具有标签 run: my-nginx
的 Pod 的 TCP
80 端口暴露到一个抽象的 Service 端口上(targetPort
:容器接收流量的端口;port
:可任意取值的抽象的 Service
端口,其他 Pod 通过该端口访问 Service)。
查看 Service
API 对象以了解 Service 所能接受的字段列表。
查看你的 Service 资源:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s
正如前面所提到的,一个 Service 由一组 Pod 提供支撑。这些 Pod 通过 endpoints
暴露出来。
Service Selector 将持续评估,结果被 POST 到一个名称为 my-nginx
的 Endpoint 对象上。
当 Pod 终止后,它会自动从 Endpoint 中移除,新的能够匹配上 Service Selector 的 Pod 将自动地被添加到 Endpoint 中。
检查该 Endpoint,注意到 IP 地址与在第一步创建的 Pod 是相同的。
kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP: 10.0.162.149
Port: <unset> 80/TCP
Endpoints: 10.244.2.5:80,10.244.3.4:80
Session Affinity: None
Events: <none>
NAME ENDPOINTS AGE
my-nginx 10.244.2.5:80,10.244.3.4:80 1m
现在,你应该能够从集群中任意节点上使用 curl 命令向 <CLUSTER-IP>:<PORT>
发送请求以访问 Nginx Service。
注意 Service IP 完全是虚拟的,它从来没有走过网络,如果对它如何工作的原理感到好奇,
可以进一步阅读服务代理
的内容。
访问 Service
Kubernetes支持两种查找服务的主要模式: 环境变量和 DNS。前者开箱即用,而后者则需要
CoreDNS 集群插件 .
说明: 如果不需要服务环境变量(因为可能与预期的程序冲突,可能要处理的变量太多,或者仅使用DNS等),则可以通过在
pod spec
上将
enableServiceLinks
标志设置为
false
来禁用此模式。
环境变量
当 Pod 在节点上运行时,kubelet 会针对每个活跃的 Service 为 Pod 添加一组环境变量。
这就引入了一个顺序的问题。为解释这个问题,让我们先检查正在运行的 Nginx Pod
的环境变量(你的环境中的 Pod 名称将会与下面示例命令中的不同):
kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
能看到环境变量中并没有你创建的 Service 相关的值。这是因为副本的创建先于 Service。
这样做的另一个缺点是,调度器可能会将所有 Pod 部署到同一台机器上,如果该机器宕机则整个 Service 都会离线。
要改正的话,我们可以先终止这 2 个 Pod,然后等待 Deployment 去重新创建它们。
这次 Service 会先于 副本存在。这将实现调度器级别的 Pod 按 Service
分布(假定所有的节点都具有同样的容量),并提供正确的环境变量:
kubectl scale deployment my-nginx --replicas= 0; kubectl scale deployment my-nginx --replicas= 2;
kubectl get pods -l run = my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-e9ihh 1/1 Running 0 5s 10.244.2.7 kubernetes-minion-ljyd
my-nginx-3800858182-j4rm4 1/1 Running 0 5s 10.244.3.8 kubernetes-minion-905m
你可能注意到,Pod 具有不同的名称,这是因为它们是被重新创建的。
kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_HOST=10.0.162.149
KUBERNETES_SERVICE_HOST=10.0.0.1
MY_NGINX_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT_HTTPS=443
DNS
Kubernetes 提供了一个自动为其它 Service 分配 DNS 名字的 DNS 插件 Service。
你可以通过如下命令检查它是否在工作:
kubectl get services kube-dns --namespace= kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 8m
本段剩余的内容假设你已经有一个拥有持久 IP 地址的 Service(my-nginx),以及一个为其
IP 分配名称的 DNS 服务器。 这里我们使用 CoreDNS 集群插件(应用名为 kube-dns
),
所以在集群中的任何 Pod 中,你都可以使用标准方法(例如:gethostbyname()
)与该 Service 通信。
如果 CoreDNS 没有在运行,你可以参照
CoreDNS README
或者安装 CoreDNS 来启用它。
让我们运行另一个 curl 应用来进行测试:
kubectl run curl --image= radial/busyboxplus:curl -i --tty
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
Hit enter for command prompt
然后,按回车并执行命令 nslookup my-nginx
:
[ root@curl-131556218-9fnch:/ ] $ nslookup my-nginx
Server: 10.0.0.10
Address 1: 10.0.0.10
Name: my-nginx
Address 1: 10.0.162.149
保护 Service
到现在为止,我们只在集群内部访问了 Nginx 服务器。在将 Service 暴露到因特网之前,我们希望确保通信信道是安全的。
为实现这一目的,需要:
用于 HTTPS 的自签名证书(除非已经有了一个身份证书)
使用证书配置的 Nginx 服务器
使 Pod 可以访问证书的 Secret
你可以从
Nginx https 示例 获取所有上述内容。
你需要安装 go 和 make 工具。如果你不想安装这些软件,可以按照后文所述的手动执行步骤执行操作。简要过程如下:
make keys KEY = /tmp/nginx.key CERT = /tmp/nginx.crt
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt
secret/nginxsecret created
NAME TYPE DATA AGE
default-token-il9rc kubernetes.io/service-account-token 1 1d
nginxsecret kubernetes.io/tls 2 1m
以下是 configmap:
kubectl create configmap nginxconfigmap --from-file= default.conf
configmap/nginxconfigmap created
NAME DATA AGE
nginxconfigmap 1 114s
以下是你在运行 make 时遇到问题时要遵循的手动步骤(例如,在 Windows 上):
# 创建公钥和相对应的私钥
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
# 对密钥实施 base64 编码
cat /d/tmp/nginx.crt | base64
cat /d/tmp/nginx.key | base64
使用前面命令的输出来创建 yaml 文件,如下所示。 base64 编码的值应全部放在一行上。
apiVersion : "v1"
kind : "Secret"
metadata :
name : "nginxsecret"
namespace : "default"
type : kubernetes.io/tls
data :
tls.crt : "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
tls.key : "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
现在使用文件创建 Secret:
kubectl apply -f nginxsecrets.yaml
kubectl get secrets
NAME TYPE DATA AGE
default-token-il9rc kubernetes.io/service-account-token 1 1d
nginxsecret kubernetes.io/tls 2 1m
现在修改 nginx 副本以启动一个使用 Secret 中的证书的 HTTPS 服务器以及相应的用于暴露其端口(80 和 443)的 Service:
apiVersion : v1
kind : Service
metadata :
name : my-nginx
labels :
run : my-nginx
spec :
type : NodePort
ports :
- port : 8080
targetPort : 80
protocol : TCP
name : http
- port : 443
protocol : TCP
name : https
selector :
run : my-nginx
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
selector :
matchLabels :
run : my-nginx
replicas : 1
template :
metadata :
labels :
run : my-nginx
spec :
volumes :
- name : secret-volume
secret :
secretName : nginxsecret
- name : configmap-volume
configMap :
name : nginxconfigmap
containers :
- name : nginxhttps
image : bprashanth/nginxhttps:1.0
ports :
- containerPort : 443
- containerPort : 80
volumeMounts :
- mountPath : /etc/nginx/ssl
name : secret-volume
- mountPath : /etc/nginx/conf.d
name : configmap-volume
关于 nginx-secure-app 清单,值得注意的几点如下:
它将 Deployment 和 Service 的规约放在了同一个文件中。
Nginx 服务器 通过
80 端口处理 HTTP 流量,通过 443 端口处理 HTTPS 流量,而 Nginx Service 则暴露了这两个端口。
每个容器能通过挂载在 /etc/nginx/ssl
的卷访问秘钥。卷和密钥需要在 Nginx 服务器启动之前 配置好。
kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml
这时,你可以从任何节点访问到 Nginx 服务器。
kubectl get pods -o yaml | grep -i podip
podIP: 10.244.3.5
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>
注意最后一步我们是如何提供 -k
参数执行 curl 命令的,这是因为在证书生成时,
我们不知道任何关于运行 nginx 的 Pod 的信息,所以不得不在执行 curl 命令时忽略 CName 不匹配的情况。
通过创建 Service,我们连接了在证书中的 CName 与在 Service 查询时被 Pod 使用的实际 DNS 名字。
让我们从一个 Pod 来测试(为了方便,这里使用同一个 Secret,Pod 仅需要使用 nginx.crt 去访问 Service):
apiVersion : apps/v1
kind : Deployment
metadata :
name : curl-deployment
spec :
selector :
matchLabels :
app : curlpod
replicas : 1
template :
metadata :
labels :
app : curlpod
spec :
volumes :
- name : secret-volume
secret :
secretName : nginxsecret
containers :
- name : curlpod
command :
- sh
- -c
- while true; do sleep 1; done
image : radial/busyboxplus:curl
volumeMounts :
- mountPath : /etc/nginx/ssl
name : secret-volume
kubectl apply -f ./curlpod.yaml
kubectl get pods -l app = curlpod
NAME READY STATUS RESTARTS AGE
curl-deployment-1515033274-1410r 1/1 Running 0 1m
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt
...
<title>Welcome to nginx!</title>
...
暴露 Service
对应用的某些部分,你可能希望将 Service 暴露在一个外部 IP 地址上。
Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。
在上一段创建的 Service 使用了 NodePort
,因此,如果你的节点有一个公网
IP,那么 Nginx HTTPS 副本已经能够处理因特网上的流量。
kubectl get svc my-nginx -o yaml | grep nodePort -C 5
uid: 07191fb3-f61a-11e5-8ae5-42010af00002
spec:
clusterIP: 10.0.162.149
ports:
- name: http
nodePort: 31704
port: 8080
protocol: TCP
targetPort: 80
- name: https
nodePort: 32453
port: 443
protocol: TCP
targetPort: 443
selector:
run: my-nginx
kubectl get nodes -o yaml | grep ExternalIP -C 1
- address: 104.197.41.11
type: ExternalIP
allocatable:
--
- address: 23.251.152.56
type: ExternalIP
allocatable:
...
$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k
...
<h1>Welcome to nginx!</h1>
让我们重新创建一个 Service 以使用云负载均衡器。
将 my-nginx
Service 的 Type
由 NodePort
改成 LoadBalancer
:
kubectl edit svc my-nginx
kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>
在 EXTERNAL-IP
列中的 IP 地址能在公网上被访问到。CLUSTER-IP
只能从集群/私有云网络中访问。
注意,在 AWS 上,类型 LoadBalancer
的服务会创建一个 ELB,且 ELB 使用主机名(比较长),而不是 IP。
ELB 的主机名太长以至于不能适配标准 kubectl get svc
的输出,所以需要通过执行
kubectl describe service my-nginx
命令来查看它。
可以看到类似如下内容:
kubectl describe service my-nginx
...
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...
接下来
5 - Ingress
FEATURE STATE: Kubernetes v1.19 [stable]
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
术语
为了表达更加清晰,本指南定义了以下术语:
节点(Node): Kubernetes 集群中的一台工作机器,是集群的一部分。
集群(Cluster): 一组运行由 Kubernetes 管理的容器化应用程序的节点。
在此示例和在大多数常见的 Kubernetes 部署环境中,集群中的节点都不在公共网络中。
边缘路由器(Edge Router): 在集群中强制执行防火墙策略的路由器。可以是由云提供商管理的网关,也可以是物理硬件。
集群网络(Cluster Network): 一组逻辑的或物理的连接,根据 Kubernetes
网络模型 在集群内实现通信。
服务(Service):Kubernetes 服务(Service) ,
使用标签 选择器(selectors)辨认一组 Pod。
除非另有说明,否则假定服务只具有在集群网络中可路由的虚拟 IP。
Ingress 是什么?
Ingress
公开了从集群外部到集群内服务 的 HTTP 和 HTTPS 路由。
流量路由由 Ingress 资源上定义的规则控制。
下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:
graph LR;
client([客户端])-. Ingress-管理的 负载均衡器 .->ingress[Ingress];
ingress-->|路由规则|service[Service];
subgraph cluster
ingress;
service-->pod1[Pod];
service-->pod2[Pod];
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class ingress,service,pod1,pod2 k8s;
class client plain;
class cluster cluster;
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 :
rules :
- http :
paths :
- path : /testpath
pathType : Prefix
backend :
service :
name : test
port :
number : 80
与所有其他 Kubernetes 资源一样,Ingress 需要指定 apiVersion
、kind
和 metadata
字段。
Ingress 对象的命名必须是合法的 DNS 子域名名称 。
关于如何使用配置文件,请参见部署应用 、
配置容器 、
管理资源 。
Ingress 经常使用注解(annotations)来配置一些选项,具体取决于 Ingress
控制器,例如重写目标注解 。
不同的 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
适用于该 host
。
路径列表 paths(例如,/testpath
),每个路径都有一个由 serviceName
和 servicePort
定义的关联后端。
在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入请求的内容。
backend
(后端)是 Service 文档 中所述的服务和端口名称的组合。
与规则的 host
和 path
匹配的对 Ingress 的 HTTP(和 HTTPS )请求将发送到列出的 backend
。
通常在 Ingress 控制器中会配置 defaultBackend
(默认后端),以服务于无法与规约中 path
匹配的所有请求。
默认后端
没有设置规则的 Ingress 将所有流量发送到同一个默认后端,而
.spec.defaultBackend
则是在这种情况下处理请求的那个默认后端。
defaultBackend
通常是
Ingress 控制器 的配置选项,而非在
Ingress 资源中指定。
如果未设置任何的 .spec.rules
,那么必须指定 .spec.defaultBackend
。
如果未设置 defaultBackend
,那么如何处理所有与规则不匹配的流量将交由
Ingress 控制器决定(请参考你的 Ingress 控制器的文档以了解它是如何处理那些流量的)。
如果没有 hosts
或 paths
与 Ingress 对象中的 HTTP 请求匹配,则流量将被路由到默认后端。
资源后端
Resource
后端是一个引用,指向同一命名空间中的另一个 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
字段可用于引用其他资源以提供额外的相关配置。
参数(parameters
)的具体类型取决于你在 .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
FEATURE STATE: Kubernetes v1.23 [stable]
如果你设置了 .spec.parameters
字段且将 .spec.parameters.scope
字段设为了 Namespace
,那么该 IngressClass 将会引用一个命名空间作用域的资源。
.spec.parameters.namespace
必须和此资源所处的命名空间相同。
参数的 kind
(和 apiGroup
一起)指向一个命名空间作用域的 API(例如:ConfigMap),而它的
name
则确定了一个位于你指定的命名空间中的具体的资源。
命名空间作用域的参数帮助集群操作者将控制细分到用于工作负载的各种配置中(比如:负载均衡设置、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 控制器分配以满足该 Ingress 的 IP。
说明: 入口控制器和负载平衡器可能需要一两分钟才能分配 IP 地址。
在此之前,你通常会看到地址字段的值被设定为 <pending>
。
简单扇出
一个扇出(fanout)配置根据请求的 HTTP URI 将来自同一 IP 地址的流量路由到多个 Service。
Ingress 允许你将负载均衡器的数量降至最低。例如,这样的设置:
graph LR;
client([客户端])-. Ingress-管理的 负载均衡器 .->ingress[Ingress, 178.91.123.132];
ingress-->|/foo|service1[Service service1:4200];
ingress-->|/bar|service2[Service service2:8080];
subgraph cluster
ingress;
service1-->pod1[Pod];
service1-->pod2[Pod];
service2-->pod3[Pod];
service2-->pod4[Pod];
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class ingress,service1,service2,pod1,pod2,pod3,pod4 k8s;
class client plain;
class cluster cluster;
将需要一个如下所示的 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 地址上。
graph LR;
client([客户端])-. Ingress-管理的 负载均衡器 .->ingress[Ingress, 178.91.123.132];
ingress-->|Host: foo.bar.com|service1[Service service1:80];
ingress-->|Host: bar.foo.com|service2[Service service2:80];
subgraph cluster
ingress;
service1-->pod1[Pod];
service1-->pod2[Pod];
service2-->pod3[Pod];
service2-->pod4[Pod];
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class ingress,service1,service2,pod1,pod2,pod3,pod4 k8s;
class client plain;
class cluster cluster;
以下 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
中定义的任何 hosts
,则可以匹配指向
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 的以键名 tls.crt
保存的证书和以键名 tls.key
保存的私钥。
例如:
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
的公用名称(CN)的证书。
这里的公共名称也被称为全限定域名(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 公开。
你可以通过用于服务的负载均衡器来获取这些功能。
值得注意的是,尽管健康检查不是通过 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 :
serviceName : service1
servicePort : 80
path : /foo
pathType : Prefix
- host : bar.baz.com
http :
paths :
- backend :
serviceName : service2
servicePort : 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
你也可以通过 kubectl replace -f
命令调用修改后的 Ingress yaml 文件来获得同样的结果。
跨可用区失败
不同的云厂商使用不同的技术来实现跨故障域的流量分布。详情请查阅相关 Ingress 控制器的文档。
请查看相关 Ingress 控制器 的文档以了解详细信息。
替代方案
不直接使用 Ingress 资源,也有多种方法暴露 Service:
接下来
6 - 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。
你可以通过将
ingressclass.kubernetes.io/is-default-class
注解
的值设置为 "true"
来将一个 IngressClass 标记为集群默认。
理想情况下,所有 Ingress 控制器都应满足此规范,但各种 Ingress 控制器的操作略有不同。
说明: 确保你查看了 ingress 控制器的文档,以了解选择它的注意事项。
接下来
7 - 拓扑感知提示
FEATURE STATE: Kubernetes v1.23 [beta]
拓扑感知提示 包含客户怎么使用服务端点的建议,从而实现了拓扑感知的路由功能。
这种方法添加了元数据,以启用 EndpointSlice 和/或 Endpoints 对象的调用者,
这样,访问这些网络端点的请求流量就可以在它的发起点附近就近路由。
例如,你可以在一个地域内路由流量,以降低通信成本,或提高网络性能。
动机
Kubernetes 集群越来越多的部署到多区域环境中。
拓扑感知提示 提供了一种把流量限制在它的发起区域之内的机制。
这个概念一般被称之为 “拓扑感知路由”。
在计算 服务(Service) 的端点时,
EndpointSlice 控制器会评估每一个端点的拓扑(地域和区域),填充提示字段,并将其分配到某个区域。
集群组件,例如kube-proxy
就可以使用这些提示信息,并用他们来影响流量的路由(倾向于拓扑上相邻的端点)。
使用拓扑感知提示
你可以通过把注解 service.kubernetes.io/topology-aware-hints
的值设置为 auto
,
来激活服务的拓扑感知提示功能。
这告诉 EndpointSlice 控制器在它认为安全的时候来设置拓扑提示。
重要的是,这并不能保证总会设置提示(hints)。
工作原理
此特性启用的功能分为两个组件:EndpointSlice 控制器和 kube-proxy。
本节概述每个组件如何实现此特性。
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多两倍的流量。
如果控制器不能确定此“期望的过载”值低于每一个区域可接受的阈值,控制器将不指派提示信息。
重要的是,这不是基于实时反馈。所以对于单独的端点仍有可能超载。
一个或多个节点信息不足: 如果任一节点没有设置标签 topology.kubernetes.io/zone
,
或没有上报可分配的 CPU 数据,控制平面将不会设置任何拓扑感知提示,
继而 kube-proxy 也就不能通过区域过滤端点。
一个或多个端点没有设置区域提示: 当这类事情发生时,
kube-proxy 会假设这是正在执行一个从/到拓扑感知提示的转移。
在这种场合下过滤Service 的端点是有风险的,所以 kube-proxy 回撤为使用所有的端点。
不在提示中的区域: 如果 kube-proxy 不能根据一个指示在它所在的区域中发现一个端点,
它回撤为使用所有节点的端点。当你的集群新增一个新的区域时,这种情况发生概率很高。
限制
当 Service 的 externalTrafficPolicy
或 internalTrafficPolicy
设置值为 Local
时,
拓扑感知提示功能不可用。
你可以在一个集群的不同服务中使用这两个特性,但不能在同一个服务中这么做。
这种方法不适用于大部分流量来自于一部分区域的服务。
相反的,这里假设入站流量将根据每个区域中节点的服务能力按比例的分配。
EndpointSlice 控制器在计算每一个区域的容量比例时,会忽略未就绪的节点。
在大量节点未就绪的场景下,这样做会带来非预期的结果。
EndpointSlice 控制器在计算每一个区域的部署比例时,并不会考虑
容忍度 。
如果服务后台的 Pod 被限制只能运行在集群节点的一个子集上,这些信息并不会被使用。
这种方法和自动扩展机制之间不能很好的协同工作。例如,如果大量流量来源于一个区域,
那只有分配到该区域的端点才可用来处理流量。这会导致
Pod 自动水平扩展
要么不能拾取此事件,要么新增 Pod 被启动到其他区域。
接下来
8 - 服务内部流量策略
FEATURE STATE: Kubernetes v1.23 [beta]
服务内部流量策略 开启了内部流量限制,只路由内部流量到和发起方处于相同节点的服务端点。
这里的”内部“流量指当前集群中的 Pod 所发起的流量。
这种机制有助于节省开销,提升效率。
使用服务内部流量策略
ServiceInternalTrafficPolicy
特性门控 是 Beta 功能,默认启用。
启用该功能后,你就可以通过将 Services 的
.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 : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
internalTrafficPolicy : Local
工作原理
kube-proxy 基于 spec.internalTrafficPolicy
的设置来过滤路由的目标服务端点。
当它的值设为 Local
时,只选择节点本地的服务端点。
当它的值设为 Cluster
或缺省时,则选择所有的服务端点。
启用特性门控
ServiceInternalTrafficPolicy
后,
spec.internalTrafficPolicy
的值默认设为 Cluster
。
限制
在一个Service上,当 externalTrafficPolicy
已设置为 Local
时,服务内部流量策略无法使用。
换句话说,在一个集群的不同 Service 上可以同时使用这两个特性,但在一个 Service 上不行。
接下来
9 - 端点切片(Endpoint Slices)
FEATURE STATE: Kubernetes v1.21 [stable]
端点切片(EndpointSlices) 提供了一种简单的方法来跟踪 Kubernetes 集群中的网络端点
(network endpoints)。它们为 Endpoints 提供了一种可伸缩和可拓展的替代方案。
动机
Endpoints API 提供了在 Kubernetes 跟踪网络端点的一种简单而直接的方法。
不幸的是,随着 Kubernetes 集群和 服务
逐渐开始为更多的后端 Pods 处理和发送请求,原来的 API 的局限性变得越来越明显。
最重要的是那些因为要处理大量网络端点而带来的挑战。
由于任一服务的所有网络端点都保存在同一个 Endpoints 资源中,这类资源可能变得
非常巨大,而这一变化会影响到 Kubernetes 组件(比如主控组件)的性能,并
在 Endpoints 变化时产生大量的网络流量和额外的处理。
EndpointSlice 能够帮助你缓解这一问题,还能为一些诸如拓扑路由这类的额外
功能提供一个可扩展的平台。
Endpoint Slice 资源
在 Kubernetes 中,EndpointSlice
包含对一组网络端点的引用。
指定选择器后控制面会自动为设置了 选择算符
的 Kubernetes 服务创建 EndpointSlice。
这些 EndpointSlice 将包含对与服务选择算符匹配的所有 Pod 的引用。
EndpointSlice 通过唯一的协议、端口号和服务名称将网络端点组织在一起。
EndpointSlice 的名称必须是合法的
DNS 子域名 。
例如,下面是 Kubernetes 服务 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 API 存储了可能对使用者有用的、有关端点的状况。
这三个状况分别是 ready
、serving
和 terminating
。
Ready(就绪)
ready
状况是映射 Pod 的 Ready
状况的。
处于运行中的 Pod,它的 Ready
状况被设置为 True
,应该将此 EndpointSlice 状况也设置为 true
。
出于兼容性原因,当 Pod 处于终止过程中,ready
永远不会为 true
。
消费者应参考 serving
状况来检查处于终止中的 Pod 的就绪情况。
该规则的唯一例外是将 spec.publishNotReadyAddresses
设置为 true
的服务。
这些服务(Service)的端点将始终将 ready
状况设置为 true
。
Serving(服务中)
FEATURE STATE: Kubernetes v1.20 [alpha]
serving
状况与 ready
状况相同,不同之处在于它不考虑终止状态。
如果 EndpointSlice API 的使用者关心 Pod 终止时的就绪情况,就应检查此状况。
说明:
尽管 serving
与 ready
几乎相同,但是它是为防止破坏 ready
的现有含义而增加的。
如果对于处于终止中的端点,ready
可能是 true
,那么对于现有的客户端来说可能是有些意外的,
因为从始至终,Endpoints 或 EndpointSlice API 从未包含处于终止中的端点。
出于这个原因,ready
对于处于终止中的端点 总是 false
,
并且在 v1.20 中添加了新的状况 serving
,以便客户端可以独立于 ready
的现有语义来跟踪处于终止中的 Pod 的就绪情况。
Terminating(终止中)
FEATURE STATE: Kubernetes v1.20 [alpha]
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
标签来标明的,
目的是方便查找隶属于某服务的所有 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 都有一组端口值,适用于资源内的所有端点。
当为服务使用命名端口时,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 的实现必须能够处理端点出现在多个切片中的状况。
关于如何执行端点去重(deduplication)的参考实现,你可以在 kube-proxy
的
EndpointSlice
实现中找到。
接下来
10 - 网络策略
如果你希望在 IP 地址或端口层面(OSI 第 3 层或第 4 层)控制网络流量,
则你可以考虑为集群中特定应用使用 Kubernetes 网络策略(NetworkPolicy)。
NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许
Pod 与网络上的各类网络“实体”
(我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语,
这些术语在 Kubernetes 中有特定含义)通信。
Pod 可以通信的 Pod 是通过如下三个标识符的组合来辩识的:
其他被允许的 Pods(例外:Pod 无法阻塞对自身的访问)
被允许的名字空间
IP 组块(例外:与 Pod 运行所在的节点的通信总是被允许的,
无论 Pod 或节点的 IP 地址)
在定义基于 Pod 或名字空间的 NetworkPolicy 时,你会使用
选择算符 来设定哪些流量
可以进入或离开与该算符匹配的 Pod。
同时,当基于 IP 的 NetworkPolicy 被创建时,我们基于 IP 组块(CIDR 范围)
来定义策略。
前置条件
网络策略通过网络插件
来实现。要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。
创建一个 NetworkPolicy 资源对象而没有控制器来使它生效的话,是没有任何作用的。
隔离和非隔离的 Pod
默认情况下,Pod 是非隔离的,它们接受任何来源的流量。
Pod 在被某 NetworkPolicy 选中时进入被隔离状态。
一旦名字空间中有 NetworkPolicy 选择了特定的 Pod,该 Pod 会拒绝该 NetworkPolicy
所不允许的连接。
(名字空间下其他未被 NetworkPolicy 所选择的 Pod 会继续接受所有的流量)
网络策略不会冲突,它们是累积的。
如果任何一个或多个策略选择了一个 Pod, 则该 Pod 受限于这些策略的
入站(Ingress)/出站(Egress)规则的并集。因此评估的顺序并不会影响策略的结果。
为了允许两个 Pods 之间的网络数据流,源端 Pod 上的出站(Egress)规则和
目标端 Pod 上的入站(Ingress)规则都需要允许该流量。
如果源端的出站(Egress)规则或目标端的入站(Ingress)规则拒绝该流量,
则流量将被拒绝。
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
字段。关于配置文件操作的一般信息,请参考
使用 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 规则)允许从带有 "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 的流量都被允许。
以下示例使你可以更改该名字空间中的默认行为。
默认拒绝所有入站流量
你可以通过创建选择所有容器但不允许任何进入这些容器的入站流量的 NetworkPolicy
来为名字空间创建 "default" 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-ingress
spec :
podSelector : {}
policyTypes :
- Ingress
这样可以确保即使容器没有选择其他任何 NetworkPolicy,也仍然可以被隔离。
此策略不会更改默认的出口隔离行为。
默认允许所有入站流量
如果要允许所有流量进入某个名字空间中的所有 Pod(即使添加了导致某些 Pod 被视为
“隔离”的策略),则可以创建一个策略来明确允许该名字空间中的所有流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-ingress
spec :
podSelector : {}
ingress :
- {}
policyTypes :
- Ingress
默认拒绝所有出站流量
你可以通过创建选择所有容器但不允许来自这些容器的任何出站流量的 NetworkPolicy
来为名字空间创建 "default" egress 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-egress
spec :
podSelector : {}
policyTypes :
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许流出流量。
此策略不会更改默认的入站流量隔离行为。
默认允许所有出站流量
如果要允许来自名字空间中所有 Pod 的所有流量(即使添加了导致某些 Pod 被视为“隔离”的策略),
则可以创建一个策略,该策略明确允许该名字空间中的所有出站流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-egress
spec :
podSelector : {}
egress :
- {}
policyTypes :
- Egress
默认拒绝所有入口和所有出站流量
你可以为名字空间创建“默认”策略,以通过在该名字空间中创建以下 NetworkPolicy
来阻止所有入站和出站流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-all
spec :
podSelector : {}
policyTypes :
- Ingress
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被
允许入站或出站流量。
SCTP 支持
FEATURE STATE: Kubernetes v1.20 [stable]
作为一个稳定特性,SCTP 支持默认是被启用的。
要在集群层面禁用 SCTP,你(或你的集群管理员)需要为 API 服务器指定
--feature-gates=SCTPSupport=false,...
来禁用 SCTPSupport
特性门控 。
启用该特性门控后,用户可以将 NetworkPolicy 的 protocol
字段设置为 SCTP
。
说明:
你必须使用支持 SCTP 协议网络策略的 CNI 插件。
针对某个端口范围
FEATURE STATE: Kubernetes v1.22 [beta]
在编写 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
中所有带有标签 db
的 Pod 使用 TCP 协议
与 10.0.0.0/24
范围内的 IP 通信,只要目标端口介于 32000 和 32768 之间就可以。
使用此字段时存在以下限制:
作为一种 Beta 阶段的特性,端口范围设定默认是被启用的。要在整个集群
范围内禁止使用 endPort
字段,你(或者你的集群管理员)需要为 API
服务器设置 -feature-gates=NetworkPolicyEndPort=false,...
以禁用
NetworkPolicyEndPort
特性门控 。
endPort
字段必须等于或者大于 port
字段的值。
两个字段的设置值都只能是数字。
说明:
你的集群所使用的 CNI 插件
必须支持在 NetworkPolicy 规约中使用 endPort
字段。
如果你的网络插件
不支持 endPort
字段,而你指定了一个包含 endPort
字段的 NetworkPolicy,
策略只对单个 port
字段生效。
基于名字指向某名字空间
FEATURE STATE: Kubernetes 1.21 [beta]
只要 NamespaceDefaultLabelName
特性门控
被启用,Kubernetes 控制面会在所有名字空间上设置一个不可变更的标签
kubernetes.io/metadata.name
。该标签的值是名字空间的名称。
如果 NetworkPolicy 无法在某些对象字段中指向某名字空间,你可以使用标准的
标签方式来指向特定名字空间。
通过网络策略(至少目前还)无法完成的工作
到 Kubernetes 1.23 为止,NetworkPolicy API 还不支持以下功能,不过
你可能可以使用操作系统组件(如 SELinux、OpenVSwitch、IPTables 等等)
或者第七层技术(Ingress 控制器、服务网格实现)或准入控制器来实现一些
替代方案。
如果你对 Kubernetes 中的网络安全性还不太了解,了解使用 NetworkPolicy API
还无法实现下面的用户场景是很值得的。
强制集群内部流量经过某公用网关(这种场景最好通过服务网格或其他代理来实现);
与 TLS 相关的场景(考虑使用服务网格或者 Ingress 控制器);
特定于节点的策略(你可以使用 CIDR 来表达这一需求不过你无法使用节点在
Kubernetes 中的其他标识信息来辩识目标节点);
基于名字来选择服务(不过,你可以使用 标签
来选择目标 Pod 或名字空间,这也通常是一种可靠的替代方案);
创建或管理由第三方来实际完成的“策略请求”;
实现适用于所有名字空间或 Pods 的默认策略(某些第三方 Kubernetes 发行版本
或项目可以做到这点);
高级的策略查询或者可达性相关工具;
生成网络安全事件日志的能力(例如,被阻塞或接收的连接请求);
显式地拒绝策略的能力(目前,NetworkPolicy 的模型默认采用拒绝操作,
其唯一的能力是添加允许策略);
禁止本地回路或指向宿主的网络流量(Pod 目前无法阻塞 localhost 访问,
它们也无法禁止来自所在节点的访问请求)。
接下来
参阅声明网络策略
演练了解更多示例;
有关 NetworkPolicy 资源所支持的常见场景的更多信息,请参见
此指南 。
11 - IPv4/IPv6 双协议栈
FEATURE STATE: Kubernetes v1.21 [beta]
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 网络接口)
支持双协议栈的网络插件(如 Kubenet 或 Calico)
配置 IPv4/IPv6 双协议栈
要使用 IPv4/IPv6 双协议栈,确保为集群的相关组件启用 IPv6DualStack
特性门控 ,
(从 1.21 版本开始,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>
说明:
IPv4 CIDR 的一个例子:10.244.0.0/16
(尽管你会提供你自己的地址范围)。
IPv6 CIDR 的一个例子:fdXY:IJKL:MNOP:15::/64
(这里演示的是格式而非有效地址 - 请看 RFC 4193 )。
从 1.21 开始 IPv4/IPv6 双协议栈默认为启用状态。
你可以在必要的时候通过为 kube-apiserver、kube-controller-manager、kubelet
和 kube-proxy 命令行设置 --feature-gates="IPv6DualStack=false"
来禁用
此特性。
服务
你可以使用 IPv4 或 IPv6 地址来创建
Service 。
服务的地址族默认为第一个服务集群 IP 范围的地址族(通过 kube-apiserver 的
--service-cluster-ip-range
参数配置)。
当你定义服务时,可以选择将其配置为双栈。若要指定所需的行为,你可以设置
.spec.ipFamilyPolicy
字段为以下值之一:
SingleStack
:单栈服务。控制面使用第一个配置的服务集群 IP 范围为服务分配集群 IP。
PreferDualStack
:
为服务分配 IPv4 和 IPv6 集群 IP 地址。
(如果集群设置了 --feature-gates="IPv6DualStack=false"
,则此设置的行为与
SingleStack
设置相同。)
RequireDualStack
:从 IPv4 和 IPv6 的地址范围分配服务的 .spec.ClusterIPs
从基于在 .spec.ipFamilies
数组中第一个元素的地址族的 .spec.ClusterIPs
列表中选择 .spec.ClusterIP
如果你想要定义哪个 IP 族用于单栈或定义双栈 IP 族的顺序,可以通过设置
服务上的可选字段 .spec.ipFamilies
来选择地址族。
说明:
.spec.ipFamilies
字段是不可变的,因为系统无法为已经存在的服务重新分配
.spec.ClusterIP
。如果你想改变 .spec.ipFamilies
,则需要删除并重新创建服务。
你可以设置 .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 : MyApp
spec :
selector :
app : 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 : MyApp
spec :
ipFamilyPolicy : PreferDualStack
selector :
app : 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 : MyApp
spec :
ipFamilyPolicy : PreferDualStack
ipFamilies :
- IPv6
- IPv4
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
现有服务的双栈默认值
下面示例演示了在服务已经存在的集群上新启用双栈时的默认行为。
(将现有集群升级到 1.21 会启用双协议栈支持,除非设置了
--feature-gates="IPv6DualStack=false"
)
在集群上启用双栈时,控制面会将现有服务(无论是 IPv4
还是 IPv6
)配置
.spec.ipFamilyPolicy
为 SingleStack
并设置 .spec.ipFamilies
为服务的当前地址族。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app : MyApp
spec :
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查现有服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app : 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 : 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 : MyApp
spec :
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查带有选择算符的现有无头服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app : MyApp
name : my-service
spec :
clusterIP : None
clusterIPs :
- None
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app : 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 伪装。
接下来