From 6cbfd7cb9cb50c0055ed05b418634f1d3c3b49c8 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Sat, 16 Apr 2022 00:04:38 -0400 Subject: [PATCH] feat: add external-dns for managing public DNS records (#288) --- README.md | 9 +++- .../default/echo-server/helm-release.yaml | 2 + .../networking/external-dns/helm-release.yaml | 50 +++++++++++++++++++ .../external-dns/kustomization.yaml | 6 +++ cluster/apps/networking/kustomization.yaml | 1 + configure.sh | 15 ++++-- provision/terraform/cloudflare/main.tf | 18 ------- tmpl/cluster/external-dns-secret.sops.yaml | 9 ++++ 8 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 cluster/apps/networking/external-dns/helm-release.yaml create mode 100644 cluster/apps/networking/external-dns/kustomization.yaml create mode 100644 tmpl/cluster/external-dns-secret.sops.yaml diff --git a/README.md b/README.md index 3194774e..f0ecf9f9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Feel free to read up on any of these technologies before you get started to be m - [cert-manager](https://cert-manager.io/) - SSL certificates - with Cloudflare DNS challenge - [calico](https://www.tigera.io/project-calico/) - CNI (container network interface) - [echo-server](https://github.com/Ealenn/Echo-Server) - REST Server Tests (Echo-Server) API (useful for debugging HTTP issues) +- [external-dns](https://github.com/kubernetes-sigs/external-dns) - Automatically configure external DNS servers for Kubernetes Ingresses and Services - [flux](https://toolkit.fluxcd.io/) - GitOps tool for deploying manifests from the `cluster` directory - [hajimari](https://github.com/toboshii/hajimari) - start page with ingress discovery - [k8s_gateway](https://github.com/ori-edge/k8s_gateway) - DNS resolver for all types of external Kubernetes resources @@ -309,19 +310,23 @@ kubectl --kubeconfig=./provision/kubeconfig get pods -n flux-system 🎉 **Congratulations** if all goes smooth you'll have a Kubernetes cluster managed by Flux, your Git repository is driving the state of your cluster. -Now it's time to pause and go get some coffee ☕ +Now it's time to pause and go get some coffee ☕ because next is describing how DNS is handled. 🧠 ## 📣 Post installation ### 🌐 DNS +📍 The `external-dns` application created in the `networking` namespace will handle creating public DNS records. `echo-server` is the only public domain exposed on your Cloudflare domain. In order to make additional applications public you must set an ingress annotation like in the `HelmRelease` for `echo-server`. You do not need to use Terraform to create additional DNS records unless you a record outside the purposes of your Kubernetes cluster. + `k8s_gateway` is deployed on the IP choosen for `${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}`. Inorder to test DNS you can point your clients DNS to the `${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}` IP address and load `https://hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` in your browser. You can also try debugging with the command `dig`, e.g. `dig @${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR} hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` and you should get a valid answer containing your `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP address. If your router (or Pi-Hole, Adguard Home or whatever) supports conditional DNS forwarding (also know as split-horizon DNS) you may have DNS requests for `${SECRET_DOMAIN}` only point to the `${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}` IP address. This will ensure only DNS requests for `${SECRET_DOMAIN}` will get routed to your `k8s_gateway` service. -To access services from the outside world port forwarded `80` and `443` in your router to the `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP, in a few moments head over to your browser and you _should_ be able to access `https://hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` from a device outside your LAN. +To access services from the outside world port forwarded `80` and `443` in your router to the `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP, in a few moments head over to your browser and you _should_ be able to access `https://echo-server.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` from a device outside your LAN. + +Now if nothing is working, that is expected. This is DNS after all! ### 👉 Troubleshooting diff --git a/cluster/apps/default/echo-server/helm-release.yaml b/cluster/apps/default/echo-server/helm-release.yaml index 0e3b60dc..bf0ee552 100644 --- a/cluster/apps/default/echo-server/helm-release.yaml +++ b/cluster/apps/default/echo-server/helm-release.yaml @@ -30,6 +30,8 @@ spec: enabled: true annotations: cert-manager.io/cluster-issuer: "letsencrypt-production" + external-dns.alpha.kubernetes.io/target: "ipv4.${SECRET_DOMAIN}" + external-dns/is-public: "true" hajimari.io/enable: "true" hajimari.io/icon: "video-input-antenna" traefik.ingress.kubernetes.io/router.entrypoints: "websecure" diff --git a/cluster/apps/networking/external-dns/helm-release.yaml b/cluster/apps/networking/external-dns/helm-release.yaml new file mode 100644 index 00000000..ca21c016 --- /dev/null +++ b/cluster/apps/networking/external-dns/helm-release.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: external-dns + namespace: networking +spec: + interval: 5m + chart: + spec: + chart: external-dns + version: 1.8.0 + sourceRef: + kind: HelmRepository + name: external-dns-charts + namespace: flux-system + interval: 5m + install: + createNamespace: true + values: + interval: 5m + logLevel: debug + provider: cloudflare + env: + - name: CF_API_EMAIL + valueFrom: + secretKeyRef: + name: external-dns-cloud-credentials + key: email + - name: CF_API_KEY + valueFrom: + secretKeyRef: + name: external-dns-cloud-credentials + key: api-key + extraArgs: + - --cloudflare-proxied + - --annotation-filter=external-dns/is-public in (true) + policy: sync + sources: + - ingress + txtPrefix: "k8s." + txtOwnerId: "default" + domainFilters: + - "${SECRET_DOMAIN}" + resources: + requests: + memory: 100Mi + cpu: 25m + limits: + memory: 250Mi diff --git a/cluster/apps/networking/external-dns/kustomization.yaml b/cluster/apps/networking/external-dns/kustomization.yaml new file mode 100644 index 00000000..73941365 --- /dev/null +++ b/cluster/apps/networking/external-dns/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - helm-release.yaml + - secret.sops.yaml diff --git a/cluster/apps/networking/kustomization.yaml b/cluster/apps/networking/kustomization.yaml index 2696558b..e707c83e 100644 --- a/cluster/apps/networking/kustomization.yaml +++ b/cluster/apps/networking/kustomization.yaml @@ -3,5 +3,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - cloudflare-ddns + - external-dns - k8s-gateway - traefik diff --git a/configure.sh b/configure.sh index 23a6f7df..6ddd1d6c 100755 --- a/configure.sh +++ b/configure.sh @@ -36,32 +36,39 @@ main() { verify_cloudflare success else - # sops configuration file + # generate sops configuration file envsubst < "${PROJECT_DIR}/tmpl/.sops.yaml" \ > "${PROJECT_DIR}/.sops.yaml" - # cluster + # generate cluster settings envsubst < "${PROJECT_DIR}/tmpl/cluster/cluster-settings.yaml" \ > "${PROJECT_DIR}/cluster/base/cluster-settings.yaml" envsubst < "${PROJECT_DIR}/tmpl/cluster/gotk-sync.yaml" \ > "${PROJECT_DIR}/cluster/base/flux-system/gotk-sync.yaml" envsubst < "${PROJECT_DIR}/tmpl/cluster/kube-vip-daemonset.yaml" \ > "${PROJECT_DIR}/cluster/core/kube-system/kube-vip/daemon-set.yaml" + # generate cluster secrets envsubst < "${PROJECT_DIR}/tmpl/cluster/cluster-secrets.sops.yaml" \ > "${PROJECT_DIR}/cluster/base/cluster-secrets.sops.yaml" envsubst < "${PROJECT_DIR}/tmpl/cluster/cert-manager-secret.sops.yaml" \ > "${PROJECT_DIR}/cluster/core/cert-manager/secret.sops.yaml" envsubst < "${PROJECT_DIR}/tmpl/cluster/cloudflare-ddns-secret.sops.yaml" \ > "${PROJECT_DIR}/cluster/apps/networking/cloudflare-ddns/secret.sops.yaml" + envsubst < "${PROJECT_DIR}/tmpl/cluster/external-dns-secret.sops.yaml" \ + > "${PROJECT_DIR}/cluster/apps/networking/external-dns/secret.sops.yaml" + # encrypt cluster secrets sops --encrypt --in-place "${PROJECT_DIR}/cluster/base/cluster-secrets.sops.yaml" sops --encrypt --in-place "${PROJECT_DIR}/cluster/core/cert-manager/secret.sops.yaml" sops --encrypt --in-place "${PROJECT_DIR}/cluster/apps/networking/cloudflare-ddns/secret.sops.yaml" - # terraform + sops --encrypt --in-place "${PROJECT_DIR}/cluster/apps/networking/external-dns/secret.sops.yaml" + # generate terraform secrets envsubst < "${PROJECT_DIR}/tmpl/terraform/secret.sops.yaml" \ > "${PROJECT_DIR}/provision/terraform/cloudflare/secret.sops.yaml" + # encrypt terraform secrets sops --encrypt --in-place "${PROJECT_DIR}/provision/terraform/cloudflare/secret.sops.yaml" - # ansible + # generate ansible settings envsubst < "${PROJECT_DIR}/tmpl/ansible/kube-vip.yml" \ > "${PROJECT_DIR}/provision/ansible/inventory/group_vars/kubernetes/kube-vip.yml" + # generate ansible hosts file and secrets generate_ansible_hosts generate_ansible_host_secrets fi diff --git a/provision/terraform/cloudflare/main.tf b/provision/terraform/cloudflare/main.tf index 11d3d399..b426d90b 100644 --- a/provision/terraform/cloudflare/main.tf +++ b/provision/terraform/cloudflare/main.tf @@ -99,21 +99,3 @@ resource "cloudflare_record" "root" { type = "CNAME" ttl = 1 } - -resource "cloudflare_record" "hajimari" { - name = "hajimari" - zone_id = lookup(data.cloudflare_zones.domain.zones[0], "id") - value = "ipv4.${data.sops_file.cloudflare_secrets.data["cloudflare_domain"]}" - proxied = true - type = "CNAME" - ttl = 1 -} - -resource "cloudflare_record" "echo_server" { - name = "echo-server" - zone_id = lookup(data.cloudflare_zones.domain.zones[0], "id") - value = "ipv4.${data.sops_file.cloudflare_secrets.data["cloudflare_domain"]}" - proxied = true - type = "CNAME" - ttl = 1 -} diff --git a/tmpl/cluster/external-dns-secret.sops.yaml b/tmpl/cluster/external-dns-secret.sops.yaml new file mode 100644 index 00000000..6362ae1d --- /dev/null +++ b/tmpl/cluster/external-dns-secret.sops.yaml @@ -0,0 +1,9 @@ +# yamllint disable +apiVersion: v1 +kind: Secret +metadata: + name: external-dns-cloud-credentials + namespace: networking +stringData: + email: ${BOOTSTRAP_CLOUDFLARE_EMAIL} + api-key: ${BOOTSTRAP_CLOUDFLARE_APIKEY}