Refer to these instructions to install ExternalDNS on TKG cluster provisioned with a TKr for vSphere 7.x.

Prerequisites

See Workflow for Installing Standard Packages on TKr for vSphere 7.x.

Install ExternalDNS

Install ExternalDNS on a TKG cluster provisioned with TKr for vSphere 7.x.
  1. List the available ExternalDNS versions in the repository.
    kubectl get packages -n tkg-system | grep external-dns
  2. Create the ExternalDNS namespace.
    kubectl create namespace tanzu-system-service-discovery --dry-run=client -o yaml | kubectl apply -f -
  3. Set the security posture on the namespace.
    kubectl label namespace tanzu-system-service-discovery pod-security.kubernetes.io/enforce=privileged
  4. Prepare the Bind deployment YAML.

    See bind-deployment.yaml.

  5. Deploy the Bind DNS server.
    kubectl apply -n tanzu-system-service-discovery -f bind-deployment.yaml
  6. Prepare the ExternalDNS deployment YAML.

    See external-dns-deploy.yaml.

  7. Create a secret using the external-dns-default-values.yaml file.
    svcip=$(kubectl get svc bind -n tanzu-system-service-discovery -o jsonpath='{.spec.clusterIP}')sed -i "s/--rfc2136-host=[0-9.]\+/--rfc2136-host=$svcip/g" external-dns-deploy.yaml
    kubectl create secret generic external-dns-default-values --from-file=values.yaml=external-dns-deploy.yaml -n tkg-system
  8. Verify the secret.
    kubectl get secret external-dns-default-values -n tkg-system
    kubectl get secret external-dns-default-values -n tkg-system -oyaml
  9. Prepare the ExternalDNS package install YAML.

    See external-dns-packageinstall.yaml.

  10. Configure Bind.
    sed -i "s/--rfc2136-host=[0-9.]\+/--rfc2136-host=$svcip/g" external-dns-packageinstall.yaml
  11. Create the External DNS package.
    kubectl apply -f external-dns-packageinstall.yaml
  12. Verify ExternalDNS installation.
    kubectl get all -n tanzu-system-service-discovery

bind-deployment.yaml

Example bind-deployment.yaml.
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: bind-config
data:
  named.conf: |
    key "externaldns-key" {
            algorithm hmac-sha256;
            secret "O0DhTJzZ0GjfuQmB9TBc1ELchv5oDMTlQs3NNOdMZJU=";
    };

    # bind needs to recurse to coredns in the case of resolving CNAME records
    # it may know about to A records. E.g This test runs on AWS which uses
    # CNAMEs for their LoadBalancer Services and bind will want to resolve
    # those CNAME records to A records using an upstream DNS server.
    options {
        recursion yes;
        forwarders {
            COREDNS_CLUSTER_IP;
        };
        forward only;
        dnssec-enable yes;
        dnssec-validation yes;
    };

    zone "k8s.example.org" {
        type master;
        file "/etc/bind/k8s.zone";
        allow-transfer {
            key "externaldns-key";
        };
        update-policy {
            grant externaldns-key zonesub ANY;
        };
    };
  k8s.zone: |
    $TTL 60 ; 1 minute
    @         IN SOA  k8s.example.org. root.k8s.example.org. (
                                    16         ; serial
                                    60         ; refresh (1 minute)
                                    60         ; retry (1 minute)
                                    60         ; expire (1 minute)
                                    60         ; minimum (1 minute)
                                    )
                            NS      ns.k8s.example.org.
    ns                      A       1.2.3.4
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bind
spec:
  selector:
    matchLabels:
      app: bind
  template:
    metadata:
      labels:
        app: bind
    spec:
      containers:
      - name: bind
        image: docker.io/internetsystemsconsortium/bind9:9.16
        imagePullPolicy: IfNotPresent
        command:
        - 'sh'
        - '-c'
        - |
          /usr/sbin/named -g -c /etc/bind/named.conf
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        volumeMounts:
          - name: named-conf-volume
            mountPath: /etc/bind/named.conf
            subPath: named.conf
          - name: k8s-zone-volume
            mountPath: /etc/bind/k8s.zone
            subPath: k8s.zone
      volumes:
        - name: data
          emptyDir: {}
        - name: named-conf-volume
          configMap:
            name: bind-config
            items:
            - key: named.conf
              path: named.conf
        - name: k8s-zone-volume
          configMap:
            name: bind-config
            items:
            - key: k8s.zone
              path: k8s.zone
---
apiVersion: v1
kind: Service
metadata:
  name: bind
  labels:
    app: bind
spec:
  selector:
    app: bind
  type: ClusterIP
  ports:
  - port: 53
    targetPort: 53
    protocol: TCP
    name: dns-tcp
  - port: 53
    targetPort: 53
    protocol: UDP
    name: dns

external-dns-deploy.yaml

Example external-dns-deploy.yaml.
deployment:
  args:
  - --source=service
  - --source=ingress
  - --txt-owner-id=k8s
  - --domain-filter=k8s.example.org
  - --namespace=default
  - --provider=rfc2136
  - --rfc2136-host=198.201.49.227
  - --rfc2136-port=53
  - --rfc2136-zone=k8s.example.org
  - --rfc2136-tsig-secret=O0DhTJzZ0GjfuQmB9TBc1ELchv5oDMTlQs3NNOdMZJU=
  - --rfc2136-tsig-secret-alg=hmac-sha256
  - --rfc2136-tsig-keyname=externaldns-key

external-dns-packageinstall.yaml

The following example can be used for BIND. Update the package version as needed.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns-default-sa
  namespace: tkg-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dns-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: external-dns-default-sa
    namespace: tkg-system
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageInstall
metadata:
  name: dns
  namespace: tkg-system
spec:
  serviceAccountName: external-dns-default-sa
  packageRef:
    refName: external-dns.tanzu.vmware.com
    versionSelection:
      constraints: 0.13.6+vmware.1-tkg.1
  values:
  - secretRef:
      name: external-dns-default-values
---
apiVersion: v1
kind: Secret
metadata:
  name: external-dns-reg-creds
  namespace: tanzu-system-service-discovery
stringData:
  values.yml: |
    ---
    namespace: tanzu-system-service-discovery
    dns:
      deployment:
        args:
        - --txt-owner-id=k8s
        - --provider=rfc2136
        - --rfc2136-host=198.201.49.227 #! IP of compatible DNS server
        - --rfc2136-port=53
        - --rfc2136-zone=mk8s.example.org #! zone where services are deployed
        - --rfc2136-tsig-secret=O0DhTJzZ0GjfuQmB9TBc1ELchv5oDMTlQs3NNOdMZJU= #! TSIG secret authorized to update DNS
        - --rfc2136-tsig-secret-alg=hmac-sha256
        - --rfc2136-tsig-keyname=externaldns-key
        - --rfc2136-tsig-axfr
        - --source=service
        - --source=ingress
        - --domain-filter=k8s.example.org1 #! zone where services are deployed
The following example can be used for AWS DNS provider (Route 53). Update the package version as needed.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dns-sa
  namespace: tkg-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dns-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: dns-sa
    namespace: tkg-system
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageInstall
metadata:
  name: dns
  namespace: tkg-system
spec:
  serviceAccountName: dns-sa
  packageRef:
    refName: dns.tanzu.vmware.com
    versionSelection:
      constraints: 0.13.6+vmware.1-tkg.1
  values:
  - secretRef:
      name: dns-data-values
---
apiVersion: v1
kind: Secret
metadata:
  name: dns-data-values
  namespace: tkg-system
stringData:
  values.yml: |
    ---
    namespace: tanzu-system-service-discovery
    dns:
      pspNames: "vmware-system-restricted"
      deployment:
        args:
        - --source=service
        - --source=ingress
        - --source=contour-httpproxy #! configure external-dns to read Contour HTTPProxy resources
        - --domain-filter=my-zone.example.org #! zone where services are deployed
        - --provider=aws
        - --policy=upsert-only #! prevent deleting any records, omit to enable full sync
        - --aws-zone-type=public #! only look at public hosted zones (public, private, no value for both)
        - --aws-prefer-cname
        - --registry=txt
        - --txt-owner-id=ROUTE_53_HOSTED_ZONE_ID #! Route53 hosted zone identifier for my-zone.example.org
        - --txt-prefix=txt #! disambiguates TXT records from CNAME records
        env:
          - name: AWS_ACCESS_KEY_ID
            valueFrom:
              secretKeyRef:
                name: route53-credentials #! Kubernetes secret for route53 credentials
                key: aws_access_key_id
          - name: AWS_SECRET_ACCESS_KEY
            valueFrom:
              secretKeyRef:
                name: route53-credentials #! Kubernetes secret for route53 credentials
                key: aws_secret_access_key
The following example can be used for an Azure DNS provider. Update the package version as needed.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dns-sa
  namespace: tkg-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dns-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: dns-sa
    namespace: tkg-system
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageInstall
metadata:
  name: dns
  namespace: tkg-system
spec:
  serviceAccountName: dns-sa
  packageRef:
    refName: dns.tanzu.vmware.com
    versionSelection:
      constraints: 0.13.6+vmware.1-tkg.1
  values:
  - secretRef:
      name: dns-data-values
---
apiVersion: v1
kind: Secret
metadata:
  name: dns-data-values
  namespace: tkg-system
stringData:
  values.yml: |
    ---
    namespace: tanzu-system-service-discovery
    dns:
      pspNames: "vmware-system-restricted"
      deployment:
        args:
        - --provider=azure
        - --source=service
        - --source=ingress
        - --source=contour-httpproxy #! read Contour HTTPProxy resources
        - --domain-filter=my-zone.example.org #! zone where services are deployed
        - --azure-resource-group=my-resource-group #! Azure resource group
        volumeMounts:
        - name: azure-config-file
          mountPath: /etc/kubernetes
          readOnly: true
        #@overlay/replace
        volumes:
        - name: azure-config-file
          secret:
            secretName: azure-config-file