DiscoveryClient for Kubernetes

此项目针对 Kubernetes 提供了 Discovery Client 的实现。此客户端允许您按名称查询 Kubernetes 端点(参见 services)。通常,Kubernetes API 服务器将服务显示为端点集合,这些端点表示 httphttps 地址,并且客户端可以从作为 Pod 运行的 Spring Boot 应用程序对其进行访问。

DiscoveryClient 还可以找到类型为 ExternalName 的服务(请参阅 ExternalName services)。目前,如果将 spring.cloud.kubernetes.discovery.include-external-name-services 设置为 true(默认为 false),只有当服务类型支持外部名称时才可用。

我们支持 3 种类型的发现客户端:

1.

Fabric8 Kubernetes Client

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-fabric8</artifactId>
</dependency>

2.

Kubernetes Java Client

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client</artifactId>
</dependency>

3.

基于 HTTP 的 DiscoveryClient

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-discoveryclient</artifactId>
</dependency>

spring-cloud-starter-kubernetes-discoveryclient 被设计可与 Spring Cloud Kubernetes DiscoveryServer 一起使用。

要启用 DiscoveryClient 的加载,请对相应的配置或应用程序类添加 @EnableDiscoveryClient,如下例所示:

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

然后,您可以通过自动注入的方式在代码中注入客户端,如下例所示:

@Autowired
private DiscoveryClient discoveryClient;

您应该问自己的第一个问题是 DiscoveryClient 应该在哪里查找服务。在 Kubernetes 环境中,这意味着哪些命名空间。这里有 3 个选项:

  • selective namespaces. For example:

spring.cloud.kubernetes.discovery.namespaces[0]=ns1
spring.cloud.kubernetes.discovery.namespaces[1]=ns2

这种配置使发现客户端仅搜索命名空间 ns1ns2 中的服务。

  • all-namespaces.

spring.cloud.kubernetes.discovery.all-namespaces=true

虽然有这样的选项,但这可能会给 kube-api 和您的应用程序带来负担。很少需要这样的设置。

  • one namespace。这是默认设置,如果你未指定以上任何一项。它基于 Namespace Resolution 中概述的规则工作。

上述选项以与 fabric8 和 k8s 客户端完全相同的方式执行。对于基于 HTTP 的客户端,需要在 server 上启用这些选项。可以通过在 deployment.yaml 中设置这些选项来实现,该选项用于在集群中部署图像,即使用环境变量。

例如:

      containers:
        - name: discovery-server
          image: springcloud/spring-cloud-kubernetes-discoveryserver:3.0.5-SNAPSHOT
          env:
            - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0
              value: "namespace-a"

配置命名空间后,要回答的下一个问题是发现哪些服务。考虑将其视为要应用的筛选器。默认情况下,不应用任何筛选,并且发现所有服务。如果您需要缩小发现客户端所能找到的范围,您有两个选择:

  • 仅使用符合某些服务标签的服务。此属性使用以下内容指定:spring.cloud.kubernetes.discovery.service-labels。它接受 Map,并且仅考虑具有此类标签(如服务定义中的 metadata.labels 中所示)的服务。

  • 另一个选项是使用 SpEL expression。这由 spring.cloud.kubernetes.discovery.filter 属性表示,并且它的值取决于你选择的客户端。如果你使用 fabric8 客户端,则此 SpEL 表达式必须针对 io.fabric8.kubernetes.api.model.Service 类进行创建。其中一个示例可能是:

spring.cloud.kubernetes.discovery.filter='#root.metadata.namespace matches "^.+A$"'

这告诉发现客户端仅获取具有以大写“A”结尾的 metadata.namespace 的服务。

如果您的发现客户端基于 k8s 原生客户端,则 SpEL 表达式必须基于 io.kubernetes.client.openapi.models.V1Service 类。上面显示的相同筛选器会在这里起作用。

如果您的发现客户端是基于 http 的,则 SeEL 表达式必须基于相同的 io.kubernetes.client.openapi.models.V1Service 类,唯一的区别在于这需要在部署 yaml 中设置为环境变量:

      containers:
        - name: discovery-server
          image: springcloud/spring-cloud-kubernetes-discoveryserver:3.0.5-SNAPSHOT
          env:
            - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER
              value: '#root.metadata.namespace matches "^.+A$"'

现在是考虑发现客户端应该返回什么的时候了。一般来说,DiscoveryClient 有两种方法:getServicesgetInstances

getServices 将返回在 metadata.name 中看到的服务名称。

这种方法将返回唯一的服务名称,即使不同命名空间(您为搜索选择的命名空间)中存在重复。

getInstances 返回 List<ServiceInstance>。除了 ServiceInstance 所具有的常规字段外,我们还添加了一些数据,如命名空间或 Pod 元数据(本文档中稍后会对此进行更多说明)。以下是我们目前返回的数据:

  1. instanceId - 服务实例的唯一 ID

  2. serviceId - 服务的名称(与调用 getServices 报告的名称相同)

  3. host - 实例的 IP(或者对于 ExternalName 类型服务的情况,为名称)

  4. port - 实例的端口号。这需要更多解释,因为选择端口号有其规则:[style="loweralpha"]

    1. 服务未定义端口,将返回 0(零)。

    2. 服务定义了一个端口,将返回该端口。

    3. 如果服务带有标签 primary-port-name,我们将使用标签值中指定名称的端口号。

    4. 如果未显示上述标签,则我们将使用 spring.cloud.kubernetes.discovery.primary-port-name 中指定的端口名称来查找端口号。

    5. 如果以上两者都没有指定,我们将使用名为 httpshttp 的端口来计算端口号。

    6. 最后,我们将选择端口列表中的第一个端口。此选项可能导致不确定的行为。

  5. 服务实例的 uri

  6. scheme httphttps(取决于 secure 结果)

  7. metadata of the service:[style="loweralpha"]

    1. labels(如果通过 spring.cloud.kubernetes.discovery.metadata.add-labels=true 请求)。标签值可以“添加前缀”值为 spring.cloud.kubernetes.discovery.metadata.labels-prefix(如果已设置)。

    2. annotations(如果通过 spring.cloud.kubernetes.discovery.metadata.add-annotations=true 请求)。注解值可以“添加前缀”值为 spring.cloud.kubernetes.discovery.metadata.annotations-prefix(如果已设置)。

    3. ports(如果通过 spring.cloud.kubernetes.discovery.metadata.add-ports=true 请求)。端口值可以“添加前缀”值为 spring.cloud.kubernetes.discovery.metadata.ports-prefix(如果已设置)。

    4. k8s_namespace 为实例所在命名空间的值。

    5. type 其中包含服务类型,例如 ClusterIPExternalName

  8. secure 如果发现的端口应视为安全。我们将使用上面概述的规则查找端口名称和号码,然后:[style="loweralpha"]

    1. 如果此服务已标记为 secured,并且它具有以下任一值:["true", "on", "yes", "1"],则将找到的端口视为安全端口。

    2. 如果找不到此类标签,请搜索名为 secured 的注释,并应用相同的上述规则。

    3. 如果此端口号属于 spring.cloud.kubernetes.discovery.known-secure-ports(默认情况下,此值包含 [443, 8443]),则将端口号视为已保护。

    4. 最后的手段是查看端口名称是否与 https 匹配;如果是则将此端口视为安全。

  9. namespace - 找到的实例的命名空间。

  10. pod-metadata 服务实例(pod)的标签和注释,格式为 Map&lt;String, Map&lt;String, String&gt;&gt;。此支持需要通过 spring.cloud.kubernetes.discovery.metadata.add-pod-labels=true 和/或 spring.cloud.kubernetes.discovery.metadata.add-pod-annotaations=true 启动

为了发现未被 kubernetes api 服务器标记为“已就绪”的服务端点地址,您可以在 application.properties 中设置以下属性(默认值:false):

spring.cloud.kubernetes.discovery.include-not-ready-addresses=true

在出于监控目的发现服务并且将允许检查尚未就绪的服务实例的 /health 终结点时,这可能很有用。如果您希望获取 ServiceInstance 列表以便还包括 ExternalName 类型服务,则需要通过 spring.cloud.kubernetes.discovery.include-external-name-services=true 启用该支持。如此一来,当调用 DiscoveryClient::getInstances 时,这些内容也将返回。可以通过检查 ServiceInstance::getMetadata 并查找名为 type 的字段来区分 ExternalName 和任何其他类型。这将是返回的服务类型:ExternalName/ClusterIP 等。如果您出于任何原因需要禁用 DiscoveryClient,则可以在 application.properties 中设置以下属性:

spring.main.cloud-platform=NONE

请注意,发现客户端的支持是自动的,取决于您在何处运行应用程序。因此,可能不需要上述设置。

某些 Spring Cloud 组件使用 DiscoveryClient 来获取有关本地服务实例的信息。要做到这一点,您需要将 Kubernetes 服务名称与 spring.application.name 属性对齐。

spring.application.name 在 Kubernetes 中为应用程序注册的名称方面毫无效果

Spring Cloud Kubernetes 还可以监视 Kubernetes 服务目录的更改,并相应地更新 DiscoveryClient 实现。为了启用此功能,您需要在应用程序中的配置类中添加 @EnableScheduling。通过“监视”,我们指的是我们将每 spring.cloud.kubernetes.discovery.catalog-services-watch-delay 毫秒发布一个心跳事件(默认值为 30000)。对于 http 发现服务器,这必须是 deployment yaml 中设置的环境变量:

      containers:
        - name: discovery-server
          image: springcloud/spring-cloud-kubernetes-discoveryserver:3.0.5-SNAPSHOT
          env:
            - name: SPRING_CLOUD_KUBERNETES_DISCOVERY_CATALOGSERVICESWATCHDELAY
              value: 3000

心跳事件将包含目标引用(及其地址的所有端点的命名空间)(有关所返回内容的具体详细信息,您可以在 KubernetesCatalogWatch 中查看)。这是一个实现细节,并且心跳事件的监听器不应依赖于这些细节。而应该通过 equals 方法查看两次连续心跳之间是否存在差异。我们将负责返回一个遵守相等契约的正确实现。将在以下任一位置查询端点:- all-namespaces(通过 spring.cloud.kubernetes.discovery.all-namespaces=true 启用)

  • selective namespaces(通过 spring.cloud.kubernetes.discovery.namespaces 启用),例如:

  • 如果未采用上述两个路径,则通过 Namespace Resolutionone namespace 进行保护。

出于任何原因,如果你想禁用目录监视器,你需要设置`spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false`。对于 http 发现服务器来说,需要在部署中设置环境变量,例如:

SPRING_CLOUD_KUBERNETES_DISCOVERY_CATALOGSERVICESWATCH_ENABLED=FALSE

目录监视的功能适用于我们支持的所有 3 个发现客户端,但在 http 客户端的情况下,您需要了解一些注意事项。

  • 首先是此功能默认禁用,需要在两个位置启用:

    • 在发现服务器中,在部署清单的环境变量中,例如:---- containers:

  • name: discovery-server image: springcloud/spring-cloud-kubernetes-discoveryserver:3.0.5-SNAPSHOT env:

  • name: SPRING_CLOUD_KUBERNETES_HTTP_DISCOVERY_CATALOG_WATCHER_ENABLED value: "TRUE"

* 在发现客户端中,在 `application.properties` 的属性中,例如:----
spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true
  • 第二点是,这仅从版本 3.0.6 开始支持。

  • 由于 http 发现有 two 组件:服务器和客户端,我们强烈建议在两者之间对齐版本,否则可能无法正常工作。

  • 如果您决定禁用目录观察程序,则需要在服务器和客户端禁用。

我们默认使用 Endpoints(参见 [role="bare"][role="bare"]https://kubernetes.io/docs/concepts/services-networking/service/#endpoints)API 来了解服务的当前状态。还有另一种方式,即通过 EndpointSlices([role="bare"][role="bare"]https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)。可以通过一个属性(默认值为 false)启用此支持: spring.cloud.kubernetes.discovery.use-endpoint-slices=true。当然,您的集群也必须支持它。事实上,如果您启用了此属性,但集群不支持,我们将无法启动应用程序。如果您决定启用此支持,还需要设置适当的角色/集群角色。例如:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: namespace-reader
rules:
  - apiGroups: ["discovery.k8s.io"]
    resources: ["endpointslices"]
    verbs: ["get", "list", "watch"]