服务
在 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
Service API 是 Kubernetes 的一部分,它是一种抽象,可帮助你通过网络公开 Pod 组。每个 Service 对象都定义了一组逻辑端点(通常这些端点是 Pod),以及有关如何使这些 Pod 可访问的策略。
例如,考虑一个运行 3 个副本的无状态图像处理后端。这些副本是可互换的——前端不关心它们使用哪个后端。虽然构成后端集合的实际 Pod 可能会改变,但前端客户端不应需要知道这一点,也不应需要自行跟踪后端集合。
Service 抽象实现了这种解耦。
Service 所针对的 Pod 集合通常由你定义的选择器决定。要了解定义 Service 端点的其他方式,请参阅没有选择器的 Service。
如果你的工作负载使用 HTTP,你可能选择使用 Ingress 来控制 Web 流量如何到达该工作负载。Ingress 不是 Service 类型,但它充当集群的入口点。Ingress 允许你将路由规则整合到单个资源中,以便你可以通过单个侦听器公开工作负载的多个组件,这些组件在集群中独立运行。
Kubernetes 的 Gateway API 提供了超越 Ingress 和 Service 的额外功能。你可以将 Gateway 添加到你的集群中——它是一个扩展 API 系列,使用 CustomResourceDefinitions 实现——然后使用这些 API 来配置对集群中运行的网络服务的访问。
云原生服务发现
如果你的应用程序能够使用 Kubernetes API 进行服务发现,你可以查询 API 服务器以获取匹配的 EndpointSlice。当 Service 中的 Pod 集合发生变化时,Kubernetes 会更新 Service 的 EndpointSlice。
对于非原生应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。
无论哪种方式,你的工作负载都可以使用这些服务发现机制来找到它想要连接的目标。
定义 Service
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 针对任何带有 `app.kubernetes.io/name: MyApp` 标签的 Pod 的 TCP 端口 9376。
Kubernetes 为此 Service 分配一个 IP 地址(**集群 IP**),该地址由虚拟 IP 地址机制使用。有关该机制的更多详细信息,请阅读虚拟 IP 和 Service 代理。
该 Service 的控制器持续扫描匹配其选择器的 Pod,然后对 Service 的 EndpointSlice 集合进行任何必要的更新。
Service 对象的名称必须是有效的 RFC 1035 标签名称。
注意
一个 Service 可以将 **任意** 传入的 `port` 映射到 `targetPort`。默认情况下,为了方便起见,`targetPort` 的值与 `port` 字段相同。端口定义
Pod 中的端口定义有名称,你可以在 Service 的 `targetPort` 属性中引用这些名称。例如,我们可以按如下方式将 Service 的 `targetPort` 绑定到 Pod 端口
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: proxy
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
name: http-web-svc
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app.kubernetes.io/name: proxy
ports:
- name: name-of-service-port
protocol: TCP
port: 80
targetPort: http-web-svc
即使 Service 中混合使用了具有单一配置名称的 Pod,并通过不同的端口号提供相同的网络协议,这仍然有效。这为部署和演进你的 Service 提供了很大的灵活性。例如,你可以在后端软件的下一个版本中更改 Pod 公开的端口号,而不会破坏客户端。
由于许多 Service 需要公开多个端口,Kubernetes 支持为单个 Service 定义多个端口。每个端口定义可以具有相同的 `protocol`,也可以具有不同的 `protocol`。
没有选择器的 Service
Service 最常见的是通过选择器抽象对 Kubernetes Pod 的访问,但当与相应的 EndpointSlice 对象集一起使用且没有选择器时,Service 可以抽象其他类型的后端,包括在集群外部运行的后端。
例如
- 你希望在生产环境中拥有一个外部数据库集群,但在测试环境中你使用自己的数据库。
- 你希望将你的 Service 指向不同 命名空间或另一个集群中的 Service。
- 你正在将工作负载迁移到 Kubernetes。在评估方法时,你只在 Kubernetes 中运行一部分后端。
在任何这些场景中,你都可以定义一个 Service,**而无需**指定选择器来匹配 Pod。例如
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"
自定义 EndpointSlice
当您为 Service 创建 EndpointSlice 对象时,您可以为 EndpointSlice 使用任何名称。命名空间中的每个 EndpointSlice 都必须具有唯一的名称。您通过在 EndpointSlice 上设置 `kubernetes.io/service-name` 标签来将 EndpointSlice 链接到 Service。
注意
端点 IP **不得**是:回环地址(IPv4 为 127.0.0.0/8,IPv6 为 ::1/128),或链路本地地址(IPv4 为 169.254.0.0/16 和 224.0.0.0/24,IPv6 为 fe80::/64)。
端点 IP 地址不能是其他 Kubernetes Service 的集群 IP,因为 kube-proxy 不支持虚拟 IP 作为目标。
对于您自己创建的 EndpointSlice 或在您自己的代码中创建的 EndpointSlice,您还应该选择一个值用于标签 `endpointslice.kubernetes.io/managed-by`。如果您编写自己的控制器代码来管理 EndpointSlice,请考虑使用类似于 `"my-domain.example/name-of-controller"` 的值。如果您使用的是第三方工具,请使用全小写工具的名称,并将空格和其他标点符号更改为短划线 (`-`)。如果人们直接使用 `kubectl` 等工具来管理 EndpointSlice,请使用描述这种手动管理的名称,例如 `"staff"` 或 `"cluster-admins"`。您应避免使用保留值 `"controller"`,该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlice。
访问没有选择器的 Service
访问没有选择器的 Service 的方式与有选择器的方式相同。在没有选择器的 Service 的示例中,流量被路由到 EndpointSlice 清单中定义的两个端点之一:一个到 10.1.2.3 或 10.4.5.6 的 TCP 连接,端口为 9376。
注意
Kubernetes API 服务器不允许代理到未映射到 Pod 的端点。像 `kubectl port-forward service/`ExternalName` Service 是一种特殊的 Service 类型,它没有选择器并使用 DNS 名称。有关更多信息,请参阅 ExternalName 部分。
EndpointSlice
Kubernetes v1.21 [stable]
EndpointSlice 是代表 Service 后备网络端点子集(一个**切片**)的对象。
你的 Kubernetes 集群跟踪每个 EndpointSlice 表示的端点数量。如果 Service 的端点太多以至于达到阈值,Kubernetes 会添加另一个空的 EndpointSlice 并在其中存储新的端点信息。默认情况下,一旦现有 EndpointSlice 都包含至少 100 个端点,Kubernetes 就会创建一个新的 EndpointSlice。直到需要添加额外端点时,Kubernetes 才创建新的 EndpointSlice。
有关此 API 的更多信息,请参阅EndpointSlice。
端点(已弃用)
EndpointSlice API 是旧版 Endpoints API 的演进。已弃用的 Endpoints API 相对于 EndpointSlice 有几个问题
- 它不支持双栈集群。
- 它不包含支持新功能所需的信息,例如 trafficDistribution。
- 如果端点列表太长而无法容纳在一个对象中,它将被截断。
因此,建议所有客户端使用 EndpointSlice API 而不是 Endpoints。
超容量端点
Kubernetes 限制了单个 Endpoints 对象中可容纳的端点数量。当 Service 的后端端点超过 1000 个时,Kubernetes 会截断 Endpoints 对象中的数据。由于 Service 可以与多个 EndpointSlice 关联,因此 1000 个后端端点限制仅影响旧版 Endpoints API。
在这种情况下,Kubernetes 最多选择 1000 个可能的后端端点存储到 Endpoints 对象中,并在 Endpoints 上设置一个注解:`endpoints.kubernetes.io/over-capacity: truncated`。如果后端 Pod 的数量低于 1000,控制平面也会删除该注解。
流量仍会发送到后端,但任何依赖旧版 Endpoints API 的负载均衡机制只会将流量发送到最多 1000 个可用的后端端点。
同样的 API 限制意味着您无法手动更新 Endpoints 以使其包含超过 1000 个端点。
应用协议
Kubernetes v1.20 [stable]
`appProtocol` 字段提供了一种为每个 Service 端口指定应用程序协议的方法。这被用作实现提供更丰富行为的提示,对于它们理解的协议。此字段的值由相应的 Endpoints 和 EndpointSlice 对象镜像。
此字段遵循标准 Kubernetes 标签语法。有效值之一是
实现定义的带前缀名称,例如 `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
对于某些 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 地址,该地址可从集群外部访问。
Kubernetes Service 类型允许你指定所需 Service 的类型。
可用的 `type` 值及其行为如下
ClusterIP
- 将 Service 公开在集群内部 IP 上。选择此值使 Service 只能从集群内部访问。这是如果你未明确指定 Service 的 `type` 时使用的默认值。你可以使用 Ingress 或 Gateway 将 Service 公开到公共互联网。
NodePort
- 在每个节点的 IP 地址上以静态端口(`NodePort`)公开 Service。为了使节点端口可用,Kubernetes 会设置一个集群 IP 地址,与你请求 `type: ClusterIP` 的 Service 相同。
LoadBalancer
- 使用外部负载均衡器在外部公开 Service。Kubernetes 不直接提供负载均衡组件;你必须提供一个,或者你可以将你的 Kubernetes 集群与云提供商集成。
ExternalName
- 将 Service 映射到 `externalName` 字段的内容(例如,到主机名 `api.foo.bar.example`)。该映射会将你的集群 DNS 服务器配置为返回一个包含该外部主机名值的 `CNAME` 记录。不设置任何类型的代理。
Service API 中的 `type` 字段被设计为嵌套功能——每个级别都建立在前一个级别之上。但这种嵌套设计有一个例外。你可以通过禁用负载均衡器 `NodePort` 分配来定义 `LoadBalancer` Service。
类型:ClusterIP
此默认 Service 类型从你的集群为此目的保留的 IP 地址池中分配一个 IP 地址。
Service 的其他几种类型都是以 `ClusterIP` 类型为基础构建的。
如果你定义的 Service 的 `spec.clusterIP` 设置为 `None`,那么 Kubernetes 不会分配 IP 地址。有关更多信息,请参阅无头 Service。
选择你自己的 IP 地址
你可以在创建 `Service` 请求时指定你自己的集群 IP 地址。为此,请设置 `spec.clusterIP` 字段。例如,如果你已经有一个要重用的现有 DNS 条目,或者旧系统已配置为特定 IP 地址且难以重新配置。
你选择的 IP 地址必须是 API 服务器配置的 `service-cluster-ip-range` CIDR 范围内的有效 IPv4 或 IPv6 地址。如果你尝试使用无效的 `clusterIP` 地址值创建 Service,API 服务器将返回 422 HTTP 状态码以指示存在问题。
阅读避免冲突,了解 Kubernetes 如何帮助减少两个不同的 Service 都试图使用相同 IP 地址的风险和影响。
类型:NodePort
如果你将 `type` 字段设置为 `NodePort`,Kubernetes 控制平面会从 `---service-node-port-range` 标志(默认:30000-32767)指定的范围内分配一个端口。每个节点都会将该端口(每个节点上的端口号相同)代理到你的 Service。你的 Service 会在其 `spec.ports[*].nodePort` 字段中报告已分配的端口。
使用 NodePort 让你能够自由设置自己的负载均衡解决方案,配置 Kubernetes 不完全支持的环境,甚至直接暴露一个或多个节点的 IP 地址。
对于节点端口 Service,Kubernetes 还会额外分配一个端口(TCP、UDP 或 SCTP 以匹配 Service 的协议)。集群中的每个节点都会自行配置,以侦听该分配的端口,并将流量转发到与该 Service 关联的一个可用端点。你可以通过连接到任何节点,使用适当的协议(例如:TCP)和适当的端口(如分配给该 Service 的端口),从集群外部联系 `type: NodePort` Service。
选择你自己的端口
如果你想要一个特定的端口号,可以在 `nodePort` 字段中指定一个值。控制平面要么分配给你该端口,要么报告 API 事务失败。这意味着你需要自己处理可能的端口冲突。你还必须使用一个有效的端口号,一个在 NodePort 使用的配置范围内。
这是一个 `type: NodePort` Service 的示例清单,其中指定了一个 NodePort 值(在此示例中为 30007)
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 范围以避免冲突
为 NodePort 服务分配端口的策略适用于自动分配和手动分配场景。当用户希望创建使用特定端口的 NodePort 服务时,目标端口可能与已分配的其他端口冲突。
为了避免此问题,NodePort 服务的端口范围分为两个波段。默认情况下,动态端口分配使用上部波段,一旦上部波段用完,它可以使用下部波段。然后,用户可以从下部波段分配,从而降低端口冲突的风险。
`type: NodePort` 服务的自定义 IP 地址配置
您可以设置集群中的节点,以使用特定的 IP 地址来提供节点端口服务。如果每个节点连接到多个网络(例如:一个用于应用程序流量的网络,另一个用于节点和控制平面之间的流量),您可能希望这样做。
如果您想指定特定的 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 服务选择回环接口。`--nodeport-addresses` 的默认值是空列表。这意味着 kube-proxy 应该考虑所有可用的网络接口用于 NodePort。(这也与早期 Kubernetes 版本兼容。)
注意
此服务显示为 `<NodeIP>:spec.ports[*].nodePort` 和 `.spec.clusterIP:spec.ports[*].port`。如果设置了 kube-proxy 的 `--nodeport-addresses` 标志或 kube-proxy 配置文件的等效字段,`<NodeIP>` 将是经过筛选的节点 IP 地址(或可能包含多个 IP 地址)。类型: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 等效的更改。然后,云控制器管理器组件配置外部负载均衡器,将流量转发到该分配的节点端口。
你可以配置一个负载均衡的 Service,以省略分配节点端口,前提是云提供商的实现支持此功能。
有些云提供商允许你指定 `loadBalancerIP`。在这些情况下,负载均衡器会使用用户指定的 `loadBalancerIP` 创建。如果未指定 `loadBalancerIP` 字段,则负载均衡器会设置一个临时 IP 地址。如果你指定了 `loadBalancerIP` 但你的云提供商不支持此功能,你设置的 `loadbalancerIP` 字段将被忽略。
注意
Service 的 `spec.loadBalancerIP` 字段在 Kubernetes v1.24 中已弃用。
该字段定义不明确,其含义因实现而异。它也不支持双栈网络。此字段可能会在未来的 API 版本中移除。
如果你正在与支持通过(提供商特定)注解为 Service 指定负载均衡器 IP 地址的提供商集成,你应该切换到这种方式。
如果你正在编写代码以实现与 Kubernetes 的负载均衡器集成,请避免使用此字段。你可以与 Gateway 而不是 Service 集成,或者你可以在 Service 上定义自己的(提供商特定)注解来指定等效的详细信息。
节点存活对负载均衡器流量的影响
负载均衡器健康检查对现代应用程序至关重要。它们用于确定负载均衡器应将流量分派到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 未定义如何为 Kubernetes 管理的负载均衡器实现健康检查,而是由云提供商(以及实现集成代码的人员)决定其行为。负载均衡器健康检查在支持 Service 的 `externalTrafficPolicy` 字段的上下文中被广泛使用。
混合协议类型的负载均衡器
Kubernetes v1.26 [stable]
(默认启用:true)默认情况下,对于 LoadBalancer 类型的 Service,当定义了多个端口时,所有端口都必须使用相同的协议,并且该协议必须是云提供商支持的协议。
特性门控 `MixedProtocolLBService`(从 v1.24 开始默认启用 kube-apiserver)允许 LoadBalancer 类型的 Service 在定义了多个端口时使用不同的协议。
注意
可用于负载均衡 Service 的协议集由您的云提供商定义;它们可能会施加超出 Kubernetes API 强制执行的限制。禁用负载均衡器 NodePort 分配
Kubernetes v1.24 [stable]
您可以选择性地为 type: LoadBalancer
的 Service 禁用节点端口分配,方法是将字段 spec.allocateLoadBalancerNodePorts
设置为 false
。这仅应在负载均衡器实现直接将流量路由到 Pod 而非使用节点端口的情况下使用。默认情况下,spec.allocateLoadBalancerNodePorts
为 true
,类型为 LoadBalancer 的 Service 将继续分配节点端口。如果将现有 Service 的 spec.allocateLoadBalancerNodePorts
设置为 false
,并且该 Service 已分配节点端口,则这些节点端口将不会自动取消分配。您必须在每个 Service 端口中显式移除 nodePorts
条目才能取消分配这些节点端口。
指定负载均衡器实现类
Kubernetes v1.24 [stable]
对于 type
设置为 LoadBalancer
的 Service,.spec.loadBalancerClass
字段使您能够使用云提供商默认值之外的负载均衡器实现。
默认情况下,.spec.loadBalancerClass
未设置,如果集群使用 --cloud-provider
组件标志配置了云提供商,则 LoadBalancer
类型的 Service 会使用云提供商的默认负载均衡器实现。
如果您指定 .spec.loadBalancerClass
,则假定与指定类匹配的负载均衡器实现正在监视 Service。任何默认的负载均衡器实现(例如,由云提供商提供的实现)将忽略设置了此字段的 Service。spec.loadBalancerClass
只能在 LoadBalancer
类型的 Service 上设置。一旦设置,就不能更改。spec.loadBalancerClass
的值必须是标签式标识符,可带有可选前缀,例如 "internal-vip
" 或 "example.com/internal-vip
"。未加前缀的名称保留给最终用户。
负载均衡器 IP 地址模式
Kubernetes v1.32 [stable]
(默认启用:true)对于 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”,具体取决于云提供商的负载均衡器如何传递流量:
- 如果流量被传递到节点,然后 DNAT 到 Pod,则目的地将设置为节点的 IP 和节点端口;
- 如果流量直接传递到 Pod,则目的地将设置为 Pod 的 IP 和端口。
Service 实现可以使用此信息来调整流量路由。
内部负载均衡器
在混合环境中,有时需要从同一(虚拟)网络地址块内的 Service 路由流量。
在分层 DNS 环境中,您需要两个 Service 才能将外部和内部流量路由到您的端点。
要设置内部负载均衡器,请根据您使用的云服务提供商,向您的 Service 添加以下注解之一:
选择其中一个选项卡。
metadata:
name: my-service
annotations:
networking.gke.io/load-balancer-type: "Internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
metadata:
name: my-service
annotations:
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
metadata:
annotations:
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/oci-load-balancer-internal: true
类型:ExternalName
ExternalName 类型的 Service 将 Service 映射到 DNS 名称,而不是典型的选择器(如 my-service
或 cassandra
)。您使用 spec.externalName
参数指定这些 Service。
例如,此 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 地址,请考虑使用无头 Service。
当查找主机 my-service.prod.svc.cluster.local
时,集群 DNS Service 返回一个 CNAME
记录,其值为 my.database.example.com
。访问 my-service
的方式与其他 Service 相同,但关键区别在于重定向发生在 DNS 级别,而不是通过代理或转发。如果您以后决定将数据库移入集群,则可以启动其 Pod、添加适当的选择器或端点,并更改 Service 的 type
。
注意
您在使用 ExternalName 处理某些常见协议(包括 HTTP 和 HTTPS)时可能会遇到问题。如果使用 ExternalName,则客户端在集群内部使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,这种差异可能导致错误或意外响应。HTTP 请求将具有原始服务器无法识别的 Host:
头;TLS 服务器将无法提供与客户端连接的主机名匹配的证书。
无头 Service
有时您不需要负载均衡和单个 Service IP。在这种情况下,您可以通过显式将集群 IP 地址(.spec.clusterIP
)指定为 "None"
来创建所谓的无头 Service。
您可以使用无头 Service 与其他服务发现机制进行接口,而无需与 Kubernetes 的实现绑定。
对于无头 Service,不会分配集群 IP,kube-proxy 不处理这些 Service,并且平台不会对其进行负载均衡或代理。
无头 Service 允许客户端直接连接到其首选的任何 Pod。无头 Service 不使用虚拟 IP 地址和代理配置路由和数据包转发;相反,无头 Service 通过内部 DNS 记录报告单个 Pod 的端点 IP 地址,这些记录通过集群的DNS 服务提供。要定义无头 Service,您需要创建一个 .spec.type
设置为 ClusterIP(这也是 type
的默认值)的 Service,并且您还需要将 .spec.clusterIP
设置为 None。
字符串值 None 是一种特殊情况,它与将 .spec.clusterIP
字段留空不同。
DNS 的自动配置方式取决于 Service 是否定义了选择器:
带选择器
对于定义了选择器的无头 Service,端点控制器在 Kubernetes API 中创建 EndpointSlice,并修改 DNS 配置以返回直接指向 Service 后端 Pod 的 A 或 AAAA 记录(IPv4 或 IPv6 地址)。
不带选择器
对于不定义选择器的无头 Service,控制平面不创建 EndpointSlice 对象。但是,DNS 系统会查找并配置:
- 用于
type: ExternalName
Service 的 DNS CNAME 记录。 - 对于除
ExternalName
之外的所有 Service 类型,DNS A / AAAA 记录用于 Service 就绪端点的所有 IP 地址。- 对于 IPv4 端点,DNS 系统创建 A 记录。
- 对于 IPv6 端点,DNS 系统创建 AAAA 记录。
当您定义没有选择器的无头 Service 时,port
必须与 targetPort
匹配。
服务发现
对于在集群内部运行的客户端,Kubernetes 支持两种主要的服务发现模式:环境变量和 DNS。
环境变量
当 Pod 在节点上运行时,kubelet 会为每个活动 Service 添加一组环境变量。它添加 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量,其中 Service 名称大写,破折号转换为下划线。
例如,暴露 TCP 端口 6379 且已分配集群 IP 地址 10.0.0.11 的 Service redis-primary
,会生成以下环境变量:
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,并且您使用环境变量方法将端口和集群 IP 发布到客户端 Pod 时,您必须在客户端 Pod 存在之前创建 Service。否则,这些客户端 Pod 的环境变量将不会被填充。
如果您只使用 DNS 来发现 Service 的集群 IP,则无需担心此顺序问题。
Kubernetes 还支持并提供与 Docker Engine 的“传统容器链接”功能兼容的变量。您可以阅读makeLinkVariables
以了解 Kubernetes 中如何实现此功能。
DNS
您可以使用附加组件为您的 Kubernetes 集群设置 DNS 服务(几乎总是应该这样做)。
一个集群感知的 DNS 服务器(例如 CoreDNS)会监视 Kubernetes API 以获取新的 Service,并为每个 Service 创建一组 DNS 记录。如果整个集群都启用了 DNS,那么所有 Pod 都应该能够自动通过其 DNS 名称解析 Service。
例如,如果您在 Kubernetes 命名空间 my-ns
中有一个名为 my-service
的 Service,则控制平面和 DNS Service 协同工作,为 my-service.my-ns
创建一个 DNS 记录。my-ns
命名空间中的 Pod 应该能够通过查找 my-service
(my-service.my-ns
也可以)来找到该服务。
其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns
。这些名称将解析到为 Service 分配的集群 IP。
Kubernetes 还支持命名端口的 DNS SRV(Service)记录。如果 my-service.my-ns
Service 有一个名为 http
的端口,协议设置为 TCP
,您可以通过 DNS SRV 查询 _http._tcp.my-service.my-ns
来发现 http
的端口号以及 IP 地址。
Kubernetes DNS 服务器是访问 ExternalName
Service 的唯一方式。您可以在Service 和 Pod 的 DNS中找到有关 ExternalName
解析的更多信息。
虚拟 IP 地址机制
阅读虚拟 IP 和 Service 代理解释了 Kubernetes 为使用虚拟 IP 地址暴露 Service 而提供的机制。
流量策略
您可以设置 .spec.internalTrafficPolicy
和 .spec.externalTrafficPolicy
字段来控制 Kubernetes 如何将流量路由到健康的(“就绪”)后端。
有关更多详细信息,请参见流量策略。
流量分发
Kubernetes v1.33 [stable]
(默认启用:true).spec.trafficDistribution
字段提供了另一种影响 Kubernetes Service 内流量路由的方式。流量策略侧重于严格的语义保证,而流量分发允许您表达偏好(例如路由到拓扑上更近的端点)。这有助于优化性能、成本或可靠性。在 Kubernetes 1.34 中,支持以下字段值:
PreferClose
- 表示优先将流量路由到与客户端位于同一区域的端点。
Kubernetes v1.34 [beta]
(默认启用:true)在 Kubernetes 1.34 中,还提供了两个附加值(除非 PreferSameTrafficDistribution
特性门控被禁用):
PreferSameZone
- 这是
PreferClose
的别名,能更清楚地表达预期的语义。 PreferSameNode
- 表示优先将流量路由到与客户端位于同一节点的端点。
如果未设置此字段,则实现将应用其默认路由策略。
有关更多详细信息,请参见流量分发
会话粘滞性
如果您想确保来自特定客户端的连接每次都传递到同一个 Pod,您可以根据客户端的 IP 地址配置会话亲和性。阅读会话亲和性以了解更多信息。
外部 IP
如果有外部 IP 路由到一个或多个集群节点,Kubernetes Service 可以通过这些 externalIPs
公开。当网络流量带着外部 IP(作为目标 IP)和与该 Service 匹配的端口到达集群时,Kubernetes 配置的规则和路由将确保流量被路由到该 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
注意
Kubernetes 不管理externalIPs
的分配;这些是集群管理员的责任。API 对象
Service 是 Kubernetes REST API 中的顶级资源。您可以在Service API 对象中找到更多详细信息。
下一步
了解有关 Service 及其如何融入 Kubernetes 的更多信息
- 遵循使用 Service 连接应用程序教程。
- 阅读Ingress,它将 HTTP 和 HTTPS 路由从集群外部暴露给集群内部的 Service。
- 阅读Gateway,它是 Kubernetes 的一个扩展,比 Ingress 提供更高的灵活性。
要获取更多背景信息,请阅读以下内容: