diff --git a/README.md b/README.md index dc902741..03acc1f7 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,18 @@ kubectl --kubeconfig=./provision/kubeconfig get nodes # k8s-1 Ready worker 4d20h v1.21.5+k3s1 ``` +### ☁️ Configuring Cloudflare DNS with Terraform + +📍 Review the Terraform scripts under `./terraform/cloudflare/` and make sure you understand what it's doing (no really review it). If your domain already has existing DNS records be sure to export those DNS settings before you continue. Ideally you can update the terraform script to manage DNS for all records if you so choose to. + +1. Pull in the Terraform deps by running `task terraform:init:cloudflare` + +2. Review the changes Terraform will make to your Cloudflare domain by running `task terraform:plan:cloudflare` + +3. Finally have Terraform execute the task by running `task terraform:apply:cloudflare` + +If Terraform was ran successfully and you have port forwarded `80` and `443` in your router to the `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP, head over to your browser and you _should_ be able to access `https://hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}`! + ### 🔹 GitOps with Flux 📍 Here we will be installing [flux](https://toolkit.fluxcd.io/) after some quick bootstrap steps. @@ -296,18 +308,6 @@ kubectl --kubeconfig=./provision/kubeconfig get pods -n flux-system 🎉 **Congratulations** you have a Kubernetes cluster managed by Flux, your Git repository is driving the state of your cluster. -### ☁️ Configure Cloudflare DNS with Terraform - -📍 Review the Terraform scripts under `./terraform/cloudflare/` and make sure you understand what it's doing (no really review it). If your domain already has existing DNS records be sure to export those DNS settings before you continue. Ideally you can update the terraform script to manage DNS for all records if you so choose to. - -1. Pull in the Terraform deps by running `task terraform:init:cloudflare` - -2. Review the changes Terraform will make to your Cloudflare domain by running `task terraform:plan:cloudflare` - -3. Finally have Terraform execute the task by running `task terraform:apply:cloudflare` - -If Terraform was ran successfully and you have port forwarded `80` and `443` in your router to the `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP, head over to your browser and you _should_ be able to access `https://hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}`! - ## 📣 Post installation ### 👉 Troubleshooting diff --git a/cluster/apps/networking/cloudflare-ddns/cloudflare-ddns.sh b/cluster/apps/networking/cloudflare-ddns/cloudflare-ddns.sh new file mode 100755 index 00000000..0ba5b703 --- /dev/null +++ b/cluster/apps/networking/cloudflare-ddns/cloudflare-ddns.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit + +current_ipv4="$(curl -s https://ipv4.icanhazip.com/)" +zone_id=$(curl -s -X GET \ + "https://api.cloudflare.com/client/v4/zones?name=${CLOUDFLARE_RECORD_NAME#*.}&status=active" \ + -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \ + -H "X-Auth-Key: ${CLOUDFLARE_APIKEY}" \ + -H "Content-Type: application/json" \ + | jq --raw-output ".result[0] | .id" +) +record_ipv4=$(curl -s -X GET \ + "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?name=${CLOUDFLARE_RECORD_NAME}&type=A" \ + -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \ + -H "X-Auth-Key: ${CLOUDFLARE_APIKEY}" \ + -H "Content-Type: application/json" \ +) +old_ip4=$(echo "$record_ipv4" | jq --raw-output '.result[0] | .content') +if [[ "${current_ipv4}" == "${old_ip4}" ]]; then + printf "%s - IP Address '%s' has not changed" "$(date -u)" "${current_ipv4}" + exit 0 +fi +record_ipv4_identifier="$(echo "$record_ipv4" | jq --raw-output '.result[0] | .id')" +update_ipv4=$(curl -s -X PUT \ + "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_ipv4_identifier}" \ + -H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \ + -H "X-Auth-Key: ${CLOUDFLARE_APIKEY}" \ + -H "Content-Type: application/json" \ + --data "{\"id\":\"${zone_id}\",\"type\":\"A\",\"proxied\":true,\"name\":\"${CLOUDFLARE_RECORD_NAME}\",\"content\":\"${current_ipv4}\"}" \ +) +if [[ "$(echo "$update_ipv4" | jq --raw-output '.success')" == "true" ]]; then + printf "%s - Success - IP Address '%s' has been updated" "$(date -u)" "${current_ipv4}" + exit 0 +else + printf "%s - Yikes - Updating IP Address '%s' has failed" "$(date -u)" "${current_ipv4}" + exit 1 +fi diff --git a/cluster/apps/networking/cloudflare-ddns/cron-job.yaml b/cluster/apps/networking/cloudflare-ddns/cron-job.yaml new file mode 100644 index 00000000..90e46b3d --- /dev/null +++ b/cluster/apps/networking/cloudflare-ddns/cron-job.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: cloudflare-ddns + namespace: networking +spec: + schedule: "0 * * * *" + concurrencyPolicy: "Forbid" + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 5 + jobTemplate: + spec: + backoffLimit: 3 + ttlSecondsAfterFinished: 300 + template: + spec: + restartPolicy: Never + containers: + - name: cloudflare-ddns + image: ghcr.io/k8s-at-home/kubectl:v1.23.3 + envFrom: + - secretRef: + name: cloudflare-ddns + command: + - "/bin/bash" + - "/app/cloudflare-ddns.sh" + volumeMounts: + - name: cloudflare-ddns + mountPath: /app/cloudflare-ddns.sh + subPath: cloudflare-ddns.sh + readOnly: true + volumes: + - name: cloudflare-ddns + projected: + defaultMode: 0775 + sources: + - configMap: + name: cloudflare-ddns + items: + - key: cloudflare-ddns.sh + path: cloudflare-ddns.sh diff --git a/cluster/apps/networking/cloudflare-ddns/kustomization.yaml b/cluster/apps/networking/cloudflare-ddns/kustomization.yaml new file mode 100644 index 00000000..57df8484 --- /dev/null +++ b/cluster/apps/networking/cloudflare-ddns/kustomization.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - cron-job.yaml + - secret.sops.yaml +namespace: networking +configMapGenerator: + - name: cloudflare-ddns + files: + - cloudflare-ddns.sh +generatorOptions: + disableNameSuffixHash: true + annotations: + kustomize.toolkit.fluxcd.io/substitute: disabled diff --git a/cluster/apps/networking/kustomization.yaml b/cluster/apps/networking/kustomization.yaml index 8f492af6..ff2f69c5 100644 --- a/cluster/apps/networking/kustomization.yaml +++ b/cluster/apps/networking/kustomization.yaml @@ -1,4 +1,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: + - cloudflare-ddns - traefik diff --git a/configure.sh b/configure.sh index 209dfd01..c1b228c0 100755 --- a/configure.sh +++ b/configure.sh @@ -50,6 +50,8 @@ main() { > "${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" 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" # terraform diff --git a/tmpl/cluster/cloudflare-ddns-secret.sops.yaml b/tmpl/cluster/cloudflare-ddns-secret.sops.yaml new file mode 100644 index 00000000..9b6a330f --- /dev/null +++ b/tmpl/cluster/cloudflare-ddns-secret.sops.yaml @@ -0,0 +1,11 @@ +# yamllint disable +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare-ddns + namespace: networking +type: Opaque +stringData: + CLOUDFLARE_EMAIL: ${BOOTSTRAP_CLOUDFLARE_EMAIL} + CLOUDFLARE_APIKEY: ${BOOTSTRAP_CLOUDFLARE_APIKEY} + CLOUDFLARE_RECORD_NAME: ipv4.${BOOTSTRAP_CLOUDFLARE_DOMAIN}