- Authors

- Name
- Phillip Pham
- @ddppham
Kubernetes Immutable Infrastructure: Read-Only Container fuer Hochsicherheit
TL;DR
- Immutable Infrastructure bedeutet: Container werden nie veraendert, sondern immer ersetzt. Kein SSH, kein kubectl exec, keine Live-Patches.
- Read-Only Root Filesystem verhindert, dass Angreifer Malware ins Dateisystem schreiben -- selbst nach einem Container-Compromise.
- Distroless Images enthalten weder Shell noch Package-Manager und reduzieren die Angriffsflaeche um bis zu 90%.
- GitOps-only Changes stellen sicher, dass jede Aenderung versioniert, reviewed und auditierbar ist.
- Die Kombination aus Immutability, Pod Security Standards und RBAC bildet eine Defense-in-Depth-Strategie.
Was Immutable Infrastructure bedeutet
Das Prinzip ist einfach: Einmal gebaut, nie veraendert. Container werden nicht gepatcht, nicht konfiguriert, nicht debugged. Jede Aenderung erzeugt ein neues Image, das durch die CI/CD-Pipeline laeuft und das alte ersetzt.
Das steht im Kontrast zu traditioneller Infrastruktur, bei der Admins sich per SSH auf Server verbinden, Pakete updaten und Konfigurationen anpassen. Dieses Modell fuehrt zu Configuration Drift -- dem schleichenden Auseinanderlaufen von Soll- und Ist-Zustand.
In Kubernetes wird Immutability auf mehreren Ebenen durchgesetzt:
| Ebene | Mutable (traditionell) | Immutable |
|---|---|---|
| Container-Image | Patchen im laufenden Container | Neues Image bauen und deployen |
| Dateisystem | Schreibbar | Read-Only Root Filesystem |
| Konfiguration | SSH-Zugriff und manuelle Aenderung | ConfigMaps/Secrets ueber Git |
| Debugging | kubectl exec, SSH | Logs, Metrics, Tracing |
| Updates | In-Place-Update | Rolling Replacement |
Read-Only Root Filesystem konfigurieren
Das Read-Only Root Filesystem ist die wichtigste technische Massnahme fuer Immutability. Es verhindert, dass Prozesse im Container Dateien schreiben koennen -- inklusive Malware, Cryptominer oder Reverse Shells.
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-webapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-webapp
template:
metadata:
labels:
app: secure-webapp
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65534
fsGroup: 65534
containers:
- name: webapp
image: gcr.io/distroless/java17-debian12:nonroot
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 64Mi
- name: cache
emptyDir:
sizeLimit: 128Mi
Wichtige Details:
readOnlyRootFilesystem: truemacht das gesamte Container-Dateisystem read-only.- EmptyDir-Volumes fuer
/tmpund Cache-Verzeichnisse bieten beschreibbare Bereiche, die beim Pod-Neustart geloescht werden. medium: Memoryfuer/tmpnutzt tmpfs -- Daten werden nie auf die Disk geschrieben.capabilities: drop: ALLentfernt alle Linux-Capabilities.
Typische Probleme und Loesungen
Viele Anwendungen erwarten ein beschreibbares Dateisystem. Hier die haeufigsten Faelle:
| Problem | Ursache | Loesung |
|---|---|---|
| Container startet nicht | App schreibt PID-File | EmptyDir fuer PID-Verzeichnis |
| Log-Fehler | App schreibt in /var/log | Logging auf stdout/stderr umstellen |
| Session-Fehler | App schreibt Session-Files | Redis/Memcached als Session-Store |
| SSL-Fehler | App aktualisiert CA-Bundle | CA-Bundle als ConfigMap mounten |
| Font-Rendering | App cached Fonts | EmptyDir fuer Font-Cache |
Distroless Images: Minimale Angriffsflaeche
Distroless Images enthalten nur die Anwendung und ihre Runtime-Abhaengigkeiten. Keine Shell, kein Package-Manager, kein curl, kein wget. Ein Angreifer, der in einen Distroless Container eindringt, findet keine Tools vor.
Vergleich: Standard vs. Slim vs. Distroless
| Image-Typ | Groesse (Java-App) | Packages | Shell | Angriffsflaeche |
|---|---|---|---|---|
| ubuntu:24.04 | 450 MB | ca. 100 | Ja | Hoch |
| eclipse-temurin:17-jre-alpine | 180 MB | ca. 30 | Ja (ash) | Mittel |
| gcr.io/distroless/java17 | 220 MB | ca. 15 | Nein | Niedrig |
| gcr.io/distroless/static | 2 MB | 0 | Nein | Minimal |
Multi-Stage Build fuer Distroless
# Build Stage
FROM eclipse-temurin:17-jdk AS build
WORKDIR /app
COPY . .
RUN ./gradlew bootJar --no-daemon
# Runtime Stage -- Distroless
FROM gcr.io/distroless/java17-debian12:nonroot
COPY /app/build/libs/app.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Fuer Go-Anwendungen ist das noch radikaler:
FROM golang:1.22 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /server .
FROM gcr.io/distroless/static:nonroot
COPY /server /server
ENTRYPOINT ["/server"]
Das resultierende Image ist wenige MB gross und enthaelt ausschliesslich die kompilierte Binary.
CVE-Vergleich
Ein Trivy-Scan zeigt den Unterschied deutlich:
# Ubuntu-basiertes Image
trivy image myapp:ubuntu
# Ergebnis: 45 LOW, 12 MEDIUM, 3 HIGH, 1 CRITICAL
# Distroless Image
trivy image myapp:distroless
# Ergebnis: 2 LOW, 0 MEDIUM, 0 HIGH, 0 CRITICAL
Weniger Packages bedeutet weniger CVEs bedeutet weniger Patching-Aufwand. Fuer eine umfassende Sicherheitsstrategie: Kubernetes Security Hardening.
No-SSH-Policy durchsetzen
In einer Immutable Infrastructure gibt es keinen SSH-Zugang zu Nodes oder Containern. Auch kubectl exec wird eingeschraenkt oder komplett deaktiviert.
kubectl exec per RBAC einschraenken
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: no-exec-policy
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
# pods/exec ist NICHT enthalten -- damit kein exec moeglich
Fuer Notfaelle kann ein separates Break-Glass-ClusterRole existieren, das nur nach einem Approval-Prozess zugewiesen wird:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: break-glass-exec
annotations:
description: "Nur fuer Notfaelle -- erfordert Ticket und Approval"
rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
Detaillierte RBAC-Konfiguration mit Namespace-Isolation: RBAC fuer Enterprise-Umgebungen.
Pod Security Standards fuer Immutability
Pod Security Standards (PSS) auf Namespace-Ebene erzwingen Immutability-Regeln fuer alle Pods:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Das restricted-Profil erzwingt unter anderem:
runAsNonRoot: trueallowPrivilegeEscalation: false- Alle Capabilities muessen gedroppt werden
- Bestimmte Volume-Typen sind verboten
Fuer die vollstaendige PSS-Konfiguration: Pod Security Standards im Detail.
GitOps-only Changes: Kein manuelles kubectl apply
In einer Immutable Infrastructure werden Aenderungen ausschliesslich ueber Git vorgenommen. Kein Entwickler fuehrt kubectl apply manuell aus. Stattdessen:
- Aenderung im Git-Repository vornehmen (Pull Request)
- Review und Approval durch Kollegen
- Merge in den Main-Branch
- GitOps-Controller (Flux oder ArgoCD) synchronisiert den Cluster
Flux-Konfiguration fuer Immutable Deployments
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: production-apps
namespace: flux-system
spec:
interval: 1m
url: https://github.com/company/k8s-production
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: production-apps
namespace: flux-system
spec:
interval: 5m
path: ./clusters/production
prune: true
sourceRef:
kind: GitRepository
name: production-apps
validation: client
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: secure-webapp
namespace: production
Die Option prune: true ist entscheidend: Ressourcen, die aus dem Git-Repository entfernt werden, werden auch aus dem Cluster geloescht. Damit bleibt der Cluster-Zustand immer identisch mit dem Git-Repository.
Fuer eine vollstaendige GitOps-Einfuehrung mit Flux: GitOps mit Flux.
Debugging ohne SSH und exec
Ohne SSH und kubectl exec stellt sich die Frage: Wie debugged man Probleme? Die Antwort liegt in Observability:
Logs
# Logs eines Pods anzeigen
kubectl logs deployment/secure-webapp -n production --tail=100
# Logs aller Container eines Pods
kubectl logs pod/secure-webapp-abc123 -n production --all-containers
# Vorherige Container-Instanz (nach Crash)
kubectl logs pod/secure-webapp-abc123 -n production --previous
Ephemeral Debug Containers
Seit Kubernetes 1.25 gibt es Ephemeral Containers. Sie werden temporaer zu einem laufenden Pod hinzugefuegt, ohne den Pod neu zu starten:
# Debug-Container an laufenden Pod anhaengen
kubectl debug -it secure-webapp-abc123 \
-n production \
--image=busybox:latest \
--target=webapp
Der Debug-Container teilt den Process-Namespace des Ziel-Containers und kann dessen Prozesse sehen. Das Original-Container-Image bleibt unveraendert.
Metrics und Tracing
Statt interaktivem Debugging setzen Immutable-Infrastructure-Teams auf:
- Prometheus + Grafana: Metriken fuer CPU, Memory, Request-Latenz, Error-Rates
- Jaeger oder Tempo: Distributed Tracing fuer Request-Flow-Analyse
- Loki oder Elasticsearch: Zentrale Log-Aggregation mit Volltextsuche
Image-Signing und Supply Chain Security
Immutable Images muessen verifizierbar sein. Cosign und Sigstore stellen sicher, dass nur signierte Images deployed werden:
# Image signieren
cosign sign --key cosign.key gcr.io/company/webapp:v1.2.3
# Signatur verifizieren
cosign verify --key cosign.pub gcr.io/company/webapp:v1.2.3
In Kubernetes erzwingt ein Admission Controller (z.B. Kyverno oder OPA Gatekeeper) die Signatur-Pruefung:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "gcr.io/company/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
Checkliste: Immutable Infrastructure Readiness
| Bereich | Massnahme | Status |
|---|---|---|
| Images | Distroless oder Minimal-Base-Images | -- |
| Images | Multi-Stage Builds, keine Build-Tools im Runtime | -- |
| Images | Image-Signing mit Cosign | -- |
| Dateisystem | readOnlyRootFilesystem: true | -- |
| Dateisystem | EmptyDir fuer tmp und Cache | -- |
| Zugriff | No-SSH-Policy dokumentiert | -- |
| Zugriff | kubectl exec per RBAC eingeschraenkt | -- |
| Zugriff | Break-Glass-Prozess definiert | -- |
| Deployment | GitOps-only (Flux oder ArgoCD) | -- |
| Deployment | Kein manuelles kubectl apply | -- |
| Security | Pod Security Standards: restricted | -- |
| Security | Capabilities: drop ALL | -- |
| Observability | Logs, Metrics, Tracing statt exec | -- |
Fuer die Security-Audit-Perspektive auf diese Massnahmen: Kubernetes Security Audit.
Fazit
Immutable Infrastructure ist kein theoretisches Ideal, sondern eine praktische Notwendigkeit fuer sicherheitskritische Kubernetes-Umgebungen. Die Kombination aus Read-Only Root Filesystem, Distroless Images, No-SSH-Policy und GitOps-only Changes reduziert die Angriffsflaeche drastisch.
Der Einstieg erfolgt schrittweise:
- Sofort: readOnlyRootFilesystem fuer neue Deployments aktivieren.
- Kurzfristig: Bestehende Images auf Distroless umstellen.
- Mittelfristig: GitOps-only-Workflow einfuehren, manuelles kubectl apply abschaffen.
- Langfristig: kubectl exec per RBAC einschraenken, Observability-Stack ausbauen.
Jeder Schritt erhoet die Sicherheit, auch ohne die gesamte Kette sofort umzusetzen.
Wenn Sie Unterstuetzung bei der Einfuehrung von Immutable Infrastructure in Ihren Kubernetes-Clustern benoetigen, melden Sie sich gerne fuer ein unverbindliches Gespraech unter /kontakt.
Kubernetes-Beratung gesucht?
Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
BSI-Grundschutz für Kubernetes-Cluster umsetzen
BSI IT-Grundschutz für Kubernetes umsetzen: Baustein SYS.1.6 Containerisierung mit Pod Security Standards, Audit-Logging und Netzwerksegmentierung.
GitOps Secrets: SOPS und Sealed Secrets im Vergleich
Secrets sicher in Git verwalten mit Sealed Secrets, Mozilla SOPS und External Secrets Operator. Praxisvergleich für GitOps-Workflows.
Falco: Runtime Security für Kubernetes-Cluster
Falco erkennt verdächtiges Verhalten in Kubernetes-Containern zur Laufzeit. Installation, Custom Rules und Alerting praxisnah erklärt.
Seccomp und AppArmor: Container-Syscalls einschränken
Seccomp und AppArmor schützen Container auf Kernel-Ebene. So erstellt ihr Seccomp-Profile und AppArmor-Policies für eure Kubernetes-Pods.
Trivy: Container-Schwachstellen automatisch scannen
Trivy findet Schwachstellen in Container-Images, Dateisystemen und Kubernetes-Clustern. Anleitung für CLI, CI/CD-Integration und den Trivy Operator.