Skip to main content
Amazon EKS 0.67.5Last updated in version 0.67.1

K8S External DNS Module

View SourceRelease Notes

This Terraform Module installs and configures the external-dns application on an EKS cluster, so that you can configure Route 53 Hosted Zones to point DNS records to Ingress endpoints.

This module uses the community helm chart, with a set of best practices input.

How does this work?

This module solves the problem of linking a known domain name to an Ingress endpoint managed with Kubernetes. It is common to setup name based virtual hosting with Ingress resources, allowing multiple domain names to route to different services on a single Ingress resource. For this to work, you need to map the domain name to the Ingress endpoint, so that requests to that domain name end up at the Ingress endpoint. Normally, you will need to manually create the DNS records yourself after the Ingress resource has been created and provisioned. However, this can be cumbersome due to the asynchronous nature of Kubernetes operations.

For example, if you are using an Ingress controller that maps to actual physical loadbalancers in the cloud (e.g the ALB Ingress Controller deployed using the eks-alb-ingress-controller module), the endpoint may take several minutes before it is available. You will have to wait for that time, continuously polling the Ingress resource until the underlying resource is provisioned and the endpoint is available before you can configure the DNS setting.

The external-dns application automates this process by watching the Ingress resource for the endpoint to be provisioned, and when it is, updating the hosted zone of the mapped hostnames with record sets that map to the Ingress endpoint.

This is automatically handled when:

  • You set a hostname for any rule on the Ingress resource.
  • There exists a corresponding Hosted Zone that maps to the hostname set on the Ingress resource.

Note that the second condition implies that the Hosted Zones must already exist. This will not automatically register public domains, or create private hosted zones for you.


Helm setup

This module uses helm v3 to deploy the controller to the Kubernetes cluster.

IAM permissions

The container deployed in this module requires IAM permissions to manage Route 53 Hosted Zones. See the eks-k8s-external-dns-iam-policy module for more information.

How do I restrict which Hosted Zones the app should manage?

If you have certain hosted zones that are considered protected and require more control over the DNS records, you can restrict the application to only manage the Hosted Zones that you explicitly want it to. To specify the zones that the app should manage, use the route53_hosted_zone_id_filters and route53_hosted_zone_domain_filters input variables. The former specifies zones by ID, while the latter specifies zones by name.

For example, if you want the app to only manage hosted zones that end with the name k8s.local, you can set route53_hosted_zone_domain_filters = ["k8s.local"] in your input variables. This means that the app will only create records for any hostnames on Ingress resources that end with the domain k8s.local, and ignore all others, even if there exists corresponding Route 53 Hosted Zones.

How do I deploy the Pods to Fargate?

To deploy the Pods to Fargate, you can use the create_fargate_profile variable to true and specify the subnet IDs for Fargate using vpc_worker_subnet_ids. Note that if you are using Fargate, you must rely on the IAM Roles for Service Accounts (IRSA) feature to grant the necessary AWS IAM permissions to the Pod. This is configured using the use_iam_role_for_service_accounts, eks_openid_connect_provider_arn, and eks_openid_connect_provider_url input variables.

How do I address throttling with the Route 53 API?

Route53 has a 5 API requests per second per account hard quota. If you have many EKS instances in an account with external-dns, you will easily hit these limits. If you find yourself hitting these limits frequently, consider tweaking the parameters of external-dns to reduce the number of API calls it makes.

The external-dns team recommends the following settings if you have more than one external-dns instance in your account:

trigger_loop_on_event = true
poll_interval = "5m"
batch_change_size = 4000
batch_change_interval = "10s"
zones_cache_duration = "3h"

Sample Usage

# ------------------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------------

module "eks_k_8_s_external_dns" {

source = ""

# ----------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------

# The AWS region to deploy ALB resources into.
aws_region = <string>

# Configuration for using the IAM role with Service Accounts feature to
# provide permissions to the helm charts. This expects a map with two
# properties: `openid_connect_provider_arn` and `openid_connect_provider_url`.
# The `openid_connect_provider_arn` is the ARN of the OpenID Connect Provider
# for EKS to retrieve IAM credentials, while `openid_connect_provider_url` is
# the URL. Set to null if you do not wish to use IAM role with Service
# Accounts, or if you wish to provide an IAM role directly via
# service_account_annotations.
iam_role_for_service_accounts_config = <object(
openid_connect_provider_arn = string
openid_connect_provider_url = string

# ----------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------

# The AWS partition used for default AWS Resources.
aws_partition = "aws"

# Duration string (e.g. 1m) indicating the interval between making changes to
# Route 53. When null, use the default defined in the chart (1s).
batch_change_interval = null

# The maximum number of changes that should be applied in a batch. When null,
# use the default defined in the chart (1000).
batch_change_size = null

# When set to true, create a dedicated Fargate execution profile for the
# external-dns service. Note that this is not necessary to deploy to Fargate.
# For example, if you already have an execution profile for the kube-system
# Namespace, you do not need another one.
create_fargate_profile = false

# Create a dependency between the resources in this module to the interpolated
# values in this list (and thus the source resources). In other words, the
# resources in this module will now depend on the resources backing the values
# in this list such that those resources need to be created before the
# resources in this module, and the resources in this module need to be
# destroyed before the resources in the list.
dependencies = []

# Used to name IAM roles for the service account. Recommended when
# var.use_iam_role_for_service_accounts is true. Also used for creating the
# Fargate profile when var.create_fargate_profile is true.
eks_cluster_name = null

# Limit sources of endpoints to a specific namespace (default: all
# namespaces).
endpoints_namespace = null

# Name of the Helm chart for external-dns. This should usually be
# 'external-dns' but may differ in the case of overriding the repository URL.
external_dns_chart_name = "external-dns"

# Helm chart repository URL to obtain the external-dns chart from. Useful when
# using Bitnami charts that are older than 6 months due to Bitnami's lifecycle
# policy which removes older chart from the main index.
external_dns_chart_repository_url = ""

# The version of the helm chart to use. Note that this is different from the
# app/container version.
external_dns_chart_version = "6.12.2"

# The container image registry to pull the images from.
image_registry = null

# Which format to output external-dns logs in (options: text, json)
log_format = "text"

# Which Kubernetes Namespace to deploy the chart into.
namespace = "kube-system"

# Annotations to apply to the Pod that is deployed, as key value pairs.
pod_annotations = {}

# ARN of IAM Role to use as the Pod execution role for Fargate. Set to null
# (default) to create a new one. Only used when var.create_fargate_profile is
# true.
pod_execution_iam_role_arn = null

# Labels to apply to the Pod that is deployed, as key value pairs.
pod_labels = {}

# Configure affinity rules for the Pod to control which nodes to schedule on.
# Each item in the list should be a map with the keys `key`, `values`, and
# `operator`, corresponding to the 3 properties of matchExpressions. Note that
# all expressions must be satisfied to schedule on the node.
pod_node_affinity = []

# Configure tolerations rules to allow the Pod to schedule on nodes that have
# been tainted. Each item in the list specifies a toleration rule.
pod_tolerations = []

# Duration string (e.g. 1m) indicating the polling interval for syncing the
# domains. When null, use the default defined in the chart (1m).
poll_interval = null

# Name of helm release for external-dns. Useful when running 2 deployments for
# custom configrations such as cross account access.
release_name = "external-dns"

# Only create records in hosted zones that match the provided domain names.
# Empty list (default) means match all zones. Zones must satisfy all three
# constraints (var.route53_hosted_zone_tag_filters,
# var.route53_hosted_zone_id_filters, and
# var.route53_hosted_zone_domain_filters).
route53_hosted_zone_domain_filters = []

# Only create records in hosted zones that match the provided IDs. Empty list
# (default) means match all zones. Zones must satisfy all three constraints
# (var.route53_hosted_zone_tag_filters, var.route53_hosted_zone_id_filters,
# and var.route53_hosted_zone_domain_filters).
route53_hosted_zone_id_filters = []

# Only create records in hosted zones that match the provided tags. Each item
# in the list should specify tag key and tag value as a map. Empty list
# (default) means match all zones. Zones must satisfy all three constraints
# (var.route53_hosted_zone_tag_filters, var.route53_hosted_zone_id_filters,
# and var.route53_hosted_zone_domain_filters).
route53_hosted_zone_tag_filters = []

# Policy for how DNS records are sychronized between sources and providers
# (options: sync, upsert-only ).
route53_record_update_policy = "upsert-only"

# Annotations to apply to the ServiceAccount created for the external-dns app,
# formatted as key value pairs.
service_account_annotations = {}

# K8s resources type to be observed for new DNS entries by ExternalDNS.
sources = ["ingress","service"]

# When enabled, triggers external-dns run loop on create/update/delete events
# (optional, in addition of regular interval)
trigger_loop_on_event = false

# A unique identifier used to identify this instance of external-dns. This is
# used to tag the DNS TXT records to know which domains are owned by this
# instance of external-dns, in case multiple external-dns services are
# managing the same Hosted Zone.
txt_owner_id = "default"

# A list of the subnets into which the EKS Cluster's administrative pods will
# be launched. These should usually be all private subnets and include one in
# each AWS Availability Zone. Required when var.create_fargate_profile is
# true.
vpc_worker_subnet_ids = []

# Duration string (e.g. 1m) indicating the amount of time the Hosted Zones are
# cached. When null, use the default defined in the chart (0 - no caching).
zones_cache_duration = null