← Alle Artikel
Zuletzt aktualisiert: 2026-03-30

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

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 — €150

100% Geld-zurück-Garantie

HR

Harald Roessler

Infrastructure Engineer mit 20+ Jahren Erfahrung. Gründer der DSNCON GmbH.