Skip to content

Add xds-kubernetes module for Kubernetes-based xDS endpoint discovery - #6914

Open
jrhee17 wants to merge 4 commits into
line:mainfrom
jrhee17:feat/xds-k8s
Open

Add xds-kubernetes module for Kubernetes-based xDS endpoint discovery#6914
jrhee17 wants to merge 4 commits into
line:mainfrom
jrhee17:feat/xds-k8s

Conversation

@jrhee17

@jrhee17 jrhee17 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Motivation:

Users running xDS-based service discovery in Kubernetes environments have no built-in way to resolve xDS clusters using Kubernetes endpoints directly. This module bridges KubernetesEndpointGroup (which watches K8s Pods/Services) with the xDS ClusterTypeFactory interface (which produces EndpointSnapshot from ClusterLoadAssignment protobufs).

Modifications:

  • Added xds-api/src/main/proto/armeria/xds/kubernetes/kubernetes_cluster_config.proto defining KubernetesClusterConfig message and KubernetesEndpointMode enum
  • Added xds-kubernetes module with:
    • KubernetesClusterTypeFactory — a ClusterTypeFactory implementation (armeria.cluster.kubernetes) that unpacks KubernetesClusterConfig from the cluster's typed_config, creates a KubernetesEndpointGroup, and bridges endpoint updates to SnapshotStream<EndpointSnapshot>. Supports optional SDS credential for K8s API authentication with automatic recreation on secret rotation.
    • KubernetesEndpointMapper — a @FunctionalInterface for converting List<Endpoint> to ClusterLoadAssignment, with ofDefault() providing a default mapping (single locality, equal weight, HEALTHY status)

Result:

  • Users can configure xDS clusters to resolve endpoints from Kubernetes services.

@jrhee17 jrhee17 added this to the 1.42.0 milestone Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Kubernetes xDS cluster module. It defines cluster configuration, discovers Kubernetes endpoints, maps them to Envoy assignments, registers the extension, and validates POD-mode routing with mocked Kubernetes resources. It also changes Kubernetes client factory shutdown to asynchronous closure.

Changes

Kubernetes xDS integration

Layer / File(s) Summary
Cluster contract and module setup
xds-api/.../kubernetes_cluster_config.proto, settings.gradle, xds-kubernetes/build.gradle, xds-kubernetes/src/main/java/.../package-info.java
Defines Kubernetes cluster settings. Registers the new module and its API dependencies.
Endpoint mapping and validation
xds-kubernetes/src/main/java/.../KubernetesEndpointMapper.java, xds-kubernetes/src/main/java/.../DefaultKubernetesEndpointMapper.java, xds-kubernetes/src/test/java/.../DefaultKubernetesEndpointMapperTest.java
Maps endpoints to Envoy ClusterLoadAssignment values. Tests ports, addresses, health, locality, deduplication, cluster names, and empty input.
Cluster factory and SPI registration
xds-kubernetes/src/main/java/.../KubernetesClusterTypeFactory.java, xds-kubernetes/src/main/java/.../KubernetesClusterTypeFactoryProvider.java, xds-kubernetes/src/main/java/.../KubernetesTypeRegistryPackageProvider.java, xds-kubernetes/src/main/resources/META-INF/services/*
Creates Kubernetes endpoint streams. Supports POD and NODE_PORT modes and optional SDS credentials. Registers the xDS providers.
Integration flow validation
it/xds-client/build.gradle, it/xds-client/src/test/java/.../KubernetesClusterTypeIntegrationTest.java
Adds Kubernetes test dependencies. Verifies mocked resource discovery, xDS bootstrap loading, and /hello routing.

Kubernetes client shutdown

Layer / File(s) Summary
Asynchronous client factory shutdown
kubernetes/src/main/java/.../ArmeriaHttpClient.java, kubernetes/src/main/java/.../ArmeriaWebSocketClient.java
Uses closeAsync() when the clients close their underlying factories.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant XdsBootstrap
  participant KubernetesClusterTypeFactory
  participant KubernetesAPI
  participant KubernetesEndpointMapper
  participant Backend
  XdsBootstrap->>KubernetesClusterTypeFactory: createEndpointStream(cluster config)
  KubernetesClusterTypeFactory->>KubernetesAPI: watch service endpoints
  KubernetesAPI-->>KubernetesClusterTypeFactory: endpoint updates
  KubernetesClusterTypeFactory->>KubernetesEndpointMapper: map endpoints
  KubernetesEndpointMapper-->>KubernetesClusterTypeFactory: ClusterLoadAssignment
  KubernetesClusterTypeFactory-->>XdsBootstrap: EndpointSnapshot
  XdsBootstrap->>Backend: route /hello
Loading

Possibly related PRs

  • line/armeria#6838: Adds the xDS extension-provider and type-registry SPI mechanisms used by the Kubernetes module.
  • line/armeria#6909: Changes Kubernetes client lifecycle handling related to asynchronous factory closure.

Suggested reviewers: ikhoon, minwoox

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the addition of the xds-kubernetes module for Kubernetes-based xDS endpoint discovery.
Description check ✅ Passed The description directly explains the Kubernetes xDS module, its configuration, endpoint mapping, credentials, and intended result.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesClusterTypeFactory.java (1)

164-169: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Map the endpoint mode with an explicit switch.

The else branch maps every value other than NODE_PORT to POD. This includes UNRECOGNIZED and any mode added to the proto later. A new proto mode would silently resolve to POD.

Use a switch on the proto enum and reject unknown values. A static import or a local alias also removes the fully qualified reference on Line 165.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesClusterTypeFactory.java`
around lines 164 - 169, Replace the if/else mapping in
KubernetesClusterTypeFactory with an explicit switch over config.getMode(),
mapping NODE_PORT and POD directly to their corresponding builder modes and
rejecting UNRECOGNIZED or any unsupported future values instead of defaulting to
POD. Use a static import or local alias for KubernetesEndpointMode to remove the
fully qualified reference.
it/xds-client/src/test/java/com/linecorp/armeria/xds/it/KubernetesClusterTypeIntegrationTest.java (2)

86-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the discovered Kubernetes endpoints.

The mapper lambda on Line 88 ignores the endpoints parameter and always returns a fixed ClusterLoadAssignment that points at backendServer. The assertion on Line 96 therefore passes whenever any endpoint update fires, regardless of what the KubernetesEndpointGroup discovered.

The test does not verify the pod IP 10.0.0.1 created on Line 107, the POD mode, or the service-to-pod resolution. Those are the behaviors this integration test is meant to cover.

Capture the received endpoints in the mapper and assert their content.

💚 Proposed change to assert discovered endpoints
     `@Test`
     void basicEndpointDiscovery() {
         final Bootstrap bootstrap = bootstrapYaml(client.getMasterUrl().toString());
+        final BlockingQueue<List<Endpoint>> discovered = new LinkedBlockingQueue<>();
         final KubernetesClusterTypeFactory factory = KubernetesClusterTypeFactory.of(
                 client.getConfiguration(),
-                (clusterName, endpoints) -> backendCla(clusterName));
+                (clusterName, endpoints) -> {
+                    discovered.add(ImmutableList.copyOf(endpoints));
+                    return backendCla(clusterName);
+                });
 
         try (XdsBootstrap xdsBootstrap = XdsBootstrap.builder(bootstrap)
                                                      .extensionFactories(factory)
                                                      .build();
              XdsHttpPreprocessor preprocessor =
                      XdsHttpPreprocessor.ofListener("listener1", xdsBootstrap)) {
             final BlockingWebClient webClient = WebClient.of(preprocessor).blocking();
             assertThat(webClient.get("/hello").contentUtf8()).isEqualTo("world");
         }
+        await().untilAsserted(() -> assertThat(discovered)
+                .anySatisfy(endpoints -> assertThat(endpoints)
+                        .extracting(Endpoint::host)
+                        .contains("10.0.0.1")));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/KubernetesClusterTypeIntegrationTest.java`
around lines 86 - 96, Update the KubernetesClusterTypeFactory mapper in the
XdsBootstrap setup to capture the discovered endpoints instead of ignoring the
endpoints parameter, and assert that the received KubernetesEndpointGroup data
resolves the POD-mode service to pod IP 10.0.0.1. Ensure the test validates the
discovered endpoint content before or alongside the existing /hello assertion,
rather than always returning the fixed backendCla result.

100-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Set the namespace explicitly on the created resources.

createK8sResources creates Deployment, Service, and Pod without a namespace, while the bootstrap YAML expects namespace: test. Call .inNamespace("test") on each resource operation, or set the namespace in the corresponding ObjectMeta, so the test does not depend on the mock client default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/KubernetesClusterTypeIntegrationTest.java`
around lines 100 - 108, Update createK8sResources to explicitly create the
Deployment, Service, and Pod in the "test" namespace by applying
inNamespace("test") to each resource operation, or by setting that namespace in
each resource's ObjectMeta; do not rely on the mock client default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapper.java`:
- Around line 41-44: Update the public map method in
DefaultKubernetesEndpointMapper to call Objects.requireNonNull on both
clusterName and endpoints, using each parameter name as its message, before
creating the LocalityLbEndpoints builder or processing endpoints.
- Around line 40-41: Annotate the public override method map in
DefaultKubernetesEndpointMapper with `@UnstableApi`, since the containing class is
not annotated and no exception applies.

In
`@xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesClusterTypeFactory.java`:
- Around line 132-142: Move the newConfigBuilder(config) call inside the
switchMapEager callback so each GenericSecretSnapshot emission creates a fresh
ConfigBuilder. Apply withOauthToken only when secretSnapshot.credential() is
non-null, then pass that per-emission builder to createSnapshot; keep the
no-credential path creating a builder once for its single snapshot.

---

Nitpick comments:
In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/KubernetesClusterTypeIntegrationTest.java`:
- Around line 86-96: Update the KubernetesClusterTypeFactory mapper in the
XdsBootstrap setup to capture the discovered endpoints instead of ignoring the
endpoints parameter, and assert that the received KubernetesEndpointGroup data
resolves the POD-mode service to pod IP 10.0.0.1. Ensure the test validates the
discovered endpoint content before or alongside the existing /hello assertion,
rather than always returning the fixed backendCla result.
- Around line 100-108: Update createK8sResources to explicitly create the
Deployment, Service, and Pod in the "test" namespace by applying
inNamespace("test") to each resource operation, or by setting that namespace in
each resource's ObjectMeta; do not rely on the mock client default.

In
`@xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesClusterTypeFactory.java`:
- Around line 164-169: Replace the if/else mapping in
KubernetesClusterTypeFactory with an explicit switch over config.getMode(),
mapping NODE_PORT and POD directly to their corresponding builder modes and
rejecting UNRECOGNIZED or any unsupported future values instead of defaulting to
POD. Use a static import or local alias for KubernetesEndpointMode to remove the
fully qualified reference.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b14f239-1c98-455d-b69b-155558c538b5

📥 Commits

Reviewing files that changed from the base of the PR and between a63a1a3 and ee80410.

📒 Files selected for processing (14)
  • it/xds-client/build.gradle
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/KubernetesClusterTypeIntegrationTest.java
  • settings.gradle
  • xds-api/src/main/proto/armeria/xds/kubernetes/kubernetes_cluster_config.proto
  • xds-kubernetes/build.gradle
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapper.java
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesClusterTypeFactory.java
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesClusterTypeFactoryProvider.java
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesEndpointMapper.java
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/KubernetesTypeRegistryPackageProvider.java
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/package-info.java
  • xds-kubernetes/src/main/resources/META-INF/services/com.linecorp.armeria.xds.XdsExtensionFactoryProvider
  • xds-kubernetes/src/main/resources/META-INF/services/com.linecorp.armeria.xds.XdsTypeRegistryPackageProvider
  • xds-kubernetes/src/test/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapperTest.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
xds-kubernetes/src/test/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapperTest.java (1)

80-93: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the same host with different ports.

All test inputs use port 30000. A mapper that deduplicates by host only would still pass this test. Add the same host with a second port and assert that both host-port pairs remain while the exact duplicate is removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds-kubernetes/src/test/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapperTest.java`
around lines 80 - 93, Update the deduplication test around
DefaultKubernetesEndpointMapper.get().map to include the same host with a second
port alongside the exact duplicate. Assert that both distinct host-port
endpoints remain and the exact duplicate is removed, verifying deduplication
uses the complete host-port pair rather than host alone.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@xds-kubernetes/src/test/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapperTest.java`:
- Around line 80-93: Update the deduplication test around
DefaultKubernetesEndpointMapper.get().map to include the same host with a second
port alongside the exact duplicate. Assert that both distinct host-port
endpoints remain and the exact duplicate is removed, verifying deduplication
uses the complete host-port pair rather than host alone.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6bcacd3b-ae56-4a92-9c4f-3e6c33075990

📥 Commits

Reviewing files that changed from the base of the PR and between 907a83d and a2ae49f.

📒 Files selected for processing (2)
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapper.java
  • xds-kubernetes/src/test/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapperTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • xds-kubernetes/src/main/java/com/linecorp/armeria/xds/kubernetes/DefaultKubernetesEndpointMapper.java

@jrhee17
jrhee17 marked this pull request as ready for review August 11, 2026 02:55
@jrhee17
jrhee17 requested review from ikhoon and minwoox as code owners August 11, 2026 02:55
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (8150425) to head (8198ede).
⚠️ Report is 583 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main   #6914       +/-   ##
============================================
- Coverage     74.46%       0   -74.47%     
============================================
  Files          1963       0     -1963     
  Lines         82437       0    -82437     
  Branches      10764       0    -10764     
============================================
- Hits          61385       0    -61385     
+ Misses        15918       0    -15918     
+ Partials       5134       0     -5134     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mergify

mergify Bot commented Aug 11, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍 👍

* with equal weight, priority 0, and
* {@link io.envoyproxy.envoy.config.core.v3.HealthStatus#HEALTHY HEALTHY} status.
*/
static KubernetesEndpointMapper ofDefault() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: of()?

import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint;
import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints;

final class DefaultKubernetesEndpointMapper implements KubernetesEndpointMapper {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can just use enum?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants