Skip to content

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"

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.net aliases only foo.example.com. Names beneath it, such as x.foo.example.com, are unaffected.
  • DNAME sub.example.com โ†’ example.net aliases everything below sub.example.com. A query for x.sub.example.com is rewritten by the resolver to x.example.net (the owner name sub.example.com itself is not redirected), so you don’t have to create a CNAME for 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: DNAME is only stored by providers whose DNS backend supports it (e.g. Gandi, NS1, OCI, PowerDNS, Scaleway, and RFC2136 backends such as BIND or Knot).
Providers whose backend has no DNAME type (e.g. AWS Route 53, Azure, Google Cloud DNS, Cloudflare) will reject the record on write.
Because DNAME masks the entire subtree beneath its owner name, do not manage other records below a name that carries a DNAME.

RBAC configuration

If you use RBAC, extend the external-dns ClusterRole with:

- apiGroups: ["externaldns.k8s.io"]
  resources: ["dnsendpoints"]
  verbs: ["get","watch","list"]
- apiGroups: ["externaldns.k8s.io"]
  resources: ["dnsendpoints/status"]
  verbs: ["*"]