talos-platform/main.tf
root 49cd3e023e
All checks were successful
Talos Platform Deploy / deploy (push) Successful in 15s
Forgejo + Longhorn mit Let's Encrypt, Traefik-Import
2026-11-13 22:39:17 +01:00

251 lines
6.7 KiB
HCL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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"
}
# Wer das Longhorn-Dashboard erreichen darf (Quell-IPs, wie sie bei Traefik ankommen)
variable "longhorn_allowed_cidrs" {
type = list(string)
default = ["172.25.64.0/24", "172.25.65.0/24"]
}
# 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 = "traefik.io/v1alpha1"
kind = "Middleware"
metadata = { name = "longhorn-allowlist", namespace = "longhorn-system" }
spec = { ipAllowList = { sourceRange = var.longhorn_allowed_cidrs } }
},
{
apiVersion = "networking.k8s.io/v1"
kind = "Ingress"
metadata = {
name = "longhorn"
namespace = "longhorn-system"
annotations = {
# Erst IP-Allowlist (nur LAN/DMZ), dann Login
"traefik.ingress.kubernetes.io/router.middlewares" = "longhorn-system-longhorn-allowlist@kubernetescrd,longhorn-system-longhorn-auth@kubernetescrd"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
}
}
spec = {
ingressClassName = "traefik"
tls = [{
secretName = "longhorn-tls"
hosts = [var.longhorn_host]
}]
rules = [{
host = var.longhorn_host
http = {
paths = [{
path = "/"
pathType = "Prefix"
backend = { service = { name = "longhorn-frontend", port = { number = 80 } } }
}]
}
}]
}
},
]
})]
}
# --- Forgejo (läuft als Docker auf BITCLAW01) über Traefik veröffentlichen ---
variable "forgejo_host" {
type = string
default = "forgejo.boday-it.ch"
}
resource "helm_release" "forgejo_proxy" {
name = "forgejo-proxy"
chart = "${path.module}/charts/forgejo-proxy"
namespace = "forgejo"
create_namespace = true
set {
name = "host"
value = var.forgejo_host
}
depends_on = [helm_release.traefik, helm_release.cluster_issuer]
}
output "traefik_lb_ip" {
value = var.traefik_lb_ip
}
# --- Traefik wieder in den State übernehmen (State ging beim Consul-Neustart verloren) ---
# Nach erfolgreichem Lauf dürfen die Blöcke bleiben oder entfernt werden.
import {
to = kubernetes_namespace.traefik
id = "traefik"
}
import {
to = helm_release.traefik
id = "traefik/traefik"
}