external-dns v0.22 changed its default annotation prefix to external-dns.kubernetes.io/ with no fallback. Renovate #129 moved the chart to 1.23.0 (app v0.23.0), and at 12:13Z it ignored every alpha-prefixed annotation in this repo: the Gateway target (public names went to A 192.168.2.20), controller: none (jackett, pihole and semaphore got published), and the L4 Service hostnames (unifi and crafty-mc were deleted). Set annotationPrefix: external-dns.alpha.kubernetes.io/ to restore the v0.21 reading. Floor the chart at 1.23.0, because the flag needs app >= 0.22. Co-Authored-By: Claude Opus 5.5 <[email protected]>
platform/externaldns
ExternalDNS — automatic public DNS records for public services (Cloudflare).
- Spec: docs/09 — DNS & Ingress (ExternalDNS is named in docs/01; no dedicated ADR)
- Serves: R-DNS-01/02/03
- Owner: WS-NET (token from WS-SEC)
What this stub is
Flux HelmRelease watching Gateway/HTTPRoute + Service resources, publishing
<service>.home.keithjackson.dev records in Cloudflare from declared intent (no manual record
editing, R-DNS-02).
Split-horizon note (docs/09)
A wildcard *.keithjackson.dev matches only one label deep, so <svc>.home.keithjackson.dev needs
its own record. internal services need NO public record — PiHole resolves them to the internal
Gateway VIP (split-horizon). ExternalDNS manages public services' records only.
Public target + in-cluster DDNS (2026-10-03)
Gateway-published records are CNAMEs to wan.home.keithjackson.dev, not the Gateway VIP:
plex.home.keithjackson.dev CNAME wan.home.keithjackson.dev <- external-dns (Gateway `target` annotation)
wan.home.keithjackson.dev A <current WAN IP> <- cloudflare-ddns (ddns.yaml), every 5m
- The
targetannotation is on the shared Gateway (platform/gateway/gateway.yaml), so it applies to every route that publishes at all.internalroutes (controller: none) still publish nothing. ddns.yamlrunsfavonia/cloudflare-ddnsin this namespace with the samecloudflare-api-token. It detects the WAN IP through Cloudflare's trace endpoint, so the pod must not carryegress-class: vpn.- LAN and pods resolve
*.home.keithjackson.devto the Gateway VIP via PiHole and CoreDNS (split-horizon) and never use the public path. - Nodes resolve through public DNS (
node_baseline_dns_servers), so from a node, the apiserver's OIDC issuerkeycloak.home.keithjackson.devresolves to the WAN IP. kubectl SSO therefore relies on the router hairpinning (NAT reflection) WAN:443 back to the Gateway.
Check: dig +short @1.1.1.1 plex.home.keithjackson.dev → wan.home.keithjackson.dev. then the WAN IP;
kubectl -n external-dns logs deploy/cloudflare-ddns.
Secrets
Cloudflare API token referenced by name (cloudflare-api-token, shared with cert-manager),
SOPS-encrypted (WS-SEC, OQ-4).
TODO (later phases)
- Phase 2 (WS-NET): confirm
gateway-httproutesource; prove an auto record for a test route (R-DNS-02, Phase-2 exit gate). - Phase 6 (WS-APPS): PiHole provides the internal split-horizon answers as it onboards.