在 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 地址,以便前端可以使用工作负载的后端部分?
这就是 Service 的用武之地。
Kubernetes Service API 是一种抽象,旨在帮助你通过网络暴露一组 Pod。每个 Service 对象定义了一组逻辑端点(通常这些端点是 Pod),以及关于如何使这些 Pod 可访问的策略。
例如,考虑一个运行 3 个副本的无状态图像处理后端。这些副本是可互换的——前端并不关心它们使用哪个后端。虽然构成后端集合的实际 Pod 可能会发生变化,但前端客户端不需要意识到这一点,也不需要自己跟踪后端集合。
Service 抽象实现了这种解耦。
Service 目标 Pod 的集合通常由你定义的 选择器(selector) 决定。要了解定义 Service 端点的其他方法,请参阅 不带选择器的 Service。
如果你的工作负载使用 HTTP,你可能选择使用 Ingress 来控制 Web 流量如何到达该工作负载。Ingress 不是一种 Service 类型,但它充当集群的入口点。Ingress 允许你将路由规则合并到一个资源中,这样你就可以将运行在集群中不同位置的多个工作负载组件暴露在一个单一的监听器之后。
Kubernetes 的 Gateway API 提供了超越 Ingress 和 Service 的额外功能。你可以将 Gateway 添加到你的集群中——它是一个扩展 API 系列,使用 CustomResourceDefinitions 实现——然后使用它们来配置集群中运行的网络服务的访问权限。
如果你的应用程序可以使用 Kubernetes API 进行服务发现,你可以查询 API 服务器 以查找匹配的 EndpointSlice。每当 Service 中的 Pod 集合发生变化时,Kubernetes 都会更新该 Service 的 EndpointSlice。
对于非原生应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。
无论哪种方式,你的工作负载都可以使用这些 服务发现 机制来找到它想要连接的目标。
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”的新 Service,其默认 ClusterIP 为 Service 类型。该 Service 针对任何带有 app.kubernetes.io/name: MyApp 标签的 Pod 的 TCP 端口 9376。
Kubernetes 为此 Service 分配一个 IP 地址(集群 IP),该地址通过虚拟 IP 地址机制使用。有关该机制的更多详细信息,请阅读 虚拟 IP 和 Service 代理。
该 Service 的控制器会持续扫描匹配其选择器的 Pod,然后对该 Service 的 EndpointSlice 集合进行必要的更新。
Service 对象的名称必须是有效的 RFC 1123 标签名。
port 映射到 targetPort。为了方便起见,默认情况下 targetPort 被设置为与 port 字段相同的值。Pod 中的端口定义有名称,你可以在 Service 的 targetPort 属性中引用这些名称。例如,我们可以通过以下方式将 Service 的 targetPort 绑定到 Pod 端口:
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
---
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
即使 Service 中混合了使用单一配置名称的 Pod,且相同的网络协议可以通过不同的端口号获得,这种方式依然有效。这为部署和演进你的 Service 提供了很大的灵活性。例如,你可以更改后端软件下一版本中 Pod 暴露的端口号,而不会破坏客户端。
Service 的默认协议是 TCP;你也可以使用任何其他 支持的协议。
由于许多 Service 需要暴露不止一个端口,Kubernetes 支持单个 Service 的 多端口定义。每个端口定义可以具有相同的 protocol,或者不同的协议。
得益于选择器,Service 最常用于抽象对 Kubernetes Pod 的访问,但当与一组对应的 EndpointSlices 对象配合使用且没有选择器时,Service 可以抽象其他类型的后端,包括运行在集群之外的后端。
例如
在这些场景中的任何一个,你都可以定义一个 没有 指定匹配 Pod 选择器的 Service。例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
由于此 Service 没有选择器,因此不会自动创建相应的 EndpointSlice 对象。你可以通过手动添加 EndpointSlice 对象,将 Service 映射到它正在运行的网络地址和端口。例如:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # by convention, use the name of the Service
# as a prefix for the name of the EndpointSlice
labels:
# You should set the "kubernetes.io/service-name" label.
# Set its value to match the name of the Service
kubernetes.io/service-name: my-service
addressType: IPv4
ports:
- name: http # should match with the name of the service port defined above
appProtocol: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- "10.4.5.6"
- addresses:
- "10.1.2.3"
当你为 Service 创建 EndpointSlice 对象时,你可以为 EndpointSlice 使用任何名称。命名空间中的每个 EndpointSlice 必须具有唯一的名称。你可以通过在 EndpointSlice 上设置 kubernetes.io/service-name 标签 将其链接到 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 Service 的集群 IP,因为 kube-proxy 不支持虚拟 IP 作为目的地。
对于你自己创建或在自己的代码中创建的 EndpointSlice,你还应该选择一个值用于标签 endpointslice.kubernetes.io/managed-by。如果你创建自己的控制器代码来管理 EndpointSlices,请考虑使用类似于 "my-domain.example/name-of-controller" 的值。如果你正在使用第三方工具,请使用该工具的全小写名称,并将空格和其他标点符号改为连字符 (-)。如果人们直接使用 kubectl 等工具来管理 EndpointSlices,请使用描述这种手动管理的名称,例如 "staff" 或 "cluster-admins"。你应该避免使用保留值 "controller",它用于标识由 Kubernetes 自身控制平面管理的 EndpointSlices。
访问没有选择器的 Service 与有选择器的 Service 工作方式相同。在没有选择器的 Service 示例 中,流量被路由到 EndpointSlice 清单中定义的两个端点之一:以 9376 端口连接到 10.1.2.3 或 10.4.5.6 的 TCP 连接。
kubectl port-forward service/<service-name> forwardedPort:servicePort 等操作在 Service 没有选择器时会失败。这可以防止 Kubernetes API 服务器被用作调用者可能无权访问的端点的代理。ExternalName Service 是一种特殊类型的 Service,它没有选择器,而是使用 DNS 名称。有关更多信息,请参阅 ExternalName 章节。
Kubernetes v1.21 [稳定]EndpointSlices 是代表 Service 后端网络端点子集(分片)的对象。
你的 Kubernetes 集群会跟踪每个 EndpointSlice 代表的端点数量。如果 Service 的端点太多而达到阈值,Kubernetes 会添加另一个空的 EndpointSlice,并将新的端点信息存储在那里。默认情况下,一旦现有的 EndpointSlices 都包含至少 100 个端点,Kubernetes 就会创建一个新的 EndpointSlice。除非需要添加额外的端点,否则 Kubernetes 不会创建新的 EndpointSlice。
有关此 API 的更多信息,请参阅 EndpointSlices。
Kubernetes v1.33 [已弃用]EndpointSlice API 是旧版 Endpoints API 的演进。已弃用的 Endpoints API 相对于 EndpointSlice 有几个问题:
因此,建议所有客户端使用 EndpointSlice API 而不是 Endpoints。
Kubernetes 限制了单个 Endpoints 对象可以容纳的端点数量。当 Service 的后端端点超过 1000 个时,Kubernetes 会截断 Endpoints 对象中的数据。由于一个 Service 可以链接多个 EndpointSlice,因此 1000 个后端端点的限制仅影响旧版 Endpoints API。
在这种情况下,Kubernetes 最多选择 1000 个可能的后端端点存储到 Endpoints 对象中,并在 Endpoints 上设置一个 注解(annotation):endpoints.kubernetes.io/over-capacity: truncated。如果后端 Pod 的数量降至 1000 以下,控制平面也会删除该注解。
流量仍然会发送到后端,但任何依赖旧版 Endpoints API 的负载均衡机制最多只会将流量发送到 1000 个可用后端端点中的一部分。
相同的 API 限制意味着你无法手动更新 Endpoints 以使其拥有超过 1000 个端点。
Kubernetes v1.20 [稳定]appProtocol 字段为每个 Service 端口提供了一种指定应用协议的方法。这被用作实现的一种提示,以便为它们理解的协议提供更丰富的功能。此字段的值会被对应的 Endpoints 和 EndpointSlice 对象镜像。
该字段遵循标准的 Kubernetes 标签语法。有效值包括:
实现定义的带前缀名称,例如 mycompany.com/my-custom-protocol。
Kubernetes 定义的前缀名称
| 协议 | 描述 |
|---|---|
kubernetes.io/h2c | 如 RFC 7540 中描述的明文 HTTP/2 |
kubernetes.io/ws | 如 RFC 6455 中描述的明文 WebSocket |
kubernetes.io/wss | 如 RFC 6455 中描述的 TLS WebSocket |
对于某些 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 无效。
对于应用程序的某些部分(例如前端),你可能希望将 Service 暴露在外部 IP 地址上,即从集群外部可访问的 IP。
Kubernetes Service 类型允许你指定你想要的 Service 类型。
可用的 type 值及其行为包括:
ClusterIPtype 时所使用的默认值。你可以使用 Ingress 或 Gateway 将 Service 暴露给公共互联网。NodePortNodePort)暴露 Service。为了使节点端口可用,Kubernetes 会设置一个集群 IP 地址,就像你请求了 type: ClusterIP 的 Service 一样。LoadBalancerExternalNameexternalName 字段的内容(例如,映射到主机名 api.foo.bar.example)。映射会将集群的 DNS 服务器配置为返回带有该外部主机名值的 CNAME 记录。不设置任何类型的代理。Service API 中的 type 字段被设计为嵌套功能——每一级都增加了上一级的功能。然而,这种嵌套设计有一个例外。你可以通过 禁用负载均衡器 NodePort 分配 来定义 LoadBalancer Service。
type: ClusterIP这种默认的 Service 类型从集群为该目的保留的 IP 地址池中分配 IP 地址。
其他几种 Service 类型都是在 ClusterIP 类型的基础上构建的。
如果你定义的 Service 将 .spec.clusterIP 设置为 "None",则 Kubernetes 不会分配 IP 地址。有关更多信息,请参阅 Headless Service。
你可以指定自己的集群 IP 地址作为 Service 创建请求的一部分。为此,请设置 .spec.clusterIP 字段。例如,如果你已经拥有想要重用的现有 DNS 条目,或者旧系统被配置为特定 IP 地址且难以重新配置。
你选择的 IP 地址必须是为 API 服务器配置的 service-cluster-ip-range CIDR 范围内的有效 IPv4 或 IPv6 地址。如果你尝试创建带有无效 clusterIP 地址值的 Service,API 服务器将返回 422 HTTP 状态码,指示存在问题。
阅读 避免冲突,了解 Kubernetes 如何帮助降低两个不同的 Service 尝试同时使用相同 IP 地址的风险和影响。
type: NodePort如果你将 type 字段设置为 NodePort,Kubernetes 控制平面会从由 --service-node-port-range 标志指定的范围(默认:30000-32767)中分配一个端口。每个节点都会将该端口(每个节点上的相同端口号)代理到你的 Service 中。你的 Service 会在其 .spec.ports[*].nodePort 字段中报告分配的端口。
使用 NodePort 可以让你自由地设置自己的负载均衡解决方案,配置不完全受 Kubernetes 支持的环境,甚至直接暴露一个或多个节点的 IP 地址。
对于节点端口 Service,Kubernetes 还会额外分配一个端口(与 Service 协议匹配的 TCP、UDP 或 SCTP)。集群中的每个节点都会配置自己监听该分配的端口,并将流量转发到与该 Service 相关联的就绪端点之一。你将能够通过使用适当的协议(例如:TCP)和适当的端口(分配给该 Service 的端口)连接到任何节点,从而从集群外部联系该 type: NodePort Service。
如果你想要特定的端口号,可以在 nodePort 字段中指定一个值。控制平面要么为你分配该端口,要么报告 API 事务失败。这意味着你需要自己注意可能的端口冲突。你还必须使用有效的端口号,即在配置用于 NodePort 的范围内的端口。
这是一个 type: NodePort 且指定了 NodePort 值(本例中为 30007)的 Service 清单示例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
# By default and for convenience, the `targetPort` is set to
# the same value as the `port` field.
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane
# will allocate a port from a range (default: 30000-32767)
nodePort: 30007
为 NodePort Service 分配端口的策略同时适用于自动分配和手动分配方案。当用户想要创建使用特定端口的 NodePort Service 时,目标端口可能会与已分配的另一个端口冲突。
为了避免此问题,NodePort Service 的端口范围被分为两个波段。动态端口分配默认使用上限波段,一旦上限波段耗尽,它可能会使用下限波段。用户随后可以从下限波段分配,以降低端口冲突的风险。
使用默认的 NodePort 范围 30000-32767 时,波段划分如下:
有关静态和动态波段如何计算的更多详细信息,请参阅 避免为 NodePort Service 分配端口时的冲突。
type: NodePort Service 的自定义 IP 地址配置你可以设置集群中的节点,使其使用特定的 IP 地址来服务节点端口 Service。如果你希望每个节点连接到多个网络(例如:一个网络用于应用流量,另一个网络用于节点与控制平面之间的流量),你可能需要这样做。
如果你想指定特定的 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 Service 选择回环接口。--nodeport-addresses 的默认值是一个空列表。这意味着 kube-proxy 应该考虑所有可用的网络接口用于 NodePort。(这也与较早的 Kubernetes 版本兼容。)
<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 的 Service,Kubernetes 通常首先执行等效于你请求 type: NodePort Service 的更改。然后,cloud-controller-manager 组件配置外部负载均衡器,以将流量转发到分配的节点端口。
你可以配置负载均衡的 Service 忽略分配节点端口,前提是云提供商实现支持此功能。
一些云提供商允许你指定 loadBalancerIP。在这种情况下,负载均衡器会使用用户指定的 loadBalancerIP 创建。如果未指定 loadBalancerIP 字段,则负载均衡器将使用临时 IP 地址设置。如果你指定了 loadBalancerIP 但你的云提供商不支持该功能,则你设置的 loadbalancerIP 字段将被忽略。
Service 的 .spec.loadBalancerIP 字段已在 Kubernetes v1.24 中弃用。
此字段定义不明确,其含义在不同实现中各不相同。它也不支持双栈网络。此字段可能会在以后的 API 版本中被删除。
如果你正在集成的提供商支持通过(提供商特定的)注解为 Service 指定负载均衡器 IP 地址,你应该转而使用该方法。
如果你正在编写用于负载均衡器与 Kubernetes 集成代码,请避免使用此字段。你可以改为与 Gateway 集成,或者在 Service 上定义你自己的(提供商特定的)注解来指定等效细节。
负载均衡器健康检查对于现代应用至关重要。它们用于确定负载均衡器应将流量分发到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 未定义如何为 Kubernetes 管理的负载均衡器实现健康检查,而是由云提供商(以及实现集成代码的人员)决定行为。负载均衡器健康检查在支持 Service 的 externalTrafficPolicy 字段的上下文中被广泛使用。
Kubernetes v1.26 [稳定](默认启用)默认情况下,对于 LoadBalancer 类型的 Service,当定义了多个端口时,所有端口必须具有相同的协议,并且协议必须是云提供商支持的协议。
特性门控 MixedProtocolLBService(自 v1.24 起默认针对 kube-apiserver 启用)允许在定义多个端口时,为 LoadBalancer 类型的 Service 使用不同的协议。
Kubernetes v1.24 [稳定]你可以选择禁用 type: LoadBalancer Service 的节点端口分配,通过将字段 spec.allocateLoadBalancerNodePorts 设置为 false。这仅应用于将流量直接路由到 Pod 的负载均衡器实现,而不是使用节点端口的实现。默认情况下,spec.allocateLoadBalancerNodePorts 为 true,类型为 LoadBalancer 的 Service 将继续分配节点端口。如果 spec.allocateLoadBalancerNodePorts 在已分配节点端口的现有 Service 上被设置为 false,则这些节点端口将不会被自动释放。你必须明确删除每个 Service 端口中的 nodePorts 条目才能释放这些节点端口。
Kubernetes v1.24 [稳定]对于 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”。未加前缀的名称保留供最终用户使用。
对于 type: LoadBalancer 的 Service,控制器可以设置 .status.loadBalancer.ingress.ipMode。.status.loadBalancer.ingress.ipMode 指定了负载均衡器 IP 的行为方式。只有在同时指定了 .status.loadBalancer.ingress.ip 字段时,才能指定该模式。
.status.loadBalancer.ingress.ipMode 有两个可能的值:“VIP” 和 “Proxy”。默认值为 “VIP”,意味着流量被发送到目的地设置为负载均衡器 IP 和端口的节点。根据云提供商的负载均衡器交付流量的方式,将其设置为 “Proxy” 时有两种情况:
Service 实现可以使用此信息来调整流量路由。
在混合环境中,有时需要路由来自相同(虚拟)网络地址块内部的 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-scheme: "internal"
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
type: ExternalName类型为 ExternalName 的 Service 将 Service 映射到 DNS 名称,而不是典型的选择器(如 my-service 或 cassandra)。你使用 spec.externalName 参数指定这些 Service。
例如,此 Service 定义将 prod 命名空间中的 my-service 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 的 Service 接受 IPv4 地址字符串,但将该字符串视为由数字组成的 DNS 名称,而不是 IP 地址(互联网不允许在 DNS 中使用此类名称)。看起来像 IPv4 地址的外部名称 Service 不会被 DNS 服务器解析。
如果你想将 Service 直接映射到特定 IP 地址,请考虑使用 Headless Service。
当查找主机 my-service.prod.svc.cluster.local 时,集群 DNS Service 返回一个值为 my.database.example.com 的 CNAME 记录。访问 my-service 的方式与其他 Service 相同,但关键区别在于重定向发生在 DNS 级别,而不是通过代理或转发。如果你以后决定将数据库迁移到集群中,你可以启动其 Pod,添加适当的选择器或端点,并更改 Service 的 type。
你可能会在某些常用协议(包括 HTTP 和 HTTPS)中使用 ExternalName 时遇到问题。如果你使用 ExternalName,那么集群内部客户端使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,这种差异可能会导致错误或意外响应。HTTP 请求将具有源服务器无法识别的 Host: 请求头;TLS 服务器将无法提供匹配客户端连接的主机名的证书。
有时你不需要负载均衡和单一的 Service IP。在这种情况下,你可以通过显式指定集群 IP 地址为 "None"(.spec.clusterIP)来创建所谓的 Headless Service。
你可以使用 Headless Service 与其他服务发现机制进行接口,而不受 Kubernetes 实现的约束。
对于 Headless Service,不会分配集群 IP,kube-proxy 不会处理这些 Service,平台也不会为它们执行负载均衡或代理。
Headless Service 允许客户端直接连接到它首选的任何 Pod。Headless Service 不使用 虚拟 IP 和代理 配置路由和数据包转发;相反,Headless Service 通过集群的 DNS 服务 返回内部 DNS 记录,从而报告单个 Pod 的端点 IP 地址。要定义 Headless Service,你需要创建一个将 .spec.type 设置为 ClusterIP(这也是 type 的默认值)的 Service,并额外将 .spec.clusterIP 设置为 None。
字符串值 None 是一个特例,与将 .spec.clusterIP 字段留空不同。
DNS 的自动配置方式取决于 Service 是否定义了选择器:
对于定义了选择器的 Headless Service,Endpoints 控制器在 Kubernetes API 中创建 EndpointSlices,并修改 DNS 配置以返回直接指向 Service 后端 Pod 的 A 或 AAAA 记录(IPv4 或 IPv6 地址)。
对于未定义选择器的 Headless Service,控制平面不会创建 EndpointSlice 对象。但是,DNS 系统会查找并配置以下任一项:
type: ExternalName Service 的 DNS CNAME 记录。ExternalName 外的所有 Service 类型)。当你定义不带选择器的 Headless Service 时,port 必须匹配 targetPort。
对于在集群内部运行的客户端,Kubernetes 支持两种主要的查找 Service 的模式:环境变量和 DNS。
当 Pod 在节点上运行时,kubelet 会为每个活跃的 Service 添加一组环境变量。它会添加 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,其中 Service 名称转换为大写,连字符转换为下划线。
例如,Service redis-primary 暴露 TCP 端口 6379 并被分配了集群 IP 地址 10.0.0.11,会产生以下环境变量:
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
当你有一个需要访问 Service 的 Pod,且你正在使用环境变量方法向客户端 Pod 发布端口和集群 IP 时,你必须在客户端 Pod 存在 之前 创建 Service。否则,这些客户端 Pod 就不会填充它们的环境变量。
如果你仅使用 DNS 来发现 Service 的集群 IP,则无需担心此排序问题。
Kubernetes 还支持并提供与 Docker Engine “旧版容器链接” 功能兼容的变量。你可以阅读 makeLinkVariables 以了解这是如何在 Kubernetes 中实现的。
你可以(而且几乎总是应该)使用 插件(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(服务)记录。如果 my-service.my-ns Service 有一个名为 http 且协议设置为 TCP 的端口,你可以对 _http._tcp.my-service.my-ns 执行 DNS SRV 查询,以发现 http 的端口号以及 IP 地址。
Kubernetes DNS 服务器是访问 ExternalName Service 的唯一方式。你可以在 DNS 服务和 Pod 中找到有关 ExternalName 解析的更多信息。
阅读 虚拟 IP 和 Service 代理,了解 Kubernetes 提供的以虚拟 IP 地址暴露 Service 的机制。
你可以设置 .spec.internalTrafficPolicy 和 .spec.externalTrafficPolicy 字段来控制 Kubernetes 如何将流量路由到健康的(“就绪”)后端。
有关更多详细信息,请参阅 流量策略。
.spec.trafficDistribution 字段提供了影响 Kubernetes Service 内流量路由的另一种方法。虽然流量策略侧重于严格的语义保证,但流量分布允许你表达 偏好(例如路由到拓扑上更近的端点)。这有助于优化性能、成本或可靠性。在 Kubernetes 1.36 中,支持以下值:
PreferSameZonePreferSameNodePreferClose(已弃用)PreferSameZone 的较旧别名,其语义不如后者清晰。如果未设置该字段,实现将应用其默认路由策略。
有关更多详细信息,请参阅 流量分布。
如果你想确保来自特定客户端的连接每次都传递到同一个 Pod,你可以根据客户端的 IP 地址配置会话保持。阅读 会话保持 以了解更多信息。
externalIPs 特性在 Kubernetes v1.36 中已弃用,所有用户都应开始迁移。请考虑改用外部负载均衡器控制器或 Gateway API 实现。如果存在路由到一个或多个集群节点的外部 IP,Kubernetes Service 可以暴露在这些 externalIPs 上。当网络流量到达集群时,如果目标 IP 是外部 IP 且端口与该 Service 匹配,Kubernetes 配置的规则和路由将确保流量被路由到该 Service 的端点之一。
当你定义 Service 时,你可以为任何 Service 类型 指定 externalIPs。在下面的示例中,名为 "my-service" 的 Service 可以通过 TCP 被客户端访问,使用 "198.51.100.32:80"(由 .spec.externalIPs[] 和 .spec.ports[].port 计算得出)。
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
externalIPs 的分配;这些由集群管理员负责。Service 是 Kubernetes REST API 中的顶级资源。你可以在 Service API 对象 中找到更多详细信息。
了解更多关于 Service 以及它们如何融入 Kubernetes 的信息:
如需更多背景信息,请阅读以下内容: