# talos-platform – gemeinsame Cluster-Dienste für Produktion (Talos) # Hier liegt alles, was ALLE Apps brauchen. Apps (boday_tf, ...) bringen nur ihr eigenes Chart mit. terraform { required_providers { kubernetes = { source = "hashicorp/kubernetes" version = "~> 2.26" } helm = { source = "hashicorp/helm" version = "~> 2.12" } } backend "consul" { address = "consul:8500" path = "terraform/states/talos_platform" } } variable "kubeconfig_path" { type = string default = "./talos_kubeconfig" } # Version des Traefik-Helm-Charts (traefik/traefik). Bewusst pinnen, damit Updates geplant passieren. variable "traefik_chart_version" { type = string } provider "kubernetes" { config_path = var.kubeconfig_path } provider "helm" { kubernetes { config_path = var.kubeconfig_path } } # Feste IP für Traefik aus dem MetalLB-Pool. Auf diese IP zeigt das OPNsense-Portforwarding (80/443). variable "traefik_lb_ip" { type = string } # Hostname des Longhorn-Dashboards (internes DNS, z.B. Unbound-Override in OPNsense) variable "longhorn_host" { type = string default = "longhorn.boday.lan" } # BasicAuth für Longhorn als htpasswd-Zeile, z.B.: htpasswd -nbB admin 'geheim' variable "longhorn_basic_auth" { type = string sensitive = true } # --- TLS: cert-manager + Let's Encrypt ------------------------------------- # Version des cert-manager-Helm-Charts (jetstack/cert-manager), bewusst gepinnt variable "cert_manager_chart_version" { type = string } # Kontakt-E-Mail für Let's Encrypt (Ablauf-Warnungen) variable "acme_email" { type = string } resource "helm_release" "cert_manager" { name = "cert-manager" repository = "https://charts.jetstack.io" chart = "cert-manager" version = var.cert_manager_chart_version namespace = "cert-manager" create_namespace = true timeout = 600 atomic = true values = [yamlencode({ crds = { enabled = true } })] } # Eigener kleiner Chart, damit Terraform beim Plan die cert-manager-CRDs noch nicht kennen muss resource "helm_release" "cluster_issuer" { name = "cluster-issuer" chart = "${path.module}/charts/cluster-issuer" namespace = "cert-manager" set { name = "email" value = var.acme_email } depends_on = [helm_release.cert_manager] } # Kein hostPort mehr -> der Talos-Standard (PSA "baseline") reicht resource "kubernetes_namespace" "traefik" { metadata { name = "traefik" } } # Traefik hinter einem MetalLB-LoadBalancer: VIP wandert bei Node-Ausfall auf den anderen Node resource "helm_release" "traefik" { name = "traefik" repository = "https://traefik.github.io/charts" chart = "traefik" version = var.traefik_chart_version namespace = kubernetes_namespace.traefik.metadata[0].name timeout = 600 atomic = true values = [yamlencode({ deployment = { replicas = 2 } # Möglichst je ein Pod pro Node (weich: bei nur einem Worker laufen beide dort) topologySpreadConstraints = [{ maxSkew = 1 topologyKey = "kubernetes.io/hostname" whenUnsatisfiable = "ScheduleAnyway" labelSelector = { matchLabels = { "app.kubernetes.io/name" = "traefik" "app.kubernetes.io/instance" = "traefik-traefik" } } }] service = { type = "LoadBalancer" annotations = { "metallb.io/loadBalancerIPs" = var.traefik_lb_ip } spec = { # Echte Client-IP bleibt erhalten (Django-Logs, CrowdSec) externalTrafficPolicy = "Local" } } ingressClass = { enabled = true isDefaultClass = true name = "traefik" } providers = { kubernetesIngress = { enabled = true } kubernetesCRD = { enabled = true } } # Gemeinsame Routen der Plattform: Longhorn-Dashboard mit Login hinter Traefik extraObjects = [ { apiVersion = "v1" kind = "Secret" metadata = { name = "longhorn-basic-auth", namespace = "longhorn-system" } type = "Opaque" stringData = { users = var.longhorn_basic_auth } }, { apiVersion = "traefik.io/v1alpha1" kind = "Middleware" metadata = { name = "longhorn-auth", namespace = "longhorn-system" } spec = { basicAuth = { secret = "longhorn-basic-auth" } } }, { apiVersion = "networking.k8s.io/v1" kind = "Ingress" metadata = { name = "longhorn" namespace = "longhorn-system" annotations = { "traefik.ingress.kubernetes.io/router.middlewares" = "longhorn-system-longhorn-auth@kubernetescrd" } } spec = { ingressClassName = "traefik" rules = [{ host = var.longhorn_host http = { paths = [{ path = "/" pathType = "Prefix" backend = { service = { name = "longhorn-frontend", port = { number = 80 } } } }] } }] } }, ] })] } output "traefik_lb_ip" { value = var.traefik_lb_ip } # Adopt Traefik into the state again (lost when Consul restarted) import { to = kubernetes_namespace.traefik id = "traefik" } import { to = helm_release.traefik id = "traefik/traefik" }