Skip to content

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 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 💪

Ingredients

Preparation

Namespace

We need a namespace to deploy our HelmRelease and associated ConfigMaps into. Per the flux design, I create this in my flux repo at flux-system/namespaces/namespace-external-dns.yaml:

Example Namespace (click to expand)
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, so per the flux design, I create this in my flux repo at flux-system/helmrepositories/helmrepository-external-dns.yaml:

Example HelmRepository (click to expand)
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 flux-system/kustomizations/kustomization-external-dns.yaml:

Example Kustomization (click to expand)
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. 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 ConfigMap (click to expand)
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: {}

That's a lot of unnecessary text!

Why not just paste in the subset of values I want to change?

You know what's harder than working out which values from a 2000-line values.yaml to change?

Answer: Working out what values to change when the upstream helm chart has refactored or added options! By pasting in the entirety of the upstream chart, when it comes time to perform upgrades, you can just duplicate your ConfigMap YAML, paste the new values into one of the copies, and compare them side by side to ensure your original values/decisions persist in the new chart.

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

I recommend changing:

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

To:

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

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 (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 snippet of CloudFlare config from ConfigMap
        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, we have a safe way of committing secrets into our repository, so to create this cloudflare secret, you'd run something like this:

  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 HelmRelease (click to expand)
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

Why not just put config in the HelmRelease?

While it's true that we could embed values directly into the HelmRelease YAML, this becomes unweildy with large helm charts. It's also simpler (less likely to result in error) if changes to HelmReleases, which affect deployment of the chart, are defined in separate files to changes in helm chart values, which affect operation of the chart.

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 leak1 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:

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:

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.

Chef's notes 📓


  1. Why yes, I have accidentally caused outages / conflicts by "leaking" DNS entries automatically! 

Tip your waiter (sponsor) 👏

Did you receive excellent service? Want to compliment the chef? (..and support development of current and future recipes!) Sponsor me on Github / Patreon, or see the contribute page for more (free or paid) ways to say thank you! 👏

Employ your chef (engage) 🤝

Is this too much of a geeky PITA? Do you just want results, stat? I do this for a living - I provide consulting and engineering expertise to businesses needing short-term, short-notice support in the cloud-native space, including AWS/Azure/GKE, Kubernetes, CI/CD and automation.

Learn more about working with me here.

Flirt with waiter (subscribe) 💌

Want to know now when this recipe gets updated, or when future recipes are added? Subscribe to the RSS feed, or leave your email address below, and we'll keep you updated.

Notify me 🔔

Be the first to know when recipes are added / improved!

    We won't send you spam. Unsubscribe at any time. No monkey-business.

    Powered By ConvertKit

    Your comments? 💬


    Last update: November 22, 2021
    Back to top