Kubernetes 的 DiscoveryClient

此项目提供了一个针对KubernetesDiscovery Client实现。此客户端允许您按名称查询 Kubernetes 端点(参见服务)。服务通常由 Kubernetes API 服务器公开为代表httphttps地址的端点集合,客户端可以从作为 Pod 运行的 Spring Boot 应用程序访问这些端点。

DiscoveryClient 还可以查找类型为ExternalName的服务(参见ExternalName 服务)。目前,只有在将以下属性spring.cloud.kubernetes.discovery.include-external-name-services设置为true时(默认为false),才支持 ExternalName 类型服务的外部名称支持。

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

1.

Fabric8 Kubernetes 客户端

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

2.

Kubernetes Java 客户端

<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 个选项

  • 选择性命名空间。例如

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

此类配置使发现客户端仅搜索ns1ns2两个命名空间中的服务。

  • 所有命名空间.

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

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

  • 一个命名空间。如果未指定以上任何一项,则这是默认设置。它根据命名空间解析中概述的规则运行。

对于 Fabric8 和 k8s 客户端,上述选项的工作方式与编写方式完全相同。对于基于 HTTP 的客户端,您需要在_服务器_上启用这些选项。这可以通过在集群中部署映像时使用的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 表达式。这由spring.cloud.kubernetes.discovery.filter属性表示,其值取决于您选择的客户端。如果您使用 Fabric8 客户端,则必须针对io.fabric8.kubernetes.api.model.Service类创建此 SpEL 表达式。一个这样的例子可能是

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

这告诉发现客户端只获取metadata.namespace以大写字母A结尾的服务。

如果您的发现客户端基于 k8s 原生客户端,则 SpEL 表达式必须基于io.kubernetes.client.openapi.models.V1Service类。上面显示的相同过滤器在这里也可以使用。

如果您的发现客户端是基于 HTTP 的客户端,则 SeEL 表达式必须基于相同的io.kubernetes.client.openapi.models.V1Service类,唯一的区别是这需要在 deployment 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 - 实例的端口号。这需要更多解释,因为选择端口号有其规则

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

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

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

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

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

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

  5. 服务实例的uri

  6. schemehttphttps(取决于secure结果)

  7. 服务的metadata

    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,如果发现的端口应被视为安全端口。我们将使用上面概述的相同规则来查找端口名称和编号,然后

    1. 如果此服务具有名为secured且值为["true", "on", "yes", "1"]的标签,则将找到的端口视为安全端口。

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

    3. 如果此端口号属于spring.cloud.kubernetes.discovery.known-secure-ports(默认值为[443, 8443]),则将端口号视为安全端口。

    4. 最后,检查端口名称是否匹配https;如果匹配,则将此端口视为安全端口。

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

  10. 服务实例(Pod)的pod-metadata标签和注释,格式为Map<String, Map<String, String>>。此支持需要通过spring.cloud.kubernetes.discovery.metadata.add-pod-labels=true和/或spring.cloud.kubernetes.discovery.metadata.add-pod-annotations=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属性对齐。

就 Kubernetes 中为应用程序注册的名称而言,spring.application.name无效。

Spring Cloud Kubernetes 还可以监视 Kubernetes 服务目录的变化,并相应地更新DiscoveryClient实现。要启用此功能,需要在应用程序的配置类中添加@EnableScheduling。“监视”是指我们将每隔spring.cloud.kubernetes.discovery.catalog-services-watch-delay毫秒(默认为30000)发布一个心跳事件。对于 HTTP 发现服务器,这必须是部署 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方法查看两次连续心跳之间是否存在差异。我们将确保返回一个符合 equals 约定的正确实现。将查询端点位于:- all-namespaces(通过spring.cloud.kubernetes.discovery.all-namespaces=true启用)

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

  • 如果未采用上述两种路径,则通过命名空间解析使用one namespace

如果出于任何原因要禁用目录观察程序,需要设置spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false。对于 HTTP 发现服务器,这需要在部署中设置环境变量,例如
SPRING_CLOUD_KUBERNETES_DISCOVERY_CATALOGSERVICESWATCH_ENABLED=FALSE

目录监视功能适用于我们支持的所有三个发现客户端,但在使用 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 发现具有两个组件:服务器和客户端,因此强烈建议使它们之间的版本保持一致,否则可能无法正常工作。

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

默认情况下,我们使用Endpoints(请参阅kubernetes.io/docs/concepts/services-networking/service/#endpoints)API 来找出服务的当前状态。但是,还有另一种方法,通过EndpointSliceskubernetes.io/docs/concepts/services-networking/endpoint-slices/)。可以通过属性启用此支持:spring.cloud.kubernetes.discovery.use-endpoint-slices=true(默认为false)。当然,您的集群也必须支持它。事实上,如果您启用了此属性,但您的集群不支持它,我们将无法启动应用程序。如果您决定启用此支持,还需要正确设置 Role/ClusterRole。例如

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"]