How do I set up ExternalDNS with Amazon EKS?

Last updated: 2022-03-01

I want to set up ExternalDNS with my Amazon Elastic Kubernetes Service (Amazon EKS).

Short description

ExternalDNS is a pod that runs in your Amazon EKS cluster. To use ExternalDNS as a plugin with your Amazon EKS, you must set up AWS Identity and Access Management (IAM) permissions to allow Amazon EKS access to Amazon Route 53.

Note: Make sure that a domain name and a Route 53 hosted zone exist.

Resolution

Set up IAM permissions and deploy ExternalDNS

1.    Set up IAM permissions to give the ExternalDNS pod permissions to create, update, and delete Route 53 records in your AWS account.

IAM policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets"
      ],
      "Resource": [
        "arn:aws:route53:::hostedzone/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Note: You can also adjust the preceding policy to allow updates to explicit hosted zone IDs.

2.    Create an IAM role for the service account using the preceding policy:

eksctl create iamserviceaccount --name SERVICE_ACCOUNT_NAME --namespace NAMESPACE --cluster CLUSTER_NAME --attach-policy-arn IAM_POLICY_ARN --approve

Note: Replace SERVICE_ACCOUNT_NAME with your service account's name, NAMESPACE with your namespace, CLUSTER_NAME with your cluster's name, and IAM_POLICY_ARN with your IAM policy's ARN.

To check the name of your service account, run the following command:

kubectl get sa

Example output:

NAME           SECRETS   AGE
default        1         23h
external-dns   1         23h

In the preceding example output, external-dns is the name that was given to the service account when it was created.

3.    Deploy ExternalDNS.

Check if RBAC is enabled in your Amazon EKS cluster:

kubectl api-versions | grep rbac.authorization.k8s.io

Note: Before you apply the following manifests, check for the latest version of ExternalDNS (from the GitHub website).

Run the following command:

kubectl apply DEPLOYMENT_MANIFEST_FILE_NAME.yaml

Note: Replace DEPLOYMENT_MANIFEST_FILE_NAME with your deployment manifest's file name.

If RBAC is enabled, then use the following manifest to deploy ExternalDNS:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.10.2
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
        - --provider=aws
        - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
        - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
        - --registry=txt
        - --txt-owner-id=my-hostedzone-identifier
      securityContext:
        fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files

If RBAC isn't enabled, then use the following manifest to deploy ExternalDNS:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.10.2
        args:
        - --source=service
        - --source=ingress
        - --domain-filter= <Your_R53_Domain_Name>  # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
        - --provider=aws
        - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
        - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
        - --registry=txt
        - --txt-owner-id=<Your_R53_HostedZone_Id>

4.    Verify that the deployment was successful:

kubectl get deployments

Example output:

NAME           READY   UP-TO-DATE   AVAILABLE   AGE
external-dns   1/1     1            1           85m

You can also check the logs to verify that the records are up to date:

kubectl logs external-dns-9f85d8d5b-sx5fg

Example output:

....
....
time="2022-02-10T20:22:02Z" level=info msg="Instantiating new Kubernetes client"
time="2022-02-10T20:22:02Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2022-02-10T20:22:02Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
time="2022-02-10T20:22:09Z" level=info msg="Applying provider record filter for domains: [<yourdomainname>.com. .<yourdomainname>.com.]"
time="2022-02-10T20:22:09Z" level=info msg="All records are already up to date"
....
....

Verify that ExternalDNS is working

1.    Create a service that's exposed as LoadBalancer and that can be routed externally through the domain name that's hosted on Route 53:

kubectl apply SERVICE_MANIFEST_FILE_NAME.yaml

Note: Replace SERVICE_MANIFEST_FILE_NAME with your service manifest's file name.

Manifest:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
    external-dns.alpha.kubernetes.io/hostname: DOMAIN_NAME
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: LoadBalancer
  selector:
    app: nginx

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - image: nginx
          name: nginx
          ports:
            - containerPort: 80
              name: http

Note: Replace DOMAIN_NAME with your domain's name.

2.    Check that the NGINX service was created with the LoadBalancer type:

kubectl get svc

Example output:

NAME         TYPE           CLUSTER-IP      EXTERNAL-IP                                                              PORT(S)        AGE
kubernetes   ClusterIP      10.100.0.1      <none>                                                                   443/TCP        26h
nginx        LoadBalancer   10.100.234.77   a1ef09255d52049f487e05b4f74faea6-954147917.us-west-1.elb.amazonaws.com   80:30792/TCP   74m

Note: The service automatically creates a Route 53 record for the hosted zone.

Check the logs to verify that the Route 53 record was created:

kubectl logs external-dns-9f85d8d5b-sx5fg

Example output:

...
...
...
time="2022-02-10T21:22:43Z" level=info msg="Applying provider record filter for domains: [<domainname>.com. .<domainname>.com.]"
time="2022-02-10T21:22:43Z" level=info msg="Desired change: CREATE <domainname>.com A [Id: /hostedzone/Z01155763Q6AN7CEI3AP6]"
time="2022-02-10T21:22:43Z" level=info msg="Desired change: CREATE <domainname>.com TXT [Id: /hostedzone/Z01155763Q6AN7CEI3AP6]"
time="2022-02-10T21:22:43Z" level=info msg="2 record(s) in zone xxx.com. [Id: /hostedzone/Z01155763Q6AN7CEI3AP6] were successfully updated"
time="2022-02-10T21:23:43Z" level=info msg="Applying provider record filter for domains: [<domainname>.com. .<domainname>.com.]"
time="2022-02-10T21:23:43Z" level=info msg="All records are already up to date"
...
...
...

To learn more about how to set up and verify ExternalDNS, see Setting up ExternalDNS for services on AWS (from the GitHub website).


Did this article help?


Do you need billing or technical support?