Service

通过单个对外可见的端点暴露在集群中运行的应用,即使工作负载被拆分到多个后端部署。

在 Kubernetes 中,Service 是一种方法,用于暴露在你的集群中以一个或多个 Pod 形式运行的网络应用。

Kubernetes 中 Service 的一个关键目标是,你无需修改现有应用来使用不熟悉的服务发现机制。你可以在 Pod 中运行代码,无论是为云原生世界设计的代码,还是已容器化的旧应用。你可以使用 Service 使该组 Pod 可在网络上访问,以便客户端可以与其交互。

如果你使用 Deployment 来运行你的应用,该 Deployment 可以动态创建和销毁 Pod。从这一刻到下一刻,你不知道有多少 Pod 在运行且健康;你甚至可能不知道这些健康的 Pod 的名称。Kubernetes Pod 会被创建和销毁以匹配集群的期望状态。Pod 是短暂的资源(你不应期望单个 Pod 可靠且持久)。

每个 Pod 都有自己的 IP 地址(Kubernetes 期望网络插件能够确保这一点)。对于集群中的一个 Deployment,某一时刻运行的 Pod 集合可能与片刻后运行该应用的 Pod 集合不同。

这导致了一个问题:如果某组 Pod(称它们为“后端”)为集群中的其他 Pod(称它们为“前端”)提供功能,前端如何找到并跟踪要连接的 IP 地址,以便前端可以使用工作负载的后端部分?

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 添加到集群中——它是一系列使用 CustomResourceDefinition 实现的扩展 API——然后使用这些 API 来配置对集群中运行的网络服务的访问。

云原生服务发现

如果你的应用可以使用 Kubernetes API 进行服务发现,你可以向 API Server 查询匹配的 EndpointSlice。只要 Service 中的 Pod 集合发生变化,Kubernetes 就会更新该 Service 的 EndpointSlice。

对于非云原生应用,Kubernetes 提供了多种方式,可在你的应用和后端 Pod 之间放置网络端口或负载均衡器。

无论哪种方式,你的工作负载都可以使用这些服务发现机制来找到它想要连接的目标。

定义一个 Service

Service 是一个对象(与 Pod 或 ConfigMap 对象相同)。你可以使用 Kubernetes API 创建、查看或修改 Service 定义。通常你使用像 kubectl 这样的工具来替你执行这些 API 调用。

例如,假设你有一组 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 地址机制使用。有关该机制的更多详情,请阅读虚拟 IP 和服务代理

该 Service 的控制器会持续扫描匹配其选择器的 Pod,然后对该 Service 的 EndpointSlice 集合进行任何必要的更新。

Service 对象的名称必须是有效的RFC 1035 标签名称

端口定义

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 的默认协议是 TCP;你也可以使用任何其他受支持的协议

因为许多 Service 需要暴露多个端口,Kubernetes 支持为单个 Service 定义多个端口。每个端口定义可以有相同的 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。

对于你自己或在自己的代码中创建的 EndpointSlice,你也应该选择一个值用于标签 endpointslice.kubernetes.io/managed-by。如果你创建自己的控制器代码来管理 EndpointSlice,请考虑使用类似 "my-domain.example/name-of-controller" 的值。如果你使用第三方工具,请使用该工具的全小写名称,并将空格和其他标点符号更改为短划线(-)。如果人们直接使用诸如 kubectl 的工具来管理 EndpointSlice,请使用一个描述这种手动管理的名称,例如 "staff""cluster-admins"。你应该避免使用保留值 "controller",该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlice。

访问无选择器的 Service

访问无选择器的 Service 的方式与有选择器的 Service 相同。在无选择器 Service 的示例中,流量被路由到 EndpointSlice 清单文件中定义的两个端点之一:一个连接到 10.1.2.3 或 10.4.5.6 的 9376 端口的 TCP 连接。

一个 ExternalName Service 是一种特殊的 Service,它没有选择器并使用 DNS 名称代替。更多信息,请参阅ExternalName 小节。

EndpointSlice

特性状态:Kubernetes v1.21 [稳定]

EndpointSlice 是表示 Service 的后端网络端点子集(一个切片)的对象。

你的 Kubernetes 集群会跟踪每个 EndpointSlice 表示多少个端点。如果一个 Service 的端点太多以至于达到阈值,那么 Kubernetes 会添加另一个空的 EndpointSlice,并将新的端点信息存储在那里。默认情况下,当所有现有的 EndpointSlice 都包含至少 100 个端点时,Kubernetes 会创建一个新的 EndpointSlice。在需要添加额外端点之前,Kubernetes 不会创建新的 EndpointSlice。

有关此 API 的更多信息,请参阅EndpointSlice

Endpoints (已弃用)

特性状态:Kubernetes v1.33 [已弃用]

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 [稳定]

appProtocol 字段提供了一种方式,来为每个 Service 端口指定应用协议。这被用作给实现者的提示,以便他们为理解的协议提供更丰富的行为。此字段的值会反映到相应的 Endpoints 和 EndpointSlice 对象中。

此字段遵循标准的 Kubernetes 标签语法。有效值包括

  • IANA 标准服务名称.

  • 实现者定义的前缀名称,例如 mycompany.com/my-custom-protocol

  • Kubernetes 定义的前缀名称

协议描述
kubernetes.io/h2cHTTP/2 明文传输,如 RFC 7540 所述
kubernetes.io/wsWebSocket 明文传输,如 RFC 6455 所述
kubernetes.io/wssWebSocket over TLS,如 RFC 6455 所述

多端口 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

Service 类型

对于应用的某些部分(例如,前端),你可能希望将 Service 暴露到外部 IP 地址上,一个可以从集群外部访问的地址。

Kubernetes Service 类型允许你指定你想要的 Service 类型。

可用的 type 值及其行为如下:

ClusterIP
在集群内部 IP 上暴露 Service。选择此值会使 Service 只能在集群内部访问。这是默认值,如果你没有显式指定 Service 的 type,将使用此值。你可以使用 IngressGateway 将 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。

type: ClusterIP

这种默认的 Service 类型会从集群为此目的保留的 IP 地址池中分配一个 IP 地址。

其他几种 Service 类型都以 ClusterIP 类型为基础。

如果你定义一个 Service,其中 .spec.clusterIP 设置为 "None",那么 Kubernetes 不会分配 IP 地址。更多信息请参阅无头 Service

选择自己的 IP 地址

你可以指定自己的集群 IP 地址作为 Service 创建请求的一部分。为此,设置 .spec.clusterIP 字段。例如,如果你已经有一个希望复用的现有 DNS 条目,或配置为特定 IP 地址且难以重新配置的旧系统。

你选择的 IP 地址必须是为 API Server 配置的 service-cluster-ip-range CIDR 范围内的有效 IPv4 或 IPv6 地址。如果你尝试使用无效的 clusterIP 地址值创建 Service,API Server 将返回 422 HTTP 状态码,表明存在问题。

阅读避免冲突,了解 Kubernetes 如何帮助减少两个不同的 Service 都尝试使用同一 IP 地址的风险和影响。

type: NodePort

如果将 type 字段设置为 NodePort,Kubernetes 控制平面将从 --service-node-port-range 标志指定的范围(默认值:30000-32767)中分配一个端口。每个节点会将该端口(每个 Node 上的端口号都相同)代理到你的 Service。Service 在其 .spec.ports[*].nodePort 字段中报告分配的端口。

使用 NodePort 让你能够自由地设置自己的负载均衡方案、配置 Kubernetes 不完全支持的环境,甚至直接暴露一个或多个节点的 IP 地址。

对于 NodePort Service,Kubernetes 还会另外分配一个端口(TCP、UDP 或 SCTP,以匹配 Service 的协议)。集群中的每个节点都会将自身配置为监听该指定端口,并将流量转发到与该 Service 关联的某个就绪 Endpoint。通过使用合适的协议(例如:TCP)并连接到任意节点上被分配给该 Service 的端口,你将能够从集群外部联系到 type: NodePort Service。

选择你自己的端口

如果你想要一个特定的端口号,可以在 nodePort 字段中指定一个值。控制平面将为你分配该端口,或者报告 API 事务失败。这意味着你需要自己处理可能发生的端口冲突。你还必须使用一个有效的端口号,一个位于为 NodePort 使用而配置的范围之内的端口号。

这里是一个示例 Manifest(清单),展示了 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 Service 分配端口的策略适用于自动分配和手动分配两种场景。当用户想要创建一个使用特定端口的 NodePort Service 时,目标端口可能与已分配的其他端口冲突。

为了避免这个问题,NodePort Service 的端口范围被划分为两个区间。动态端口分配默认使用高区间,并在高区间用尽后可能会使用低区间。用户然后可以从低区间进行分配,从而降低端口冲突的风险。

type: NodePort Service 的自定义 IP 地址配置

你可以设置集群中的节点使用特定的 IP 地址来提供 NodePort 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 版本兼容。)

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 字段将被忽略。

节点存活状态对负载均衡器流量的影响

负载均衡器健康检查对现代应用至关重要。它们用于确定负载均衡器应将流量分派到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 没有定义如何实现 Kubernetes 管理的负载均衡器的健康检查,相反,由云提供商(以及实现集成代码的人员)来决定其行为。负载均衡器健康检查在支持 Service 的 externalTrafficPolicy 字段的背景下被广泛使用。

混合协议类型的负载均衡器

特性状态: Kubernetes v1.26 [stable] (默认启用:true)

默认情况下,对于 LoadBalancer 类型的 Service,当定义了多个端口时,所有端口必须使用相同的协议,且该协议必须是云提供商支持的协议之一。

特性门控 MixedProtocolLBService(自 v1.24 起默认已为 kube-apiserver 启用)允许在定义了多个端口时,为 LoadBalancer 类型的 Service 使用不同的协议。

禁用负载均衡器 NodePort 分配

特性状态: Kubernetes v1.24 [stable]

你可以选择禁用 type: LoadBalancer 的 Service 的节点端口分配,方法是将字段 spec.allocateLoadBalancerNodePorts 设置为 false。这只应用于那些直接将流量路由到 Pod 而非使用节点端口的负载均衡器实现。默认情况下,spec.allocateLoadBalancerNodePortstrue,并且 LoadBalancer 类型的 Service 将继续分配节点端口。如果在已分配节点端口的现有 Service 上将 spec.allocateLoadBalancerNodePorts 设置为 false,这些节点端口将**不会**自动取消分配。你必须显式地移除每个 Service 端口中的 nodePorts 条目来取消分配这些节点端口。

指定负载均衡器实现的类别

特性状态: Kubernetes v1.24 [stable]

对于 type 设置为 LoadBalancer 的 Service,`.spec.loadBalancerClass` 字段使你能够使用除云提供商默认实现之外的负载均衡器实现。

默认情况下,`.spec.loadBalancerClass` 未设置,并且 LoadBalancer 类型的 Service 会使用云提供商的默认负载均衡器实现(如果集群使用 --cloud-provider 组件标志配置了云提供商)。

如果你指定 .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 才能将外部和内部流量都路由到你的 Endpoint。

要设置内部负载均衡器,根据你使用的云服务提供商,为 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

type: ExternalName

ExternalName 类型的 Service 将 Service 映射到一个 DNS 名称,而不是映射到一个典型的选择器(如 my-servicecassandra)。你使用 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

当查找主机 my-service.prod.svc.cluster.local 时,集群 DNS Service 返回一个值为 my.database.example.comCNAME 记录。访问 my-service 的方式与其他 Service 相同,但关键的区别在于重定向发生在 DNS 层面,而不是通过代理或转发。如果你后来决定将数据库迁移到集群中,你可以启动其 Pod,添加适当的选择器或 Endpoint,并更改 Service 的 type

Headless Service

有时你不需要负载均衡和单个 Service IP。在这种情况下,你可以通过显式地将集群 IP 地址(.spec.clusterIP)指定为 "None" 来创建所谓的 *headless Service*。

你可以使用 headless Service 与其他服务发现机制对接,而无需受限于 Kubernetes 的实现。

对于 headless Service,不会分配集群 IP,kube-proxy 不处理这些 Service,并且平台不会为它们进行负载均衡或代理。

headless Service 允许客户端直接连接到它偏好的任何 Pod。Headless 的 Service 不会使用虚拟 IP 地址和代理配置路由和数据包转发;相反,headless Service 通过内部 DNS 记录报告各个 Pod 的 Endpoint IP 地址,这些记录通过集群的DNS Service 提供服务。要定义一个 headless Service,你需要创建一个 Service,将其 .spec.type 设置为 ClusterIP(这也是 type 的默认值),并且额外将 .spec.clusterIP 设置为 None。

字符串值 None 是一个特例,与未设置 .spec.clusterIP 字段不同。

DNS 如何自动配置取决于 Service 是否定义了选择器:

使用选择器时

对于定义了选择器的 headless Service,Endpoint Controller 会在 Kubernetes API 中创建 EndpointSlice,并修改 DNS 配置以返回直接指向支持该 Service 的 Pod 的 A 或 AAAA 记录(IPv4 或 IPv6 地址)。

不使用选择器时

对于未定义选择器的 headless Service,控制平面不会创建 EndpointSlice 对象。然而,DNS 系统会查找并配置:

  • type: ExternalName Service 的 DNS CNAME 记录。
  • 对于除 ExternalName 之外的所有 Service 类型,为 Service 所有就绪 Endpoint 的 IP 地址创建 DNS A / AAAA 记录。
    • 对于 IPv4 Endpoint,DNS 系统创建 A 记录。
    • 对于 IPv6 Endpoint,DNS 系统创建 AAAA 记录。

当你定义一个没有选择器的 headless Service 时,port 必须与 targetPort 匹配。

发现 Service

对于在集群内部运行的客户端,Kubernetes 支持两种主要的 Service 发现模式:环境变量和 DNS。

环境变量

当 Pod 在 Node 上运行时,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

Kubernetes 还支持并提供了与 Docker Engine 的“*传统容器链接*”功能兼容的变量。你可以阅读 makeLinkVariables 来查看 Kubernetes 中如何实现此功能。

DNS

你(而且几乎总是应该)通过插件为你的 Kubernetes 集群设置一个 DNS Service。

集群感知的 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 来找到该 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 内流量路由的方式。流量策略侧重于严格的语义保证,而流量分配允许你表达偏好(例如路由到拓扑上更近的 Endpoint)。这有助于优化性能、成本或可靠性。在 Kubernetes 1.33 中,支持以下字段值:

PreferClose
表示倾向于将流量路由到与客户端位于同一可用区的 Endpoint。
特性状态: Kubernetes v1.33 [alpha] (默认启用:false)

启用 PreferSameTrafficDistribution 特性门控后,可以使用另外两个值:

PreferSameZone
这是 PreferClose 的别名,更清晰地表达了预期语义。
PreferSameNode
表示倾向于将流量路由到与客户端位于同一节点的 Endpoint。

如果未设置该字段,实现将应用其默认路由策略。

参见流量分配了解更多细节。

会话保持

如果你想确保来自特定客户端的连接每次都传递到同一个 Pod,可以根据客户端的 IP 地址配置会话亲和性。阅读会话亲和性了解更多。

外部 IP

如果有路由到一个或多个集群节点的外部 IP,Kubernetes Service 可以在这些 externalIPs 上暴露。当网络流量到达集群时,如果其外部 IP(作为目标 IP)和端口与该 Service 匹配,Kubernetes 配置的规则和路由会确保流量被路由到该 Service 的某个 Endpoint。

定义 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

API 对象

Service 是 Kubernetes REST API 中的顶级资源。你可以在Service API 对象中找到更多详细信息。

下一步

了解更多关于 Service 以及它们如何融入 Kubernetes 的信息

  • 遵循使用 Service 连接应用教程。
  • 阅读Ingress,它将集群外部的 HTTP 和 HTTPS 路由暴露给集群内部的 Service。
  • 阅读Gateway,这是 Kubernetes 的一个扩展,提供比 Ingress 更高的灵活性。

有关更多背景信息,请阅读以下内容:

上次修改时间:2025 年 4 月 9 日下午 5:08 (太平洋时间): 更新有关废弃 Endpoints API 的文档 (#49831) (649bda2cbd)