Kubernetes DiscoveryClient

本專案為 Kubernetes 提供了 Discovery Client 的實現。此客戶端允許您按名稱查詢 Kubernetes 端點(參見 服務)。服務通常由 Kubernetes API 伺服器暴露為一組端點,這些端點代表 httphttps 地址,客戶端可以從作為 pod 執行的 Spring Boot 應用程式訪問這些地址。

DiscoveryClient 還可以查詢 ExternalName 型別的服務(參見 ExternalName services)。目前,只有將屬性 spring.cloud.kubernetes.discovery.include-external-name-services 設定為 true 時,才支援外部名稱型別的服務(預設為 false)。

我們支援以下 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 個選項

  • selective namespaces (選擇性名稱空間)。例如

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 的客戶端,您需要在*伺服器*上啟用這些選項。這可以透過在用於在叢集中部署映象的 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$"'

這告訴發現客戶端只獲取 metadata.namespace 以大寫字母 A 結尾的服務。

如果您的發現客戶端基於 k8s-native 客戶端,則 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 - 例項的埠號。這需要更多解釋,因為選擇埠號有其規則

    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-metadata (pod 的標籤和註解),形式為 Map<String, Map<String, String>>。此支援需要透過 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` 端點。如果您想獲取包含 ExternalName 型別服務的 ServiceInstance 列表,您需要透過 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 啟用),例如

  • 如果上述兩種方式均未採用,則透過 Namespace Resolution 使用 one 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 發現有*兩個*元件:伺服器和客戶端,我們強烈建議它們之間的版本保持一致,否則可能無法正常工作。

  • 如果您決定停用目錄觀察者,則需要在伺服器和客戶端中都停用它。

預設情況下,我們使用 `Endpoints` (參見 kubernetes.io/docs/concepts/services-networking/service/#endpoints) API 來查詢服務的當前狀態。不過還有另一種方法,透過 `EndpointSlices` (kubernetes.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"]