# External DNS

Kubernetes' internal DNS / service-discovery means that every service is resolvable within the cluster. You can create a Wordpress pod with a database URL pointing to "mysql", and trust that it'll find the service named "mysql" in the same namespace. (*Or "mysql.weirdothernamespace" if you prefer*)

This super-handy DNS magic only works within the cluster though. When you wanted to connect to the hypothetical Wordpress service from **outside** of the cluster, you'd need to manually create a DNS entry pointing to the [LoadBalancer](/kubernetes/loadbalancer/) IP of that service. While using wildcard DNS might make this a **little** easier, it's still too manual and not at all "*gitopsy*" enough!

ExternalDNS is a controller for Kubernetes which watches the objects you create (*Services, Ingresses, etc*), and configures External DNS providers (*like CloudFlare, Route53, etc*) accordingly. With External DNS, you **can** just deploy an ingress referencing "*mywordywordpressblog.batman.com*", and have that DNS entry autocreated on your provider within minutes 💪

!!! summary "Ingredients"

    * [x] A [Kubernetes cluster](/kubernetes/cluster/) 
    * [x] [Flux deployment process](/kubernetes/deployment/flux/) bootstrapped
    * [x] API credentials for a [supported DNS provider](https://github.com/kubernetes-sigs/external-dns)

## Preparation

### Namespace

We need a namespace to deploy our HelmRelease and associated ConfigMaps into. Per the [flux design](/kubernetes/deployment/flux/), I create this in my flux repo at `bootstrap/namespaces/namespace-external-dns.yaml`:

??? example "Example Namespace (click to expand)"

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: external-dns
```

### HelmRepository

Next, we need to define a HelmRepository (*a repository of helm charts*), to which we'll refer when we create the HelmRelease. We only need to do this once per-repository. In this case, we're using the (*prolific*) [bitnami chart repository](https://github.com/bitnami/charts/tree/master/bitnami), so per the [flux design](/kubernetes/deployment/flux/), I create this in my flux repo at `bootstrap/helmrepositories/helmrepository-external-dns.yaml`:

??? example "Example HelmRepository (click to expand)"
    ```yaml
    apiVersion: source.toolkit.fluxcd.io/v1beta1
    kind: HelmRepository
    metadata:
      name: bitnami
      namespace: flux-system
    spec:
      interval: 15m
      url: https://charts.bitnami.com/bitnami
    ```

### Kustomization

Now that the "global" elements of this deployment (*just the HelmRepository in this case*z*) have been defined, we do some "flux-ception", and go one layer deeper, adding another Kustomization, telling flux to deploy any YAMLs found in the repo at `/external-dns`. I create this Kustomization in my flux repo at `bootstrap/kustomizations/kustomization-external-dns.yaml`:

??? example "Example Kustomization (click to expand)"
    ```yaml
    apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
    kind: Kustomization
    metadata:
      name: external-dns
      namespace: flux-system
    spec:
      interval: 15m
      path: ./external-dns
      prune: true # remove any elements later removed from the above path
      timeout: 2m # if not set, this defaults to interval duration, which is 1h
      sourceRef:
        kind: GitRepository
        name: flux-system
      validation: server
      healthChecks:
        - apiVersion: apps/v1
          kind: Deployment
          name: external-dns
          namespace: external-dns
    ```

### ConfigMap

Now we're into the external-dns-specific YAMLs. First, we create a ConfigMap, containing the entire contents of the helm chart's [values.yaml](https://github.com/bitnami-labs/external-dns/blob/main/helm/external-dns/values.yaml). Paste the values into a `values.yaml` key as illustrated below, indented 4 tabs (*since they're "encapsulated" within the ConfigMap YAML*). I create this in my flux repo at `external-dns/configmap-external-dns-helm-chart-value-overrides.yaml`:

??? example "Example ConfigMap (click to expand)"
    ```yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: external-dns-helm-chart-value-overrides
      namespace: external-dns
    data:
      values.yaml: |-
        ## @section Global parameters
        ## Global Docker image parameters
        ## Please, note that this will override the image parameters, including dependencies, configured to use the global value
        ## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass

        ## @param global.imageRegistry Global Docker image registry
        ## @param global.imagePullSecrets Global Docker registry secret names as an array
        ##
        global:
          imageRegistry: ""
          ## E.g.
          ## imagePullSecrets:
          ##   - myRegistryKeySecretName
          ##
          imagePullSecrets: []

        ## @section Common parameters

        ## @param nameOverride String to partially override external-dns.fullname template (will maintain the release name)
        ##
        nameOverride: ""
        ## @param fullnameOverride String to fully override external-dns.fullname template
        ##
        fullnameOverride: ""
        ## @param clusterDomain Kubernetes Cluster Domain
        ##
        clusterDomain: cluster.local

        ## @section external-dns parameters

        ## Bitnami external-dns image version
        ## ref: https://hub.docker.com/r/bitnami/external-dns/tags/
        ## @param image.registry ExternalDNS image registry
        ## @param image.repository ExternalDNS image repository
        ## @param image.tag ExternalDNS Image tag (immutable tags are recommended)
        ## @param image.pullPolicy ExternalDNS image pull policy
        ## @param image.pullSecrets ExternalDNS image pull secrets
        ##
        image:
          registry: docker.io
          repository: bitnami/external-dns
          tag: 0.10.1-debian-10-r5
          ## Specify a imagePullPolicy
          ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'
          ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images
          ##
          pullPolicy: IfNotPresent
          ## Optionally specify an array of imagePullSecrets.
          ## Secrets must be manually created in the namespace.
          ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
          ## e.g:
          ## pullSecrets:
          ##   - myRegistryKeySecretName
          ##
          pullSecrets: []

        ## @param hostAliases Deployment pod host aliases
        ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/
        ##
        hostAliases: []
        ## @param sources [array] K8s resources type to be observed for new DNS entries by ExternalDNS
        ##
        sources:
          # - crd
          - service
          - ingress
          # - contour-httpproxy
        ## @param provider DNS provider where the DNS records will be created.
        ## Available providers are:
        ## - alibabacloud, aws, azure, azure-private-dns, cloudflare, coredns, designate, digitalocean, google, hetzner, infoblox, linode, rfc2136, transip
        ##
        provider: aws
        ## Flags related to processing sources
        ## ref: https://github.com/kubernetes-sigs/external-dns/blob/master/pkg/apis/externaldns/types.go#L272
        ## @param namespace Limit sources of endpoints to a specific namespace (default: all namespaces)
        ##
        namespace: ""
        ## @param fqdnTemplates Templated strings that are used to generate DNS names from sources that don't define a hostname themselves
        ##
        fqdnTemplates: []
        ## @param combineFQDNAnnotation Combine FQDN template and annotations instead of overwriting
        ##
        combineFQDNAnnotation: false
        ## @param ignoreHostnameAnnotation Ignore hostname annotation when generating DNS names, valid only when fqdn-template is set
        ##
        ignoreHostnameAnnotation: false
        ## @param publishInternalServices Allow external-dns to publish DNS records for ClusterIP services
        ##
        publishInternalServices: false
        ## @param publishHostIP Allow external-dns to publish host-ip for headless services
        ##
        publishHostIP: false
        ## @param serviceTypeFilter The service types to take care about (default: all, options: ClusterIP, NodePort, LoadBalancer, ExternalName)
        ##
        serviceTypeFilter: []
        ## Alibaba cloud configuration to be set via arguments/env. variables
        ## These will be added to /etc/kubernetes/alibaba-cloud.json via secret
        ##
        alibabacloud:
          ## @param alibabacloud.accessKeyId When using the Alibaba Cloud provider, set `accessKeyId` in the Alibaba Cloud configuration file (optional)
          ##
          accessKeyId: ""
          ## @param alibabacloud.accessKeySecret When using the Alibaba Cloud provider, set `accessKeySecret` in the Alibaba Cloud configuration file (optional)
          ##
          accessKeySecret: ""
          ## @param alibabacloud.regionId When using the Alibaba Cloud provider, set `regionId` in the Alibaba Cloud configuration file (optional)
          ##
          regionId: ""
          ## @param alibabacloud.vpcId Alibaba Cloud VPC Id
          ##
          vpcId: ""
          ## @param alibabacloud.secretName Use an existing secret with key "alibaba-cloud.json" defined.
          ## This ignores alibabacloud.accessKeyId, and alibabacloud.accessKeySecret
          ##
          secretName: ""
          ## @param alibabacloud.zoneType Zone Filter. Available values are: public, private, or no value for both
          ##
          zoneType: ""
        ## AWS configuration to be set via arguments/env. variables
        ##
        aws:
          ## AWS credentials
          ## @param aws.credentials.secretKey When using the AWS provider, set `aws_secret_access_key` in the AWS credentials (optional)
          ## @param aws.credentials.accessKey When using the AWS provider, set `aws_access_key_id` in the AWS credentials (optional)
          ## @param aws.credentials.mountPath When using the AWS provider, determine `mountPath` for `credentials` secret
          ##
          credentials:
            secretKey: ""
            accessKey: ""
            ## Before external-dns 0.5.9 home dir should be `/root/.aws`
            ##
            mountPath: "/.aws"
            ## @param aws.credentials.secretName Use an existing secret with key "credentials" defined.
            ## This ignores aws.credentials.secretKey, and aws.credentials.accessKey
            ##
            secretName: ""
          ## @param aws.region When using the AWS provider, `AWS_DEFAULT_REGION` to set in the environment (optional)
          ##
          region: "us-east-1"
          ## @param aws.zoneType When using the AWS provider, filter for zones of this type (optional, options: public, private)
          ##
          zoneType: ""
          ## @param aws.assumeRoleArn When using the AWS provider, assume role by specifying --aws-assume-role to the external-dns daemon
          ##
          assumeRoleArn: ""
          ## @param aws.apiRetries Maximum number of retries for AWS API calls before giving up
          ##
          apiRetries: 3
          ## @param aws.batchChangeSize When using the AWS provider, set the maximum number of changes that will be applied in each batch
          ##
          batchChangeSize: 1000
          ## @param aws.zoneTags When using the AWS provider, filter for zones with these tags
          ##
          zoneTags: []
          ## @param aws.preferCNAME When using the AWS provider, replaces Alias records with CNAME (options: true, false)
          ##
          preferCNAME: ""
          ## @param aws.evaluateTargetHealth When using the AWS provider, sets the evaluate target health flag (options: true, false)
          ##
          evaluateTargetHealth: ""
        ## Azure configuration to be set via arguments/env. variables
        ##
        azure:
          ## When a secret to load azure.json is not specified, the host's /etc/kubernetes/azure.json will be used
          ## @param azure.secretName When using the Azure provider, set the secret containing the `azure.json` file
          ##
          secretName: ""
          ## @param azure.cloud When using the Azure provider, set the Azure Cloud
          ##
          cloud: ""
          ## @param azure.resourceGroup When using the Azure provider, set the Azure Resource Group
          ##
          resourceGroup: ""
          ## @param azure.tenantId When using the Azure provider, set the Azure Tenant ID
          ##
          tenantId: ""
          ## @param azure.subscriptionId When using the Azure provider, set the Azure Subscription ID
          ##
          subscriptionId: ""
          ## @param azure.aadClientId When using the Azure provider, set the Azure AAD Client ID
          ##
          aadClientId: ""
          ## @param azure.aadClientSecret When using the Azure provider, set the Azure AAD Client Secret
          ##
          aadClientSecret: ""
          ## @param azure.useManagedIdentityExtension When using the Azure provider, set if you use Azure MSI
          ##
          useManagedIdentityExtension: false
          ## @param azure.userAssignedIdentityID When using the Azure provider with Azure MSI, set Client ID of Azure user-assigned managed identity (optional, otherwise system-assigned managed identity is used)
          ##
          userAssignedIdentityID: ""
        ## Cloudflare configuration to be set via arguments/env. variables
        ##
        cloudflare:
          ## @param cloudflare.apiToken When using the Cloudflare provider, `CF_API_TOKEN` to set (optional)
          ##
          apiToken: ""
          ## @param cloudflare.apiKey When using the Cloudflare provider, `CF_API_KEY` to set (optional)
          ##
          apiKey: ""
          ## @param cloudflare.secretName When using the Cloudflare provider, it's the name of the secret containing cloudflare_api_token or cloudflare_api_key.
          ## This ignores cloudflare.apiToken, and cloudflare.apiKey
          ##
          secretName: ""
          ## @param cloudflare.email When using the Cloudflare provider, `CF_API_EMAIL` to set (optional). Needed when using CF_API_KEY
          ##
          email: ""
          ## @param cloudflare.proxied When using the Cloudflare provider, enable the proxy feature (DDOS protection, CDN...) (optional)
          ##
          proxied: true
        ## CoreDNS configuration to be set via arguments/env variables
        ##
        coredns:
          ## @param coredns.etcdEndpoints When using the CoreDNS provider, set etcd backend endpoints (comma-separated list)
          ## Secure (https) endpoints can be used as well, in that case `etcdTLS` section
          ## should be filled in accordingly
          ##
          etcdEndpoints: "http://etcd-extdns:2379"
          ## Configuration of the secure communication and client authentication to the etcd cluster
          ## If enabled all the values under this key must hold a valid data
          ##
          etcdTLS:
            ## @param coredns.etcdTLS.enabled When using the CoreDNS provider, enable secure communication with etcd
            ##
            enabled: false
            ## @param coredns.etcdTLS.autoGenerated Generate automatically self-signed TLS certificates
            ##
            autoGenerated: false
            ## @param coredns.etcdTLS.secretName When using the CoreDNS provider, specify a name of existing Secret with etcd certs and keys
            ## ref: https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/security.md
            ## ref (secret creation):
            ##  https://github.com/bitnami/charts/tree/master/bitnami/etcd#configure-certificates-for-client-communication
            ##
            secretName: "etcd-client-certs"
            ## @param coredns.etcdTLS.mountPath When using the CoreDNS provider, set destination dir to mount data from `coredns.etcdTLS.secretName` to
            ##
            mountPath: "/etc/coredns/tls/etcd"
            ## @param coredns.etcdTLS.caFilename When using the CoreDNS provider, specify CA PEM file name from the `coredns.etcdTLS.secretName`
            ##
            caFilename: "ca.crt"
            ## @param coredns.etcdTLS.certFilename When using the CoreDNS provider, specify cert PEM file name from the `coredns.etcdTLS.secretName`
            ## Will be used by external-dns to authenticate against etcd
            ##
            certFilename: "cert.pem"
            ## @param coredns.etcdTLS.keyFilename When using the CoreDNS provider, specify private key PEM file name from the `coredns.etcdTLS.secretName`
            ## Will be used by external-dns to authenticate against etcd
            ##
            keyFilename: "key.pem"
        ## OpenStack Designate provider configuration to be set via arguments/env. variables
        ##
        designate:
          ## Set Openstack environment variables (optional). Username and password will be saved in a kubernetes secret
          ## The alternative to this is to export the necessary Openstack environment variables in the extraEnv argument
          ## @param designate.username When using the Designate provider, specify the OpenStack authentication username. (optional)
          ## @param designate.password When using the Designate provider, specify the OpenStack authentication password. (optional)
          ## @param designate.authUrl When using the Designate provider, specify the OpenStack authentication Url. (optional)
          ## @param designate.regionName When using the Designate provider, specify the OpenStack region name. (optional)
          ## @param designate.userDomainName When using the Designate provider, specify the OpenStack user domain name. (optional)
          ## @param designate.projectName When using the Designate provider, specify the OpenStack project name. (optional)
          ## @param designate.username When using the Designate provider, specify the OpenStack authentication username. (optional)
          ## e.g:
          ##   username: "someuser"
          ##   password: "p@55w0rd"
          ##   authUrl: "https://mykeystone.example.net:5000/v3/"
          ##   regionName: "dev"
          ##   userDomainName: "development"
          ##   projectName: "myteamname"
          ##
          username: ""
          password: ""
          authUrl: ""
          regionName: ""
          userDomainName: ""
          projectName: ""
          ## @param designate.customCAHostPath When using the Designate provider, use a CA file already on the host to validate Openstack APIs.  This conflicts with `designate.customCA.enabled`
          ## This conflicts setting the above customCA to true and chart rendering will fail if you set customCA to true and specify customCAHostPath
          ##
          customCAHostPath: ""
          ## Use a custom CA (optional)
          ## @param designate.customCA.enabled When using the Designate provider, enable a custom CA (optional)
          ## @param designate.customCA.content When using the Designate provider, set the content of the custom CA
          ## @param designate.customCA.mountPath When using the Designate provider, set the mountPath in which to mount the custom CA configuration
          ## @param designate.customCA.filename When using the Designate provider, set the custom CA configuration filename
          ##
          customCA:
            enabled: false
            content: ""
            mountPath: "/config/designate"
            filename: "designate-ca.pem"
        ## DigitalOcean configuration to be set via arguments/env. variables
        ##
        digitalocean:
          ## @param digitalocean.apiToken When using the DigitalOcean provider, `DO_TOKEN` to set (optional)
          ##
          apiToken: ""
          ## @param digitalocean.secretName Use an existing secret with key "digitalocean_api_token" defined.
          ## This ignores digitalocean.apiToken
          ##
          secretName: ""
        ## Google configuration to be set via arguments/env. variables
        ##
        google:
          ## @param google.project When using the Google provider, specify the Google project (required when provider=google)
          ##
          project: ""
          ## @param google.serviceAccountSecret When using the Google provider, specify the existing secret which contains credentials.json (optional)
          ##
          serviceAccountSecret: ""
          ## @param google.serviceAccountSecretKey When using the Google provider with an existing secret, specify the key name (optional)
          ##
          serviceAccountSecretKey: "credentials.json"
          ## @param google.serviceAccountKey When using the Google provider, specify the service account key JSON file. In this case a new secret will be created holding this service account (optional)
          ##
          serviceAccountKey: ""
        ## Hetzner configuration to be set via arguments/env. variables
        ##
        hetzner:
          ## @param hetzner.token When using the Hetzner provider, specify your token here. (required when `hetzner.secretName` is not provided. In this case a new secret will be created holding the token.)
          ## Mutually exclusive with `hetzner.secretName`.
          ##
          token: ""
          ## @param hetzner.secretName When using the Hetzner provider, specify the existing secret which contains your token. Disables the usage of `hetzner.token` (optional)
          ##
          secretName: ""
          ## @param hetzner.secretKey When using the Hetzner provider with an existing secret, specify the key name (optional)
          ##
          secretKey: "hetzner_token"
        ## Infoblox configuration to be set via arguments/env. variables
        ##
        infoblox:
          ## @param infoblox.wapiUsername When using the Infoblox provider, specify the Infoblox WAPI username
          ##
          wapiUsername: "admin"
          ## @param infoblox.wapiPassword When using the Infoblox provider, specify the Infoblox WAPI password (required when provider=infoblox)
          ##
          wapiPassword: ""
          ## @param infoblox.gridHost When using the Infoblox provider, specify the Infoblox Grid host (required when provider=infoblox)
          ##
          gridHost: ""
          ## @param infoblox.view Infoblox view
          ##
          view: ""
          ## Optional keys
          ##
          ## Existing secret name, when in place wapiUsername and wapiPassword are not required
          ## secretName: ""
          ##
          ## @param infoblox.domainFilter When using the Infoblox provider, specify the domain (optional)
          ##
          domainFilter: ""
          ## @param infoblox.noSslVerify When using the Infoblox provider, disable SSL verification (optional)
          ##
          noSslVerify: false
          ## @param infoblox.wapiPort When using the Infoblox provider, specify the Infoblox WAPI port (optional)
          ##
          wapiPort: ""
          ## @param infoblox.wapiVersion When using the Infoblox provider, specify the Infoblox WAPI version (optional)
          ##
          wapiVersion: ""
          ## @param infoblox.wapiConnectionPoolSize When using the Infoblox provider, specify the Infoblox WAPI request connection pool size (optional)
          ##
          wapiConnectionPoolSize: ""
          ## @param infoblox.wapiHttpTimeout When using the Infoblox provider, specify the Infoblox WAPI request timeout in seconds (optional)
          ##
          wapiHttpTimeout: ""
          ## @param infoblox.maxResults When using the Infoblox provider, specify the Infoblox Max Results (optional)
          ##
          maxResults: ""
        ## Linode configuration to be set via arguments/env. variables
        ##
        linode:
          ## @param linode.apiToken When using the Linode provider, `LINODE_TOKEN` to set (optional)
          ##
          apiToken: ""
          ## @param linode.secretName Use an existing secret with key "linode_api_token" defined.
          ## This ignores linode.apiToken
          ##
          secretName: ""
        ## NS1 configuration to be set via arguments/env. variables
        ## @param ns1.minTTL When using the ns1 provider, specify minimal TTL, as an integer, for records
        ##
        ns1:
          minTTL: 10
        ## OVH configuration to be set via arguments/env. variables
        ##
        ovh:
          ## @param ovh.consumerKey When using the OVH provider, specify the existing consumer key. (required when provider=ovh and `ovh.secretName` is not provided.)
          ##
          consumerKey: ""
          ## @param ovh.applicationKey When using the OVH provider with an existing application, specify the application key. (required when provider=ovh and `ovh.secretName` is not provided.)
          ##
          applicationKey: ""
          ## @param ovh.applicationSecret When using the OVH provider with an existing application, specify the application secret. (required when provider=ovh and `ovh.secretName` is not provided.)
          ##
          applicationSecret: ""
          ## @param ovh.secretName When using the OVH provider, it's the name of the secret containing `ovh_consumer_key`, `ovh_application_key` and `ovh_application_secret`. Disables usage of other `ovh`.
          ## with following keys:
          ##  - ovh_consumer_key
          ##  - ovh_application_key
          ##  - ovh_application_secret
          ## This ignores consumerKey, applicationKey & applicationSecret
          ##
          secretName: ""
        ## Scaleway configuration to be set via arguments/env. variables
        ##
        scaleway:
          ## @param scaleway.scwAccessKey When using the Scaleway provider, specify an existing access key. (required when provider=scaleway)
          ##
          scwAccessKey: ""
          ## @param scaleway.scwSecretKey When using the Scaleway provider, specify an existing secret key. (required when provider=scaleway)
          ##
          scwSecretKey: ""
          ## @param scaleway.scwDefaultOrganizationId When using the Scaleway provider, specify the existing organization id. (required when provider=scaleway)
          ##
          scwDefaultOrganizationId: ""
        ## RFC 2136 configuration to be set via arguments/env. variables
        ##
        rfc2136:
          ## @param rfc2136.host When using the rfc2136 provider, specify the RFC2136 host (required when provider=rfc2136)
          ##
          host: ""
          ## @param rfc2136.port When using the rfc2136 provider, specify the RFC2136 port (optional)
          ##
          port: 53
          ## @param rfc2136.zone When using the rfc2136 provider, specify the zone (required when provider=rfc2136)
          ##
          zone: ""
          ## @param rfc2136.tsigSecret When using the rfc2136 provider, specify the tsig secret to enable security. (do not specify if `rfc2136.secretName` is provided.) (optional)
          ##
          tsigSecret: ""
          ## @param rfc2136.secretName When using the rfc2136 provider, specify the existing secret which contains your tsig secret. Disables the usage of `rfc2136.tsigSecret` (optional)
          ##
          secretName: ""
          ## @param rfc2136.tsigSecretAlg When using the rfc2136 provider, specify the tsig secret to enable security (optional)
          ##
          tsigSecretAlg: hmac-sha256
          ## @param rfc2136.tsigKeyname When using the rfc2136 provider, specify the tsig keyname to enable security (optional)
          ##
          tsigKeyname: externaldns-key
          ## @param rfc2136.tsigAxfr When using the rfc2136 provider, enable AFXR to enable security (optional)
          ##
          tsigAxfr: true
          ## @param rfc2136.minTTL When using the rfc2136 provider, specify minimal TTL (in duration format) for records[ns, us, ms, s, m, h], see more https://golang.org/pkg/time/#ParseDuration
          ##
          minTTL: "0s"
          ## @param rfc2136.rfc3645Enabled When using the rfc2136 provider, extend using RFC3645 to support secure updates over Kerberos with GSS-TSIG
          ##
          rfc3645Enabled: false
          ## @param rfc2136.kerberosConfig When using the rfc2136 provider with rfc3645Enabled, the contents of a configuration file for krb5 (optional)
          ##
          kerberosConfig: ""
          ## @param rfc2136.kerberosUsername When using the rfc2136 provider with rfc3645Enabled, specify the username to authenticate with (optional)
          ##
          kerberosUsername: ""
          ## @param rfc2136.kerberosPassword When using the rfc2136 provider with rfc3645Enabled, specify the password to authenticate with (optional)
          ##
          kerberosPassword: ""
          ## @param rfc2136.kerberosRealm When using the rfc2136 provider with rfc3645Enabled, specify the realm to authenticate to (required when provider=rfc2136 and rfc2136.rfc3645Enabled=true)
          ##
          kerberosRealm: ""

        ## PowerDNS configuration to be set via arguments/env. variables
        ##
        pdns:
          ## @param pdns.apiUrl When using the PowerDNS provider, specify the API URL of the server.
          ##
          apiUrl: ""
          ## @param pdns.apiPort When using the PowerDNS provider, specify the API port of the server.
          ##
          apiPort: "8081"
          ## @param pdns.apiKey When using the PowerDNS provider, specify the API key of the server.
          ##
          apiKey: ""
          ## @param pdns.secretName When using the PowerDNS provider, specify as secret name containing the API Key
          ##
          secretName: ""
        ## TransIP configuration to be set via arguments/env. variables
        ##
        transip:
          ## @param transip.account When using the TransIP provider, specify the account name.
          ##
          account: ""
          ## @param transip.apiKey When using the TransIP provider, specify the API key to use.
          ##
          apiKey: ""
        ## VinylDNS configuration to be set via arguments/env. variables
        ##
        vinyldns:
          ## @param vinyldns.host When using the VinylDNS provider, specify the VinylDNS API host.
          ##
          host: ""
          ## @param vinyldns.accessKey When using the VinylDNS provider, specify the Access Key to use.
          ##
          accessKey: ""
          ## @param vinyldns.secretKey When using the VinylDNS provider, specify the Secret key to use.
          ##
          secretKey: ""
        ## @param domainFilters Limit possible target zones by domain suffixes (optional)
        ##
        domainFilters: []
        ## @param excludeDomains Exclude subdomains (optional)
        ##
        excludeDomains: []
        ## @param regexDomainFilter Limit possible target zones by regex domain suffixes (optional)
        ## If regexDomainFilter is specified, domainFilters will be ignored
        ##
        regexDomainFilter: ""
        ## @param regexDomainExclusion Exclude subdomains by using regex pattern (optional)
        ## If regexDomainFilter is specified, excludeDomains will be ignored and external-dns will use regexDomainExclusion even though regexDomainExclusion is empty
        ##
        regexDomainExclusion: ""
        ## @param zoneNameFilters Filter target zones by zone domain (optional)
        ##
        zoneNameFilters: []
        ## @param zoneIdFilters Limit possible target zones by zone id (optional)
        ##
        zoneIdFilters: []
        ## @param annotationFilter Filter sources managed by external-dns via annotation using label selector (optional)
        ##
        annotationFilter: ""
        ## @param dryRun When enabled, prints DNS record changes rather than actually performing them (optional)
        ##
        dryRun: false
        ## @param triggerLoopOnEvent When enabled, triggers run loop on create/update/delete events in addition to regular interval (optional)
        ##
        triggerLoopOnEvent: false
        ## @param interval Interval update period to use
        ##
        interval: "1m"
        ## @param logLevel Verbosity of the logs (options: panic, debug, info, warning, error, fatal, trace)
        ##
        logLevel: info
        ## @param logFormat Which format to output logs in (options: text, json)
        ##
        logFormat: text
        ## @param policy Modify how DNS records are synchronized between sources and providers (options: sync, upsert-only )
        ##
        policy: upsert-only
        ## @param registry Registry method to use (options: txt, aws-sd, noop)
        ## ref: https://github.com/kubernetes-sigs/external-dns/blob/master/docs/proposal/registry.md
        ##
        registry: "txt"
        ## @param txtPrefix When using the TXT registry, a prefix for ownership records that avoids collision with CNAME entries (optional)<CNAME record> (Mutual exclusive with txt-suffix)
        ##
        txtPrefix: ""
        ## @param txtSuffix When using the TXT registry, a suffix for ownership records that avoids collision with CNAME entries (optional)<CNAME record>.suffix (Mutual exclusive with txt-prefix)
        ##
        txtSuffix: ""
        ## @param txtOwnerId A name that identifies this instance of ExternalDNS. Currently used by registry types: txt & aws-sd (optional)
        ## But other registry types might be added in the future.
        ##
        txtOwnerId: ""
        ## @param forceTxtOwnerId (backward compatibility) When using the non-TXT registry, it will pass the value defined by `txtOwnerId` down to the application (optional)
        ## This setting added for backward compatibility for
        ## customers who already used bitnami/external-dns helm chart
        ## to privision 'aws-sd' registry type.
        ## Previously bitnami/external-dns helm chart did not pass
        ## txtOwnerId value down to the external-dns application
        ## so the app itself sets that value to be a string 'default'.
        ## If existing customers force the actual txtOwnerId value to be
        ## passed properly, their external-dns updates will stop working
        ## because the owner's value for exting DNS records in
        ## AWS Service Discovery would remain 'default'.
        ## NOTE: It is up to the end user to update AWS Service Discovery
        ## 'default' values in description fields to make it work with new
        ## value passed as txtOwnerId when forceTxtOwnerId=true
        forceTxtOwnerId: false
        ## @param extraArgs Extra arguments to be passed to external-dns
        ##
        extraArgs: {}
        ## @param extraEnv Extra environment variables to be passed to external-dns
        ##
        ## extraEnv:
        ## - name: VARNAME1
        ##   value: value1
        ## - name: VARNAME2
        ##   valueFrom:
        ##     secretKeyRef:
        ##       name: existing-secret
        ##       key: varname2-key
        ##
        extraEnv: []
        ## @param replicas Desired number of ExternalDNS replicas
        ##
        replicas: 1
        ## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
        ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
        ##
        podAffinityPreset: ""
        ## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
        ## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
        ## Allowed values: soft, hard
        ##
        podAntiAffinityPreset: soft
        ## Node affinity preset
        ## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
        ##
        nodeAffinityPreset:
          ## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard`
          ##
          type: ""
          ## @param nodeAffinityPreset.key Node label key to match Ignored if `affinity` is set.
          ## E.g.
          ## key: "kubernetes.io/e2e-az-name"
          ##
          key: ""
          ## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set.
          ## E.g.
          ## values:
          ##   - e2e-az1
          ##   - e2e-az2
          ##
          values: []
        ## @param affinity Affinity for pod assignment
        ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
        ## Note: podAffinityPreset, podAntiAffinityPreset, and  nodeAffinityPreset will be ignored when it's set
        ##
        affinity: {}
        ## @param nodeSelector Node labels for pod assignment
        ## Ref: https://kubernetes.io/docs/user-guide/node-selection/
        ##
        nodeSelector: {}
        ## @param tolerations Tolerations for pod assignment
        ## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
        ##
        tolerations: []
        ## @param podAnnotations Additional annotations to apply to the pod.
        ##
        podAnnotations: {}
        ## @param podLabels Additional labels to be added to pods
        ##
        podLabels: {}
        ## @param priorityClassName priorityClassName
        ##
        priorityClassName: ""
        ## @param secretAnnotations Additional annotations to apply to the secret
        ##
        secretAnnotations: {}
        ## Options for the source type "crd"
        ##
        crd:
          ## @param crd.create Install and use the integrated DNSEndpoint CRD
          ##
          create: false
          ## @param crd.apiversion Sets the API version for the CRD to watch
          ##
          apiversion: ""
          ## @param crd.kind Sets the kind for the CRD to watch
          ##
          kind: ""
        ## Kubernetes svc configutarion
        ##
        service:
          ## @param service.enabled Whether to create Service resource or not
          ##
          enabled: true
          ## @param service.type Kubernetes Service type
          ##
          type: ClusterIP
          ## @param service.port ExternalDNS client port
          ##
          port: 7979
          ## @param service.nodePort Port to bind to for NodePort service type (client port)
          ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
          ##
          nodePort: ""
          ## @param service.clusterIP IP address to assign to service
          ##
          clusterIP: ""
          ## @param service.externalIPs Service external IP addresses
          ##
          externalIPs: []
          ## @param service.loadBalancerIP IP address to assign to load balancer (if supported)
          ##
          loadBalancerIP: ""
          ## @param service.loadBalancerSourceRanges List of IP CIDRs allowed access to load balancer (if supported)
          ##
          loadBalancerSourceRanges: []
          ## @param service.annotations Annotations to add to service
          ## set the LoadBalancer service type to internal only.
          ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer
          ##
          annotations: {}
          ## @param service.labels Provide any additional labels which may be required.
          ## This can be used to have external-dns show up in `kubectl cluster-info`
          ##  kubernetes.io/cluster-service: "true"
          ##  kubernetes.io/name: "external-dns"
          ##
          labels: {}
        ## ServiceAccount parameters
        ## https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
        ##
        serviceAccount:
          ## @param serviceAccount.create Determine whether a Service Account should be created or it should reuse a exiting one.
          ##
          create: true
          ## @param serviceAccount.name ServiceAccount to use. A name is generated using the external-dns.fullname template if it is not set
          ##
          name: ""
          ## @param serviceAccount.annotations Additional Service Account annotations
          ##
          annotations: {}
          ## @param serviceAccount.automountServiceAccountToken Automount API credentials for a service account.
          ##
          automountServiceAccountToken: true
        ## RBAC parameters
        ## https://kubernetes.io/docs/reference/access-authn-authz/rbac/
        ##
        rbac:
          ## @param rbac.create Whether to create & use RBAC resources or not
          ##
          create: true
          ## @param rbac.clusterRole Whether to create Cluster Role. When set to false creates a Role in `namespace`
          ##
          clusterRole: true
          ## @param rbac.apiVersion Version of the RBAC API
          ##
          apiVersion: v1
          ## @param rbac.pspEnabled Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later
          ##
          pspEnabled: false
        ## @param securityContext Security context for the container
        ## https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
        ## Example:
        ## securityContext:
        ##   allowPrivilegeEscalation: false
        ##   readOnlyRootFilesystem: true
        ##   capabilities:
        ##     drop: ["ALL"]
        ##
        securityContext: {}
        ## @param podSecurityContext.fsGroup Group ID for the container
        ## @param podSecurityContext.runAsUser User ID for the container
        ##
        podSecurityContext:
          fsGroup: 1001
          runAsUser: 1001
        ## Container resource requests and limits
        ## ref: http://kubernetes.io/docs/user-guide/compute-resources/
        ## We usually recommend not to specify default resources and to leave this as a conscious
        ## choice for the user. This also increases chances charts run on environments with little
        ## resources, such as Minikube. If you do want to specify resources, uncomment the following
        ## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
        ## @param resources.limits The resources limits for the container
        ## @param resources.requests The requested resources for the container
        ##
        resources:
          ## Example:
          ## limits:
          ##    cpu: 50m
          ##    memory: 50Mi
          limits: {}
          ## Examples:
          ## requests:
          ##    cpu: 10m
          ##    memory: 50Mi
          requests: {}
        ## Configure extra options for liveness probe
        ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes
        ## @param livenessProbe.enabled Enable livenessProbe
        ## @param livenessProbe.httpGet.path Request path for livenessProbe
        ## @param livenessProbe.httpGet.port Port for livenessProbe
        ## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe
        ## @param livenessProbe.periodSeconds Period seconds for livenessProbe
        ## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe
        ## @param livenessProbe.failureThreshold Failure threshold for livenessProbe
        ## @param livenessProbe.successThreshold Success threshold for livenessProbe
        ##
        livenessProbe:
          enabled: true
          httpGet:
            path: /healthz
            port: http
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 2
          successThreshold: 1
        ## Configure extra options for readiness probe
        ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes
        ## @param readinessProbe.enabled Enable readinessProbe
        ## @param readinessProbe.httpGet.path Request path for readinessProbe
        ## @param readinessProbe.httpGet.port Port for readinessProbe
        ## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe
        ## @param readinessProbe.periodSeconds Period seconds for readinessProbe
        ## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe
        ## @param readinessProbe.failureThreshold Failure threshold for readinessProbe
        ## @param readinessProbe.successThreshold Success threshold for readinessProbe
        ##
        readinessProbe:
          enabled: true
          httpGet:
            path: /healthz
            port: http
          initialDelaySeconds: 5
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 6
          successThreshold: 1
        ## @param extraVolumes A list of volumes to be added to the pod
        ##
        extraVolumes: []
        ## @param extraVolumeMounts A list of volume mounts to be added to the pod
        ##
        extraVolumeMounts: []
        ## @param podDisruptionBudget Configure PodDisruptionBudget
        ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/
        podDisruptionBudget: {}
        ## Prometheus Exporter / Metrics
        ##
        metrics:
          ## @param metrics.enabled Enable prometheus to access external-dns metrics endpoint
          ##
          enabled: false
          ## @param metrics.podAnnotations Annotations for enabling prometheus to access the metrics endpoint
          ##
          podAnnotations: {}
          ## Prometheus Operator ServiceMonitor configuration
          ##
          serviceMonitor:
            ## @param metrics.serviceMonitor.enabled Create ServiceMonitor object
            ##
            enabled: false
            ## @param metrics.serviceMonitor.namespace Namespace in which Prometheus is running
            ##
            namespace: ""
            ## @param metrics.serviceMonitor.interval Interval at which metrics should be scraped
            ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint
            ##
            interval: ""
            ## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended
            ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint
            ##
            scrapeTimeout: ""
            ## @param metrics.serviceMonitor.selector Additional labels for ServiceMonitor object
            ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration
            ## e.g:
            ## selector:
            ##   prometheus: my-prometheus
            ##
            selector: {}

    ```
--8<-- "kubernetes-why-full-values-in-configmap.md"

Then work your way through the values you pasted, and change any which are specific to your configuration.

I recommend changing:

```yaml
        sources:
          # - crd
          - service
          - ingress
          # - contour-httpproxy
```

To:

```yaml
        sources:
          - crd
          # - service
          # - ingress
          # - contour-httpproxy
```

!!! question "Why only use CRDs as a source?"
    > I thought the whole point of this magic was to create DNS entries from services or ingresses!

    You can do that, yes. However, I prefer to be prescriptive, and explicitly decide when a DNS entry will be created. By [using CRDs](#using-crds) (*External DNS creates a new type of resource called a "DNSEndpoint"*), I add my DNS entries as YAML files into each kustomization, and I can still employ wildcard DNS where appropriate.

### Secret

As you work your way through `values.yaml`, you'll notice that it contains specific placholders for credentials for various DNS providers.
Take for example, this config for cloudflare:

???+ example "Example snippet of CloudFlare config from ConfigMap"
    ```yaml
            cloudflare:
              ## @param cloudflare.apiToken When using the Cloudflare provider, `CF_API_TOKEN` to set (optional)
              ##
              apiToken: ""
              ## @param cloudflare.apiKey When using the Cloudflare provider, `CF_API_KEY` to set (optional)
              ##
              apiKey: ""
              ## @param cloudflare.secretName When using the Cloudflare provider, it's the name of the secret containing cloudflare_api_token or cloudflare_api_key.
              ## This ignores cloudflare.apiToken, and cloudflare.apiKey
              ##
              secretName: ""
              ## @param cloudflare.email When using the Cloudflare provider, `CF_API_EMAIL` to set (optional). Needed when using CF_API_KEY
              ##
              email: ""
              ## @param cloudflare.proxied When using the Cloudflare provider, enable the proxy feature (DDOS protection, CDN...) (optional)
              ##
              proxied: true
    ```

In the case of CloudFlare (*and this may differ per-provider*), you can either enter your credentials in cleartext (*baaad idea, since we intend to commit these files into a repo*), or you can reference a secret, which External DNS will expect to find in its namespace.

Thanks to [Sealed Secrets](/kubernetes/sealed-secrets/), we have a safe way of committing secrets into our repository, so to create this cloudflare secret, you'd run something like this:

```bash
  kubectl create secret generic cloudflare-api-token \
  --namespace external-dns \
  --dry-run=client \
  --from-literal=cloudflare_api_token=gobbledegook -o json \
  | kubeseal --cert <path to public cert> \
  | kubectl create -f - \
  > <path to repo>/external-dns/sealedsecret-cloudflare-api-token.yaml
```

And your sealed secret would end up in `external-dns/sealedsecret-cloudflare-api-token.yaml`.

### HelmRelease

Lastly, having set the scene above, we define the HelmRelease which will actually deploy the external-dns controller into the cluster, with the config we defined above. I save this in my flux repo as `external-dns/helmrelease-external-dns.yaml`:

??? example "Example HelmRelease (click to expand)"
    ```yaml
    apiVersion: helm.toolkit.fluxcd.io/v2beta1
    kind: HelmRelease
    metadata:
    name: external-dns
    namespace: external-dns
    spec:
    chart:
        spec:
        chart: external-dns
        version: 4.x
        sourceRef:
            kind: HelmRepository
            name: bitnami
            namespace: flux-system
    interval: 15m
    timeout: 5m
    releaseName: external-dns
    valuesFrom:
    - kind: ConfigMap
        name: external-dns-helm-chart-value-overrides
        valuesKey: values.yaml # This is the default, but best to be explicit for clarity
    ```

--8<-- "kubernetes-why-not-config-in-helmrelease.md"

## Serving

Once you've committed your YAML files into your repo, you should soon see some pods appear in the `external-dns` namespace!

### Using CRDs

If you're the sort of person who doesn't like to just leak[^1] every service/ingress name into public DNS, you may prefer to manage your DNS entries using CRDs.

You can instruct ExternalDNS to create any DNS entry you please, using a **DNSEndpoint** resource, and place these in the appropriate folder in your flux repo to be deployed with your HelmRelease:

```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
  name: batcave.example.com
  namespace: batcave
spec:
  endpoints:
  - dnsName: batcave.example.com
    recordTTL: 180
    recordType: A
    targets:
    - 192.168.99.216
```

You can even create wildcard DNS entries, for example by setting `dnsName: *.batcave.example.com`.

Finally, (*and this is how I prefer to manage mine*), you can create a few A records for "permanent" endpoints stuff like Ingresses, and then point arbitrary DNS names to these records, like this:

```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
  name: "robinsroost.example.com"
  namespace: batcave
spec:
  endpoints:
  - dnsName: "robinsroost.example.com"
    recordTTL: 180
    recordType: CNAME
    targets:
    - "batcave.example.com"
```

### Troubleshooting

If DNS entries **aren't** created as you expect, then the best approach is to check the external-dns logs, by running `kubectl logs -n external-dns -l app.kubernetes.io/name=external-dns`.

--8<-- "recipe-footer.md"

[^1]: Why yes, I **have** accidentally caused outages / conflicts by "leaking" DNS entries automatically!
