2026-11-08 01:25:56 +00:00
|
|
|
|
# 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-08 02:08:54 +00:00
|
|
|
|
# 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-13 15:46:13 +00:00
|
|
|
|
# --- 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]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-08 01:25:56 +00:00
|
|
|
|
# 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
|
2026-11-13 15:46:13 +00:00
|
|
|
|
timeout = 600
|
|
|
|
|
|
atomic = true
|
2026-11-08 01:25:56 +00:00
|
|
|
|
|
|
|
|
|
|
values = [yamlencode({
|
|
|
|
|
|
deployment = { replicas = 2 }
|
|
|
|
|
|
|
2026-11-13 15:46:13 +00:00
|
|
|
|
# Möglichst je ein Pod pro Node (weich: bei nur einem Worker laufen beide dort)
|
2026-11-08 01:25:56 +00:00
|
|
|
|
topologySpreadConstraints = [{
|
|
|
|
|
|
maxSkew = 1
|
|
|
|
|
|
topologyKey = "kubernetes.io/hostname"
|
2026-11-09 07:05:00 +00:00
|
|
|
|
whenUnsatisfiable = "ScheduleAnyway"
|
2026-11-08 01:25:56 +00:00
|
|
|
|
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 }
|
|
|
|
|
|
}
|
2026-11-08 02:08:54 +00:00
|
|
|
|
|
|
|
|
|
|
# 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 } } }
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
2026-11-08 01:25:56 +00:00
|
|
|
|
})]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
output "traefik_lb_ip" {
|
|
|
|
|
|
value = var.traefik_lb_ip
|
|
|
|
|
|
}
|
2026-11-13 18:18:48 +00:00
|
|
|
|
|
|
|
|
|
|
# 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"
|
|
|
|
|
|
}
|