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/)