Kubernetes 的 DiscoveryClient

此專案為 Kubernetes 提供 Discovery Client 的實作。此用戶端可讓您依名稱查詢 Kubernetes 端點 (請參閱服務)。服務通常由 Kubernetes API 伺服器公開為端點集合,這些端點代表 httphttps 位址,且用戶端可以從作為 Pod 執行的 Spring Boot 應用程式存取。

DiscoveryClient 也可以尋找類型為 ExternalName 的服務 (請參閱 ExternalName 服務)。目前,只有在將以下屬性 spring.cloud.kubernetes.discovery.include-external-name-services 設定為 true (預設為 false) 時,才支援外部名稱服務類型。

我們支援 3 種 DiscoveryClient 類型

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 個選項

  • 選擇性命名空間。例如

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

此組態會讓 Discovery Client 僅在兩個命名空間 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"

設定命名空間之後,下一個要回答的問題是要探索哪些服務。將其視為要套用的篩選器。預設情況下,不會套用任何篩選,且會探索所有服務。如果您需要縮小 Discovery Client 可以找到的範圍,您有兩個選項

  • 僅採用符合特定服務標籤的服務。此屬性以 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$"'

這會告知 Discovery Client 僅取得具有以上大寫 A 結尾的 metadata.namespace 的服務。

如果您的 Discovery Client 是以 k8s-native 用戶端為基礎,則 SpEL 運算式必須以 io.kubernetes.client.openapi.models.V1Service 類別為基礎。上面顯示的相同篩選器在這裡也適用。

如果您的 Discovery Client 是 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$"'

現在是時候思考 Discovery Client 應該傳回什麼。一般而言,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-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

請注意,Discovery Client 的支援是自動的,取決於您執行應用程式的位置。因此,可能不需要上述設定。

某些 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 Discovery Server,這必須是在 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 方法。我們將負責傳回符合 equals 合約的正確實作。端點將在以下任一項中查詢:- all-namespaces (透過 spring.cloud.kubernetes.discovery.all-namespaces=true 啟用)

  • 選擇性命名空間 (透過 spring.cloud.kubernetes.discovery.namespaces 啟用),例如

  • 如果未採用上述兩種路徑,則透過 命名空間解析 單一命名空間

如果基於任何原因,您想要停用目錄監視器,則需要設定 spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false。對於 HTTP Discovery Server,這需要是例如部署中設定的環境變數
SPRING_CLOUD_KUBERNETES_DISCOVERY_CATALOGSERVICESWATCH_ENABLED=FALSE

目錄監視的功能適用於我們支援的所有 3 個 Discovery Client,但如果使用 HTTP 用戶端,則需要注意一些注意事項。

  • 第一個是預設停用此功能,且需要在兩個位置啟用

    • 在 Discovery Server 中,透過部署資訊清單中的環境變數,例如

      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"
    • 在 Discovery Client 中,透過 application.properties 中的屬性,例如

      spring.cloud.kubernetes.http.discovery.catalog.watcher.enabled=true
  • 第二點是僅自版本 3.0.6 及更高版本起支援此功能。

  • 由於 HTTP Discovery 具有兩個组件:伺服器和用戶端,因此我們強烈建議對齊它們之間的版本,否則可能無法運作。

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

預設情況下,我們使用 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"]