Skip to content

AWS EKS ExternalDNS Install with Terraform

Step-01: Introduction

  • External DNS: Used for Updating Route53 RecordSets from Kubernetes
  • We need to create IAM Policy, k8s Service Account & IAM Role and associate them together for external-dns pod to add or remove entries in AWS Route53 Hosted Zones.
  • Update External-DNS default manifest to support our needs
  • Deploy & Verify logs

Step-02: c1-versions.tf

  • Create DynamoDB Table dev-aws-externaldns
  • Create S3 Bucket Key as dev/aws-externaldns/terraform.tfstate
  • Project Folder: 03-externaldns-install-terraform-manifests
    # Terraform Settings Block
    terraform {
      required_version = ">= 1.0.0"
      required_providers {
        aws = {
          source = "hashicorp/aws"
          version = "~> 4.12"
         }
        helm = {
          source = "hashicorp/helm"
          #version = "2.5.1"
          version = "~> 2.5"
        }
        kubernetes = {
          source = "hashicorp/kubernetes"
          version = "~> 2.11"
        }      
      }
      # Adding Backend as S3 for Remote State Storage
      backend "s3" {
        bucket = "terraform-on-aws-eks"
        key    = "dev/aws-externaldns/terraform.tfstate"
        region = "us-east-1" 
    
        # For State Locking
        dynamodb_table = "dev-aws-externaldns"    
      }     
    }
    
    # Terraform AWS Provider Block
    provider "aws" {
      region = var.aws_region
    }
    

Step-03: c2-remote-state-datasource.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Terraform Remote State Datasource - Remote Backend AWS S3
    data "terraform_remote_state" "eks" {
      backend = "s3"
      config = {
        bucket = "terraform-on-aws-eks"
        key    = "dev/eks-cluster/terraform.tfstate"
        region = var.aws_region
      }
    }
    

Step-04: c3-01-generic-variables.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Input Variables - Placeholder file
    # AWS Region
    variable "aws_region" {
      description = "Region in which AWS Resources to be created"
      type = string
      default = "us-east-1"  
    }
    # Environment Variable
    variable "environment" {
      description = "Environment Variable used as a prefix"
      type = string
      default = "dev"
    }
    # Business Division
    variable "business_divsion" {
      description = "Business Division in the large organization this Infrastructure belongs"
      type = string
      default = "SAP"
    }
    

Step-05: c3-02-local-values.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Define Local Values in Terraform
    locals {
      owners = var.business_divsion
      environment = var.environment
      name = "${var.business_divsion}-${var.environment}"
      common_tags = {
        owners = local.owners
        environment = local.environment
      }
      eks_cluster_name = "${data.terraform_remote_state.eks.outputs.cluster_id}"  
    } 
    

Step-06: c4-01-externaldns-iam-policy-and-role.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Resource: Create External DNS IAM Policy 
    resource "aws_iam_policy" "externaldns_iam_policy" {
      name        = "${local.name}-AllowExternalDNSUpdates"
      path        = "/"
      description = "External DNS IAM Policy"
      policy = jsonencode({
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "route53:ChangeResourceRecordSets"
          ],
          "Resource": [
            "arn:aws:route53:::hostedzone/*"
          ]
        },
        {
          "Effect": "Allow",
          "Action": [
            "route53:ListHostedZones",
            "route53:ListResourceRecordSets"
          ],
          "Resource": [
            "*"
          ]
        }
      ]
    })
    }
    
    output "externaldns_iam_policy_arn" {
      value = aws_iam_policy.externaldns_iam_policy.arn 
    } 
    
    # Resource: Create IAM Role 
    resource "aws_iam_role" "externaldns_iam_role" {
      name = "${local.name}-externaldns-iam-role"
    
      # Terraform's "jsonencode" function converts a Terraform expression result to valid JSON syntax.
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Action = "sts:AssumeRoleWithWebIdentity"
            Effect = "Allow"
            Sid    = ""
            Principal = {
              Federated = "${data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_arn}"
            }
            Condition = {
              StringEquals = {
                "${data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_extract_from_arn}:aud": "sts.amazonaws.com",            
                "${data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_extract_from_arn}:sub": "system:serviceaccount:default:external-dns"
              }
            }        
          },
        ]
      })
    
      tags = {
        tag-key = "AllowExternalDNSUpdates"
      }
    }
    
    # Associate External DNS IAM Policy to IAM Role
    resource "aws_iam_role_policy_attachment" "externaldns_iam_role_policy_attach" {
      policy_arn = aws_iam_policy.externaldns_iam_policy.arn 
      role       = aws_iam_role.externaldns_iam_role.name
    }
    
    output "externaldns_iam_role_arn" {
      description = "External DNS IAM Role ARN"
      value = aws_iam_role.externaldns_iam_role.arn
    }
    

Step-07: c4-02-externaldns-helm-provider.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Datasource: EKS Cluster Auth 
    data "aws_eks_cluster_auth" "cluster" {
      name = data.terraform_remote_state.eks.outputs.cluster_id
    }
    
    # HELM Provider
    provider "helm" {
      kubernetes {
        host                   = data.terraform_remote_state.eks.outputs.cluster_endpoint
        cluster_ca_certificate = base64decode(data.terraform_remote_state.eks.outputs.cluster_certificate_authority_data)
        token                  = data.aws_eks_cluster_auth.cluster.token
      }
    }
    

Step-08: c4-03-externaldns-install.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Resource: Helm Release 
    resource "helm_release" "external_dns" {
      depends_on = [aws_iam_role.externaldns_iam_role]            
      name       = "external-dns"
    
      repository = "https://kubernetes-sigs.github.io/external-dns/"
      chart      = "external-dns"
    
      namespace = "default"     
    
      set {
        name = "image.repository"
        value = "k8s.gcr.io/external-dns/external-dns" 
      }       
    
      set {
        name  = "serviceAccount.create"
        value = "true"
      }
    
      set {
        name  = "serviceAccount.name"
        value = "external-dns"
      }
    
      set {
        name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
        value = "${aws_iam_role.externaldns_iam_role.arn}"
      }
    
      set {
        name  = "provider" # Default is aws (https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns)
        value = "aws"
      }    
    
      set {
        name  = "policy" # Default is "upsert-only" which means DNS records will not get deleted even equivalent Ingress resources are deleted (https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns)
        value = "sync"   # "sync" will ensure that when ingress resource is deleted, equivalent DNS record in Route53 will get deleted
      }    
    
    }
    

Step-09: c4-04-externaldns-outputs.tf

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Helm Release Outputs
    output "externaldns_helm_metadata" {
      description = "Metadata Block outlining status of the deployed release."
      value = helm_release.external_dns.metadata
    }
    

Step-10: terraform.tfvars

  • Project Folder: 03-externaldns-install-terraform-manifests
    # Generic Variables
    aws_region = "us-east-1"
    environment = "dev"
    business_divsion = "hr"
    

Step-11: Execute Terraform Commands

# Change Directory
cd 03-externaldns-install-terraform-manifests

# Terraform Initialize
terraform init

# Terraform validate
terraform validate

# Terraform plan
terraform plan

# Terraform Apply
terraform apply -auto-approve

Step-12: Verify External DNS Installation on EKS Cluster

# List All resources from default Namespace
kubectl get all

# List pods (external-dns pod should be in running state)
kubectl get pods

# Verify Deployment by checking logs
kubectl logs -f $(kubectl get po | egrep -o 'external-dns[A-Za-z0-9-]+')

Step-13: Don't Clean-Up LBC Controller, EKS Cluster & ExternalDNS

  • Dont destroy the Terraform Projects in below three folders
  • Terraform Project Folder: 01-ekscluster-terraform-manifests
  • Terraform Project Folder: 02-lbc-install-terraform-manifests
  • Terraform Project Folder: 03-externaldns-install-terraform-manifests
  • We are going to use them for all upcoming Usecases.
  • Destroy Resorces Order
  • 03-externaldns-install-terraform-manifests
  • 02-lbc-install-terraform-manifests
  • 01-ekscluster-terraform-manifests
    ##############################################################
    ## Destroy External DNS
    # Change Directroy
    cd 03-externaldns-install-terraform-manifests
    
    # Terraform Destroy
    terraform init
    terraform apply -destroy -auto-approve
    ##############################################################
    ## Destroy  LBC
    # Change Directroy
    cd 02-lbc-install-terraform-manifests
    
    # Terraform Destroy
    terraform init
    terraform apply -destroy -auto-approve
    ##############################################################
    ## Destroy EKS Cluster
    # Change Directroy
    cd 01-ekscluster-terraform-manifests
    
    # Terraform Destroy
    terraform init
    terraform apply -destroy -auto-approve
    ##############################################################
    

References

🎉 New Course
Ultimate DevOps Real-World Project Implementation on AWS
$15.99 $84.99 81% OFF
DEVOPS2026FEB
Enroll Now on Udemy →
🎉 Offer