Kubernetes 用の DiscoveryClient

このプロジェクトは、Kubernetes (英語) 用のディスカバリクライアント [GitHub] (英語) の実装を提供します。このクライアントを使用すると、Kubernetes エンドポイント ( サービス (英語) を参照) を名前でクエリできます。サービスは通常、http および https アドレスを表すエンドポイントのコレクションとして Kubernetes API サーバーによって公開され、クライアントは pod として実行されている Spring Boot アプリケーションからアクセスできます。

DiscoveryClient は、型 ExternalName のサービスも見つけることができます (ExternalName サービス (英語) を参照)。現時点では、外部名サポート型のサービスは、次のプロパティ 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

このような構成では、検出クライアントは 2 つの名前空間 ns1 と ns2 内のサービスのみを検索するようになります。

  • all-namespaces.

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

このようなオプションは存在しますが、これは kube-api とアプリケーションの両方に負担をかける可能性があります。このような設定が必要になることはまれです。

  • one namespace。上記のいずれも指定しない場合は、これがデフォルト設定になります。これは、名前空間の解決で概説されているルールに基づいて機能します。

上記のオプションは、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"

名前空間の設定が完了したら、次にどのサービスを検出するかという問題に答える必要があります。これは、どのフィルターを適用するかという問題として考えてください。デフォルトでは、フィルターはまったく適用されず、すべてのサービスが検出されます。検出クライアントが検出できるものを絞り込む必要がある場合は、次の 2 つのオプションがあります。

  • 特定のサービスラベルに一致するサービスのみを取得します。このプロパティは、spring.cloud.kubernetes.discovery.service-labels で指定されます。Map を受け入れ、そのようなラベルを持つサービス (サービス定義の metadata.labels で表示) のみが考慮されます。

  • もう 1 つのオプションは、SpEL 式を使用することです。これは 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 の env 変数として設定する必要があることです。

      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 には getServices と getInstances という 2 つのメソッドがあります。

getServices は、metadata.name に表示されるサービス名を返します。

このメソッドは、検索用に選択した異なる名前空間間で重複するサービス名があっても、一意のサービス名を返します。

getInstances は List<ServiceInstance> を返します。ServiceInstance が持つ通常のフィールドの他に、名前空間や pod メタデータなどのデータも追加します (これらについてはドキュメントで詳しく説明します)。現時点で返されるデータは次のとおりです。

  1. instanceId - サービスインスタンスの一意の ID

  2. serviceId - サービスの名前 (getServices を呼び出して報告されたものと同じです)

  3. host - インスタンスの IP (または ExternalName 型のサービスの場合は名前)

  4. port - インスタンスのポート番号。ポート番号の選択にはルールがあるため、もう少し説明が必要です。

    1. サービスにポートが定義されていない場合は、0 (ゼロ) が返されます。

    2. サービスに 1 つのポートが定義されている場合は、そのポートが返されます。

    3. サービスにラベル primary-port-name がある場合は、ラベルの値に指定された名前を持つポート番号が使用されます。

    4. 上記のラベルが存在しない場合は、spring.cloud.kubernetes.discovery.primary-port-name で指定されたポート名を使用してポート番号を検索します。

    5. 上記のどちらも指定されていない場合は、https または http という名前のポートを使用してポート番号を計算します。

    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 (例: ClusterIP または ExternalName

  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.  Map<String, Map<String, String>> 形式のサービスインスタンス (pod) の pod-metadata ラベルとアノテーション。このサポートは、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 を呼び出すと、それらも返されます。ExternalName と他の型を区別するには、ServiceInstance::getMetadata を調べて type というフィールドを検索します。これが返されるサービスの型になります: 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 検出サーバーの場合、これは デプロイ 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 メソッドを使用して、後続の 2 つのハートビートに違いがあるかどうかを確認する必要があります。equals 契約に準拠した正しい実装を返すように注意します。エンドポイントは、次のいずれかで照会されます。- all-namespaces (spring.cloud.kubernetes.discovery.all-namespaces=true 経由で有効化)

  • selective namespaces (spring.cloud.kubernetes.discovery.namespaces 経由で有効化)、例:

  • 上記の 2 つのパスが使用されない場合は、名前空間の解決経由の one namespace になります。

何らかの理由でカタログウォッチャーを無効にする場合は、spring.cloud.kubernetes.discovery.catalog-services-watch.enabled=false を設定する必要があります。http 検出サーバーの場合、これは デプロイに設定された環境変数である必要があります。例:
SPRING_CLOUD_KUBERNETES_DISCOVERY_CATALOGSERVICESWATCH_ENABLED=FALSE

カタログウォッチの機能は、サポートしている 3 つの検出クライアントすべてで動作しますが、http クライアントの場合は注意が必要な点がいくつかあります。

  • 1 つ目は、この機能はデフォルトで無効になっており、次の 2 か所で有効にする必要があることです。

    • デプロイマニフェストの環境変数を介して検出サーバーで、例:

      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
  • 2 番目のポイントは、これがバージョン 3.0.6 以降でのみサポートされていることです。

  • http 検出にはサーバーとクライアントの 2 つのコンポーネントがあるため、それらのバージョンを合わせることを強くお勧めします。そうしないと、機能しない可能性があります。

  • カタログウォッチャーを無効にする場合は、サーバーとクライアントの両方で無効にする必要があります。

デフォルトでは、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)。もちろん、クラスターもそれをサポートする必要があります。実際、このプロパティを有効にしても、クラスターがそれをサポートしていない場合、アプリケーションの起動は失敗します。このようなサポートを有効にする場合は、適切なロール /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"]