Ein Ingenieur, der an normale Server gewöhnt ist, kommt zu Kubernetes und macht das, was er immer gemacht hat: er exportiert HTTP_PROXY in die Umgebung, startet den Prozess neu und wartet darauf, dass der gesamte ausgehende Traffic über den Proxy läuft. Manchmal funktioniert das. Viel häufiger legt es die halbe Cluster lahm, während die andere Hälfte weiterhin direkt ins Internet geht und den Proxy umgeht. Und dann beginnt der spannende Teil: Warum haben einige Pods die Variablen übernommen und andere nicht, warum sehen sich die Services plötzlich nicht mehr und warum läuft der Healthcheck auf einmal über einen Proxy-Server, der nicht in der Liste der vertrauenswürdigen steht.

Dieser Artikel ist eine ausführliche Analyse dessen, wie ausgehender Traffic im Cluster tatsächlich funktioniert und wo ein Proxy korrekt eingebunden wird. Wir werden nicht die grundlegende Konfiguration von Umgebungsvariablen wiederholen: Das wissen Sie ohnehin. Es geht um die Besonderheiten von Kubernetes, wo vertraute Ansätze unerwartete Effekte zeigen. Alle Beispiele basieren auf realen Manifest-Fragmenten, und als Proxy-Infrastruktur dient Proxeon (proxeon.net).

Grundlagen: Warum im Cluster alles anders ist

Beginnen wir mit dem Fundament. Auf einem klassischen Server ist der Begriff des ausgehenden Traffics trivial: Es gibt eine Netzwerkschnittstelle, eine Routing-Tabelle, Systemumgebungsvariablen, und fast alles, was Sie starten, erbt sie von der übergeordneten Shell. Man exportiert die Variable im Profil – der gesamte Benutzerprozess sieht sie.

In Kubernetes gibt es diesen zentralen Punkt nicht. Hier gibt es den Pod – die kleinste Deployment-Einheit, in der ein oder mehrere Container leben. Der Pod hat einen eigenen Netzwerk-Namespace, eine eigene IP-Adresse, einen eigenen Satz von Umgebungsvariablen, der durch das Manifest definiert wird. Eine Shell, von der ein Prozess etwas erben könnte, gibt es schlicht nicht. Eine Umgebungsvariable erscheint im Container nur, wenn Sie sie explizit in der Pod-Spezifikation oder im Image deklarieren.

Was ist ausgehender Traffic eines Pods

Wenn ein Container eine externe Adresse anspricht, durchläuft das Paket einen langen Weg. Zuerst verlässt es den Netzwerk-Namespace des Containers über ein virtuelles Interface. Dann gelangt es in den Netzwerk-Stack des Nodes, wo sich das CNI-Plugin darum kümmert – die Komponente, die für das Clusternetzwerk verantwortlich ist. Danach kommen Regeln von iptables oder eBPF ins Spiel, der Mechanismus SNAT (Source Network Address Translation), wonach das Paket über das Netzwerk-Interface des Nodes in die Außenwelt gelangt.

Der entscheidende Punkt: Aus Sicht des externen Dienstes kommt die Anfrage nicht von der Adresse des Pods, sondern von der Adresse des Cluster-Nodes. Der Pod ist hinter der Adressübersetzung verborgen. Das ist das Erste, was Neulinge überrascht, wenn sie den Zugriff über eine IP-Whitelist einrichten wollen: Sie fügen die Pod-Adresse zur Liste hinzu, aber der Traffic kommt von einer völlig anderen Adresse.

Zwei Arten von ausgehendem Traffic

Es ist wichtig, von Anfang an zwei grundlegend verschiedene Ströme zu unterscheiden:

  • Ost-West – Traffic zwischen Services innerhalb des Clusters. Ein Pod spricht einen anderen über einen Service, eine ClusterIP oder einen DNS-Namen wie my-service.namespace.svc.cluster.local an.
  • Nord-Süd – Traffic nach außen, zu externen APIs, Datenbanken, Partnerservices, Objektspeichern.

Ein Proxy wird fast immer nur für Nord-Süd-Traffic benötigt. Und der häufigste und schmerzhafteste Fehler besteht darin, dass eine falsche Proxy-Konfiguration versehentlich auch den Ost-West-Traffic erfasst und die interne Kommunikation unterbricht. Genau deshalb ist die Ausschlussliste hier wichtiger als irgendwo sonst. Aber dazu später.

Tiefenanalyse: Drei Ebenen, auf denen ein Proxy lebt

Es gibt genau drei architektonische Ebenen, auf denen ein Proxy in den ausgehenden Pfad eines Pods eingebunden werden kann. Jede löst die Aufgabe auf ihre Weise, jede hat ihren Preis. Betrachten wir alle drei ehrlich, mit Vor- und Nachteilen.

Ebene 1: Der Container selbst

Das ist der Fall, wenn die Anwendung im Container selbst vom Proxy weiß. Sie liest die Umgebungsvariablen HTTP_PROXY und HTTPS_PROXY oder hat einen Proxy in ihrer eigenen Konfiguration und leitet ihre HTTP-Anfragen darüber. Die Proxy-Logik ist in die Client-Bibliothek der Anwendung selbst integriert.

Vorteile. Maximale Einfachheit beim Einstieg. Es muss nichts zum Cluster hinzugefügt werden, keine zusätzlichen Komponenten. Kontrolle auf der Ebene des konkreten Pods: Sie wissen genau, welche Anwendung wohin geht.

Nachteile. Jede Anwendung muss diese Variablen lesen können, und das können bei weitem nicht alle. Die Konfiguration verteilt sich über Dutzende von Manifesten. Eine Proxy-Adresse zu aktualisieren bedeutet, alle Deployments durchzugehen. Man vergisst leicht einen Service, und er geht direkt. Es gibt keine zentralisierte Richtlinie.

Ebene 2: Sidecar-Container

Hier wird neben dem Hauptcontainer im selben Pod ein zweiter gestartet – der Sidecar. Er fängt den ausgehenden Traffic ab und leitet ihn über den Proxy. Die Anwendung muss von der Existenz eines Proxys überhaupt nichts wissen: Sie sendet Anfragen wie gewohnt, und der Sidecar proxyt sie unbemerkt. So funktionieren Service Meshes wie Istio und Linkerd, und nach demselben Prinzip kann ein leichter lokaler Proxy-Agent eingesetzt werden.

Vorteile. Transparenz für die Anwendung. Einheitliche Richtlinie über das Pod-Template. Möglichkeit, über das Proxying hinaus Observability hinzuzufügen: Metriken, Tracing, Wiederholungsversuche, Timeouts. Der Sidecar ist im Pod isoliert und teilt dessen Lebenszyklus.

Nachteile. Overhead: Jeder Pod hat nun zwei Container, also mehr Speicher und CPU. Das Debugging wird komplexer – die Kette hat ein zusätzliches Glied. Die Startreihenfolge der Container ist wichtig: Wenn die Anwendung vor dem Sidecar startet, können die ersten Anfragen fehlschlagen. In Kubernetes 1.28+ wird dieses Problem durch native sidecar containers als Init-Container mit der Richtlinie restartPolicy Always gelöst.

Ebene 3: Egress-Gateway des Clusters

Das ist ein dedizierter Node oder Pod, über den der gesamte ausgehende Traffic des Clusters zwangsweise geleitet wird. Pakete werden durch das CNI zum Egress-Gateway geroutet, und dieses kommuniziert entweder mit einem externen Proxy oder fungiert selbst als Austrittspunkt mit einer stabilen ausgehenden IP.

Vorteile. Einheitlicher Kontrollpunkt für den gesamten Cluster. Eine stabile, vorhersehbare ausgehende Adresse, die bequem zur Whitelist externer Partner hinzugefügt werden kann. Die Richtlinie wird an einer Stelle geändert. Anwendungen wissen nichts davon.

Nachteile. Das Egress-Gateway wird zum kritischen Punkt: Fällt es aus, steht der gesamte Nord-Süd-Traffic. Redundanz und Monitoring sind erforderlich. Die Konfiguration ist komplexer und erfordert Unterstützung durch das CNI. Die Granularität ist geringer: Es ist schwieriger, ohne zusätzliche Regeln unterschiedliche Richtlinien für verschiedene Anwendungen festzulegen.

Wie man die Ebene wählt

Eine praktische Orientierung aus der Erfahrung: Ein kleines Projekt mit ein paar Services, die einen externen Proxy benötigen – Ebene des Containers. Ein mittleres Cluster mit Dutzenden Services und der Anforderung einer einheitlichen Richtlinie – Sidecar. Eine große Infrastruktur, in der externe Partner eine feste ausgehende IP und ein Audit des gesamten Traffics verlangen – Egress-Gateway. Oft werden die Ebenen kombiniert: Egress-Gateway für die Basiskontrolle plus Umgebungsvariablen für die Feinabstimmung einzelner Pods über Proxeon.

Umgebungsvariablen im Manifest und die Rolle von NO_PROXY

Kommen wir zum am meisten unterschätzten Detail. Die Variablen HTTP_PROXY, HTTPS_PROXY und NO_PROXY werden im Manifest über den env-Block der Container-Spezifikation gesetzt. Die klassischen Namen sollten in Kleinbuchstaben dupliziert werden, weil einige Bibliotheken genau die kleingeschriebenen Varianten lesen.

apiVersion: apps/v1
kind: Deployment
metadata:
 name: worker
spec:
 replicas: 2
 selector:
matchLabels:
 app: worker
 template:
metadata:
 labels:
app: worker
spec:
 containers:
- name: app
 image: registry.example.com/worker:1.4.0
 env:
- name: HTTP_PROXY
 value: "http://gate.proxeon.net:8080"
- name: HTTPS_PROXY
 value: "http://gate.proxeon.net:8080"
- name: NO_PROXY
 value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"
- name: http_proxy
 value: "http://gate.proxeon.net:8080"
- name: https_proxy
 value: "http://gate.proxeon.net:8080"
- name: no_proxy
 value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"

Warum das Cluster ohne korrektes NO_PROXY auseinanderfällt

Hier liegt der Kern des Problems. Sobald Sie HTTP_PROXY deklariert haben, beginnt der HTTP-Client der Anwendung, absolut alle Anfragen über den Proxy zu leiten, einschließlich der Aufrufe benachbarter Services innerhalb des Clusters. Aber interne Services sind nur innerhalb des Clusternetzes erreichbar – der externe Proxy-Server kann sie physisch nicht erreichen. Das Ergebnis: Eine Anfrage an orders.default.svc.cluster.local geht an den externen Proxy, dieser versucht, den Namen aufzulösen, kann es nicht und gibt einen Fehler zurück. Die interne Kommunikation bricht sofort zusammen.

NO_PROXY ist eine Ausschlussliste – Adressen und Domains, die den Proxy umgehen und direkt gesendet werden sollen. Auf einem normalen Server nimmt man localhost und vielleicht ein paar interne Subnetze auf. In Kubernetes wird diese Liste kritisch wichtig, weil sie die gesamte interne Topologie des Clusters abdecken muss. Verpassen Sie ein Suffix, und ein Teil des Service-zu-Service-Traffics läuft über den externen Proxy.

Was unbedingt in NO_PROXY des Clusters gehören muss

  • localhost und 127.0.0.1 – Aufrufe innerhalb des Pods selbst.
  • Bereich des Pod-CIDR – das Subnetz, aus dem Pod-Adressen vergeben werden, zum Beispiel 10.0.0.0/8 oder der genauere Bereich Ihres Clusters.
  • Bereich des Service-CIDR – das Subnetz der ClusterIP-Services, oft 10.96.0.0/12.
  • DNS-Suffixe des Clusters – .svc, .svc.cluster.local, .cluster.local. Genau sie decken alle internen DNS-Namen der Services ab.
  • kubernetes.default – der Name des API-Servers, den Anwendungen und Agenten ansprechen.
  • Cloud-Metadaten – die Adresse 169.254.169.254, falls Sie in einer Cloud-Umgebung sind, damit Metadaten-Anfragen nicht über den Proxy laufen.

Feinheiten der NO_PROXY-Syntax

Hier verbirgt sich eine ganze Schicht von Nicht-Offensichtlichkeiten, über die selbst erfahrene Ingenieure stolpern.

Erstens interpretieren verschiedene Bibliotheken die Einträge unterschiedlich. Manche gehen davon aus, dass .cluster.local mit führendem Punkt ein Suffix bedeutet und mit allen Subdomains übereinstimmt. Andere verlangen das Format cluster.local ohne Punkt. Die Praxis: Geben Sie beide Varianten an, mit und ohne Punkt, um möglichst viele Clients abzudecken.

Zweitens ist die Unterstützung der CIDR-Notation nicht universell. Die Go-Bibliothek versteht 10.0.0.0/8, aber ältere Versionen einiger Clients in anderen Sprachen nicht – sie benötigen einzelne Adressen oder Bereiche in einem anderen Format. Prüfen Sie das Verhalten genau Ihres Stacks.

Drittens Ports. Wenn ein Eintrag in NO_PROXY ohne Port angegeben ist, gilt er normalerweise für jeden Port des Hosts. Aber einige Clients vergleichen streng mit dem Port. Bei nicht standardmäßigen Ports interner Services ist das wichtig zu prüfen.

Ein Insight aus der Praxis: Neun von zehn Vorfällen mit gebrochener interner Kommunikation nach der Proxy-Einführung sind auf ein unvollständiges NO_PROXY zurückzuführen. Stellen Sie eine Referenzliste für Ihren Cluster einmal zusammen, legen Sie sie in eine gemeinsame ConfigMap und verwenden Sie sie in allen Deployments wieder. Das spart Dutzende Stunden Debugging.

Was Umgebungsvariablen NICHT übernimmt

Die naive Überzeugung „Ich habe HTTP_PROXY gesetzt, also läuft der gesamte Traffic über den Proxy" ist im Cluster doppelt falsch. Es gibt eine ganze Klasse von Komponenten, die diese Variablen einfach ignorieren. Man muss sie im Voraus kennen.

kubelet und Systemkomponenten

Umgebungsvariablen eines Containers sind nur für Prozesse innerhalb dieses Containers sichtbar. kubelet – der Node-Agent, der Images herunterlädt, Container startet und mit dem API-Server kommuniziert – lebt auf der Ebene des Nodes, nicht des Pods. Er liest keine env aus dem Manifest. Wenn Sie möchten, dass kubelet Images über einen Proxy zieht, erfolgt die Konfiguration auf der Ebene des systemd-Service kubelet oder der Konfiguration der Container-Runtime, nicht in der Pod-Spezifikation.

# /etc/systemd/system/kubelet.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://gate.proxeon.net:8080"
Environment="HTTPS_PROXY=http://gate.proxeon.net:8080"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"

Dasselbe gilt für die Container-Runtime – containerd oder CRI-O. Das Herunterladen von Images erfolgt über ihren eigenen Netzwerkkontext. Die Proxy-Konfiguration für die Image-Registry wird in der Runtime-Konfiguration festgelegt, und das ist eine von Pods getrennte Geschichte.

Images mit eigener Konfiguration

Viele beliebte Images haben integrierte Netzwerkeinstellungen, die Umgebungsvariablen überschreiben. Zum Beispiel können Paketmanager, Webserver oder Proxy-Tools im Image eine eigene Konfigurationsdatei lesen statt der Umgebung. Wenn die Anwendung im Container beispielsweise eine Konfigurationsdatei mit explizit angegebenen Verbindungsparametern verwendet, wird sie Ihre env-Variablen einfach nicht bemerken.

Eine separate Kategorie sind Anwendungen, bei denen der Netzwerk-Client vor dem Lesen der Umgebung initialisiert wird oder die Einstellungen beim Start zwischenspeichert. Eine Änderung der Variable ohne Neustart des Prozesses hat hier keine Wirkung.

Einzelne SDKs und Sprach-Runtimes

Das ist die heimtückischste Gruppe. Die Beachtung von HTTP_PROXY ist eine Konvention, kein Standard. Manche halten sich daran, manche nicht.

  • Go. Der Standard-http.Client respektiert über ProxyFromEnvironment die Variablen. Aber wenn der Code einen Transport mit explizitem Proxy nil erstellt, werden die Variablen ignoriert.
  • Python. Die Bibliothek requests liest die Umgebung standardmäßig. Aber Low-Level-Sockets, einige gRPC-Clients und asynchrone Bibliotheken nicht.
  • Java. Die JVM verwendet eigene Systemeigenschaften http.proxyHost und https.proxyHost und liest Umgebungsvariablen standardmäßig überhaupt nicht. Sie müssen über JAVA_TOOL_OPTIONS übergeben werden.
  • Node.js. Das eingebaute http-Modul respektiert Umgebungsvariablen nicht. Es braucht Drittanbieter-Agenten, die sie lesen.
  • gRPC. Einige Implementierungen lesen die spezielle Variable grpc_proxy statt der Standardvariablen.

Die Schlussfolgerung ist einfach: Man darf nicht annehmen, dass der gesamte Traffic über die Variable läuft, nur weil sie deklariert ist. Prüfen Sie jede Runtime separat. Für die JVM sieht das Manifest beispielsweise so aus:

env:
 - name: JAVA_TOOL_OPTIONS
value: "-Dhttp.proxyHost=gate.proxeon.net -Dhttp.proxyPort=8080 -Dhttps.proxyHost=gate.proxeon.net -Dhttps.proxyPort=8080 -Dhttp.nonProxyHosts=localhost|127.0.0.1|*.svc|*.cluster.local"

Beachten Sie: In der JVM ist das Trennzeichen für Ausnahmen ein senkrechter Strich, kein Komma, und die Muster verwenden ein Sternchen. Eine weitere Falle für diejenigen, die NO_PROXY mechanisch kopieren.

Secrets: Proxy-Credentials über Secret, nicht im Manifest

Wenn Ihr Proxy eine Authentifizierung erfordert, stellt sich die Frage der Speicherung von Login und Passwort. Die Versuchung ist groß: Sie direkt in die URL im env-Wert zu schreiben. Das darf man nicht tun, und hier ist warum.

Deployment-Manifeste liegen fast immer im Versionskontrollsystem. Ein Passwort im Klartext in env ist ein Credential-Leak in die Git-Historie, die allen mit Repository-Zugriff zugänglich ist. Außerdem sind env-Werte für jeden sichtbar, der kubectl describe pod ausführen kann. Das ist ein direkter Verstoß gegen das Prinzip der geringsten Privilegien.

Der richtige Weg ist das Objekt Secret. Es speichert sensible Daten getrennt, mit der Möglichkeit, den Zugriff über RBAC einzuschränken und die Verschlüsselung im etcd-Speicher zu aktivieren.

apiVersion: v1
kind: Secret
metadata:
 name: proxeon-credentials
type: Opaque
stringData:
 proxy-user: my_account
 proxy-pass: s3cr3t_token_value

Dann binden wir die Werte aus dem Secret über secretKeyRef in die Umgebungsvariablen des Containers ein, und die Proxy-URL selbst wird so zusammengesetzt, dass die Credentials nicht im Manifest selbst auftauchen:

env:
 - name: PROXY_USER
valueFrom:
 secretKeyRef:
name: proxeon-credentials
key: proxy-user
 - name: PROXY_PASS
valueFrom:
 secretKeyRef:
name: proxeon-credentials
key: proxy-pass
 - name: HTTPS_PROXY
value: "http://$(PROXY_USER):$(PROXY_PASS)@gate.proxeon.net:8080"
 - name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"

Kubernetes ersetzt Werte aus zuvor deklarierten Variablen durch die Syntax mit Dollarzeichen und Klammern. So kann die URL mit Credentials zur Laufzeit zusammengesetzt werden, ohne das Passwort direkt in den Manifesttext aufzunehmen. Beachten Sie: Der zusammengesetzte Wert von HTTPS_PROXY ist dennoch in der env des laufenden Containers sichtbar, daher sollten Sie zusätzlich einschränken, wer exec und describe für diese Pods ausführen darf.

Best Practices für den Umgang mit Secrets

  • Aktivieren Sie die Verschlüsselung von etcd at rest – sonst werden Secrets nur in base64 gespeichert, was kein Schutz ist.
  • Beschränken Sie den Zugriff auf Secrets über RBAC: Ein Pod sollte nur sein eigenes Secret sehen.
  • Rotieren Sie Proxy-Credentials regelmäßig und automatisieren Sie den Neustart von Pods nach der Rotation.
  • Erwägen Sie externe Secret-Manager mit Anbindung über einen CSI-Treiber, damit Credentials gar nicht erst in etcd gelangen.
  • Loggen Sie niemals die zusammengesetzte Proxy-URL – das Passwort würde in das Log-Sammelsystem gelangen.

Sidecar-Ansatz: Wann er gerechtfertigt ist und was er bringt

Wir haben den Sidecar bereits als eine der drei Ebenen genannt. Jetzt betrachten wir ihn als eigenständige Strategie genauer, denn er ist das flexibelste Werkzeug, wenn Sie bereit sind, ihn mit Ressourcen zu bezahlen.

Wann ein Sidecar gerechtfertigt ist

  • Die Anwendung kann HTTP_PROXY nicht lesen und der Code kann nicht oder nur teuer umgeschrieben werden.
  • Es wird eine einheitliche Proxy-Richtlinie benötigt, ohne jede Anwendung zu ändern.
  • Es ist eine transparente Verkehrsabfangung erforderlich, einschließlich Nicht-HTTP-Protokollen.
  • Es wird Observability benötigt: Metriken ausgehender Verbindungen, Tracing, Audit.
  • Es werden Richtlinien für Wiederholungen, Timeouts, Circuit Breaking auf Verbindungsebene benötigt.

Was ein Sidecar über das Proxying hinaus bietet

Hier liegt der eigentliche Wert. Ein Sidecar ist nicht nur ein Paketweiterleiter. Richtig konfiguriert wird er zum Kontroll- und Beobachtungspunkt für den gesamten ausgehenden Traffic des Pods.

  • Metriken. Wie viele Anfragen gingen nach außen, an welche Hosts, mit welcher Latenz, wie viele Fehler. Ohne Sidecar müssten diese Daten in jeder Anwendung separat erfasst werden.
  • Tracing. Verteilte Traces ausgehender Aufrufe, verknüpft mit eingehenden Anfragen.
  • Zuverlässigkeitsrichtlinien. Automatische Wiederholungen idempotenter Anfragen, Timeouts, Begrenzung gleichzeitiger Verbindungen.
  • Einheitliches TLS. Der Sidecar kann sichere Verbindungen zentral terminieren und aufbauen.
  • Audit und Compliance. Ein vollständiges Protokoll darüber, wohin die Anwendung genau gegangen ist – unverzichtbar für die Compliance.

Beispiel eines Pods mit Sidecar-Proxy

Unten sehen Sie ein vereinfachtes Template, bei dem der Hauptcontainer ausgehende HTTP-Anfragen an den lokalen Sidecar sendet und dieser sie über Proxeon weiterproxyt. Für die Anwendung ist die Proxy-Adresse localhost, was automatisch den Service-zu-Service-Traffic vom externen Proxying ausschließt, wenn die Anwendung benachbarte Services direkt anspricht.

apiVersion: v1
kind: Pod
metadata:
 name: app-with-proxy-sidecar
 labels:
app: billing
spec:
 containers:
- name: app
 image: registry.example.com/billing:2.1.0
 env:
- name: HTTP_PROXY
 value: "http://127.0.0.1:3128"
- name: HTTPS_PROXY
 value: "http://127.0.0.1:3128"
- name: NO_PROXY
 value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"
- name: egress-proxy
 image: registry.example.com/egress-agent:1.0.0
 ports:
- containerPort: 3128
 env:
- name: UPSTREAM_PROXY
 value: "http://gate.proxeon.net:8080"
 resources:
requests:
 cpu: 50m
 memory: 64Mi
limits:
 cpu: 200m
 memory: 128Mi

Native Sidecar und Startreihenfolge

Das klassische Problem des Sidecars ist das Race beim Start. Wenn die Hauptanwendung startet und ihre erste ausgehende Anfrage stellt, bevor der Sidecar bereit ist, Verbindungen anzunehmen, schlägt die Anfrage fehl. Ab Kubernetes 1.28 gibt es Unterstützung für native Sidecar-Container: Sie werden im Block initContainers mit restartPolicy Always deklariert und starten garantiert sowie bleiben bis zum Hauptcontainer am Leben. Das löst das Race-Problem sauber, ohne Behelfslösungen wie Verzögerungen und Wiederholungen beim Start.

spec:
 initContainers:
- name: egress-proxy
 image: registry.example.com/egress-agent:1.0.0
 restartPolicy: Always
 ports:
- containerPort: 3128

Egress-Gateway: Stabile ausgehende Adresse für das Cluster

Die dritte Ebene verdient eine separate Betrachtung, denn genau sie löst die Aufgabe, wegen der man meist überhaupt erst zu Proxys im Cluster kommt: eine vorhersehbare ausgehende IP.

Warum eine feste ausgehende IP benötigt wird

Externe Partner, Zahlungsgateways, Datenanbieter arbeiten nicht selten mit einer IP-Whitelist. Sie sagen: Wir akzeptieren Anfragen nur von diesen IPs. In einem normalen Cluster ist die ausgehende Adresse eines Pods die Adresse des Nodes, auf dem der Pod gerade gelandet ist. Es gibt viele Nodes, sie skalieren, werden ersetzt, beim Autoscaling hinzugefügt. Die Adresse ist unvorhersehbar. Der Partner kann nicht den gesamten Node-Pool in die Whitelist aufnehmen, der sich zudem ändert.

Das Egress-Gateway löst das: Der gesamte ausgehende Traffic wird an einem Punkt mit einer festen Adresse gesammelt. Der Partner trägt ein oder zwei stabile IPs in die Whitelist ein – und alles funktioniert. Bei Verwendung von Proxeon als ausgehendem Punkt erhalten Sie eine stabile externe Adresse, die Sie dem Partner einmal geben.

Wie der Traffic zum Egress-Gateway geroutet wird

Der Mechanismus hängt vom CNI ab. Einige CNI-Plugins unterstützen ein Egress-Policy-Objekt, in dem Sie beschreiben: Traffic von Pods mit bestimmten Labels, der nach außen geht, soll über einen bestimmten Node oder eine bestimmte Adresse ausgehen. Das Plugin konfiguriert die entsprechenden SNAT- und Routing-Regeln automatisch. Konzeptionell sieht es so aus:

apiVersion: policy.example.io/v1
kind: EgressPolicy
metadata:
 name: billing-egress
spec:
 selector:
matchLabels:
 egress: proxeon
 egressIP: 203.0.113.10
 destinationCIDRs:
- 0.0.0.0/0

Die genaue Syntax unterscheidet sich zwischen den CNIs, daher konsultieren Sie die Dokumentation Ihres Plugins. Die Idee ist unverändert: Pods markieren, deren Traffic über das Gateway geleitet wird, und eine stabile Ausgangsadresse festlegen.

Zuverlässigkeit des Egress-Gateways

Da das Gateway ein einzelner Punkt ist, ist es auch ein einzelner Ausfallpunkt. Überlebensregeln:

  • Redundanz: mindestens zwei Gateway-Nodes mit automatischem Failover.
  • Überwachen Sie die Verfügbarkeit des Ausgangs mit einer separaten synthetischen Anfrage nach außen jede Minute.
  • Achten Sie auf den Durchsatz: Der gesamte Nord-Süd-Traffic fließt über das Gateway, ein Engpass wirkt sich auf alles aus.
  • Trennen Sie Richtlinien: Kritischer Traffic und Hintergrund-Traffic sollten besser über verschiedene Pfade laufen, damit Hintergrundlast den wichtigen Verkehr nicht behindert.

Diagnose des ausgehenden Traffics im Cluster

Wenn etwas schiefgeht – und das wird es – braucht man ein Arsenal an Prüfungen. Stellen wir einen praktischen Satz von Befehlen und Ansätzen zusammen.

Schritt 1: Die reale ausgehende IP aus dem Pod herausfinden

Das Erste, was wir prüfen: Von welcher Adresse aus ist der Pod für die Außenwelt sichtbar. Wir starten einen ephemeren Debug-Container oder exec in einen bestehenden Pod und sprechen einen Dienst an, der Ihre öffentliche Adresse zurückgibt.

kubectl run nettest --rm -it --image=registry.example.com/nettools:1.0 --restart=Never -- sh
# внутри контейнера
curl -s https://api.ipify.org
curl -s -x http://gate.proxeon.net:8080 https://api.ipify.org

Die erste Anfrage zeigt die Adresse ohne Proxy, die zweite – explizit über den Proxy. Wenn beide gleich sind, obwohl Sie unterschiedliche erwartet haben, wird der Proxy nicht angewendet. Wenn die zweite die erwartete Adresse von Proxeon zurückgibt, funktioniert der Proxy und das Problem liegt in der Anwendungskonfiguration.

Schritt 2: Prüfen, ob die Anwendung die Variablen sieht

kubectl exec deploy/worker -c app -- env | grep -i proxy

Wenn die Ausgabe leer ist – sind die Variablen nicht im Container angekommen. Prüfen Sie das Manifest und ob der Pod nach der Änderung neu erstellt wurde. Zur Erinnerung: Eine Änderung von env erfordert die Neuerstellung des Pods, sie wird nicht im laufenden Betrieb übernommen.

Schritt 3: DNS-Auflösung von innen prüfen

Viele Fehler maskieren sich als Proxy-Problem, sind aber tatsächlich DNS. Prüfen wir die Auflösung eines internen und eines externen Namens:

kubectl exec deploy/worker -c app -- nslookup orders.default.svc.cluster.local
kubectl exec deploy/worker -c app -- nslookup api.external-partner.com

Wenn der interne Name nicht aufgelöst wird – liegt das Problem in CoreDNS oder darin, dass die Anfrage wegen eines fehlenden Suffixes in NO_PROXY an den externen Proxy ging. Das ist eine direkte Bestätigung, dass die Ausschlussliste unvollständig ist.

Schritt 4: Wo man Ablehnungen findet

  • Anwendungslogs. Suchen Sie nach Verbindungsfehlern, Timeouts, Proxy-Authentifizierungsfehlern (normalerweise Code 407).
  • Sidecar-Logs. Falls vorhanden, sieht man dort, welche Anfragen er angenommen und wohin er sie geleitet hat.
  • Metriken des Egress-Gateways. Anstieg von Fehlern und Latenzen am Gateway.
  • Pod-Events. kubectl describe pod zeigt Startprobleme, einschließlich des Sidecar-Races.
  • NetworkPolicy. Prüfen Sie, ob eine Netzwerkrichtlinie den ausgehenden Traffic blockiert – eine häufige Ursache für stumme Fehler.

Typische Problemsignaturen

  • Code 407 vom Proxy – falsche oder fehlende Credentials. Prüfen Sie das Secret.
  • Timeout beim Zugriff auf einen internen Service nach Aktivierung des Proxys – unvollständiges NO_PROXY.
  • Unterschiedliche ausgehende IPs bei wiederholten Anfragen – Verkehr läuft nicht über das Egress-Gateway, das normale SNAT des Nodes funktioniert.
  • Erste Anfragen nach dem Start schlagen fehl, danach funktioniert alles – Sidecar-Race, wechseln Sie zu native Sidecar.
  • Java-Anwendung ignoriert den Proxy – JAVA_TOOL_OPTIONS und Systemeigenschaften vergessen.

Typische Fehler, die man vermeiden sollte

Fassen wir an einem Ort die Stolperfallen zusammen, über die man am häufigsten stolpert. Prüfen Sie sich anhand dieser Liste vor, nicht nach dem Vorfall.

Unvollständiges NO_PROXY

Der absolute Spitzenreiter. Service-CIDR vergessen, Suffix .svc vergessen, die Cloud-Metadaten-Adresse nicht berücksichtigt – und man erhält eine Kaskade rätselhafter Fehler. Gehen Sie immer von der vollständigen Referenzliste für Ihren Cluster aus.

Proxy-Passwort im Klartext im Manifest

Leak in Git und in der describe-Ausgabe. Nur Secret, nur eingeschränkter Zugriff.

Annahme, dass alle Runtimes die Variablen respektieren

JVM, Node.js, einige gRPC-Clients ignorieren HTTP_PROXY. Prüfen Sie jeden Stack separat und konfigurieren Sie ihn auf native Weise.

Ignorieren von kubelet und Runtime

Das Herunterladen von Images über einen Proxy wird auf Node-Ebene konfiguriert, nicht im Pod. Wundern Sie sich nicht, dass Images nicht gezogen werden, wenn Sie nur env im Manifest konfiguriert haben.

Race beim Start des Sidecars

Die Anwendung startet vor dem Proxy und lässt die ersten Anfragen fehlschlagen. Die Lösung sind native sidecar containers.

Egress-Gateway ohne Redundanz

Ein einzelner Ausfallpunkt ohne Duplizierung legt den gesamten ausgehenden Traffic lahm. Redundanz und Monitoring sind Pflicht.

Vermischung von CIDR-Formaten

Sie haben 10.0.0.0/8 für einen Client angegeben, der CIDR nicht versteht. Prüfen Sie, welches Format Ihre Bibliothek akzeptiert, und geben Sie eine Alternative.

Fehlende Neuerstellung von Pods nach env-Änderung

Sie haben die Variable im Deployment geändert, aber alte Pods laufen weiter mit den alten Werten bis zum Neustart. Stellen Sie ein Rollout sicher.

Vergessene Kleinschreibung der Variablen

Einige Bibliotheken lesen nur http_proxy in Kleinbuchstaben. Duplizieren Sie die Namen in beiden Schreibweisen.

Werkzeuge und Ressourcen

Was man für die Arbeit mit ausgehendem Traffic im Cluster zur Hand haben sollte.

Diagnose

  • kubectl exec und kubectl debug – die Grundlage für Prüfungen von innen aus dem Pod.
  • Ephemere Debug-Container – ermöglichen das Anhängen eines Sets von Netzwerktools an einen laufenden Pod ohne Neuaufbau des Images.
  • Image mit Netzwerk-Utilities – curl, dig, nslookup, traceroute, in einem Container für schnelle Prüfungen gebündelt.
  • Dienst zur Rückgabe der öffentlichen IP – Prüfung der realen ausgehenden Adresse.

Infrastruktur

  • ConfigMap mit referenziellem NO_PROXY – eine einzige Quelle der Wahrheit für alle Deployments.
  • Secret und externe Secret-Manager – für Proxy-Credentials.
  • Service Mesh – wenn ein Sidecar mit Observability out of the box benötigt wird.
  • Egress-Policies des CNI – für das Routing zum Gateway.
  • Proxeon (proxeon.net) – Proxy-Infrastruktur für eine stabile ausgehende Adresse und authentifizierten Zugriff.

Monitoring

  • Metriken ausgehender Verbindungen vom Sidecar oder Egress-Gateway.
  • Synthetische Verfügbarkeitsprüfungen externer Adressen aus dem Cluster.
  • Alerts auf Anstieg von Codes 407 und Timeouts ausgehender Anfragen.
  • Dashboard mit der Verteilung des ausgehenden Traffics nach Zielhosts.

Fallstudien und Ergebnisse

Betrachten wir drei verallgemeinerte Szenarien, die zeigen, wie die Wahl der Ebene das Ergebnis beeinflusst.

Fall 1: Zahlungsintegration und IP-Whitelist

Ein Team integrierte ein externes Zahlungsgateway, das Anfragen nur von vereinbarten Adressen akzeptiert. Zuerst probierte man Umgebungsvariablen auf Container-Ebene – und stieß darauf, dass beim Autoscaling die Pods auf neue Nodes mit neuen Adressen verteilt wurden und die ausgehende IP ohnehin die Node-Adresse blieb, nicht der Proxy. Ein Teil der Anfragen begann abgelehnt zu werden.

Lösung: Der Zahlungsservice wurde auf Egress über Proxeon mit fester ausgehender Adresse umgestellt. Der Partner trug eine IP in die Whitelist ein. Die Ablehnungen wegen unbekannter Adresse verschwanden. Ein zusätzlicher Effekt – ein zentralisiertes Journal aller Aufrufe des Zahlungsgateways für das Audit.

Fall 2: Bruch der Service-zu-Service-Kommunikation nach der Proxy-Einführung

Ein Unternehmen fügte HTTP_PROXY in alle Deployments auf einmal über ein gemeinsames Template hinzu. Nach einigen Minuten häuften sich Fehler: Die Services sahen sich nicht mehr. Interne Aufrufe gingen an den externen Proxy, der sie nicht auflösen konnte.

Die Diagnose dauerte, bis man die Auflösung von innen aus dem Pod prüfte und sah, dass der Name .svc.cluster.local an den Proxy ging. Die Ursache – NO_PROXY enthielt nur localhost. Man erstellte eine vollständige Referenzliste mit Pod-CIDR, Service-CIDR und allen DNS-Suffixen, legte sie in eine ConfigMap und band sie in alle Pods ein. Die interne Kommunikation wurde wiederhergestellt. Seitdem ist das referenzielle NO_PROXY ein obligatorischer Bestandteil des Deployment-Templates.

Fall 3: Observability des ausgehenden Traffics über Sidecar

Sicherheitsanforderung: genau wissen, wohin jede Anwendung nach außen geht, mit vollständigem Journal. Die Anwendungen waren in verschiedenen Sprachen, ein Teil konnte HTTP_PROXY nicht lesen. Die Codeänderung Dutzender Services – zu teuer.

Man führte einen Sidecar-Proxy im Pod-Template ein. Die Anwendungen leiten ausgehenden Traffic an den lokalen Agenten, dieser proxyt über Proxeon und schreibt Metriken: Zielhost, Latenz, Antwortcode. Es entstand ein Dashboard ausgehender Aufrufe pro Service. Zusätzlich wurden Timeouts und Wiederholungen auf Sidecar-Ebene konfiguriert, was die Zahl kaskadierender Ausfälle bei kurzzeitiger Nichtverfügbarkeit externer APIs reduzierte. Der Preis waren zusätzliche Ressourcen für den Sidecar, aber der Gewinn an Observability und Zuverlässigkeit rechtfertigte das.

FAQ: Häufige Fragen von Ingenieuren

Warum geht der Traffic zum benachbarten Pod über den externen Proxy, obwohl das doch offensichtlich eine interne Adresse ist?

Weil der HTTP-Client nicht weiß, dass die Adresse intern ist. Er sieht den Namen oder die Adresse und leitet, wenn diese nicht in NO_PROXY enthalten ist, die Anfrage gemäß den Regeln an den Proxy. Der Client unterscheidet nicht selbst zwischen Ost-West und Nord-Süd – für ihn erledigt das die Ausschlussliste. Fügen Sie interne Suffixe und Subnetze zu NO_PROXY hinzu.

Muss man einen Proxy auch für das Herunterladen von Images konfigurieren?

Ja, aber nicht über env des Pods. Das Herunterladen von Images erfolgt durch die Container-Runtime und kubelet auf Node-Ebene. Der Proxy für die Registry wird in der Runtime-Konfiguration oder im systemd-Unit von kubelet konfiguriert. Die Umgebungsvariablen des Containers beeinflussen das überhaupt nicht.

Was ist wichtiger für eine stabile ausgehende IP – Sidecar oder Egress-Gateway?

Das Egress-Gateway. Ein Sidecar proxyt den Traffic eines einzelnen Pods, aber die ausgehende Adresse wird dennoch dadurch bestimmt, wohin die Verbindung weitergeht. Für eine garantiert feste Adresse, die ein externer Partner akzeptiert, benötigt man entweder ein Egress-Gateway oder den Ausgang über einen externen Punkt mit konstanter Adresse, beispielsweise über Proxeon.

Warum ignoriert eine Java-Anwendung HTTP_PROXY?

Die JVM liest aus historischen Gründen keine Umgebungsvariablen für Proxys. Sie verwendet eigene Systemeigenschaften http.proxyHost, https.proxyHost und Ausnahmen http.nonProxyHosts. Übergeben Sie sie über JAVA_TOOL_OPTIONS. Und denken Sie daran: Das Trennzeichen für Ausnahmen in der JVM ist ein senkrechter Strich, und Muster verwenden ein Sternchen, nicht CIDR.

Wie speichert man das Proxy-Passwort sicher?

In einem Secret-Objekt mit RBAC-beschränktem Zugriff und aktivierter etcd-Verschlüsselung. Binden Sie die Werte über secretKeyRef ein. Schreiben Sie das Passwort nicht im Klartext ins Manifest, sonst gelangt es in Git und in die describe-Ausgabe. Für erhöhte Anforderungen verwenden Sie einen externen Secret-Manager über CSI.

Ich habe die Umgebungsvariable geändert, aber das Verhalten hat sich nicht geändert – warum?

Umgebungsvariablen werden beim Start des Containers festgelegt. Solange der Pod nicht neu erstellt wurde, arbeitet er mit den alten Werten. Aktualisieren Sie das Deployment so, dass ein Rollout der Pods erfolgt. Prüfen Sie außerdem, ob die Anwendung die Umgebung überhaupt liest und nicht die Einstellungen bei der Initialisierung zwischenspeichert.

Wie finde ich heraus, welches NO_PROXY-Format genau meine Bibliothek benötigt?

Empirisch: Konfigurieren Sie den Proxy, sprechen Sie einen internen Namen von innen aus dem Pod an und beobachten Sie, ob die Anfrage an den Proxy oder direkt ging. Wenn sie an den Proxy ging – ist das Format nicht erkannt. Probieren Sie die Variante mit führendem Punkt und ohne, fügen Sie einzelne Adressen statt CIDR hinzu, prüfen Sie die Groß-/Kleinschreibung des Variablennamens. Die Dokumentation des konkreten Clients ist die beste Orientierung.

Sollte man immer ein Service Mesh nur wegen des Proxying verwenden?

Nein. Service Mesh ist ein mächtiges Werkzeug mit Observability und Richtlinien, aber es bringt spürbaren Overhead und operative Komplexität mit sich. Wenn die Aufgabe nur darin besteht, ausgehenden Traffic über einen Proxy zu leiten, sind ein leichter Sidecar-Agent oder ein Egress-Gateway günstiger. Führen Sie Mesh ein, wenn Sie wirklich seine vollen Fähigkeiten benötigen.

Wie umgeht man Traffic, der überhaupt nicht HTTP ist?

Die Variablen HTTP_PROXY funktionieren nur für HTTP- und HTTPS-Clients, die sie respektieren. Für beliebige TCP-Verbindungen benötigt man eine transparente Abfangung auf Sidecar- oder Egress-Gateway-Ebene mit entsprechenden Routing-Regeln. Hier sind Umgebungsvariablen machtlos.

Kann man unterschiedliche Proxy-Richtlinien für verschiedene Pods festlegen?

Ja. Auf Container-Ebene – durch verschiedene env in verschiedenen Deployments. Auf Egress-Ebene – durch Label-Selektoren in Egress-Policies, die markierte Pods zum gewünschten Ausgang leiten. Die Kombination bietet Flexibilität: Basisausgang über das Gateway plus individuelle Einstellungen für einzelne Services.

Fazit: Wir stellen eine Rollout-Checkliste zusammen

Wir haben den Weg von der Frage, warum die Konfiguration „wie auf einem normalen Server" im Cluster nicht funktioniert, bis zu den drei Ebenen der Proxy-Einbindung, den Feinheiten von NO_PROXY, Secrets, Sidecar, Egress-Gateway und Diagnose durchlaufen. Die wichtigste Erkenntnis: In Kubernetes ist ausgehender Traffic keine einzelne Variable, sondern ein geschichtetes System, bei dem nicht das Wichtigste ist, wie man den Proxy einschaltet, sondern wie man mit ihm nicht die interne Kommunikation zerstört.

Stellen wir die finale Rollout-Checkliste zusammen, die man bei jeder Einführung vor Augen haben sollte.

Checkliste für den Proxy-Rollout im Cluster

  • Ebene bestimmt. Container, Sidecar oder Egress-Gateway bewusst und für die konkrete Aufgabe gewählt.
  • Vollständiges NO_PROXY erstellt.