Terraform und Kubernetes
K8s Ressourcen und Helm via Terraform.
Lernziele
Nach dieser Lektion kannst du den Kubernetes- und Helm-Provider sinnvoll einsetzen und erklären, wann Terraform statt kubectl oder GitOps das richtige Werkzeug ist. Du konfigurierst die Provider-Anbindung an einen Cluster, legst Namespaces und Deployments an und installierst Helm-Releases. Du kennst die Grenzen — insbesondere fehlende Drift-Reconciliation.
Das Problem: zwei Welten, eine Plattform
Als Platform Engineer jonglierst du zwischen Cloud und Cluster. Das VPC und der EKS-Cluster kommen aus dem AWS-Provider — aber wer legt den ingress-nginx-Namespace an, installiert cert-manager und konfiguriert die RBAC-Rollen für Team A? Viele Teams lösen das mit Shell-Skripten, die helm install und kubectl apply hintereinander aufrufen. Das ist imperativ, nicht reviewbar und bricht bei Fehlern in der Mitte.
Terraform kann beide Ebenen in einem Plan vereinen — vorausgesetzt, du verstehst, was es gut kann und was nicht.
Kubernetes-Provider: einzelne Ressourcen deklarativ
Der kubernetes-Provider spricht die kube-API — analog zum AWS-Provider für EC2:
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.30"
}
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
# oder explizit für CI:
# host = var.cluster_endpoint
# cluster_ca_certificate = base64decode(var.cluster_ca)
# token = var.cluster_token
}
resource "kubernetes_namespace" "platform" {
metadata {
name = "platform-system"
labels = {
"pod-security.kubernetes.io/enforce" = "baseline"
}
}
}
resource "kubernetes_deployment" "demo" {
metadata {
name = "demo"
namespace = kubernetes_namespace.platform.metadata[0].name
}
spec {
replicas = 2
selector {
match_labels = { app = "demo" }
}
template {
metadata { labels = { app = "demo" } }
spec {
container {
name = "demo"
image = "nginx:1.27"
port { container_port = 80 }
}
}
}
}
}
Die HCL-Syntax für K8s-Ressourcen ist verbose — das ist der Preis für Deklarativität ohne YAML. Für komplexe Manifeste mit vielen Feldern nutzen manche Teams yamlencode() oder den kubernetes_manifest-Resource-Typ (unterstützt beliebige API-Objekte inkl. CRDs).
Helm-Provider: Charts als Releases
Für Drittsoftware ist der Helm-Provider meist praktikabler als jedes Chart-Feld in HCL nachzubauen:
terraform {
required_providers {
helm = {
source = "hashicorp/helm"
version = "~> 2.13"
}
}
}
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
}
resource "helm_release" "ingress_nginx" {
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
version = "4.11.0"
namespace = kubernetes_namespace.platform.metadata[0].name
values = [
yamlencode({
controller = {
replicaCount = 2
service = { type = "LoadBalancer" }
}
})
]
}
Terraform managed den Release-Zustand — Upgrades laufen über terraform plan, Rollback über Git-Revert + Apply, nicht über helm rollback. Das passt zum IaC-Workflow, widerspricht aber dem nativen Helm-Workflow mit Revisionen.
Wann Terraform, wann kubectl, wann GitOps?
| Ansatz | Stärke | Schwäche |
|---|---|---|
| Terraform (K8s/Helm) | Ein Plan für Cloud + Cluster; State-Tracking | Imperativ ausgeführt; kein kontinuierliches Reconcile |
| kubectl / Helm CLI | Schnell, direkt, Helm-Revisions | Kein Review-Flow, kein Team-State |
| Argo CD / Flux | Kontinuierliche Reconciliation, Drift-Fix | Zusätzliche Control Plane im Cluster |
Typisches Plattform-Muster: Terraform legt Cluster, Basiskomponenten und Shared Services an (Ingress, Monitoring, CSI-Driver). Produktteams deployen darüber hinaus per GitOps oder CI — nicht alles durch den Platform-Terraform-Stack.
Terraform + Kubernetes ist imperativ wie reines Helm: Ändert jemand danach mit kubectl edit am Deployment vorbei, merkt Terraform das erst beim nächsten plan. GitOps schließt genau diese Lücke.
Provider-Kette: Cloud → Cluster → Helm
In Produktion hängt die Provider-Konfiguration oft von Cloud-Outputs ab:
data "aws_eks_cluster" "platform" {
name = module.eks.cluster_name
}
data "aws_eks_cluster_auth" "platform" {
name = module.eks.cluster_name
}
provider "kubernetes" {
host = data.aws_eks_cluster.platform.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.platform.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.platform.token
}
provider "helm" {
kubernetes {
host = data.aws_eks_cluster.platform.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.platform.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.platform.token
}
}
Der Abhängigkeitsgraph stellt sicher: Erst Cluster, dann K8s-Ressourcen, dann Helm-Releases. Ein apply ohne funktionierenden Cluster schlägt beim K8s-Provider fehl — deshalb splitten viele Teams Cloud- und Cluster-Stack (Lektion 6).
Praxis: Namespace und ConfigMap in kind
kind create cluster --name tf-k8s-lab
mkdir k8s-lab && cd k8s-lab
cat > main.tf <<'EOF'
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.30"
}
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_namespace" "lab" {
metadata { name = "tf-lab" }
}
resource "kubernetes_config_map" "config" {
metadata {
name = "app-config"
namespace = kubernetes_namespace.lab.metadata[0].name
}
data = { greeting = "Hallo aus Terraform" }
}
EOF
terraform init && terraform apply -auto-approve
kubectl get cm -n tf-lab
terraform destroy -auto-approve
kind delete cluster --name tf-k8s-lab
Typische Stolperfallen
Server-Side Apply vs. Terraform: Ab Kubernetes 1.18+ nutzt kubectl SSA; der Provider hat eigene Merge-Semantik. Felder, die der Cluster defaulted, können zu perpetual Drift im Plan führen — lifecycle { ignore_changes = [...] } als gezielter Workaround.
Helm-CRDs: Charts mit CRDs (Prometheus Operator, Crossplane) brauchen oft einen zweistufigen Apply oder crds-Handling — sonst schlägt der Release fehl, weil die API-Types fehlen.
Alles in einen Stack: Cloud + 50 Helm-Releases in einem State — jeder Plan dauert Minuten, jeder Fehler blockiert alles. Basiskomponenten in Terraform, App-Deployments woanders.
Terraform vs. GitOps doppelt: Dasselbe Deployment per Terraform und Argo CD managen erzeugt Kampf um Ownership. Klare Grenze ziehen.
Interview-Vorbereitung
„Wie nutzt du Terraform mit Kubernetes?” — K8s-Provider für einzelne Ressourcen, Helm-Provider für Charts; Provider-Konfiguration aus Cloud-Outputs; typisch für Cluster-Bootstrap und Shared Services.
Follow-ups:
- „Terraform oder Argo CD?” — Terraform: imperative Apply-Zyklen, gut für Bootstrap; GitOps: kontinuierliches Reconcile, gut für App-Lifecycle.
- „Was passiert bei kubectl edit an einer TF-managed Resource?” — Drift; nächster Plan will zurück zum Code-Zustand.
- „Warum ist der Helm-Provider verbose?” — Values als HCL/yamlencode statt values.yaml — Trade-off für einen Plan über Cloud und Cluster.
Zusammenfassung
Der Kubernetes-Provider legt einzelne Cluster-Ressourcen deklarativ an; der Helm-Provider installiert Charts als Releases. Beide hängen typischerweise an Cloud-Outputs für die Authentifizierung. Terraform eignet sich für Cluster-Bootstrap und Shared Services — nicht als Ersatz für GitOps im App-Betrieb. Die imperativ ausgeführte Natur bleibt eine bewusste Grenze, die du im Interview benennen solltest.
In der nächsten Lektion automatisieren wir Plan und Apply in CI/CD-Pipelines.