edu@dorner-it
WerkzeugeProfi30 min

Kubernetes Profi: Operators, Policies & GitOps

Was Cluster wirklich produktionsreif macht: Operators und CRDs, Network Policies, Pod Security, GitOps mit Argo CD oder Flux, Observability, Multi-Tenancy und eine Produktions-Checkliste.

Zuletzt aktualisiert: 12. Mai 2026

kubernetes-fortgeschritten hat dich auf das Niveau gebracht, das die meisten Apps in Prod brauchen. Dieser Kurs ist für alle, die einen Cluster betreiben — oder eine Plattform für mehrere Teams bauen. Hier zählt nicht mehr nur die App, sondern auch wie sicher, beobachtbar und nachvollziehbar dein Cluster läuft.

Lernziele

  • Du erweiterst die k8s-API mit CRDs und kennst das Operator-Pattern
  • Du segmentierst Netzwerk und Sicherheit mit Policies
  • Du betreibst Cluster nach dem GitOps-Prinzip
  • Du baust eine sinnvolle Observability-Basis
  • Du kennst die wichtigsten Multi-Tenancy- und DR-Patterns

Operators & Custom Resources

Eine CRD (Custom Resource Definition) ist k8s' Erweiterungs-API: du fügst eigene Ressourcen wie Database, Certificate oder Cluster hinzu. Ein Operator ist Code, der diese Ressourcen interpretiert und den Soll-Zustand herstellt — wie ein eingebauter Controller, nur für eure Domäne.

flowchart LR
    User[User] -- "kubectl apply" --> CR[Database CR]
    CR --> API[API-Server]
    API --> Op[Operator-Pod]
    Op -- "erzeugt" --> SS[StatefulSet]
    Op -- "erzeugt" --> Svc[Service]
    Op -- "erzeugt" --> Sec[Secret]
    Op -- "managed" --> Backup[Backup-CronJob]
apiVersion: db.example.com/v1
kind: Database
metadata:
  name: orders
spec:
  engine: postgres-16
  size: 100Gi
  backupSchedule: "0 2 * * *"

Statt 6 YAMLs schreibst du eine Ressource — der Operator macht den Rest.

Wann selbst bauen, wann kaufen: Für 95 % der gängigen Use Cases gibt es etablierte Operators (CloudNativePG, cert-manager, ArgoCD, Strimzi, …). Eigenen Operator nur für eure Domäne — und mit Frameworks wie kubebuilder oder Operator SDK.

Network Policies

Per Default kann jeder Pod jeden anderen Pod im Cluster erreichen. Network Policies sind White-Listing auf L3/L4-Ebene.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-api
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: api
      ports:
        - protocol: TCP
          port: 5432
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53

Wichtig: Network Policies brauchen einen CNI, der sie umsetzt (Calico, Cilium, …). Ohne wird die Policy zwar akzeptiert, aber niemand setzt sie durch.

Für L7-Regeln (HTTP-Pfade, mTLS) → Service Mesh (Istio, Linkerd) oder Cilium Network Policy.

Pod Security & Workload Hardening

Die alte PodSecurityPolicy ist tot. Heute gilt Pod Security Admission:

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit:   restricted
    pod-security.kubernetes.io/warn:    restricted

Drei Profile (privileged, baseline, restricted) decken die häufigsten Anforderungen ab. Für komplexere Regeln (RegoX/CEL) → OPA Gatekeeper oder Kyverno.

# Kyverno-Policy: keine `latest`-Tags
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest
spec:
  validationFailureAction: enforce
  rules:
    - name: validate-image-tag
      match:
        resources:
          kinds: [Pod]
      validate:
        message: "Image-Tag :latest ist nicht erlaubt."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

Workload-Hygiene-Checkliste

  • runAsNonRoot: true
  • readOnlyRootFilesystem: true (mit emptyDir/tmpfs für Schreib- pfade)
  • allowPrivilegeEscalation: false
  • seccompProfile: RuntimeDefault
  • Capabilities tropfweise droppen, nur Nötiges adden
  • Image aus interner Registry mit Signatur (cosign / Sigstore)

RBAC mit Augenmaß

Roles + RoleBindings im Namespace, ClusterRoles + ClusterRoleBindings darüber.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-a
  name: dev
rules:
  - apiGroups: ["", "apps"]
    resources: ["pods", "deployments", "services"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

Anti-Pattern: cluster-admin für CI-Pipelines. Stattdessen dedizierte ServiceAccounts mit minimalen Rechten — z. B. nur für einen Namespace und nur die nötigen Verbs.

GitOps — Argo CD oder Flux

GitOps dreht das Deployment um: nicht „CI pusht in Cluster", sondern Cluster pullt seinen Soll-Zustand aus Git.

flowchart LR
    Dev[Developer] -- "PR / merge" --> Git[(Git-Repo)]
    subgraph cluster["Cluster"]
        Agent["Argo CD / Flux"]
        State[Cluster-Zustand]
    end
    Git -- "watch" --> Agent
    Agent -- "apply / prune" --> State
    State -- "report" --> Agent
    Agent -- "Dashboard, Alerts" --> Dev

Vorteile:

  • Audit-Trail — jedes Change ist ein Commit.
  • Rollback = git revert.
  • Drift Detection — manuelle kubectl edits werden sichtbar (und optional automatisch zurückgerollt).
  • Cluster-Zugriff (kubeconfig) raus aus CI-Secrets.

Argo CD Beispiel (App-of-Apps verzichtet hier aus Platzgründen):

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/team/manifests
    path: prod/web
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Flux vs. Argo CD: Flux ist eher CLI/CRD-first, Argo CD bringt eine starke UI mit. Beide sind solide — Auswahl nach Team- Präferenz.

Observability — die 3 Säulen

Säule Stack (klassisch)
Metriken Prometheus + Grafana, OpenTelemetry-Metrics
Logs Loki / Elasticsearch + Fluent Bit
Traces Jaeger / Tempo + OpenTelemetry

Minimum für Prod:

  • Cluster-Metriken (kube-state-metrics, node-exporter)
  • App-Metriken (/metrics-Endpoints, OTel)
  • Strukturierte App-Logs (JSON) und zentrale Aggregation
  • Alerts auf SLOs (Latenz, Error-Rate) — nicht auf CPU%

SLOs statt CPU-Alarme

Lieber 99 % der Requests < 300 ms als „CPU > 80 %". CPU-Auslastung sagt nichts über Nutzererfahrung. SLOs definieren, was wirklich zählt.

Service Mesh — wann sinnvoll

Ein Service Mesh (Istio, Linkerd, Cilium Service Mesh) liefert:

  • mTLS zwischen Services automatisch
  • L7-Routing, Canary, Circuit Breaker
  • Detailtraces ohne Code-Änderung

Kosten: zusätzliche Komplexität, Latenz, Sidecar-Overhead (außer bei Cilium / Ambient Mesh).

Faustregel: Bei <20 Services und ohne harte mTLS-Pflicht meist over-engineered. Bei >50 Services oder regulierten Umgebungen oft Gold wert. Linkerd ist der schlankste Einstieg, Istio der mächtigste.

Multi-Tenancy

Mehrere Teams im gleichen Cluster — Optionen, von weich nach hart:

Pattern Trennung Aufwand
Namespace + RBAC logisch niedrig
+ ResourceQuota, LimitRange Ressourcen niedrig
+ NetworkPolicy Netzwerk mittel
Virtual Clusters (vcluster) starke API-Trennung mittel
Eigene Cluster pro Tenant physisch hoch

ResourceQuota Beispiel:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a
  namespace: team-a
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    persistentvolumeclaims: "10"
    services.loadbalancers: "2"

Cluster-Autoscaling

Zwei Ebenen, oft kombiniert:

  • Cluster Autoscaler — fügt Nodes hinzu/entfernt sie je nach Pending Pods.
  • Karpenter (AWS, generischer werdend) — schnelleres, granulareres Node-Scheduling als CA.
  • HPA / VPA auf Pod-Ebene (siehe Fortgeschritten-Kurs).

Best Practice: PodDisruptionBudgets setzen, damit Autoscaler nicht ganze Services aus Versehen leerräumt:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

Backup & Disaster Recovery

Cluster-Manifeste sind ersetzbar (kommen aus Git via GitOps). Daten nicht.

  • Velero — Backup von Ressourcen + PVs auf S3-kompatible Storage.
  • App-Level-Backups (Datenbank-Dumps via Operator) zusätzlich.
  • Regelmäßig Restore üben — ein Backup, das nie restored wurde, ist kein Backup.

Produktions-Checkliste

Vor dem Go-Live einmal durchgehen:

  • Probes (liveness, readiness, ggf. startup) realistisch konfiguriert
  • Requests/Limits gemessen statt geraten
  • PodDisruptionBudget für jedes Deployment > 1 Replica
  • RollingUpdate mit maxUnavailable: 0 (oder bewusst anders)
  • HPA + funktionierender metrics-server
  • Network Policies mindestens „Default Deny" pro Namespace
  • Pod Security Admission auf restricted für App-Namespaces
  • Images aus interner Registry, ohne :latest
  • Backups (Velero + DB-Operator) plus geübter Restore
  • Logs strukturiert, zentral aggregiert
  • Alerts auf SLOs, On-Call dokumentiert
  • GitOps eingerichtet, kein manuelles kubectl apply in Prod
  • Cluster- + App-Audit-Logs, Aufbewahrung geklärt
  • DR-Plan dokumentiert und einmal pro Quartal getestet

Häufige Stolperfallen

„Network Policy hat keine Wirkung"

Dein CNI unterstützt Policies nicht (flannel ohne extra Plugin), oder die Policy matcht keine Pods. kubectl describe netpol und kubectl get pods --show-labels helfen.

„Argo CD ist permanent out-of-sync"

Mutating Webhooks (z. B. Istio-Sidecar-Injection) verändern die Pods nach dem Apply. Lösung: Argo CD darauf konfigurieren, diese Felder zu ignorieren (ignoreDifferences).

„Operator hängt, CR bleibt Pending"

Logs des Operator-Pods checken — meist fehlende Rechte (RBAC) oder ein fehlerhafter externer Dienst (z. B. Cloud-Provider-API).

„Cluster Autoscaler fügt keine Nodes hinzu"

Pods sind unschedulable, aber Autoscaler sieht den Bedarf nicht — meist falsche Labels/Taints oder ein Node-Pool-Limit erreicht. Logs des Autoscalers sind eindeutig.

Praxis-Übung

Nimm den Cluster aus den vorigen Kursen und mach ihn ein Stück produktionstauglicher:

  1. Installiere cert-manager (ein klassischer Operator) und stelle ein TLS-Zertifikat für deinen Ingress automatisch aus (Staging-CA von Let's Encrypt).
  2. Setze eine Default-Deny Network Policy in einem Namespace, dann explizite Allow-Regeln nur für die nötigen Verbindungen.
  3. Aktiviere Pod Security Admission restricted und passe deine Deployments an (runAsNonRoot, readOnlyRootFilesystem).
  4. Installiere Argo CD, verbinde ein Git-Repo mit deinen Manifesten und deploye das hello-Beispiel per GitOps. Mach einen Commit und beobachte den Sync.
  5. Schau dir kube-prometheus-stack an (Helm Chart) und exploriere die Grafana-Dashboards.

Weiterführendes