Kubernetes Sicherheits- & Gesundheits-Checkliste: Ist Ihr Cluster produktionsbereit?
K8s Produktionsbereitschafts-Checkliste. RBAC, Network Policies, Resource Limits, Secrets, Monitoring.
Zusammenfassung
Ein Kubernetes-Cluster, der Workloads ausführt, ist nicht dasselbe wie ein produktionsbereiter Cluster. Diese Checkliste deckt die 8 am häufigsten fehlkonfigurierten Bereiche ab: RBAC, Network Policies, Resource Limits, Pod Security, Secrets Management, Helm-Hygiene, Monitoring und etcd-Backups. Gehen Sie jeden Abschnitt durch, führen Sie die Befehle aus und beheben Sie, was fehlschlägt. Wenn alles besteht, ist Ihr Cluster in besserem Zustand als 90% dessen, was wir auditieren.
Voraussetzungen
- kubectl-Zugang zum Cluster mit Admin-Berechtigungen
- Helm 3 installiert
- Ein funktionierender Cluster (gemanagt oder self-hosted)
- Zugang zum etcd des Clusters (für den Backup-Abschnitt, nur self-hosted)
Schritt 1: RBAC-Audit
Role-Based Access Control ist das Fundament der Cluster-Sicherheit. Fehlkonfiguriertes RBAC ist der häufigste Befund bei Kubernetes-Audits.
Überprivilegierte Service Accounts prüfen
# Alle ClusterRoleBindings auflisten, die cluster-admin gewähren
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") |
{name: .metadata.name, subjects: .subjects}'
# Erwartet: Nur Systemkomponenten und Ihr Admin-Benutzer
# Warnsignal: Anwendungs-Service-Accounts mit cluster-admin
Default Service Account Nutzung prüfen
# Pods finden, die den Default Service Account nutzen
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.serviceAccountName=="default" or
.spec.serviceAccountName==null) |
{namespace: .metadata.namespace, name: .metadata.name}'
Jeder Workload sollte seinen eigenen Service Account mit minimalen Berechtigungen haben. Der Default Service Account sollte nicht verwendet werden — niemals.
Automount von Service-Account-Tokens deaktivieren
# Prüfen, welche Pods unnötig Tokens einbinden
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.automountServiceAccountToken != false) |
{namespace: .metadata.namespace, name: .metadata.name}'
# Fix: Im Pod-Spec hinzufügen:
# spec:
# automountServiceAccountToken: false
Wenn ein Pod nicht mit der Kubernetes-API kommunizieren muss (die meisten tun es nicht), deaktivieren Sie das Token-Automounting. Ein kompromittierter Pod mit Service-Account-Token ist ein Eskalationspfad zum gesamten Cluster.
Bestehende Rollen auditieren
# Alle Rollen und ihre Berechtigungen auflisten
kubectl get roles --all-namespaces -o json | \
jq '.items[] | {namespace: .metadata.namespace,
name: .metadata.name, rules: .rules}'
# Achten Sie auf:
# - Wildcard-Verben: ["*"] (zu breit)
# - Wildcard-Ressourcen: ["*"] (zu breit)
# - Secrets-Zugriff ohne Rechtfertigung
# - Pod-Exec-Berechtigungen (erlaubt Shell in jeden Pod)
Schritt 2: Network Policies
Standardmäßig kann jeder Pod mit jedem anderen Pod kommunizieren. Das heißt, ein kompromittierter Pod kann Ihre Datenbank, Ihren Secrets-Store und Ihre internen APIs erreichen.
Prüfen, ob Network Policies existieren
# Alle Network Policies auflisten
kubectl get networkpolicies --all-namespaces
# Wenn das leer zurückkommt, haben Sie NULL Netzwerksegmentierung.
# Jeder Pod kann mit jedem anderen Pod kommunizieren.
Default-Deny-Policy
Beginnen Sie mit Default-Deny für jeden Namespace, dann whitelisten Sie das Benötigte:
# default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Gilt für alle Pods im Namespace
policyTypes:
- Ingress
- Egress
Spezifischen Traffic erlauben
# allow-frontend-to-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
DNS erlauben (erforderlich)
# allow-dns.yaml (in jedem Namespace mit deny-all anwenden)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Schritt 3: Resource Limits und Requests
Ohne Resource Limits kann ein fehlerhafter Pod den gesamten Node aushungern. Ohne Requests trifft der Scheduler schlechte Platzierungsentscheidungen.
Pods ohne Limits finden
# Pods ohne Resource Limits
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.containers[].resources.limits == null) |
{namespace: .metadata.namespace, name: .metadata.name}'
Empfohlene Resource-Konfiguration
resources:
requests:
cpu: "100m" # Garantiertes Minimum an CPU
memory: "128Mi" # Garantiertes Minimum an Memory
limits:
cpu: "500m" # Maximal erlaubte CPU
memory: "512Mi" # Maximal erlaubter Memory (OOMKilled bei Überschreitung)
Namespace-Level-Defaults mit LimitRange setzen
# limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
type: Container
Namespace-Level-Quotas setzen
# resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: namespace-quota
namespace: production
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50"
Schritt 4: Pod Security
Pods, die als Root mit vollen Capabilities laufen, sind das Kubernetes-Äquivalent zu "alles als Administrator auf Windows XP ausführen".
Privilegierte Pods finden
# Pods, die als Root oder im privilegierten Modus laufen
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(
.spec.containers[].securityContext.privileged == true or
.spec.containers[].securityContext.runAsUser == 0
) | {namespace: .metadata.namespace, name: .metadata.name}'
Sicheres Pod-Template
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
Pod Security Standards (PSS)
# Restricted Security Standard auf Namespace erzwingen
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
Schritt 5: Secrets Management
Kubernetes Secrets sind base64-kodiert, nicht verschlüsselt. Jeder mit Lesezugriff auf Secrets in einem Namespace kann sie trivial dekodieren.
etcd-Verschlüsselung prüfen
# Überprüfen, ob Verschlüsselung im Ruhezustand konfiguriert ist
ps aux | grep kube-apiserver | grep encryption-provider-config
# Wenn nicht vorhanden, werden Secrets im KLARTEXT in etcd gespeichert
etcd-Verschlüsselung im Ruhezustand aktivieren
# encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret:
- identity: {} # Fallback zum Lesen alter unverschlüsselter Secrets
Externes Secrets Management
Für Produktion sollten Sie externe Secrets-Stores in Betracht ziehen:
# External Secrets Operator mit HashiCorp Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: secret/data/production/database
property: password
Secret-Zugriff auditieren
# Wer kann Secrets im Production-Namespace lesen?
kubectl auth can-i get secrets -n production \
--as=system:serviceaccount:production:default
# Alle Subjekte mit Secrets-Zugriff auflisten
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | \
jq '.items[] | select(.roleRef.name as $role |
["admin","cluster-admin","edit"] | index($role)) |
{name: .metadata.name, subjects: .subjects}'
Schritt 6: Helm-Hygiene
Helm ist der häufigste Weg, Anwendungen in Kubernetes zu deployen, aber schlampige Helm-Praktiken erzeugen Sicherheits- und Zuverlässigkeitsprobleme.
Veraltete Charts prüfen
# Alle Helm-Releases mit Chart-Versionen auflisten
helm list --all-namespaces
# Nach Updates suchen
helm repo update
helm search repo --versions | head -5
Helm-Values-Hygiene
# Niemals Secrets in values.yaml — sie landen in:
# 1. Ihrem Git-Repository (selbst mit .gitignore, in der History)
# 2. Helm-Release-Secrets (als K8s-Secrets gespeichert)
# 3. Ihren CI/CD-Logs
# SCHLECHT:
# values.yaml
# database:
# password: "supersecret123"
# GUT: Externe Secrets referenzieren
# values.yaml
# database:
# existingSecret: "db-credentials"
# existingSecretKey: "password"
Chart-Versionen pinnen
# SCHLECHT: Keine Versions-Pinning
helm install myapp bitnami/postgresql
# GUT: Chart-Version pinnen
helm install myapp bitnami/postgresql --version 16.4.1
# BESSER: In einem Helmfile oder Values-File pinnen
# helmfile.yaml
releases:
- name: postgresql
chart: bitnami/postgresql
version: 16.4.1
values:
- values/postgresql.yaml
Helm-Release-History auditieren
# Release-History auf fehlgeschlagene Deployments prüfen
helm history myapp -n production
# Alte Revisionen bereinigen (letzte 5 behalten)
helm upgrade myapp -n production --history-max 5
Schritt 7: Monitoring mit Prometheus und Grafana
Wenn Sie nicht sehen können, was Ihr Cluster tut, können Sie ihn nicht absichern oder betreiben. Monitoring ist für Produktion nicht optional.
kube-prometheus-stack installieren
helm repo add prometheus-community \
https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace \
--set grafana.adminPassword="sofort-aendern" \
--set prometheus.prometheusSpec.retention=30d \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi
Essentielle Alerts konfigurieren
# Must-have Alerts (in kube-prometheus-stack enthalten):
# - KubePodCrashLooping: Pod startet wiederholt neu
# - KubePodNotReady: Pod hängt im nicht-ready Status
# - KubeDeploymentReplicasMismatch: Soll != Ist Replicas
# - KubeNodeNotReady: Node offline
# - KubeQuotaExceeded: Resource Quota erreicht
# - etcdHighNumberOfLeaderChanges: etcd-Instabilität
# - CPUThrottlingHigh: Pods werden CPU-gedrosselt
# Benutzerdefinierter Alert:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: custom-alerts
namespace: monitoring
spec:
groups:
- name: custom.rules
rules:
- alert: HighMemoryUsage
expr: |
container_memory_working_set_bytes /
container_spec_memory_limit_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} Memory > 90% des Limits"
Wichtige Grafana-Dashboards
# Diese Dashboard-IDs in Grafana importieren:
# 315 - Kubernetes Cluster-Übersicht
# 6417 - Kubernetes Pod-Ressourcen
# 13770 - Kubernetes Node-Ressourcen
# 14981 - kube-prometheus-stack Übersicht
Schritt 8: etcd-Backup und Disaster Recovery
etcd ist das Gehirn Ihres Kubernetes-Clusters. Verlieren Sie etcd, verlieren Sie alles. Dieser Abschnitt gilt primär für self-hosted Cluster — gemanagte Kubernetes-Anbieter kümmern sich um das etcd-Backup.
Manuelles etcd-Backup
# Snapshot erstellen
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Snapshot verifizieren
ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-$(date +%Y%m%d).db \
--write-table
Automatisierter Backup-CronJob
# etcd-backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: etcd-backup
namespace: kube-system
spec:
schedule: "0 */6 * * *" # Alle 6 Stunden
jobTemplate:
spec:
template:
spec:
containers:
- name: etcd-backup
image: bitnami/etcd:latest
command:
- /bin/sh
- -c
- |
etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M).db \
--endpoints=https://etcd:2379 \
--cacert=/certs/ca.crt \
--cert=/certs/server.crt \
--key=/certs/server.key
volumeMounts:
- name: backup-volume
mountPath: /backup
- name: etcd-certs
mountPath: /certs
restartPolicy: OnFailure
volumes:
- name: backup-volume
persistentVolumeClaim:
claimName: etcd-backup-pvc
- name: etcd-certs
secret:
secretName: etcd-certs
Restore testen
# KRITISCH: Restore auf einem Nicht-Produktions-Cluster testen
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-snapshot.db \
--data-dir=/var/lib/etcd-restored \
--initial-cluster="default=https://127.0.0.1:2380" \
--initial-advertise-peer-urls="https://127.0.0.1:2380" \
--name=default
# Ein Backup, dessen Restore Sie nicht getestet haben, ist kein Backup.
Problembehandlung & Hinweise
"Network Policies blockieren legitimen Traffic"
Beginnen Sie mit Default-Deny + Allow-DNS in einem Staging-Namespace. Fügen Sie dann Allow-Policies einzeln hinzu. Nutzen Sie kubectl describe networkpolicy um zu verifizieren, dass Selektoren zu Ihren Pods passen. Prüfen Sie, ob Ihr CNI-Plugin Network Policies unterstützt — nicht alle tun das (Flannel nicht, Calico und Cilium schon).
"Pods werden OOMKilled nach Setzen der Memory Limits"
Ihre Memory Limits sind zu niedrig. Prüfen Sie den tatsächlichen Memory-Verbrauch mit kubectl top pods oder Prometheus-Metriken bevor Sie Limits setzen. Setzen Sie das Limit auf das 1,5-2-fache des typischen Verbrauchs. Java-Anwendungen sind besonders trickreich — die JVM nutzt Memory außerhalb des Heaps.
"Pods starten nicht nach Aktivierung der Pod Security Standards"
Beginnen Sie mit dem warn-Modus statt enforce. Das loggt Verstöße, ohne zu blockieren. Beheben Sie Verstöße einzeln, dann wechseln Sie zu Enforce. Das häufigste Problem: Container, die als Root laufen müssen. Nutzen Sie Init-Container oder modifizierte Images, die als Non-Root laufen.
"Prometheus verbraucht zu viel Storage"
Reduzieren Sie die Retention-Period, erhöhen Sie das Scrape-Interval für weniger kritische Metriken oder nutzen Sie Remote Write zu einem Langzeitspeicher wie Thanos oder Cortex. Prüfen Sie auch auf High-Cardinality-Metriken (Metriken mit vielen einzigartigen Label-Kombinationen).
Prävention & Best Practices
Das Audit automatisieren
Nutzen Sie Tools, die Ihren Cluster kontinuierlich scannen: kube-bench (CIS-Benchmarks), Trivy (Container-Schwachstellen), Polaris (Konfigurations-Best-Practices), Falco (Runtime-Sicherheit). Führen Sie diese in CI/CD und als Cluster-residente Agents aus.
GitOps für alles
Jede Konfiguration in diesem Leitfaden sollte in einem Git-Repository leben und via GitOps (ArgoCD oder Flux) angewendet werden. Kein manuelles kubectl apply in Produktion. Wenn es nicht in Git ist, ist es nicht passiert.
Regelmäßiger Audit-Zeitplan
Führen Sie diese Checkliste vierteljährlich durch. Setzen Sie Kalendererinnerungen. Sicherheit verschlechtert sich mit der Zeit, wenn neue Workloads hinzukommen, Berechtigungen "vorübergehend" erweitert werden und Monitoring während Feature-Sprints vernachlässigt wird.
Upgrade-Strategie
Halten Sie Ihre Kubernetes-Version im unterstützten Fenster (neueste 3 Minor-Versionen). Patch-Versionen sollten innerhalb von 2 Wochen nach Release angewendet werden. Haben Sie ein getestetes Upgrade-Runbook, das Backup-Verifizierung vor jedem Upgrade beinhaltet.
Experten-Hilfe gebraucht?
Professionelles K8s-Audit? €150, 60-Min Review + priorisierter Aktionsbericht.
Jetzt buchen — €150100% Geld-zurück-Garantie