fix(external-dns): harden single-replica writer against node failure - #2128
Merged
Conversation
Coroot flags external-dns:Deployment as "single instance - not resilient to node failure". external-dns v0.21.0 (chart 1.21.1) has NO leader election — the multi-replica proposal is status `not-planned` and the `--enable-leader-election` flag does not exist in the binary — so a 2nd ACTIVE replica is unsafe: both write to Cloudflare under one txtOwnerId, causing duplicate writes / rate-limit churn / records fighting. The validate-replica-floor policy exempts external-dns for this reason. Instead of a hazardous duplicate-writes 2-replica config, minimise the node-failure blast radius of the deliberately-single writer: - priorityClassName: system-cluster-critical so the replacement pod reschedules ahead of normal workloads on node loss (matches its peers CoreDNS and the hcloud-csi controller), shrinking the DNS-drift window. - drain-safe PodDisruptionBudget (maxUnavailable: 1, the #1880 pattern; chart ships no PDB template) so a node drain cycles the single pod cleanly instead of stalling. - document the TXT-registry ownership fence that makes a reschedule safe and the upgrade path to true active/standby HA once external-dns ships leader election. Validated with `kubectl kustomize k8s/providers/hetzner/infrastructure/external-dns`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
devantler
marked this pull request as ready for review
June 17, 2026 22:30
devantler
enabled auto-merge
June 17, 2026 22:30
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Jun 18, 2026
Contributor
|
🎉 This PR is included in version 1.64.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
Coroot flags
external-dns:Deployment:external-dns(the cluster external-dns, Cloudflare provider) as "single instance - not resilient to node failure". The cluster is not resource-constrained, so the question is the correct HA shape — not whether we can afford replicas.Leader-election finding for the pinned version (researched, not guessed)
The pinned chart is
external-dns1.21.1, whoseappVersionis external-dns v0.21.0 (both are the latest releases as of this PR).external-dns does NOT support leader election at this version — or at any version. Evidence:
pkg/apis/externaldns/types.go) at v0.21.0 and atmastercontains no leader-election flag. The only concurrency control is--once(run-once mode).enable-leader-electionanywhere in the external-dns repo (default branch) is indocs/proposal/001-leader-election.md— i.e. it is a design proposal, not an implementation.not-planned.values.yamlexposes noreplicaCount/replicas/leaderElection/leasekey (it offers onlydeploymentStrategy), and no PDB template.So
--enable-leader-election/ a single-active-writer multi-replica mode is not available here.Why a naive 2-replica config is the wrong fix (the hazard)
external-dns uses a TXT-registry ownership model: every managed record is paired with a
_externaldns.TXT carrying a singletxtOwnerId(${domain}). Two active replicas share that one owner id, so they would both reconcile the same records and issue duplicate Cloudflare API writes — causing rate-limit churn and records fighting each other (flapping). This is exactly why the repo'svalidate-replica-floorKyverno policy exempts external-dns by name ("chart has no replicaCount; >1 replica = duplicate Cloudflare writes + throttling"). Without leader election there is no safe way to keep a second replica passive.Decision: minimise the node-failure blast radius of a deliberately-single writer
Per the honest fix for a single writer that cannot do active/standby HA at this version:
priorityClassName: system-cluster-criticalon the Deployment. external-dns programs the cluster's public DNS; its infrastructure peers CoreDNS and the hcloud-csi controller use the same class. On a node loss the scheduler now places the replacement pod ahead of normal workloads, shrinking the window in which live DNS drifts from desired state. (This is the real node-failure-resilience lever for a single pod: fast reschedule.)PodDisruptionBudget(maxUnavailable: 1, the platform-wide PDBs with minAvailable: 1 over ≤1-replica workloads make nodes undrainable (blocks ksail recycles & rolling reboots) #1880 pattern) added as a separate manifest because the chart ships no PDB template. It lets a node drain evict and reschedule the single pod cleanly instead of stalling on an un-budgeted pod.--enable-leader-election, currentlynot-planned), switch toreplicas: 2+ leader election (only the leader writes) + atopologySpreadConstraints, and drop the single-replica note. Track the upstream proposal before bumping the replica count.No duplicate-writes 2-replica config is shipped.
Changes
k8s/providers/hetzner/infrastructure/external-dns/helm-release.yaml— addpriorityClassName: system-cluster-critical; document the no-leader-election finding, the duplicate-writes hazard, the TXT-ownership safety, and the upgrade path.k8s/providers/hetzner/infrastructure/external-dns/pod-disruption-budget.yaml— new drain-safe PDB (maxUnavailable: 1).k8s/providers/hetzner/infrastructure/external-dns/kustomization.yaml— register the PDB.Validation
kubectl kustomize k8s/providers/hetzner/infrastructure/external-dnsbuilds cleanly (exit 0); the rendered HelmRelease carriespriorityClassName: system-cluster-criticaland the PDB renders with the correctapp.kubernetes.io/name|instance: external-dnsselector.🤖 Generated with Claude Code