sigs.k8s.io/external-dns@v0.14.1/docs/tutorials/nginx-ingress.md (about)

     1  # Setting up ExternalDNS on GKE with nginx-ingress-controller
     2  
     3  This tutorial describes how to setup ExternalDNS for usage within a GKE cluster that doesn't make use of Google's [default ingress controller](https://github.com/kubernetes/ingress-gce) but rather uses [nginx-ingress-controller](https://github.com/kubernetes/ingress-nginx) for that task.
     4  
     5  ## Set up your environment
     6  
     7  Setup your environment to work with Google Cloud Platform. Fill in your values as needed, e.g. target project.
     8  
     9  ```console
    10  $ gcloud config set project "zalando-external-dns-test"
    11  $ gcloud config set compute/region "europe-west1"
    12  $ gcloud config set compute/zone "europe-west1-d"
    13  ```
    14  
    15  ## GKE Node Scopes
    16  
    17  The following instructions use instance scopes to provide ExternalDNS with the
    18  permissions it needs to manage DNS records. Note that since these permissions
    19  are associated with the instance, all pods in the cluster will also have these
    20  permissions. As such, this approach is not suitable for anything but testing
    21  environments.
    22  
    23  Create a GKE cluster without using the default ingress controller.
    24  
    25  ```console
    26  $ gcloud container clusters create "external-dns" \
    27      --num-nodes 1 \
    28      --scopes "https://www.googleapis.com/auth/ndev.clouddns.readwrite"
    29  ```
    30  
    31  Create a DNS zone which will contain the managed DNS records.
    32  
    33  ```console
    34  $ gcloud dns managed-zones create "external-dns-test-gcp-zalan-do" \
    35      --dns-name "external-dns-test.gcp.zalan.do." \
    36      --description "Automatically managed zone by ExternalDNS"
    37  ```
    38  
    39  Make a note of the nameservers that were assigned to your new zone.
    40  
    41  ```console
    42  $ gcloud dns record-sets list \
    43      --zone "external-dns-test-gcp-zalan-do" \
    44      --name "external-dns-test.gcp.zalan.do." \
    45      --type NS
    46  NAME                             TYPE  TTL    DATA
    47  external-dns-test.gcp.zalan.do.  NS    21600  ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
    48  ```
    49  
    50  In this case it's `ns-cloud-{e1-e4}.googledomains.com.` but your's could slightly differ, e.g. `{a1-a4}`, `{b1-b4}` etc.
    51  
    52  Tell the parent zone where to find the DNS records for this zone by adding the corresponding NS records there. Assuming the parent zone is "gcp-zalan-do" and the domain is "gcp.zalan.do" and that it's also hosted at Google we would do the following.
    53  
    54  ```console
    55  $ gcloud dns record-sets transaction start --zone "gcp-zalan-do"
    56  $ gcloud dns record-sets transaction add ns-cloud-e{1..4}.googledomains.com. \
    57      --name "external-dns-test.gcp.zalan.do." --ttl 300 --type NS --zone "gcp-zalan-do"
    58  $ gcloud dns record-sets transaction execute --zone "gcp-zalan-do"
    59  ```
    60  
    61  Connect your `kubectl` client to the cluster you just created and bind your GCP
    62  user to the cluster admin role in Kubernetes.
    63  
    64  ```console
    65  $ gcloud container clusters get-credentials "external-dns"
    66  $ kubectl create clusterrolebinding cluster-admin-me \
    67      --clusterrole=cluster-admin --user="$(gcloud config get-value account)"
    68  ```
    69  
    70  ### Deploy the nginx ingress controller
    71  
    72  First, you need to deploy the nginx-based ingress controller. It can be deployed in at least two modes: Leveraging a Layer 4 load balancer in front of the nginx proxies or directly targeting pods with hostPorts on your worker nodes. ExternalDNS doesn't really care and supports both modes.
    73  
    74  #### Default Backend
    75  
    76  The nginx controller uses a default backend that it serves when no Ingress rule matches. This is a separate Service that can be picked by you. We'll use the default backend that's used by other ingress controllers for that matter. Apply the following manifests to your cluster to deploy the default backend.
    77  
    78  ```yaml
    79  apiVersion: v1
    80  kind: Service
    81  metadata:
    82    name: default-http-backend
    83  spec:
    84    ports:
    85    - port: 80
    86      targetPort: 8080
    87    selector:
    88      app: default-http-backend
    89  
    90  ---
    91  
    92  apiVersion: apps/v1
    93  kind: Deployment
    94  metadata:
    95    name: default-http-backend
    96  spec:
    97    selector:
    98      matchLabels:
    99        app: default-http-backend
   100    template:
   101      metadata:
   102        labels:
   103          app: default-http-backend
   104      spec:
   105        containers:
   106        - name: default-http-backend
   107          image: gcr.io/google_containers/defaultbackend:1.3
   108  ```
   109  
   110  #### Without a separate TCP load balancer
   111  
   112  By default, the controller will update your Ingress objects with the public IPs of the nodes running your nginx controller instances. You should run multiple instances in case of pod or node failure. The controller will do leader election and will put multiple IPs as targets in your Ingress objects in that case. It could also make sense to run it as a DaemonSet. However, we'll just run a single replica. You have to open the respective ports on all of your worker nodes to allow nginx to receive traffic.
   113  
   114  ```console
   115  $ gcloud compute firewall-rules create "allow-http" --allow tcp:80 --source-ranges "0.0.0.0/0" --target-tags "gke-external-dns-9488ba14-node"
   116  $ gcloud compute firewall-rules create "allow-https" --allow tcp:443 --source-ranges "0.0.0.0/0" --target-tags "gke-external-dns-9488ba14-node"
   117  ```
   118  
   119  Change `--target-tags` to the corresponding tags of your nodes. You can find them by describing your instances or by looking at the default firewall rules created by GKE for your cluster.
   120  
   121  Apply the following manifests to your cluster to deploy the nginx-based ingress controller. Note, how it receives a reference to the default backend's Service and that it listens on hostPorts. (You may have to use `hostNetwork: true` as well.)
   122  
   123  ```yaml
   124  apiVersion: apps/v1
   125  kind: Deployment
   126  metadata:
   127    name: nginx-ingress-controller
   128  spec:
   129    selector:
   130      matchLabels:
   131        app: nginx-ingress-controller
   132    template:
   133      metadata:
   134        labels:
   135          app: nginx-ingress-controller
   136      spec:
   137        containers:
   138        - name: nginx-ingress-controller
   139          image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
   140          args:
   141          - /nginx-ingress-controller
   142          - --default-backend-service=default/default-http-backend
   143          env:
   144            - name: POD_NAME
   145              valueFrom:
   146                fieldRef:
   147                  fieldPath: metadata.name
   148            - name: POD_NAMESPACE
   149              valueFrom:
   150                fieldRef:
   151                  fieldPath: metadata.namespace
   152          ports:
   153          - containerPort: 80
   154            hostPort: 80
   155          - containerPort: 443
   156            hostPort: 443
   157  ```
   158  
   159  #### With a separate TCP load balancer
   160  
   161  However, you can also have the ingress controller proxied by a Kubernetes Service. This will instruct the controller to populate this Service's external IP as the external IP of the Ingress. This exposes the nginx proxies via a Layer 4 load balancer (`type=LoadBalancer`) which is more reliable than the other method. With that approach, you can run as many nginx proxy instances on your cluster as you like or have them autoscaled. This is the preferred way of running the nginx controller.
   162  
   163  Apply the following manifests to your cluster. Note, how the controller is receiving an additional flag telling it which Service it should treat as its public endpoint and how it doesn't need hostPorts anymore.
   164  
   165  Apply the following manifests to run the controller in this mode.
   166  
   167  ```yaml
   168  apiVersion: v1
   169  kind: Service
   170  metadata:
   171    name: nginx-ingress-controller
   172  spec:
   173    type: LoadBalancer
   174    ports:
   175    - name: http
   176      port: 80
   177      targetPort: 80
   178    - name: https
   179      port: 443
   180      targetPort: 443
   181    selector:
   182      app: nginx-ingress-controller
   183  
   184  ---
   185  
   186  apiVersion: apps/v1
   187  kind: Deployment
   188  metadata:
   189    name: nginx-ingress-controller
   190  spec:
   191    selector:
   192      matchLabels:
   193        app: nginx-ingress-controller
   194    template:
   195      metadata:
   196        labels:
   197          app: nginx-ingress-controller
   198      spec:
   199        containers:
   200        - name: nginx-ingress-controller
   201          image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
   202          args:
   203          - /nginx-ingress-controller
   204          - --default-backend-service=default/default-http-backend
   205          - --publish-service=default/nginx-ingress-controller
   206          env:
   207            - name: POD_NAME
   208              valueFrom:
   209                fieldRef:
   210                  fieldPath: metadata.name
   211            - name: POD_NAMESPACE
   212              valueFrom:
   213                fieldRef:
   214                  fieldPath: metadata.namespace
   215          ports:
   216          - containerPort: 80
   217          - containerPort: 443
   218  ```
   219  
   220  ### Deploy ExternalDNS
   221  
   222  Apply the following manifest file to deploy ExternalDNS.
   223  
   224  ```yaml
   225  apiVersion: v1
   226  kind: ServiceAccount
   227  metadata:
   228    name: external-dns
   229  ---
   230  apiVersion: rbac.authorization.k8s.io/v1
   231  kind: ClusterRole
   232  metadata:
   233    name: external-dns
   234  rules:
   235  - apiGroups: [""]
   236    resources: ["services","endpoints","pods"]
   237    verbs: ["get","watch","list"]
   238  - apiGroups: ["extensions","networking.k8s.io"]
   239    resources: ["ingresses"] 
   240    verbs: ["get","watch","list"]
   241  - apiGroups: [""]
   242    resources: ["nodes"]
   243    verbs: ["list"]
   244  ---
   245  apiVersion: rbac.authorization.k8s.io/v1
   246  kind: ClusterRoleBinding
   247  metadata:
   248    name: external-dns-viewer
   249  roleRef:
   250    apiGroup: rbac.authorization.k8s.io
   251    kind: ClusterRole
   252    name: external-dns
   253  subjects:
   254  - kind: ServiceAccount
   255    name: external-dns
   256    namespace: default
   257  ---
   258  apiVersion: apps/v1
   259  kind: Deployment
   260  metadata:
   261    name: external-dns
   262  spec:
   263    strategy:
   264      type: Recreate
   265    selector:
   266      matchLabels:
   267        app: external-dns
   268    template:
   269      metadata:
   270        labels:
   271          app: external-dns
   272      spec:
   273        serviceAccountName: external-dns
   274        containers:
   275        - name: external-dns
   276          image: registry.k8s.io/external-dns/external-dns:v0.14.0
   277          args:
   278          - --source=ingress
   279          - --domain-filter=external-dns-test.gcp.zalan.do
   280          - --provider=google
   281          - --google-project=zalando-external-dns-test
   282          - --registry=txt
   283          - --txt-owner-id=my-identifier
   284  ```
   285  
   286  Use `--dry-run` if you want to be extra careful on the first run. Note, that you will not see any records created when you are running in dry-run mode. You can, however, inspect the logs and watch what would have been done.
   287  
   288  ### Deploy a sample application
   289  
   290  Create the following sample application to test that ExternalDNS works.
   291  
   292  ```yaml
   293  apiVersion: networking.k8s.io/v1
   294  kind: Ingress
   295  metadata:
   296    name: nginx
   297  spec:
   298    ingressClassName: nginx
   299    rules:
   300    - host: via-ingress.external-dns-test.gcp.zalan.do
   301      http:
   302        paths:
   303        - path: /
   304          backend:
   305            service:
   306              name: nginx
   307              port:
   308                number: 80
   309          pathType: Prefix
   310  
   311  ---
   312  
   313  apiVersion: v1
   314  kind: Service
   315  metadata:
   316    name: nginx
   317  spec:
   318    ports:
   319    - port: 80
   320      targetPort: 80
   321    selector:
   322      app: nginx
   323  
   324  ---
   325  
   326  apiVersion: apps/v1
   327  kind: Deployment
   328  metadata:
   329    name: nginx
   330  spec:
   331    selector:
   332      matchLabels:
   333        app: nginx
   334    template:
   335      metadata:
   336        labels:
   337          app: nginx
   338      spec:
   339        containers:
   340        - image: nginx
   341          name: nginx
   342          ports:
   343          - containerPort: 80
   344  ```
   345  
   346  After roughly two minutes check that a corresponding DNS record for your Ingress was created.
   347  
   348  ```console
   349  $ gcloud dns record-sets list \
   350      --zone "external-dns-test-gcp-zalan-do" \
   351      --name "via-ingress.external-dns-test.gcp.zalan.do." \
   352      --type A
   353  NAME                                         TYPE  TTL  DATA
   354  via-ingress.external-dns-test.gcp.zalan.do.  A     300  35.187.1.246
   355  ```
   356  
   357  Let's check that we can resolve this DNS name as well.
   358  
   359  ```console
   360  dig +short @ns-cloud-e1.googledomains.com. via-ingress.external-dns-test.gcp.zalan.do.
   361  35.187.1.246
   362  ```
   363  
   364  Try with `curl` as well.
   365  
   366  ```console
   367  $ curl via-ingress.external-dns-test.gcp.zalan.do
   368  <!DOCTYPE html>
   369  <html>
   370  <head>
   371  <title>Welcome to nginx!</title>
   372  ...
   373  </head>
   374  <body>
   375  ...
   376  </body>
   377  </html>
   378  ```
   379  
   380  ### Clean up
   381  
   382  Make sure to delete all Service and Ingress objects before terminating the cluster so all load balancers and DNS entries get cleaned up correctly.
   383  
   384  ```console
   385  $ kubectl delete service nginx-ingress-controller
   386  $ kubectl delete ingress nginx
   387  ```
   388  
   389  Give ExternalDNS some time to clean up the DNS records for you. Then delete the managed zone and cluster.
   390  
   391  ```console
   392  $ gcloud dns managed-zones delete "external-dns-test-gcp-zalan-do"
   393  $ gcloud container clusters delete "external-dns"
   394  ```
   395  
   396  Also delete the NS records for your removed zone from the parent zone.
   397  
   398  ```console
   399  $ gcloud dns record-sets transaction start --zone "gcp-zalan-do"
   400  $ gcloud dns record-sets transaction remove ns-cloud-e{1..4}.googledomains.com. \
   401      --name "external-dns-test.gcp.zalan.do." --ttl 300 --type NS --zone "gcp-zalan-do"
   402  $ gcloud dns record-sets transaction execute --zone "gcp-zalan-do"
   403  ```
   404  
   405  ## GKE with Workload Identity
   406  
   407  The following instructions use [GKE workload
   408  identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
   409  to provide ExternalDNS with the permissions it needs to manage DNS records.
   410  Workload identity is the Google-recommended way to provide GKE workloads access
   411  to GCP APIs.
   412  
   413  Create a GKE cluster with workload identity enabled and without the
   414  HttpLoadBalancing add-on.
   415  
   416  ```console
   417  $ gcloud container clusters create external-dns \
   418      --workload-metadata-from-node=GKE_METADATA_SERVER \
   419      --identity-namespace=zalando-external-dns-test.svc.id.goog \
   420      --addons=HorizontalPodAutoscaling
   421  ```
   422  
   423  Create a GCP service account (GSA) for ExternalDNS and save its email address.
   424  
   425  ```console
   426  $ sa_name="Kubernetes external-dns"
   427  $ gcloud iam service-accounts create sa-edns --display-name="$sa_name"
   428  $ sa_email=$(gcloud iam service-accounts list --format='value(email)' \
   429      --filter="displayName:$sa_name")
   430  ```
   431  
   432  Bind the ExternalDNS GSA to the DNS admin role.
   433  
   434  ```console
   435  $ gcloud projects add-iam-policy-binding zalando-external-dns-test \
   436      --member="serviceAccount:$sa_email" --role=roles/dns.admin
   437  ```
   438  
   439  Link the ExternalDNS GSA to the Kubernetes service account (KSA) that
   440  external-dns will run under, i.e., the external-dns KSA in the external-dns
   441  namespaces.
   442  
   443  ```console
   444  $ gcloud iam service-accounts add-iam-policy-binding "$sa_email" \
   445      --member="serviceAccount:zalando-external-dns-test.svc.id.goog[external-dns/external-dns]" \
   446      --role=roles/iam.workloadIdentityUser
   447  ```
   448  
   449  Create a DNS zone which will contain the managed DNS records.
   450  
   451  ```console
   452  $ gcloud dns managed-zones create external-dns-test-gcp-zalan-do \
   453      --dns-name=external-dns-test.gcp.zalan.do. \
   454      --description="Automatically managed zone by ExternalDNS"
   455  ```
   456  
   457  Make a note of the nameservers that were assigned to your new zone.
   458  
   459  ```console
   460  $ gcloud dns record-sets list \
   461      --zone=external-dns-test-gcp-zalan-do \
   462      --name=external-dns-test.gcp.zalan.do. \
   463      --type NS
   464  NAME                             TYPE  TTL    DATA
   465  external-dns-test.gcp.zalan.do.  NS    21600  ns-cloud-e1.googledomains.com.,ns-cloud-e2.googledomains.com.,ns-cloud-e3.googledomains.com.,ns-cloud-e4.googledomains.com.
   466  ```
   467  
   468  In this case it's `ns-cloud-{e1-e4}.googledomains.com.` but your's could
   469  slightly differ, e.g. `{a1-a4}`, `{b1-b4}` etc.
   470  
   471  Tell the parent zone where to find the DNS records for this zone by adding the
   472  corresponding NS records there. Assuming the parent zone is "gcp-zalan-do" and
   473  the domain is "gcp.zalan.do" and that it's also hosted at Google we would do the
   474  following.
   475  
   476  ```console
   477  $ gcloud dns record-sets transaction start --zone=gcp-zalan-do
   478  $ gcloud dns record-sets transaction add ns-cloud-e{1..4}.googledomains.com. \
   479      --name=external-dns-test.gcp.zalan.do. --ttl 300 --type NS --zone=gcp-zalan-do
   480  $ gcloud dns record-sets transaction execute --zone=gcp-zalan-do
   481  ```
   482  
   483  Connect your `kubectl` client to the cluster you just created and bind your GCP
   484  user to the cluster admin role in Kubernetes.
   485  
   486  ```console
   487  $ gcloud container clusters get-credentials external-dns
   488  $ kubectl create clusterrolebinding cluster-admin-me \
   489      --clusterrole=cluster-admin --user="$(gcloud config get-value account)"
   490  ```
   491  
   492  ### Deploy ingress-nginx
   493  
   494  Follow the [ingress-nginx GKE installation
   495  instructions](https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke) to
   496  deploy it to the cluster.
   497  
   498  ```console
   499  $ kubectl apply -f \
   500      https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.35.0/deploy/static/provider/cloud/deploy.yaml
   501  ```
   502  
   503  ### Deploy ExternalDNS
   504  
   505  Apply the following manifest file to deploy external-dns.
   506  
   507  ```yaml
   508  apiVersion: v1
   509  kind: Namespace
   510  metadata:
   511    name: external-dns
   512  ---
   513  apiVersion: v1
   514  kind: ServiceAccount
   515  metadata:
   516    name: external-dns
   517    namespace: external-dns
   518  ---
   519  apiVersion: rbac.authorization.k8s.io/v1
   520  kind: ClusterRole
   521  metadata:
   522    name: external-dns
   523  rules:
   524    - apiGroups: [""]
   525      resources: ["services", "endpoints", "pods"]
   526      verbs: ["get", "watch", "list"]
   527    - apiGroups: ["extensions", "networking.k8s.io"]
   528      resources: ["ingresses"]
   529      verbs: ["get", "watch", "list"]
   530    - apiGroups: [""]
   531      resources: ["nodes"]
   532      verbs: ["list"]
   533  ---
   534  apiVersion: rbac.authorization.k8s.io/v1
   535  kind: ClusterRoleBinding
   536  metadata:
   537    name: external-dns-viewer
   538  roleRef:
   539    apiGroup: rbac.authorization.k8s.io
   540    kind: ClusterRole
   541    name: external-dns
   542  subjects:
   543    - kind: ServiceAccount
   544      name: external-dns
   545      namespace: external-dns
   546  ---
   547  apiVersion: apps/v1
   548  kind: Deployment
   549  metadata:
   550    name: external-dns
   551    namespace: external-dns
   552  spec:
   553    strategy:
   554      type: Recreate
   555    selector:
   556      matchLabels:
   557        app: external-dns
   558    template:
   559      metadata:
   560        labels:
   561          app: external-dns
   562      spec:
   563        containers:
   564          - args:
   565              - --source=ingress
   566              - --domain-filter=external-dns-test.gcp.zalan.do
   567              - --provider=google
   568              - --google-project=zalando-external-dns-test
   569              - --registry=txt
   570              - --txt-owner-id=my-identifier
   571            image: registry.k8s.io/external-dns/external-dns:v0.14.0
   572            name: external-dns
   573        securityContext:
   574          fsGroup: 65534
   575          runAsUser: 65534
   576        serviceAccountName: external-dns
   577  ```
   578  
   579  Then add the proper workload identity annotation to the cert-manager service
   580  account.
   581  
   582  ```bash
   583  $ kubectl annotate serviceaccount --namespace=external-dns external-dns \
   584      "iam.gke.io/gcp-service-account=$sa_email"
   585  ```
   586  
   587  ### Deploy a sample application
   588  
   589  Create the following sample application to test that ExternalDNS works.
   590  
   591  ```yaml
   592  apiVersion: networking.k8s.io/v1
   593  kind: Ingress
   594  metadata:
   595    name: nginx
   596  spec:
   597    ingressClassName: nginx
   598    rules:
   599    - host: via-ingress.external-dns-test.gcp.zalan.do
   600      http:
   601        paths:
   602        - path: /
   603          backend:
   604            service:
   605              name: nginx
   606              port:
   607                number: 80
   608          pathType: Prefix
   609  ---
   610  apiVersion: v1
   611  kind: Service
   612  metadata:
   613    name: nginx
   614  spec:
   615    ports:
   616    - port: 80
   617      targetPort: 80
   618    selector:
   619      app: nginx
   620  ---
   621  apiVersion: apps/v1
   622  kind: Deployment
   623  metadata:
   624    name: nginx
   625  spec:
   626    selector:
   627      matchLabels:
   628        app: nginx
   629    template:
   630      metadata:
   631        labels:
   632          app: nginx
   633      spec:
   634        containers:
   635        - image: nginx
   636          name: nginx
   637          ports:
   638          - containerPort: 80
   639  ```
   640  
   641  After roughly two minutes check that a corresponding DNS record for your ingress
   642  was created.
   643  
   644  ```console
   645  $ gcloud dns record-sets list \
   646      --zone "external-dns-test-gcp-zalan-do" \
   647      --name "via-ingress.external-dns-test.gcp.zalan.do." \
   648      --type A
   649  NAME                                         TYPE  TTL  DATA
   650  via-ingress.external-dns-test.gcp.zalan.do.  A     300  35.187.1.246
   651  ```
   652  
   653  Let's check that we can resolve this DNS name as well.
   654  
   655  ```console
   656  $ dig +short @ns-cloud-e1.googledomains.com. via-ingress.external-dns-test.gcp.zalan.do.
   657  35.187.1.246
   658  ```
   659  
   660  Try with `curl` as well.
   661  
   662  ```console
   663  $ curl via-ingress.external-dns-test.gcp.zalan.do
   664  <!DOCTYPE html>
   665  <html>
   666  <head>
   667  <title>Welcome to nginx!</title>
   668  ...
   669  </head>
   670  <body>
   671  ...
   672  </body>
   673  </html>
   674  ```
   675  
   676  ### Clean up
   677  
   678  Make sure to delete all service and ingress objects before terminating the
   679  cluster so all load balancers and DNS entries get cleaned up correctly.
   680  
   681  ```console
   682  $ kubectl delete service --namespace=ingress-nginx ingress-nginx-controller
   683  $ kubectl delete ingress nginx
   684  ```
   685  
   686  Give ExternalDNS some time to clean up the DNS records for you. Then delete the
   687  managed zone and cluster.
   688  
   689  ```console
   690  $ gcloud dns managed-zones delete external-dns-test-gcp-zalan-do
   691  $ gcloud container clusters delete external-dns
   692  ```
   693  
   694  Also delete the NS records for your removed zone from the parent zone.
   695  
   696  ```console
   697  $ gcloud dns record-sets transaction start --zone gcp-zalan-do
   698  $ gcloud dns record-sets transaction remove ns-cloud-e{1..4}.googledomains.com. \
   699      --name=external-dns-test.gcp.zalan.do. --ttl 300 --type NS --zone=gcp-zalan-do
   700  $ gcloud dns record-sets transaction execute --zone=gcp-zalan-do
   701  ```
   702  
   703  ## User Demo How-To Blogs and Examples
   704  
   705  * Run external-dns on GKE with workload identity. See [Kubernetes, ingress-nginx, cert-manager & external-dns](https://blog.atomist.com/kubernetes-ingress-nginx-cert-manager-external-dns/)