服务

将集群中运行的应用程序暴露在单个面向外部的端点之后,即使工作负载分布在多个后端也是如此。

在 Kubernetes 中,服务是一种暴露网络应用程序的方法,该应用程序作为一个或多个 Pod 在集群中运行。

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

如果您使用 Deployment 来运行您的应用程序,该 Deployment 可以动态创建和销毁 Pod。从一个时刻到下一个时刻,您不知道有多少 Pod 在工作且运行正常;您甚至可能不知道这些运行正常的 Pod 的名称。创建和销毁 Kubernetes Pod,以匹配集群的期望状态。Pod 是临时资源(您不应期望单个 Pod 是可靠且持久的)。

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

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

输入*服务*。

Kubernetes 中的服务

作为 Kubernetes 一部分的 Service API 是一种抽象,可帮助您通过网络公开 Pod 组。每个 Service 对象定义一组逻辑端点(通常这些端点是 Pod),以及如何使这些 Pod 可访问的策略。

例如,考虑一个以 3 个副本运行的无状态图像处理后端。这些副本是可互换的,前端不关心它们使用哪个后端。虽然组成后端集的实际 Pod 可能会更改,但前端客户端不应需要知道这一点,也不应需要自己跟踪后端集。

Service 抽象实现了这种解耦。

Service 定位的 Pod 集通常由您定义的选择算符 确定。要了解有关定义 Service 端点的其他方法,请参阅没有选择算符的服务

如果您的工作负载使用 HTTP,您可能会选择使用Ingress来控制 Web 流量如何到达该工作负载。Ingress 不是 Service 类型,但它充当集群的入口点。Ingress 允许您将路由规则整合到单个资源中,以便您可以将工作负载的多个组件(在集群中单独运行)暴露在单个侦听器之后。

Kubernetes 的 Gateway API 提供了超出 Ingress 和 Service 的额外功能。您可以将 Gateway 添加到您的集群 - 它是一系列扩展 API,使用CustomResourceDefinitions 实现 - 然后使用它们来配置对在集群中运行的网络服务的访问。

云原生服务发现

如果您能够在应用程序中使用 Kubernetes API 进行服务发现,则可以查询 API 服务器 以获取匹配的 EndpointSlices。只要 Service 中的 Pod 集发生变化,Kubernetes 就会更新 Service 的 EndpointSlices。

对于非原生应用程序,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 将 TCP 端口 9376 定位到任何带有 app.kubernetes.io/name: MyApp 标签的 Pod。

Kubernetes 为此 Service 分配了一个 IP 地址(*集群 IP*),该地址由虚拟 IP 地址机制使用。有关该机制的更多详细信息,请阅读虚拟 IP 和服务代理

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

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 的访问,但是当与一组对应的 EndpointSlices 对象一起使用且没有选择器时,Service 可以抽象其他类型的后端,包括在集群外部运行的后端。

例如

  • 您希望在生产环境中使用外部数据库集群,但在测试环境中使用自己的数据库。
  • 您希望将 Service 指向不同 命名空间 或另一个集群中的 Service。
  • 您正在将工作负载迁移到 Kubernetes。在评估该方法时,您只在 Kubernetes 中运行一部分后端。

在任何这些情况下,您都可以定义一个 没有 指定选择器来匹配 Pod 的 Service。例如

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376

由于此 Service 没有选择器,因此不会自动创建相应的 EndpointSlice(和旧版 Endpoints)对象。您可以通过手动添加 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"

自定义 EndpointSlices

当您为 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 的 示例 中,流量被路由到 EndpointSlice 清单中定义的两个端点之一:端口 9376 上到 10.1.2.3 或 10.4.5.6 的 TCP 连接。

ExternalName Service 是一种特殊的 Service,它没有选择器,而是使用 DNS 名称。有关更多信息,请参阅 ExternalName 部分。

端点切片

功能状态: Kubernetes v1.21 [稳定]

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

您的 Kubernetes 集群会跟踪每个 EndpointSlice 代表的端点数量。如果 Service 的端点过多而达到阈值,则 Kubernetes 会添加另一个空的 EndpointSlice 并将新的端点信息存储在那里。默认情况下,Kubernetes 会在现有 EndpointSlice 都包含至少 100 个端点后创建一个新的 EndpointSlice。只有当需要添加额外的端点时,Kubernetes 才会创建新的 EndpointSlice。

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

Endpoints

在 Kubernetes API 中,Endpoints(资源类型是复数形式)定义了一系列网络端点,通常由 Service 引用,以定义可以将流量发送到哪些 Pod。

EndpointSlice API 是建议的 Endpoints 替代方案。

超容量端点

Kubernetes 限制了单个 Endpoints 对象中可以容纳的端点数量。当 Service 的后端端点超过 1000 个时,Kubernetes 会截断 Endpoints 对象中的数据。由于一个 Service 可以与多个 EndpointSlice 链接,因此 1000 个后端端点的限制只会影响旧版 Endpoints API。

在这种情况下,Kubernetes 最多选择 1000 个可能的后端端点存储到 Endpoints 对象中,并在 Endpoints 上设置一个 注解endpoints.kubernetes.io/over-capacity: truncated。如果后端 Pod 的数量降至 1000 以下,控制平面也会删除该注解。

流量仍会发送到后端,但是任何依赖于旧版 Endpoints API 的负载均衡机制都只会将流量发送到最多 1000 个可用的后端端点。

相同的 API 限制意味着您无法手动更新 Endpoints 以使其具有超过 1000 个端点。

应用协议

功能状态: Kubernetes v1.20 [稳定]

appProtocol 字段提供了一种为每个 Service 端口指定应用程序协议的方法。这用作实现的提示,以便为它们理解的协议提供更丰富的行为。此字段的值会由相应的 Endpoints 和 EndpointSlice 对象镜像。

此字段遵循标准的 Kubernetes 标签语法。有效值之一是:

  • IANA 标准服务名称.

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

  • Kubernetes 定义的前缀名称

协议描述
kubernetes.io/h2cRFC 7540 中所述,通过明文的 HTTP/2
kubernetes.io/wsRFC 6455 中所述,通过明文的 WebSocket
kubernetes.io/wssRFC 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

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 类型为基础。

如果您定义了一个将 .spec.clusterIP 设置为 "None" 的 Service,则 Kubernetes 不会分配 IP 地址。有关更多信息,请参阅 无头 Service

选择您自己的 IP 地址

您可以将自己的集群 IP 地址指定为 Service 创建请求的一部分。为此,请设置 .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 的 Service 自定义 IP 地址配置

您可以设置集群中的节点,以使用特定的 IP 地址来为节点端口服务提供服务。如果每个节点都连接到多个网络(例如:一个用于应用程序流量的网络,另一个用于节点和控制平面之间的流量的网络),您可能需要这样做。

如果要指定特定的 IP 地址来代理端口,可以为 kube-proxy 设置 --nodeport-addresses 标志,或者设置 kube-proxy 配置文件中等效的 nodePortAddresses 字段为特定的 IP 块。

此标志接受以逗号分隔的 IP 块列表(例如,10.0.0.0/8192.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 兼容。)

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

节点活跃度对负载均衡器流量的影响

负载均衡器运行状况检查对于现代应用程序至关重要。它们用于确定负载均衡器应将流量调度到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 没有定义如何为 Kubernetes 管理的负载均衡器实现运行状况检查,而是由云提供商(以及实现集成代码的人员)来决定行为。负载均衡器运行状况检查在支持 Service 的 externalTrafficPolicy 字段的上下文中被广泛使用。

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

功能状态: Kubernetes v1.26 [稳定版](默认启用:true)

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

功能门控 MixedProtocolLBService(在 v1.24 版本中默认情况下为 kube-apiserver 启用)允许在定义了多个端口时,对 LoadBalancer 类型的 Service 使用不同的协议。

禁用负载均衡器 NodePort 分配

功能状态: Kubernetes v1.24 [稳定版]

您可以选择通过将字段 spec.allocateLoadBalancerNodePorts 设置为 false 来禁用 type: LoadBalancer 的 Service 的节点端口分配。这仅应用于将流量直接路由到 Pod 而不是使用节点端口的负载均衡器实现。默认情况下,spec.allocateLoadBalancerNodePortstrue,并且 LoadBalancer 类型的 Service 将继续分配节点端口。如果在已分配节点端口的现有 Service 上将 spec.allocateLoadBalancerNodePorts 设置为 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"。无前缀名称保留供最终用户使用。

负载均衡器 IP 地址模式

功能状态: Kubernetes v1.32 [稳定版](默认启用: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 路由出去。

在分割 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-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 服务会返回一个值为 my.database.example.comCNAME 记录。访问 my-service 的工作方式与其他 Service 相同,但关键的区别在于重定向发生在 DNS 级别,而不是通过代理或转发。如果你稍后决定将数据库移动到你的集群中,你可以启动其 Pod、添加适当的选择器或端点,并更改 Service 的 type

无头 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 类型,为 Service 的所有就绪端点的 IP 地址提供 DNS A / AAAA 记录。
    • 对于 IPv4 端点,DNS 系统会创建 A 记录。
    • 对于 IPv6 端点,DNS 系统会创建 AAAA 记录。

当你定义一个没有选择器的无头 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

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

DNS

你可以(并且几乎总是应该)使用插件为你的 Kubernetes 集群设置 DNS 服务。

像 CoreDNS 这样的集群感知 DNS 服务器,会监视 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 来找到该 Service(my-service.my-ns 也应该可以)。

其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns。这些名称将解析为分配给 Service 的集群 IP。

Kubernetes 还支持命名端口的 DNS SRV(Service)记录。如果 my-service.my-ns Service 有一个名为 http 的端口,且协议设置为 TCP,你可以对 _http._tcp.my-service.my-ns 执行 DNS SRV 查询,以发现 http 的端口号以及 IP 地址。

Kubernetes DNS 服务器是访问 ExternalName Service 的唯一方法。你可以在Service 和 Pod 的 DNS 中找到有关 ExternalName 解析的更多信息。

虚拟 IP 寻址机制

阅读 虚拟 IP 和 Service 代理,其中解释了 Kubernetes 提供的使用虚拟 IP 地址公开 Service 的机制。

流量策略

你可以设置 .spec.internalTrafficPolicy.spec.externalTrafficPolicy 字段,以控制 Kubernetes 如何将流量路由到健康的(“就绪”)后端。

有关更多详细信息,请参阅流量策略

流量分配

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

.spec.trafficDistribution 字段提供了另一种影响 Kubernetes Service 内流量路由的方式。虽然流量策略侧重于严格的语义保证,但流量分配允许你表达偏好(例如,路由到拓扑上更接近的端点)。这有助于优化性能、成本或可靠性。如果你为你的集群及其所有节点启用了 ServiceTrafficDistribution 特性门控,则可以使用此可选字段。在 Kubernetes 1.32 中,支持以下字段值:

PreferClose
表示首选将流量路由到拓扑上与客户端接近的端点。对“拓扑上接近”的解释可能因实现而异,可能包括同一节点、机架、区域甚至区域内的端点。设置此值使实现有权做出不同的权衡,例如,优化邻近性而不是负载的均匀分配。如果此类权衡不可接受,则用户不应设置此值。

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

有关更多详细信息,请参阅流量分配

会话粘性

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

外部 IP

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

当你定义 Service 时,可以为任何Service 类型指定 externalIPs。在下面的示例中,客户端可以使用 TCP 通过 "198.51.100.32:80" 访问名为 "my-service" 的 Service(从 .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。
  • 阅读有关 网关的内容,它是 Kubernetes 的扩展,提供了比 Ingress 更大的灵活性。

有关更多上下文,请阅读以下内容

上次修改时间为太平洋标准时间 2024 年 9 月 16 日晚上 11:37: 将 LoadBalancerIPMode 晋升为 GA 参考 - https://github.com/kubernetes/kubernetes/pull/127348 (338c7b1347)