Repository navigation
feat(global-router): add the Multi-Cluster Services registry API contract #17630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
estroz
wants to merge
43
commits into
main
Choose a base branch
from
estroz/mcs-port-02-api-contract
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
9e368cf
feat(global-router): add a crate for types the Global Router componen…
atchernych 008d8f2
refactor(global-router): drop doc comments that restate their items
atchernych 71f89c9
fix(global-router): check a registration's model and correct three do…
atchernych 8e30f3c
fix(global-router): let a client follow any lease the service grants
atchernych 8b6c23e
feat(global-router): name a pool's PoolRelay by every address of its pod
atchernych c7ee161
test(global-router): drop checks the JSON round trips already make
atchernych 48637b7
feat(global-router): add the pool discovery contract and its stores
atchernych 03eae14
fix(global-router): mark a stale MCS view incomplete and log empty ones
atchernych 36125d9
docs(global-router): say that an MCS watch stream waits for its first…
atchernych c410173
perf(global-router): read EndpointSlice lists from the API server's c…
atchernych 959e2d5
perf(global-router): list EndpointSlices unpaged, so the cache serves…
atchernych a962b76
docs(global-router): say that pool Services need a ServiceExport, not…
atchernych 1a1372a
fix(global-router): address the standards review of the discovery stores
atchernych ce637fc
fix(global-router): address the second review of the discovery stores
atchernych 130aaef
fix(global-router): address the third review of the discovery stores
atchernych 0d55a3c
feat(http-server): copy the reusable HTTP server core from #17178
atchernych 8ae83b1
feat(global-router): add the Global Discovery HTTP service
atchernych 4a70569
feat(global-router): add a container image and manifest for the Globa…
atchernych a38eb3d
fix(global-router): accept hyphenated pool suffixes and state the man…
atchernych d0dc8fb
fix(global-router): bound the lease the Global Discovery Service grants
atchernych f8d3d54
refactor(global-router): pass registrations and listings straight thr…
atchernych 6db71b3
fix(global-router): bound how long the Kubernetes client waits
atchernych c6efecb
Merge branch 'main' into atchernych/global-common
atchernych 4bb3f61
Merge branch 'atchernych/global-common' into atchernych/global-discov…
atchernych 5d30317
Merge branch 'atchernych/global-discovery-service' into atchernych/gl…
atchernych 7919ee9
Merge branch 'main' into atchernych/global-common
atchernych 4323f2a
refactor(global-router): rename the shared types as reviewers asked
atchernych 5aadafa
refactor(global-router): build the discovery client on the shared types
atchernych 0a33716
Merge branch 'atchernych/global-common' into atchernych/global-discov…
atchernych 69d703e
refactor(global-router): follow the shared types' renames
atchernych 8428c92
Merge branch 'atchernych/global-discovery-service' into atchernych/gl…
atchernych e3f8acd
refactor(global-router): follow the shared types' renames
atchernych 547d554
Merge branch 'main' into atchernych/global-discovery-service
atchernych 2a80836
docs(global-router): document the stores' watch streams, as the bots …
atchernych 459c03f
fix(global-router): use Karmada's real Service name limit, 55
atchernych c09f797
Merge branch 'atchernych/global-discovery-service' into atchernych/gl…
atchernych f5c0508
docs(global-router): word the Karmada name check for any exported Ser…
atchernych 044d6b5
Merge branch 'atchernych/global-discovery-service' into atchernych/gl…
atchernych fb5ba6c
refactor(global-router): split --store into --pool-discovery and --re…
atchernych d829bc7
refactor(global-router): tighten the new flags after an adversarial r…
atchernych 06b4f63
docs(global-router): call the discovery implementations backends, not…
atchernych 4bd3635
Merge branch 'atchernych/global-discovery-service' into atchernych/gl…
atchernych 2d1aa97
feat(global-router): add the Multi-Cluster Services registry API cont…
estroz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # syntax=docker/dockerfile:1.10.0 | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # CPU-only image for the Global Discovery Service, which serves pool discovery | ||
| # for the Global Router over HTTP; see global-discovery-service.yaml. Build it | ||
| # from the repository root: | ||
| # docker build -f container/Dockerfile.global-discovery-service -t global-discovery-service . | ||
| FROM rust:1.96.1-trixie AS builder | ||
| RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ | ||
| build-essential cmake pkg-config protobuf-compiler libclang-dev libssl-dev curl \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
| WORKDIR /workspace | ||
| COPY . . | ||
| RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ | ||
| --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ | ||
| cargo build --locked --release -p dynamo-global-discovery-service --bin global-discovery-service | ||
|
|
||
| FROM debian:trixie-slim AS global-discovery-service | ||
| RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ | ||
| ca-certificates libstdc++6 \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
| COPY --from=builder /workspace/target/release/global-discovery-service /usr/local/bin/global-discovery-service | ||
| USER 10001:0 | ||
| EXPOSE 8080 | ||
| ENTRYPOINT ["/usr/local/bin/global-discovery-service"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # Global Discovery Service with pools from Karmada, applied directly to the hub | ||
| # cluster (not through Karmada). Replace IMAGE with the pushed image, for example: | ||
| # sed "s#IMAGE#<registry>/global-discovery-service:<tag>#" global-discovery-service.yaml | kubectl apply -f - | ||
| # It needs only read access to EndpointSlices. It lists the pools whose | ||
| # `<DGD name>-pool-relay` Services Karmada imports, and the replicas of the | ||
| # Global Router Service named by the --router-* arguments: set those to the | ||
| # router's namespace, its Service and the port PoolRelays connect to. | ||
| # | ||
| # It reads the imported slices with the pod's credentials, so this cluster | ||
| # needs a ServiceImport for each pool's PoolRelay Service and for the router | ||
| # Service. To read the Karmada control plane instead, which holds every | ||
| # exported Service's slices, mount its kubeconfig from a Secret, point | ||
| # KUBECONFIG at it, and grant the same read access there. | ||
| apiVersion: v1 | ||
| kind: Namespace | ||
| metadata: | ||
| name: global-discovery | ||
| --- | ||
| apiVersion: v1 | ||
| kind: ServiceAccount | ||
| metadata: | ||
| name: global-discovery-service | ||
| namespace: global-discovery | ||
| --- | ||
| apiVersion: rbac.authorization.k8s.io/v1 | ||
| kind: ClusterRole | ||
| metadata: | ||
| name: global-discovery-service | ||
| rules: | ||
| - apiGroups: ["discovery.k8s.io"] | ||
| resources: ["endpointslices"] | ||
| verbs: ["get", "list", "watch"] | ||
| --- | ||
| apiVersion: rbac.authorization.k8s.io/v1 | ||
| kind: ClusterRoleBinding | ||
| metadata: | ||
| name: global-discovery-service | ||
| roleRef: | ||
| apiGroup: rbac.authorization.k8s.io | ||
| kind: ClusterRole | ||
| name: global-discovery-service | ||
| subjects: | ||
| - kind: ServiceAccount | ||
| name: global-discovery-service | ||
| namespace: global-discovery | ||
| --- | ||
| apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: global-discovery-service | ||
| namespace: global-discovery | ||
| spec: | ||
| replicas: 1 | ||
| selector: | ||
| matchLabels: | ||
| app.kubernetes.io/name: global-discovery-service | ||
| template: | ||
| metadata: | ||
| labels: | ||
| app.kubernetes.io/name: global-discovery-service | ||
| spec: | ||
| serviceAccountName: global-discovery-service | ||
| securityContext: | ||
| runAsNonRoot: true | ||
| containers: | ||
| - name: global-discovery | ||
| image: IMAGE | ||
| args: | ||
| - --listen=0.0.0.0:8080 | ||
| - --pool-discovery=karmada | ||
| - --router-namespace=global-discovery | ||
| - --router-service=global-router | ||
| - --router-port-name=http | ||
| env: | ||
| - name: DYN_LOG | ||
| value: info | ||
| ports: | ||
| - name: http | ||
| containerPort: 8080 | ||
| readinessProbe: | ||
| httpGet: | ||
| path: /healthz | ||
| port: http | ||
| periodSeconds: 5 | ||
| lifecycle: | ||
| # Keep serving while the Pod leaves the Service's endpoints. | ||
| preStop: | ||
| exec: | ||
| command: ["sleep", "5"] | ||
| resources: | ||
| requests: | ||
| cpu: 50m | ||
| memory: 64Mi | ||
| limits: | ||
| memory: 256Mi | ||
| securityContext: | ||
| allowPrivilegeEscalation: false | ||
| readOnlyRootFilesystem: true | ||
| capabilities: | ||
| drop: ["ALL"] | ||
| --- | ||
| apiVersion: v1 | ||
| kind: Service | ||
| metadata: | ||
| name: global-discovery-service | ||
| namespace: global-discovery | ||
| spec: | ||
| selector: | ||
| app.kubernetes.io/name: global-discovery-service | ||
| ports: | ||
| - name: http | ||
| port: 8080 | ||
| targetPort: http |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package api | ||
|
|
||
| import ( | ||
| "crypto/sha256" | ||
| "encoding/base32" | ||
| "strings" | ||
| ) | ||
|
|
||
| // DerivedServicePrefix starts every derived Service name. | ||
| const DerivedServicePrefix = "derived-" | ||
|
|
||
| var base32HexNoPad = base32.HexEncoding.WithPadding(base32.NoPadding) | ||
|
|
||
| // DerivedServiceName returns the name of the derived Service that an | ||
| // importing cluster creates for the ServiceImport namespace/name: | ||
| // "derived-" followed by the first ten characters of the lowercase, | ||
| // unpadded base32hex encoding of the SHA-256 of "namespace/name". | ||
| // | ||
| // The algorithm is the derivedName function of sigs.k8s.io/mcs-api | ||
| // controllers, so the name is the same in every cluster and in every MCS | ||
| // implementation that follows mcs-api. Clients compute it locally to dial an | ||
| // import, for example derived-<hash>.<namespace>.svc.cluster.local. | ||
| func DerivedServiceName(namespace, name string) string { | ||
| sum := sha256.Sum256([]byte(namespace + "/" + name)) | ||
| return DerivedServicePrefix + strings.ToLower(base32HexNoPad.EncodeToString(sum[:]))[:10] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package api | ||
|
|
||
| import ( | ||
| "regexp" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| // Golden values computed independently with Python | ||
| // (base64.b32hexencode(hashlib.sha256(b"ns/name").digest())) and with the | ||
| // derivedName function of sigs.k8s.io/mcs-api v0.5.x controllers/common.go. | ||
| var derivedGolden = []struct { | ||
| namespace, name, want string | ||
| }{ | ||
| {"default", "nginx", "derived-iuq6ea9f4j"}, | ||
| {"llm", "llm-request-router-backend", "derived-b756q8b74k"}, | ||
| {"llm-stack", "backend-router", "derived-t1d4et30ia"}, | ||
| {"dynamo", "pool-frontend", "derived-apb6bcevn9"}, | ||
| {"a", "b", "derived-o56drg1jup"}, | ||
| {"kube-system", "kube-dns", "derived-bnmpvejc1s"}, | ||
| {"", "", "derived-h9fdlck2cc"}, | ||
| } | ||
|
|
||
| func TestDerivedServiceNameGolden(t *testing.T) { | ||
| for _, tt := range derivedGolden { | ||
| assert.Equal(t, tt.want, DerivedServiceName(tt.namespace, tt.name), "%s/%s", tt.namespace, tt.name) | ||
| } | ||
| } | ||
|
|
||
| func TestDerivedServiceNameIsDNSLabel(t *testing.T) { | ||
| label := regexp.MustCompile(`^derived-[0-9a-v]{10}$`) | ||
| for _, tt := range derivedGolden { | ||
| got := DerivedServiceName(tt.namespace, tt.name) | ||
| assert.Regexp(t, label, got) | ||
| assert.Len(t, got, len(DerivedServicePrefix)+10) | ||
| } | ||
| } | ||
|
|
||
| // The separator is part of the hashed input, so moving characters between the | ||
| // namespace and the name changes the result. | ||
| func TestDerivedServiceNameSeparator(t *testing.T) { | ||
| assert.NotEqual(t, DerivedServiceName("ab", "c"), DerivedServiceName("a", "bc")) | ||
| assert.Equal(t, DerivedServiceName("ns", "svc"), DerivedServiceName("ns", "svc")) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add owner globs for the new directories. The
codeownerscheck fails without them.The strict CODEOWNERS step reports 28 changed files that only the catch-all covers. These are the new trees that this stack adds, for example
deploy/global-discovery-mcs/andlib/global-discovery-service/. This change adds globs only forlib/http-server/and the two container files. Add the missing directories under the owning area (presumablyglobal-router), then regenerateCODEOWNERSand commit both files together.🔧 Proposed areas.yaml addition
As per coding guidelines: "If the
codeownerscheck fails after adding a new directory, claim it with one line under the owning area inareas.yaml, regenerate, and commit both files together." This finding also uses the same retrieved learning.Committable suggestion
Sources: Coding guidelines, Learnings, Pipeline failures