## Summary This PR adds a new domain modifier `IGNORE_EXTERNAL_DNS()` that automatically detects and ignores DNS records managed by Kubernetes [external-dns](https://github.com/kubernetes-sigs/external-dns) controller. **Related Issue:** This addresses the feature request discussed in StackExchange/dnscontrol#935 (Idea: Ownership system), where @tlimoncelli indicated openness to accepting a PR for this functionality. ## Problem When running DNSControl alongside Kubernetes external-dns, users face a challenge: - **external-dns** dynamically creates DNS records based on Kubernetes Ingress/Service resources - Users cannot use `IGNORE()` because they cannot predict which record names external-dns will create - Using `NO_PURGE()` is too broad - it prevents DNSControl from cleaning up any orphaned records The fundamental issue is that `IGNORE()` requires static patterns known at config-time, but external-dns creates records dynamically at runtime. ## Solution `IGNORE_EXTERNAL_DNS()` solves this by detecting external-dns managed records at runtime: ```javascript D("example.com", REG_CHANGEME, DnsProvider(DSP_MY_PROVIDER), IGNORE_EXTERNAL_DNS(), // Automatically ignore external-dns managed records A("@", "1.2.3.4"), CNAME("www", "@") ); ``` ### How It Works external-dns uses a TXT record registry to track ownership. For each managed record, it creates a TXT record like: - `a-myapp.example.com` → TXT containing `heritage=external-dns,external-dns/owner=...` - `cname-api.example.com` → TXT containing `heritage=external-dns,external-dns/owner=...` This PR: 1. Scans existing TXT records for the `heritage=external-dns` marker 2. Parses the TXT record name prefix (e.g., `a-`, `cname-`) to determine the managed record type 3. Automatically adds those records to the ignore list during diff operations ## Changes | File | Purpose | |------|---------| | `models/domain.go` | Add `IgnoreExternalDNS` field to DomainConfig | | `pkg/js/helpers.js` | Add `IGNORE_EXTERNAL_DNS()` JavaScript helper | | `pkg/diff2/externaldns.go` | Core detection logic for external-dns TXT records | | `pkg/diff2/externaldns_test.go` | Unit tests for detection logic | | `pkg/diff2/handsoff.go` | Integrate external-dns detection into handsoff() | | `pkg/diff2/diff2.go` | Pass IgnoreExternalDNS flag to handsoff() | | `commands/types/dnscontrol.d.ts` | TypeScript definitions for IDE support | | `documentation/.../IGNORE_EXTERNAL_DNS.md` | User documentation | ## Design Philosophy This follows DNSControl's pattern of convenience builders (like `M365_BUILDER`, `SPF_BUILDER`, `DKIM_BUILDER`) that make complex operations simple. Just as those builders abstract away implementation details, `IGNORE_EXTERNAL_DNS()` abstracts away the complexity of detecting external-dns managed records. ## Testing All unit tests pass: ``` go test ./pkg/diff2/... -v # Tests detection logic go test ./pkg/js/... # Tests JS helpers go build ./... # Builds successfully ``` ## Caveats Documented - Only supports TXT registry (the default for external-dns) - Requires external-dns to use default naming conventions - May need updates if external-dns changes its registry format --------- Co-authored-by: Tom Limoncelli <6293917+tlimoncelli@users.noreply.github.com>
7.1 KiB
| name | parameters | parameter_types | |||
|---|---|---|---|---|---|
| IGNORE_EXTERNAL_DNS |
|
|
IGNORE_EXTERNAL_DNS makes DNSControl automatically detect and ignore DNS records
managed by Kubernetes external-dns.
Background
External-dns is a popular Kubernetes controller that synchronizes exposed Kubernetes Services and Ingresses with DNS providers. It creates DNS records automatically based on annotations on your Kubernetes resources.
External-dns uses TXT records to track ownership of the DNS records it manages. These TXT records contain metadata in this format:
"heritage=external-dns,external-dns/owner=<owner-id>,external-dns/resource=<resource>"
When you have both DNSControl and external-dns managing the same DNS zone, conflicts can occur. DNSControl will try to delete records created by external-dns, and external-dns will recreate them, leading to an endless update cycle.
How it works
When IGNORE_EXTERNAL_DNS is enabled, DNSControl will:
- Scan existing TXT records for the external-dns heritage marker (
heritage=external-dns) - Parse the TXT record name to determine which DNS record it manages
- Automatically ignore both the TXT ownership record and the corresponding DNS record
External-dns creates TXT records with prefixes based on record type:
a-<name>for A recordsaaaa-<name>for AAAA recordscname-<name>for CNAME recordsns-<name>for NS recordsmx-<name>for MX recordssrv-<name>for SRV recordstxt-<name>for TXT records (when external-dns manages TXT records)
For example, if external-dns creates an A record at myapp.example.com, it will
also create a TXT record at a-myapp.example.com containing the heritage information.
Usage
{% code title="dnsconfig.js" %}
// Default: detect standard external-dns prefixes (a-, cname-, etc.)
D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER),
IGNORE_EXTERNAL_DNS(),
// Your static DNS records managed by DNSControl
A("www", "1.2.3.4"),
A("mail", "1.2.3.5"),
MX("@", 10, "mail"),
// Records created by external-dns (from Kubernetes Ingresses/Services)
// will be automatically detected and ignored
);
{% endcode %}
Custom Prefix Support
If your external-dns is configured with a custom --txt-prefix (as documented in the
external-dns TXT registry docs),
pass that prefix to IGNORE_EXTERNAL_DNS():
{% code title="dnsconfig.js" %}
// If external-dns is configured with --txt-prefix="extdns-"
D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER),
IGNORE_EXTERNAL_DNS("extdns-"),
A("www", "1.2.3.4"),
);
{% endcode %}
This will match TXT records like extdns-www, extdns-api, etc.
Without a prefix argument, it detects:
- The default
%{record_type}-format (prefixes likea-,cname-, etc.) - Legacy format (TXT record with same name as managed record)
Example scenario
Suppose you have:
- A Kubernetes cluster running external-dns with
--txt-owner-id=my-cluster - An Ingress resource that creates an A record for
myapp.example.compointing to10.0.0.1
External-dns will create:
- An A record:
myapp.example.com→10.0.0.1 - A TXT record:
a-myapp.example.com→"heritage=external-dns,external-dns/owner=my-cluster,external-dns/resource=ingress/default/myapp"
With IGNORE_EXTERNAL_DNS enabled, DNSControl will:
- Detect the TXT record at
a-myapp.example.comas an external-dns ownership record - Ignore both the TXT record and the A record at
myapp.example.com - Only manage the records you explicitly define in your
dnsconfig.js
Comparison with other options
| Feature | Use case |
|---|---|
IGNORE_EXTERNAL_DNS |
Automatically ignore all external-dns managed records |
IGNORE("*.k8s", "A,AAAA,CNAME,TXT") |
Ignore records under a specific subdomain pattern |
NO_PURGE |
Don't delete any records (less precise, records may accumulate) |
Caveats
One per domain
Only one IGNORE_EXTERNAL_DNS() should be used per domain. If you call it multiple
times, the last prefix wins. If you have multiple external-dns instances with
different prefixes managing the same zone, use IGNORE() patterns for additional
prefixes.
TXT Registry Format
This feature relies on external-dns's TXT registry, which is the default registry type. The TXT record content format is well-documented:
"heritage=external-dns,external-dns/owner=<owner-id>,external-dns/resource=<resource>"
This feature detects the heritage=external-dns marker in TXT records to identify
external-dns managed records.
Custom Prefix Support
This feature supports custom prefixes configured via external-dns's --txt-prefix flag.
If you're using a custom prefix, pass it to IGNORE_EXTERNAL_DNS():
// If external-dns uses --txt-prefix="extdns-"
IGNORE_EXTERNAL_DNS("extdns-")
// If external-dns uses --txt-prefix="myprefix-%{record_type}-"
IGNORE_EXTERNAL_DNS("myprefix-") // The record type part is handled automatically
// If external-dns uses --txt-prefix="extdns-%{record_type}." (period format)
// This is recommended for apex domain support per external-dns docs
IGNORE_EXTERNAL_DNS("extdns-") // Works with both hyphen and period format
Without a prefix argument, it detects:
- Default format:
%{record_type}-prefix (e.g.,a-,cname-) - Legacy format: Same name as managed record (no prefix)
Period Format for Apex Domains
If you need external-dns to manage apex (root) domain records, the external-dns
documentation recommends using a prefix with %{record_type} followed by a period:
# external-dns deployment args
args:
- --txt-prefix=extdns-%{record_type}.
This creates TXT records like extdns-a.www for the www A record, and extdns-a
for the apex A record. DNSControl's IGNORE_EXTERNAL_DNS supports both formats:
- Hyphen format:
extdns-a-www(from--txt-prefix=extdns-with default%{record_type}-) - Period format:
extdns-a.www(from--txt-prefix=extdns-%{record_type}.)
Note: Suffix-based naming (--txt-suffix) is not currently supported.
Unsupported Registries
External-dns supports multiple registry types. This feature only supports:
- ✅ TXT registry (default) - Stores metadata in TXT records
The following registries are not supported:
- ❌ DynamoDB registry - Stores metadata in AWS DynamoDB
- ❌ AWS-SD registry - Stores metadata in AWS Service Discovery
- ❌ noop registry - No metadata persistence
Legacy TXT Format
External-dns versions prior to v0.16 created TXT records without the record type
prefix (e.g., myapp.example.com instead of a-myapp.example.com). This legacy
format is supported but may match more records than intended since the record type
cannot be determined.
See also
IGNOREfor manually ignoring specific records with glob patternsNO_PURGEfor preventing deletion of all unmanaged records- External-dns documentation