CRD Source¶
CRD source provides a generic mechanism to manage DNS records in your favorite DNS provider supported by external-dns.
Details¶
CRD source watches for a user specified CRD to extract Endpoints from its Spec.
So users need to create such a CRD and register it to the kubernetes cluster and then create new object(s) of the CRD specifying the Endpoints.
Registering CRD¶
Here is typical example of CRD API type which provides Endpoints to CRD source:
type TTL int64
type Targets []string
type ProviderSpecificProperty struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
type ProviderSpecific []ProviderSpecificProperty
type Labels map[string]string
type Endpoint struct {
// The hostname of the DNS record
DNSName string `json:"dnsName,omitempty"`
// The targets the DNS record points to
Targets Targets `json:"targets,omitempty"`
// RecordType type of record, e.g. CNAME, A, SRV, TXT etc
RecordType string `json:"recordType,omitempty"`
// TTL for the record
RecordTTL TTL `json:"recordTTL,omitempty"`
// Labels stores labels defined for the Endpoint
// +optional
Labels Labels `json:"labels,omitempty"`
// ProviderSpecific stores provider specific config
// +optional
ProviderSpecific ProviderSpecific `json:"providerSpecific,omitempty"`
}
type DNSEndpointSpec struct {
Endpoints []*Endpoint `json:"endpoints,omitempty"`
}
type DNSEndpointStatus struct {
// The generation observed by the external-dns controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// DNSEndpoint is the CRD wrapper for Endpoint
// +k8s:openapi-gen=true
// +kubebuilder:resource:path=dnsendpoints
// +kubebuilder:subresource:status
type DNSEndpoint struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DNSEndpointSpec `json:"spec,omitempty"`
Status DNSEndpointStatus `json:"status,omitempty"`
}
Refer to kubebuilder to create and register the CRD.
Usage¶
One can use CRD source by specifying --source flag with crd and specifying the ApiVersion and Kind of the CRD with --crd-source-apiversion and crd-source-kind respectively.
for e.g:
build/external-dns --source crd --crd-source-apiversion externaldns.k8s.io/v1alpha1 --crd-source-kind DNSEndpoint --provider inmemory --policy sync --once --dry-run
As with every source, --annotation-filter is optional. If you set it, ExternalDNS applies it to DNSEndpoint objects too, so a DNSEndpoint must match the filter itself or the CRD source will ignore it.
For example, with:
build/external-dns --source crd --crd-source-apiversion externaldns.k8s.io/v1alpha1 --crd-source-kind DNSEndpoint --provider inmemory --once --dry-run --annotation-filter=external-dns=public
the DNSEndpoint metadata must include a matching annotation:
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplednsrecord
annotations:
external-dns: public
spec:
endpoints:
- dnsName: foo.bar.com
recordTTL: 180
recordType: A
targets:
- 192.168.99.216
Creating DNS Records¶
Create the objects of CRD type by filling in the fields of CRD and DNS record would be created accordingly.
Example¶
Here is an example CRD manifest generated by kubebuilder.
Apply this to register the CRD
$ kubectl apply --server-side=true -f "https://raw.githubusercontent.com/kubernetes-sigs/external-dns/master/config/crd/standard/dnsendpoints.externaldns.k8s.io.yaml"
customresourcedefinition.apiextensions.k8s.io "dnsendpoints.externaldns.k8s.io" created
Then you can create the dns-endpoint yaml similar to dnsendpoint-example
$ kubectl apply -f docs/sources/crd/dnsendpoint-example.yaml
dnsendpoint.externaldns.k8s.io "examplednsrecord" created
Run external-dns in dry-mode to see whether external-dns picks up the DNS record from CRD.
$ build/external-dns --source crd --crd-source-apiversion externaldns.k8s.io/v1alpha1 --crd-source-kind DNSEndpoint --provider inmemory --policy sync --once --dry-run
INFO[0000] running in dry-run mode. No changes to DNS records will be made.
INFO[0000] Connected to cluster at https://192.168.99.100:8443
INFO[0000] CREATE: foo.bar.com 180 IN A 192.168.99.216
INFO[0000] CREATE: foo.bar.com 0 IN TXT "heritage=external-dns,external-dns/owner=default"
Validation¶
A DNSEndpoint is checked in two places. The CRD schema rejects structural mistakes
at kubectl apply time:
dnsNameandrecordTypeare required.dnsNamemust be a DNS name (a leading
*.wildcard and_-prefixed labels such as_acme-challengeare allowed), at most
253 characters, or 254 written as an absolute name with a trailing dot.recordTypemust be one ofA,AAAA,CNAME,TXT,SRV,NS,PTR,MX,NAPTR.recordTTLmust be between0and2147483647(RFC 2181 §8).0means “unset” —
the provider applies its own default.- Each target is between 1 and 4096 characters, and an endpoint carries at most 1000
targets. ADNSEndpointcarries at most 1000 endpoints and an endpoint at most 100
providerSpecificentries. SRVandNAPTRtargets must be absolute (end with a dot).PTRrecords must use adnsNameunder.in-addr.arpaor.ip6.arpa, written
without a trailing dot (1.0.0.10.in-addr.arpa).
An object over any of these limits is rejected whole. Past 1000 endpoints, split them
across several DNSEndpoint objects, keeping each record in one object: targets cannot
be split, and two objects declaring the same dnsName and recordType conflict —
external-dns publishes one and drops the other instead of merging their targets.
This schema also validates the DNSRecord objects the CRD registry
writes, where a rejected write aborts the sync instead of teaching anyone anything. So a
rule should not reject what external-dns can legitimately build.
The rest is checked when external-dns reads the object, before anything is planned. A
rejected endpoint is dropped from the plan and reported in the external-dns log:
A/AAAAtargets must be addresses of the matching family, unless the endpoint sets
thealiasprovider-specific property.MXtargets must be<preference> <host>, e.g.10 mail.example.com.SRVtargets must be the full<priority> <weight> <port> <host>(RFC 2782).PTRrecords need at least one non-empty target.- Every other record type rejects a target with a trailing dot, except
CNAMEandTXT,
where both forms are valid (RFC 1035 §5.1).
Leave targets empty only when --default-targets is configured.
Using CRD source to manage DNS records in different DNS providers¶
CRD source provides a generic mechanism and declarative way to manage DNS records in different DNS providers using external-dns.
Not all the record types are enabled by default so the required record types must be enabled by using --managed-record-types.
external-dns --source=crd \
--domain-filter=example.com \
--managed-record-types=A \
--managed-record-types=CNAME \
--managed-record-types=NS
- Example for record type
A
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplearecord
spec:
endpoints:
- dnsName: example.com
recordTTL: 60
recordType: A
targets:
- 10.0.0.1
- Example for record type
CNAME
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplecnamerecord
spec:
endpoints:
- dnsName: test-a.example.com
recordTTL: 300
recordType: CNAME
targets:
- example.com
Note: CNAME and DNAME targets accept both bare hostnames (
example.com) and absolute FQDNs with a trailing dot (example.com.), as defined by RFC 1035 §5.1. Other record types (A, AAAA, NS, etc.) do not accept a trailing dot.
- Example for record type
NS
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: ns-record
spec:
endpoints:
- dnsName: zone.example.com
recordTTL: 300
recordType: NS
targets:
- ns1.example.com
- ns2.example.com
- Example for record type
DNAME
DNAME (RFC 6672) redirects an entire subtree of the DNS namespace to another domain. It has a single domain-name target and, like CNAME, accepts both bare hostnames and absolute FQDNs with a trailing dot.
The difference from CNAME is what gets aliased:
CNAME foo.example.com → bar.example.netaliases onlyfoo.example.com. Names beneath it, such asx.foo.example.com, are unaffected.DNAME sub.example.com → example.netaliases everything belowsub.example.com. A query forx.sub.example.comis rewritten by the resolver tox.example.net(the owner namesub.example.comitself is not redirected), so you don’t have to create aCNAMEfor every subdomain.
This makes DNAME useful for redirecting or renaming a whole subtree — for example, moving *.old.example.com to new.example.net — without maintaining a record per name.
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: dname-record
spec:
endpoints:
- dnsName: sub.example.com
recordTTL: 300
recordType: DNAME
targets:
- example.net
Note:
DNAMEis only stored by providers whose DNS backend supports it (e.g. NS1, OCI, PowerDNS, Scaleway, and RFC2136 backends such as BIND or Knot).
Providers whose backend has noDNAMEtype (e.g. AWS Route 53, Azure, Google Cloud DNS, Cloudflare) will reject the record on write.
BecauseDNAMEmasks the entire subtree beneath its owner name, do not manage other records below a name that carries aDNAME.
RBAC configuration¶
If you use RBAC, extend the external-dns ClusterRole with: