Ingress

ingress-nginx

Create namespace for ingress-nginx:

resource "kubernetes_namespace" "ingress_nginx" {
  metadata {
    name = "ingress-nginx"
    labels = {
      "pod-security.kubernetes.io/enforce" = "restricted"
    }
  }
}

Deploy the nginx-ingress-controller Helm chart from Bitnami:

resource "helm_release" "ingress_nginx" {
  name       = "ingress-nginx"
  namespace  = kubernetes_namespace.ingress_nginx.metadata.0.name
  repository = "oci://registry-1.docker.io/bitnamicharts"
  chart      = "nginx-ingress-controller"
  version    = "11.0.0"

  values = [yamlencode({

    # Needed so that FastAPI url_for respects https under nginx+uvicorn
    # See: https://stackoverflow.com/a/66244482
    proxySetHeaders = {
      X-Forwarded-For   = "$proxy_add_x_forwarded_for"
      X-Forwarded-Proto = "$scheme"
    }

    publishService = {
      # This MUST be enabled, otherwise Ingress resources will publish internal
      # node IPs via external-dns.
      enabled = true
    }

  })]
}

cert-manager

Create namespace for cert-manager:

resource "kubernetes_namespace" "cert_manager" {
  metadata {
    name = "cert-manager"
    labels = {
      "pod-security.kubernetes.io/enforce" = "restricted"
    }
  }
}

Deploy the cert-manager Helm chart:

# https://cert-manager.io/docs/installation/helm/
# https://github.com/cert-manager/cert-manager/blob/master/deploy/charts/cert-manager/values.yaml
resource "helm_release" "cert_manager" {
  name       = "cert-manager"
  namespace  = kubernetes_namespace.cert_manager.metadata.0.name
  repository = "https://charts.jetstack.io"
  chart      = "cert-manager"
  version    = "1.14.4"

  values = [yamlencode({
    global = {
      leaderElection = {
        # kube-system is managed by Autopilot so we move the leader election
        # namespace to cert-manager
        namespace = kubernetes_namespace.cert_manager.metadata.0.name
      }
    }
  })]
}

Create a SelfSigned issuer:

resource "kubernetes_manifest" "selfsigned_issuer" {
  manifest = {
    apiVersion = "cert-manager.io/v1"
    kind       = "ClusterIssuer"
    metadata = {
      name = "selfsigned"
    }
    spec = {
      selfSigned = {}
    }
  }
}

external-dns

Setting up external-dns requires a domain. This example uses CloudFlare as a provider.

Create sensitive variable for CloudFlare API token:

variable "cloudflare_api_token" {
  type = string

  sensitive = true
}

Add CloudFlare provider:

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.26.0"
    }
  }
  required_version = ">=1.0"
}

Add domain to locals:

locals {
  domain = "example.com"
}

Set CloudFlare zone:

data "cloudflare_zone" "dyff" {
  name = local.domain
}

Create namespace for external-dns:

resource "kubernetes_namespace" "external_dns" {
  metadata {
    name = "external-dns"
    labels = {
      "pod-security.kubernetes.io/enforce" = "restricted"
    }
  }
}

Deploy the external-dns Helm chart from Bitnami:

# https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/cloudflare.md
# https://github.com/bitnami/charts/blob/master/bitnami/external-dns/values.yaml
resource "helm_release" "external_dns" {
  name       = "external-dns"
  namespace  = kubernetes_namespace.external_dns.metadata.0.name
  repository = "oci://registry-1.docker.io/bitnamicharts"
  chart      = "external-dns"
  version    = "7.0.0"

  values = [yamlencode({
    domainFilters = [local.domain]
    policy        = "sync"
    provider      = "cloudflare"

    # This is required to prevent the different environments from destroying
    # each other's entries.
    txtOwnerId = local.name

    zoneIdFilters = [data.cloudflare_zone.dyff.zone_id]

    cloudflare = {
      proxied = true
    }
  })]

  set_sensitive {
    name  = "cloudflare.apiToken"
    value = var.cloudflare_api_token
  }
}