Skip to content

docs(networking): publish Kubernetes API endpoint via external-dns with kuberture#539

Open
lexfrei wants to merge 1 commit into
mainfrom
docs/kuberture-system-package
Open

docs(networking): publish Kubernetes API endpoint via external-dns with kuberture#539
lexfrei wants to merge 1 commit into
mainfrom
docs/kuberture-system-package

Conversation

@lexfrei
Copy link
Copy Markdown
Contributor

@lexfrei lexfrei commented May 14, 2026

What this PR does

Adds documentation for the kuberture system package being introduced in cozystack/cozystack#2647. New page at content/en/docs/next/networking/kuberture.md, weight 26 (slots in right after hairpin-proxy-protocol at weight 25 in the networking section).

The page covers:

  • The problem kuberture solves: external-dns does not support EndpointSlice as a source, so the canonical control-plane endpoint at default/kubernetes cannot be published to DNS without a bridge.
  • How kuberture works: a small in-cluster controller that watches EndpointSlice and stamps annotated headless Services (one per output) for external-dns to consume.
  • Single-instance enabling against the platform cozystack.external-dns with a copy-pasteable Package CR.
  • The cozystack-relevant flexibility: a single kuberture install can serve multiple external-dns instances simultaneously by stamping a different annotationPrefix per output, with the matching external-dns running with --annotation-filter. Includes a worked two-output example.
  • The four addressSource strategies (endpointslice / node-internal / node-external / node-public) and when each fits.
  • Disable path, including the cozystack-operator "no automatic Package garbage collection" wrinkle that's also documented for other optional packages, and the external-dns policy: upsert-only interaction on record retraction.
  • Supply-chain notes: chart and image both live under the maintainer's personal lexfrei/* registry namespace, are pinned by digest in the cozystack package, and air-gapped operators must mirror both into their internal registry.

Style follows networking/hairpin-proxy-protocol.md (the sibling system-addon doc for ouroboros): handwritten, narrative, single-line markdown paragraphs.

Release note

docs(networking): document the kuberture system package for publishing the Kubernetes API endpoint to DNS via external-dns.

Summary by CodeRabbit

  • Documentation
    • Added guide for publishing the Kubernetes API endpoint to DNS via the kuberture component, including configuration examples, setup procedures, and external-dns integration patterns.

Review Change Stack

@netlify
Copy link
Copy Markdown

netlify Bot commented May 14, 2026

Deploy Preview for cozystack ready!

Name Link
🔨 Latest commit 4b586a3
🔍 Latest deploy log https://app.netlify.com/projects/cozystack/deploys/6a058befa1d3220008bfc6c2
😎 Deploy Preview https://deploy-preview-539--cozystack.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive documentation page for the kuberture component, explaining how to publish the Kubernetes API endpoint to DNS via external-dns. The documentation covers the technical motivation, configuration patterns, operational procedures, and supply chain references.

Changes

kuberture DNS Publishing Documentation

Layer / File(s) Summary
Documentation setup and problem context
content/en/docs/next/networking/kuberture.md (lines 1–17)
Page metadata and overview explaining the limitation of external-dns when consuming Kubernetes EndpointSlice objects directly and the resulting DNS service gap compared to the upstream default/kubernetes slice.
kuberture mechanism and basic deployment
content/en/docs/next/networking/kuberture.md (lines 18–82)
Technical explanation of how kuberture watches EndpointSlice, converts it to annotated headless Service objects that external-dns can read, and documents how to enable kuberture via bundles.enabledPackages with a single output consuming the platform external-dns instance.
Advanced deployment and operations
content/en/docs/next/networking/kuberture.md (lines 83–145)
Multi-instance support using per-output annotationPrefix routing, address source resolution strategies and addressType filtering, procedures for disabling the package, manual cleanup via Package CR deletion, and external-dns record retention behavior.
Supply chain details and related documentation
content/en/docs/next/networking/kuberture.md (lines 146–157)
Chart and controller image sourcing, version pinning guidance, air-gapped environment mirroring instructions, and links to related configuration, component, and package reference documentation.

Estimated Code Review Effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A bundle with teeth, that watches so bright,
Converting slices to Services in the night,
kuberture publishes what DNS desires,
Through external-dns it fuels network spires!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding documentation for publishing the Kubernetes API endpoint via external-dns with kuberture. It is specific, concise, and directly reflects the new documentation page and its primary purpose.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/kuberture-system-package

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces documentation for kuberture, a component designed to bridge the gap between Kubernetes EndpointSlices and external-dns by creating annotated headless Services. The review feedback identifies a potential compatibility issue regarding how standard external-dns instances handle custom annotation prefixes for data extraction. Additionally, a suggestion was made to improve the clarity of the configuration documentation by better explaining the default behavior when the annotation prefix is omitted.


## Routing to multiple external-dns instances

A single `kuberture` install can address any number of `external-dns` instances by varying `annotationPrefix` per output. Each `external-dns` instance is configured (via `--annotation-filter`) to act on a specific prefix; `kuberture` stamps the matching prefix on each Service:
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.

high

Standard external-dns does not support custom prefixes for data annotations like hostname, target, or ttl. It typically expects these keys to be prefixed with external-dns.alpha.kubernetes.io/. While --annotation-filter can be used to filter which Services an instance processes, it does not change the prefix used for data extraction. If kuberture only stamps annotations with a custom prefix, standard external-dns instances will not find the required DNS data. Please clarify if this feature requires a specific external-dns configuration or if kuberture should also stamp the standard annotations alongside the filter annotation.


Each Service carries **only** its own prefix's annotations — there is no cross-pollution between outputs.

`annotationPrefix` accepts two forms: omit the field to inherit the controller default `external-dns.alpha.kubernetes.io/`, or set it to a non-empty string ending in `/`. The empty string `""` is rejected by the chart's values schema; omission is the only zero-prefix path.
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.

medium

The term "zero-prefix path" is confusing here. Since omitting the field results in the default prefix (external-dns.alpha.kubernetes.io/) being used, it is not actually a "zero" (empty) prefix. It would be clearer to state that omission is the way to use the default prefix.

Suggested change
`annotationPrefix` accepts two forms: omit the field to inherit the controller default `external-dns.alpha.kubernetes.io/`, or set it to a non-empty string ending in `/`. The empty string `""` is rejected by the chart's values schema; omission is the only zero-prefix path.
`annotationPrefix` accepts two forms: omit the field to inherit the controller default `external-dns.alpha.kubernetes.io/`, or set it to a non-empty string ending in `/`. The empty string `""` is rejected by the chart's values schema; omission is the only way to use the default prefix.

…th kuberture

Document the kuberture system package: the EndpointSlice-to-Service
bridge that lets external-dns publish the cluster's own API endpoint.
Covers the problem (external-dns does not support EndpointSlice as a
source), the kuberture flow, single-instance enabling against the
platform external-dns, multi-instance routing via annotationPrefix,
the four addressSource strategies, the disable path, and the
maintainer-personal-namespace supply-chain caveat.

Companion to cozystack/cozystack#2647 which adds the package.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei lexfrei force-pushed the docs/kuberture-system-package branch from ad08b7b to 4b586a3 Compare May 14, 2026 08:46
@lexfrei lexfrei marked this pull request as ready for review May 14, 2026 08:50
@lexfrei lexfrei requested review from kvaps and lllamnyp as code owners May 14, 2026 08:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@content/en/docs/next/networking/kuberture.md`:
- Line 40: The phrase "wants published" in the kuberture docs sentence is
awkward; update the sentence in content/en/docs/next/networking/kuberture.md
(the paragraph describing the operator DNS requirement for `kuberture`) to use
clearer wording such as "wants to publish" or "wants to be published" (e.g.,
replace "the DNS hostname the operator actually wants published" with "the DNS
hostname the operator actually wants to publish" or "the DNS hostname the
operator actually wants to be published") so the operator guidance reads
naturally.
- Around line 117-118: The docs incorrectly suggest using
--annotation-filter=internal-dns.example.com to match annotations like
internal-dns.example.com/hostname; update the text to use a full annotation key
(e.g., --annotation-filter=internal-dns.example.com/hostname) or note that
--annotation-filter requires exact annotation key matches, and reference the
example resource name kuberture-internal and the actual annotation keys
(internal-dns.example.com/hostname, internal-dns.example.com/target,
internal-dns.example.com/ttl) so readers know to pick one exact suffixed key for
the filter.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 783947a6-813d-4389-bb83-24d0682751be

📥 Commits

Reviewing files that changed from the base of the PR and between f466d34 and 4b586a3.

📒 Files selected for processing (1)
  • content/en/docs/next/networking/kuberture.md


## Enabling on the host

`kuberture` is an optional system package, opt-in via `bundles.enabledPackages`. The package itself ships no usable default beyond enabling `ServiceMonitor` for the platform Prometheus — the deployment fails fast if `config.outputs` is empty. The operator must declare at least one output, because the only thing `kuberture` cannot infer is the DNS hostname the operator actually wants published.
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten wording on Line 40

“wants published” reads awkwardly. Prefer “wants to publish” (or “wants to be published”) for clarity in operator guidance.

Suggested edit
-...the DNS hostname the operator actually wants published.
+...the DNS hostname the operator actually wants to publish.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`kuberture` is an optional system package, opt-in via `bundles.enabledPackages`. The package itself ships no usable default beyond enabling `ServiceMonitor` for the platform Prometheus — the deployment fails fast if `config.outputs` is empty. The operator must declare at least one output, because the only thing `kuberture` cannot infer is the DNS hostname the operator actually wants published.
`kuberture` is an optional system package, opt-in via `bundles.enabledPackages`. The package itself ships no usable default beyond enabling `ServiceMonitor` for the platform Prometheus — the deployment fails fast if `config.outputs` is empty. The operator must declare at least one output, because the only thing `kuberture` cannot infer is the DNS hostname the operator actually wants to publish.
🧰 Tools
🪛 LanguageTool

[style] ~40-~40: The double modal “wants published” is nonstandard (only accepted in certain dialects). Consider “to be published”.
Context: ...NS hostname the operator actually wants published. Single output, consumed by the platfo...

(NEEDS_FIXED)

🤖 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 `@content/en/docs/next/networking/kuberture.md` at line 40, The phrase "wants
published" in the kuberture docs sentence is awkward; update the sentence in
content/en/docs/next/networking/kuberture.md (the paragraph describing the
operator DNS requirement for `kuberture`) to use clearer wording such as "wants
to publish" or "wants to be published" (e.g., replace "the DNS hostname the
operator actually wants published" with "the DNS hostname the operator actually
wants to publish" or "the DNS hostname the operator actually wants to be
published") so the operator guidance reads naturally.

Comment on lines +117 to +118
- `kuberture-internal` carries `internal-dns.example.com/*` annotations and is invisible to the platform `external-dns`; a second `external-dns` instance configured with `--annotation-filter=internal-dns.example.com` consumes it.

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

external-dns --annotation-filter syntax: does a bare key selector (e.g., internal-dns.example.com) match annotation keys that only exist as internal-dns.example.com/hostname, internal-dns.example.com/target, internal-dns.example.com/ttl? Please provide official docs/examples.

💡 Result:

A bare key selector like external-dns.example.com does NOT match annotation keys that only exist as external-dns.example.com/hostname, external-dns.example.com/target, or external-dns.example.com/ttl. Reason (based on external-dns docs + Kubernetes label/selector semantics): external-dns documents that --annotation-filter uses label selector semantics [1]. In Kubernetes label selectors, the selector key must match the label key exactly (a selector key is not a prefix match) [2]. Therefore, a selector for key internal-dns.example.com will only match an annotation whose key is exactly internal-dns.example.com—it will not match keys that merely share that prefix but have additional path segments such as /hostname. Official docs/examples to cite: 1) external-dns flag definition: --annotation-filter “Filter resources queried for endpoints by annotation, using label selector semantics” [1]. 2) external-dns annotations/keys are treated as exact annotation keys (e.g., separate annotations for .../hostname, .../target, .../ttl) [3]. Example (correct for your intent if you want to filter on only the sub-annotation): use the full key including the suffix, e.g.: - --annotation-filter=external-dns.alpha.kubernetes.io/hostname - --annotation-filter=external-dns.alpha.kubernetes.io/target - --annotation-filter=external-dns.alpha.kubernetes.io/ttl If you instead want a prefix-based match, external-dns’s --annotation-filter is not documented as supporting prefix/pattern matching for the key portion; you would need another approach (e.g., separate external-dns instances/config, or label filtering where available) [1].

Citations:


Correct the --annotation-filter example on Line 118—bare key selectors don't match suffixed annotation keys

The filter --annotation-filter=internal-dns.example.com will not match the annotation keys you describe (internal-dns.example.com/hostname, internal-dns.example.com/target, internal-dns.example.com/ttl). The --annotation-filter flag uses Kubernetes label selector semantics, which require exact key matching—internal-dns.example.com only matches an annotation with that exact key, not keys with additional path segments. To filter kuberture-internal resources, specify the full key: --annotation-filter=internal-dns.example.com/hostname (or choose one of the suffixed keys, or document the filter limitation if filtering on one suffix is insufficient).

🤖 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 `@content/en/docs/next/networking/kuberture.md` around lines 117 - 118, The
docs incorrectly suggest using --annotation-filter=internal-dns.example.com to
match annotations like internal-dns.example.com/hostname; update the text to use
a full annotation key (e.g.,
--annotation-filter=internal-dns.example.com/hostname) or note that
--annotation-filter requires exact annotation key matches, and reference the
example resource name kuberture-internal and the actual annotation keys
(internal-dns.example.com/hostname, internal-dns.example.com/target,
internal-dns.example.com/ttl) so readers know to pick one exact suffixed key for
the filter.

@lexfrei lexfrei self-assigned this May 14, 2026
Copy link
Copy Markdown
Contributor

@Arsolitt Arsolitt left a comment

Choose a reason for hiding this comment

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

Page is well-structured and aligned with the companion package PR. One blocker: the multi-instance routing recipe documents --annotation-filter where external-dns actually needs --annotation-prefix. Please also address the unresolved CodeRabbit/Gemini bot threads — note that the high-priority Gemini one is wrong on its premise (external-dns does support custom annotation prefixes via --annotation-prefix), but its underlying observation that the doc's current flag choice doesn't match standard external-dns behaviour is correct.

This renders two headless Services in `cozy-kuberture`:

- `kuberture-public` carries the default `external-dns.alpha.kubernetes.io/*` annotations and is consumed by the platform `external-dns`.
- `kuberture-internal` carries `internal-dns.example.com/*` annotations and is invisible to the platform `external-dns`; a second `external-dns` instance configured with `--annotation-filter=internal-dns.example.com` consumes it.
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.

The recipe says a second external-dns instance configured with --annotation-filter=internal-dns.example.com consumes kuberture-internal. That flag does not produce the documented behaviour.

--annotation-filter is a label-selector filter on which objects external-dns considers at all; it uses Kubernetes label-selector semantics with exact key matching (source/annotations/filter.go at v0.20.0). A bare key internal-dns.example.com does not match an annotation key internal-dns.example.com/hostname. Even rewriting it as --annotation-filter=internal-dns.example.com/hostname (an existence selector) only filters which services external-dns looks at; the instance still reads hostname/target/ttl under its configured --annotation-prefix (default external-dns.alpha.kubernetes.io/), so the suffixed annotations on kuberture-internal would be invisible to it.

The flag that does what the docs describe is --annotation-prefix=internal-dns.example.com/. external-dns calls annotations.SetAnnotationPrefix(cfg.AnnotationPrefix) on startup (controller/execute.go:66 at v0.20.0) and rebuilds all hostname/target/ttl annotation keys under the supplied prefix. The upstream Split Horizon DNS doc (docs/advanced/split-horizon.md in kubernetes-sigs/external-dns) states verbatim: "To enable split horizon DNS, configure each instance to use a different annotation prefix using the --annotation-prefix flag."

Same wording on line 85 ("via --annotation-filter"). Both occurrences should be --annotation-prefix=<your-prefix>/ (note the trailing slash, matching the chart's values.schema.json pattern: /$).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants