Skip to content

--registry=crd would spend no write budget on ownership records, but the path is untested #24

Description

@sshine

The README currently says,

A domain filter is mandatory. With --registry=txt external-dns filters deletions by owner ID, so a zone it has never written to is safe — but --registry=noop reports an empty owner ID and that filter is skipped entirely, at which point every record in a zone we report that the source does not produce becomes a deletion. There is deliberately no "manage the whole account" mode.

There is an untested third option worth exploring:

--registry=crd keeps ownership in a cluster resource rather than in the zone. For deSEC specifically that removes the single largest avoidable consumer of the write budget.

Why --registry=crd is friendlier to the rate limit

  • Ownership TXT records are RRset writes, counted against dns_api_per_domain_expensive (300/day, per zone) exactly like the records the user asked for. With --txt-prefix=externaldns-%{record_type}. that is one extra RRset per record type per name. Under crd there are none.
  • It removes the apex artefact outright. The --txt-prefix advice in the same README section exists only because the default prefix names an apex endpoint's companion record outside the zone (external-dns#5010). No companion record, no footgun.
  • NewCRDRegistry rejects an empty owner ID at construction (registry/crd/crd.go:69), so the deletion filter can never be skipped. That is a stronger guarantee than txt gives, and the opposite of noop, which is what the paragraph warns about.

Cost, for the docs to state honestly: ownership becomes cluster-local. Lose the cluster and you lose it, whereas TXT records can be read back out of the zone.

What is already verified

  • The registry is provider-agnostic. registry/factory/registry.go maps RegistryCRD: crd.New with no provider constraint, and NewCRDRegistry takes any provider.Provider, so it wraps the webhook provider like any other. Nothing in the webhook protocol changes.
  • The resource is DNSRecord in externaldns.k8s.io/v1alpha1 (apis/v1alpha1/dnsrecord.go). Not DNSEndpoint, which is the CRD source; the two are easy to confuse.
  • The registry reads through a controller-runtime informer cache and writes through the API server, and sets status conditions on each record. It skips records whose Ready condition is not true (crd.go:155), which is behaviour with no analogue under txt and is worth understanding before recommending it.

Two things that will block a naive attempt

It is unreleased. registry/crd does not exist at v0.21.0, the latest release (2026-04-06): the flag enum there is txt, noop, dynamodb, aws-sd. The registry landed on master on 2026-06-21 in kubernetes-sigs/external-dns#5372. So this needs a build from master, or waiting for the next release. Worth checking again before starting.

It cannot be proven in the offline lab. The lab in docs/testing.md §3 exists precisely because external-dns runs without a cluster when the only source is fake. crd builds a REST config and talks to the API server, so it does not. This belongs under "Where a real cluster is still needed", and proving it needs kind or k3d plus the DNSRecord CRD installed.

Consequences for this codebase

Under crd, external-dns synthesizes no ownership records at all. Two behaviours we currently rely on stop being exercised by that path:

Both must stay, since txt is the default and the one most people will run. The point is only that a crd run does not cover them, so it cannot replace the existing tests.

The task

  1. Prove the path works: a kind cluster, external-dns from master, --registry=crd --source=fake, against this webhook and a deSEC test account. Confirm a second reconcile writes nothing.
  2. Make whatever adjustments that turns up. None are expected, since the registry sits above the provider and the webhook contract is unchanged, but "expected" is the reason this issue says untested.
  3. Update the docs to recommend it, with the cluster-local ownership trade-off stated. The README is generated, so the edit goes in crates/external-dns-desec-webhook/src/lib.rs and then just readme; docs/rate-limits.md should say what it saves against the per-zone budget.

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsImprovements or additions to documentationfeatureA feature or enhancement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions