·
KubernetesNetworkPolicySecurityCNIGitOpsPlattformbetrieb

Kubernetes NetworkPolicies: Default Deny ohne Ausfälle einführen

Wie Sie Kubernetes NetworkPolicies und Default Deny kontrolliert einführen, DNS und externe Ziele testen und Ausfälle im laufenden Betrieb vermeiden.

Die Anwendung läuft stabil. Frontend, API und Datenbank sprechen miteinander, das Monitoring bekommt seine Metriken, externe Dienste sind erreichbar.

Dann wird im Namespace eine Default-Deny-Policy aktiviert.

Wenige Minuten später schlagen Datenbankverbindungen fehl. Der OAuth-Login funktioniert nicht mehr, das Telemetrie-Backend bekommt keine Daten, einige Pods scheitern an ihrer Startup Probe. In den Anwendungen ist kein Fehler. Ihre tatsächlichen Netzwerkabhängigkeiten waren vor der Umstellung schlicht nicht bekannt.

NetworkPolicies sind der richtige Weg, Workloads in Kubernetes voneinander zu isolieren. Ein unvorbereitetes default-deny macht aus bisher implizit erlaubter Kommunikation aber einen Ausfall, der schwer zu diagnostizieren ist. Dieser Beitrag zeigt, wie sich das vermeiden lässt.

Ein gespeichertes Policy-Objekt beweist noch keine Isolation

Kubernetes definiert mit NetworkPolicy nur die API für die Kontrolle von Verkehr auf Layer 3 und 4. Durchgesetzt wird sie vom Netzwerk-Plugin. Die Dokumentation ist an dieser Stelle deutlich: “Creating a NetworkPolicy resource without a controller that implements it will have no effect.”

Der API-Server nimmt Ihre Policy also an, auch wenn im Cluster niemand sie umsetzt. kubectl get networkpolicies --all-namespaces liefert dann eine saubere Liste und trotzdem keine Isolation.

Vor der Planung sollte deshalb geklärt sein, welches CNI-Plugin in welcher Version läuft, ob es NetworkPolicy für Ingress und Egress umsetzt, welche Einschränkungen es bei IPv6, SCTP, Portbereichen oder hostNetwork gibt, ob die Distribution zusätzlich eigene Policy-Ressourcen mitbringt und wie sich verworfene Verbindungen beobachten lassen.

Beweisen lässt sich die Durchsetzung nur mit einem echten Verbindungstest: zwei Pods in einem Test-Namespace, eine Default-Deny-Policy dazwischen, ein neuer Verbindungsversuch. Erst wenn der scheitert, arbeitet das Plugin tatsächlich.


Isolation entsteht auf beiden Seiten

Ein Pod ist standardmäßig weder für eingehenden noch für ausgehenden Verkehr isoliert. Er wird es in einer Richtung erst dann, wenn mindestens eine NetworkPolicy ihn auswählt und diese Richtung in policyTypes steht. Ingress und Egress werden unabhängig voneinander betrachtet.

Eine Default-Deny-Policy für beide Richtungen sieht so aus:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Der leere podSelector wählt alle Pods im Namespace aus, auch die, die erst später dazukommen. Weil die Policy keine Regeln für ingress oder egress enthält, wird nichts freigegeben. Genau diese Vollständigkeit macht Default Deny sinnvoll und bei einer unvorbereiteten Einführung riskant.

Freigaben sind additiv. Policies haben keine Reihenfolge und überschreiben sich nicht gegenseitig: “Network policies do not conflict; they are additive.” Erlaubt Policy A Port 8080 und Policy B Port 9090, sind beide Ports offen. Ein explizites Deny mit höherer Priorität kennt die Standard-API nicht. Die Ablehnung entsteht allein daraus, dass ein Pod isoliert ist und keine passende Regel greift.

Daraus folgt der häufigste Diagnosefehler beim Einstieg. Zwischen zwei isolierten Pods müssen beide Seiten die Verbindung erlauben, die Egress-Regeln der Quelle und die Ingress-Regeln des Ziels. Das Datenbankteam sieht also seine korrekte Ingress-Freigabe, die Anwendung kommt trotzdem nicht durch, und die fehlende Regel liegt im Anwendungs-Namespace.


Erst wissen, wer mit wem spricht

Vor der Isolation braucht jede Anwendung eine dokumentierte Kommunikationsmatrix. Für einen überschaubaren Shop sieht die etwa so aus:

QuelleZielPortZweck
GatewayFrontendTCP 8080externer HTTP-Traffic
FrontendAPITCP 8080Anwendungsaufrufe
APIPostgreSQLTCP 5432persistente Daten
alle PodsCluster-DNSUDP/TCP 53Namensauflösung
APIOIDC ProviderTCP 443Tokenprüfung
APIObject StorageTCP 443Dokumente
PodsTelemetrie-CollectorTCP 4317OTLP/gRPC
OperatorKubernetes APITCP 443Reconciliation

Diese Matrix lässt sich nicht allein aus Deployments und Service-Objekten ableiten. Brauchbare Quellen sind Architektur- und Betriebsdokumentation, die Anwendungskonfiguration, DNS-Abfragen, Connection Tracking, Flow Logs des Netzwerk-Plugins, Firewall- und Proxy-Logs und nicht zuletzt das Gespräch mit den Anwendungsverantwortlichen.

Beobachteter Traffic ist dabei nicht automatisch notwendiger Traffic. Ein fehlkonfigurierter Agent oder ein kompromittierter Pod erzeugt ebenfalls Verbindungen. Für jeden Flow gehört deshalb geklärt, ob er fachlich gebraucht wird, ob sich das Ziel stabil über Labels beschreiben lässt, ob eine engere Portfreigabe reicht und wer Eigentümer von Quelle und Ziel ist.


DNS fällt zuerst aus

Eine Default-Deny-Egress-Policy blockiert auch DNS. Die Kubernetes-Dokumentation warnt an dieser Stelle ausdrücklich, dass dann eine eigene Policy für den Zugriff auf den DNS-Dienst nötig ist. In den Anwendungslogs sieht der Ausfall aber selten nach einem Netzwerkproblem aus, sondern nach einem Fehler in der Anwendung oder im TLS-Handshake:

dial tcp: lookup database.data.svc.cluster.local on 10.96.0.10:53:
read udp 10.244.3.17:41232->10.96.0.10:53: i/o timeout

Die passende Freigabe:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-cluster-dns
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Die Labels müssen Sie im eigenen Cluster prüfen, denn nicht jede Distribution nutzt dieselben:

kubectl get pods --all-namespaces --show-labels | grep -E 'coredns|kube-dns'

Auch der Pfad kann anders aussehen. Mit NodeLocal DNSCache läuft die Auflösung über eine Adresse auf dem Node, und die Policy muss gegen genau diese Architektur getestet werden. Nur UDP 53 freizugeben reicht ohnehin nicht, DNS nutzt für größere Antworten auch TCP.


Zwei Leerzeichen entscheiden über die Reichweite

Diese Regel erlaubt Pods mit app=gateway aus dem Namespace edge:

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: edge
        podSelector:
          matchLabels:
            app: gateway

Diese hier bedeutet etwas anderes:

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: edge
      - podSelector:
          matchLabels:
            app: gateway

Im ersten Fall stehen beide Selektoren im selben Listenelement und werden mit UND verknüpft. Im zweiten Fall sind es zwei Listenelemente, also eine ODER-Verknüpfung: erlaubt sind alle Pods aus dem Namespace edge und zusätzlich alle Pods mit app=gateway aus dem Namespace der Policy. Die Freigabe ist damit erheblich breiter als beabsichtigt. Die Dokumentation empfiehlt für solche Fälle, sich mit kubectl describe anzusehen, wie Kubernetes die Policy interpretiert hat. Das gehört in jedes Review von Policy-Änderungen.

Namespaces werden übrigens nicht über ein Namensfeld referenziert, sondern über Labels. Kubernetes setzt dafür auf jedem Namespace das unveränderliche Label kubernetes.io/metadata.name mit dem Namespace-Namen als Wert. Eigene Labels wie environment=production oder network-zone=backend sind für Gruppen von Namespaces nützlich, taugen aber nur dann als Sicherheitsgrenze, wenn niemand sie selbst setzen kann. Wenn ein Team seinen Namespace eigenständig als network-zone=trusted markieren darf, ist das Label kein Sicherheitsmerkmal, sondern eine Absichtserklärung. RBAC auf Namespace-Änderungen und Admission-Regeln für geschützte Labels gehören deshalb zur Policy-Architektur dazu.


Beispiel: den Checkout-Dienst freigeben

Der Checkout-Dienst im Namespace shop braucht eingehenden Traffic vom Gateway, DNS, PostgreSQL im Namespace data und HTTPS zu einem externen Zahlungsdienst. Nach der Default-Deny-Policy von oben kommen dazu drei Freigaben.

Ingress vom Gateway:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-gateway-to-checkout
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: checkout
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: edge
          podSelector:
            matchLabels:
              app: gateway
      ports:
        - protocol: TCP
          port: 8080

Der Port ist hier der Container-Zielport, nicht der Service-Port. Die beiden können voneinander abweichen, und das ist eine klassische Fehlerquelle.

Egress zur Datenbank:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-checkout-to-postgres
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: checkout
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: data
          podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

Ist PostgreSQL im Namespace data ebenfalls für Ingress isoliert, braucht es dort die Gegenregel mit demselben Port und vertauschten Selektoren. Ohne sie greift die Isolation auf der Zielseite, egal wie sauber die Egress-Regel im Namespace shop aussieht.

Egress zum externen Zahlungsdienst:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-checkout-to-payment-api
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: checkout
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 203.0.113.0/24
      ports:
        - protocol: TCP
          port: 443

203.0.113.0/24 ist ein nach RFC 5737 für Dokumentation reservierter Bereich. In der echten Policy steht der vom Dienstanbieter bestätigte Adressbereich, und genau da beginnt der Betriebsaufwand: Ändert der Anbieter seine Adressen, muss die Policy nachgezogen werden. Ein externes Ziel über seinen DNS-Namen auszuwählen, kann die Standard-API nicht.


Wo die Standard-API aufhört

ipBlock arbeitet mit CIDR-Bereichen, und das klingt eindeutiger, als es ist. Die Dokumentation hält im Abschnitt zum Verhalten der ipBlock-Selektoren fest, dass Ingress- und Egress-Mechanismen häufig Quell- oder Ziel-IP umschreiben und dabei nicht definiert ist, ob das vor oder nach der Auswertung der NetworkPolicy passiert. Das Verhalten unterscheidet sich zwischen Netzwerk-Plugin, Cloud Provider, Service-Implementierung und Load-Balancer-Typ. Praktisch heißt das: Bei eingehendem Traffic sieht die Policy je nach Datenpfad die ursprüngliche Client-IP, die Load-Balancer-IP oder die Node-IP. Regeln auf externe IP-Bereiche müssen deshalb im konkreten Cluster getestet werden. Was im lokalen Testcluster funktioniert, verhält sich nach einem CNI-Wechsel oder in einem Managed Cluster nicht zwingend gleich.

Die Standard-API deckt Pod- und Namespace-Auswahl, IP-Bereiche, TCP, UDP und SCTP, einzelne Ports und über endPort auch Portbereiche ab. Was sie in Kubernetes 1.37 nicht kann, listet die Dokumentation selbst: Ziele über den Service-Namen auswählen, alles rund um TLS, explizite Deny-Regeln, clusterweit wirkende Default-Policies, Logging von Netzwerkereignissen, erzwungene Weiterleitung über einen zentralen Gateway und Regeln anhand der Kubernetes-Identität eines Nodes. Externe DNS-Namen als Ziel fehlen ebenfalls, weil die API als Peers nur podSelector, namespaceSelector und ipBlock kennt.

Diese Lücken lassen sich mit einem Service Mesh, mit CNI-eigenen Policy-Typen oder mit einer Firewall außerhalb des Clusters teilweise schließen. Ein Blick lohnt sich auch auf die ClusterNetworkPolicy aus dem Projekt network-policy-api, die genau diese Punkte adressiert: clusterweiter Scope, Tiers, Accept/Deny/Pass mit numerischer Priorität sowie Peers für Nodes, Netze und Domainnamen. Sie ist allerdings weiterhin eine Alpha-CRD außerhalb des Kubernetes-Kerns und damit noch keine Grundlage für ein produktives Sicherheitskonzept. Jede Regel sollte deshalb dokumentiert haben, ob sie mit der Standard-API arbeitet, eine Erweiterung des Netzwerk-Plugins braucht oder außerhalb des Clusters umgesetzt wird. “Wir verwenden NetworkPolicies” beschreibt für sich genommen noch kein Sicherheitsmodell.

Ein eigener Fall sind hostNetwork-Pods. Die Dokumentation nennt das Verhalten hier ausdrücklich undefiniert. In der häufigsten Implementierung kann das Netzwerk-Plugin den Traffic einzelner hostNetwork-Pods nicht von normalem Node-Traffic unterscheiden und behandelt ihn entsprechend. Dazu kommt, dass Verkehr von und zu dem Node, auf dem ein Pod läuft, bei ipBlock-Regeln immer erlaubt ist. Ein Pod kann den Zugriff von seinem eigenen Node also gar nicht per NetworkPolicy blockieren. Für Node-Agenten, Monitoring-DaemonSets, Storage-Plugins und manche Ingress-Komponenten braucht es deshalb andere Mittel: Host-Firewall, CNI-eigene Host Policies, getrennte Management-Segmente und vor allem so wenig hostNetwork wie möglich.


Warum ein erfolgreicher Test nichts beweisen muss

Policies werden verteilt auf den Nodes umgesetzt. Die Dokumentation formuliert das im Abschnitt zum Pod-Lebenszyklus ungewöhnlich klar: Jede angelegte NetworkPolicy wird irgendwann von einem Netzwerk-Plugin verarbeitet, aber die Kubernetes-API sagt nicht, wann genau das passiert. Pods können kurz nach ihrer Erstellung oder nach einer Änderung eine leicht abweichende Sicht auf die Policies haben. Wer Default Deny und alle Allow-Regeln in einem Commit ausrollt, hat also keine Garantie, dass beides auf jedem Node gleichzeitig wirksam wird. Anwendungen sollten kurzzeitige Nichterreichbarkeit beim Start entsprechend abfangen, Retries mit begrenztem Backoff sind hier belastbarer als ein einzelner Verbindungsversuch mit anschließendem Prozessabbruch.

Der zweite Grund für falsche Sicherheit sind bestehende Verbindungen. Was mit einer offenen TCP-Verbindung passiert, die eine neue Policy eigentlich verbietet, ist laut Dokumentation implementierungsabhängig. Manche Plugins beenden sie sofort, andere lassen sie weiterlaufen. Ein erfolgreicher Datenbankzugriff nach einer Policy-Änderung kann deshalb schlicht bedeuten, dass ein bestehender Connection Pool benutzt wurde. Tests müssen neue Verbindungen erzeugen, am besten aus einem frisch gestarteten Pod und von mehreren Nodes aus.

Und ein Test darf nicht nur zeigen, dass die gewünschte Verbindung funktioniert. Ebenso wichtig ist der Nachweis, dass die unerwünschte scheitert. Für den Checkout-Dienst heißt das:

TestErwartung
Gateway → Checkout:8080erlaubt
anderer Namespace → Checkout:8080blockiert
Checkout → PostgreSQL:5432erlaubt
Checkout → anderer Datenbank-Podblockiert
Checkout → DNS:53erlaubt
Checkout → Payment API:443erlaubt
Checkout → beliebiges Internetziel:443blockiert
anderer Pod → Payment API:443blockiert

Ein temporärer Test-Pod mit denselben Netzwerklabels wie die Anwendung reicht dafür aus:

kubectl run network-check \
  --namespace shop \
  --labels app=checkout \
  --image=busybox:1.36 \
  --restart=Never \
  --rm -it \
  -- sh

Darin lassen sich die Flows einzeln prüfen:

nslookup postgres.data.svc.cluster.local
nc -vz postgres.data.svc.cluster.local 5432
nc -vz api.zahlungsdienst.example 443

nc -vz ist hier wget vorzuziehen, weil das TLS in BusyBox keine Zertifikate prüft und gegen manche Gegenstellen ohnehin scheitert. Für einen reinen Konnektivitätstest reicht die TCP-Verbindung. Das verwendete Image sollte intern freigegeben und vor dem ersten Incident verfügbar sein, nicht erst danach.

Was die Kubernetes-API hier nicht liefert, ist Sichtbarkeit. Es gibt weder einen standardisierten Durchsetzungsstatus noch Logs für blockierte Verbindungen. Ob eine Policy auf allen Nodes aktiv ist, welche Regel einen Flow erlaubt hat und welcher Flow verworfen wurde, beantwortet nur das Netzwerk-Plugin. Flow Logs, ein erkennbarer Drop-Grund und Reachability-Werkzeuge sind deshalb ein echtes Auswahlkriterium beim CNI. Auf Anwendungsseite gehören DNS-Timeouts, Verbindungsfehler, Datenbank-Pool-Fehler, fehlgeschlagene Probes und Lücken in der Telemetrie ins Monitoring. Ein Drop Counter allein sagt nämlich nicht, ob der verworfene Flow unerwünscht oder geschäftskritisch war.


Der Rollout in Phasen

NetworkPolicies gehören in Git, das ist unstrittig. GitOps garantiert aber keine sichere Reihenfolge. Ein Commit, der gleichzeitig Default Deny aktiviert, Labels ändert, alte Freigaben löscht, neue hinzufügt und nebenbei Anwendung und Datenbank aktualisiert, macht jede spätere Ursachensuche unnötig schwer.

Eine tragfähige Reihenfolge sieht so aus:

  1. Im Test-Namespace nachweisen, dass das CNI Policies tatsächlich durchsetzt.
  2. Namespaces nach Kritikalität und Komplexität sortieren. Erste Kandidaten sind überschaubare, intern betriebene Anwendungen, nicht DNS, Storage oder die GitOps-Infrastruktur selbst.
  3. Kommunikationsmatrix erstellen und fachlich bestätigen lassen.
  4. Allow-Regeln ausrollen, solange die Pods noch nicht isoliert sind. Selektoren, Ports und Namespace-Labels lassen sich so prüfen, ohne etwas zu unterbrechen.
  5. Ingress isolieren und positiv wie negativ testen. Ingress ist meist der leichtere Anfang, weil die vorgesehenen Aufrufer interner Services klarer bekannt sind als alle Egress-Ziele.
  6. DNS und die übrigen Infrastrukturabhängigkeiten freigeben, dann Egress isolieren.
  7. Einen vollständigen Betriebszyklus beobachten: Deployments, Backups, Skalierungen, Zertifikatserneuerungen, Batch-Jobs, Störungsfälle.
  8. Temporäre und veraltete Freigaben wieder entfernen. Jede Policy behält Eigentümer, Zweck und Testfall.

Der Rückweg gehört ebenfalls vorbereitet. Die Default-Deny-Policy einfach zu löschen, öffnet den Namespace möglicherweise weiter als vor dem Vorfall beabsichtigt. Ein kontrollierter Rollback stellt die letzte bekannte Policy-Version wieder her und prüft danach erneut beide Seiten, erlaubte und verbotene Flows.


Praxisbeispiel: die scheinbar vollständige Egress-Policy

Ein Anwendungsteam dokumentiert drei Abhängigkeiten: PostgreSQL, DNS und ein externes Payment API. Die Freigaben werden erstellt, Default-Deny-Egress wird aktiviert, Bestellungen funktionieren.

Zwei Wochen später beginnt die automatische Zertifikatserneuerung. Der zuständige Controller muss dafür einen externen ACME-Endpunkt erreichen, hat aber keine Egress-Regel. Die Erneuerung schlägt fehl. Kurz darauf fällt auf, dass der Telemetrie-Sidecar seit der Umstellung keine Traces mehr exportiert. Im Anwendungstest war das nicht sichtbar, weil die Geschäftslogik weiterlief.

Die Einführung war technisch getestet, aber nie über einen vollständigen Betriebszyklus beobachtet worden. Genau das ist der Unterschied zwischen “funktioniert die Anwendung jetzt” und “funktionieren alle notwendigen Betriebsprozesse weiterhin”. Bei einer Egress-Isolation fehlen erfahrungsgemäß dieselben Ziele: Cluster-DNS, Kubernetes API, Secret Stores und Vault, OIDC-Dienste, Object Storage, Registries, SMTP-Relays, Telemetrie-Collector, Backup-Ziele, Zertifikats- und Revocation-Dienste sowie Metadaten-Endpunkte der Cloud. Operatoren verdienen dabei besondere Aufmerksamkeit, weil sie im Normalbetrieb ruhig sind und eine Verbindung erst bei Reconciliation, Zertifikatserneuerung, Restore, Skalierung oder Upgrade brauchen.


Bevor Sie Default Deny produktiv aktivieren

Default Deny ist ein sinnvolles Ziel. Es sollte das Ergebnis einer verstandenen und getesteten Kommunikationsarchitektur sein, nicht ihr Ausgangspunkt. Ein paar Fragen zeigen schnell, wo Sie stehen:

  • Ist nachgewiesen, dass Ihr CNI Policies durchsetzt, und zwar mit echten Verbindungen statt mit kubectl get?
  • Existiert eine Kommunikationsmatrix pro Anwendung, in der Ingress und Egress getrennt betrachtet sind?
  • Ist der tatsächliche DNS-Pfad bekannt, einschließlich TCP 53 und eventuellem NodeLocal DNSCache?
  • Sind seltene Abläufe erfasst, also Operatoren, Zertifikatserneuerung, Backups, Wartungsjobs?
  • Erzeugen Ihre Tests neue Verbindungen, und prüfen sie auch, was blockiert sein soll?
  • Liefert Ihr Netzwerk-Plugin Flow Logs oder eine gleichwertige Diagnose?
  • Ist der Rollback praktisch erprobt, nicht nur beschrieben?

Wenn mehrere dieser Fragen offen sind, ist der Cluster noch nicht bereit für Default Deny. Dann ist der erste Schritt nicht die Policy, sondern die Bestandsaufnahme.


Wissen Sie, welche Verbindungen Ihre Kubernetes-Workloads tatsächlich brauchen?

Ich bin Björn Ohlrich und begleite seit über 20 Jahren Infrastruktur-Projekte, seit einigen Jahren mit Schwerpunkt Kubernetes On-Premise in mittelständischen und regulierten Umgebungen. Zertifiziert als CKA, RHCE und RHCSA.

Im Kubernetes Review prüfe ich bestehende Plattformen unter anderem auf CNI-Funktion, Namespace-Isolation, NetworkPolicies, kritische Kommunikationswege und betriebliche Diagnosefähigkeit. Daraus lässt sich ein schrittweiser Default-Deny-Rollout mit überprüfbaren Verbindungstests und nachvollziehbaren GitOps-Regeln entwickeln. Wenn Sie den Betrieb danach nicht allein stemmen wollen, ist das Thema in den Managed Services aufgehoben.

Lassen Sie uns unverbindlich sprechen. Ein Gespräch reicht meist, um einzuschätzen, wo Ihre Plattform steht.


Quellen

Björn Ohlrich

Björn Ohlrich

Kubernetes & AI Ops Consultant

Fragen zu GitOps oder Kubernetes?

Lassen Sie uns besprechen, wie ich Sie bei Ihrer Cloud-Native-Journey unterstützen kann.